Skip to content

Parse and Use Basecamp Rate Limit Headers #112

@brigleb

Description

@brigleb

Problem

The rate limiter uses hardcoded values (50 requests/10s) and doesn't adapt to actual API responses. Basecamp returns rate limit information that we currently ignore.

Current behavior (internal/api/ratelimit.go:26):

globalRateLimiter = NewRateLimiter(50, 10*time.Second) // Hardcoded

Per Basecamp API docs:

"The first rate limit you'll commonly encounter is currently 50 requests per 10 second period per IP address."
"Multiple dynamic limits exist for different request types (GET vs POST) and time windows."

Proposed Solution

1. Add header parsing in internal/api/ratelimit.go

type RateLimitInfo struct {
    Limit      int       // Total requests allowed per window
    Remaining  int       // Requests remaining in current window
    Reset      time.Time // When the window resets
    RetryAfter int       // Seconds to wait (from 429 responses)
}

func ParseRateLimitHeaders(headers http.Header) *RateLimitInfo

func (rl *RateLimiter) UpdateFromHeaders(info *RateLimitInfo)

2. Modify internal/api/client.go to extract headers

  • After each successful response, check for rate limit headers
  • Update the global rate limiter with actual remaining capacity
  • When Remaining is low (< 5), proactively increase delay between requests

3. Handle 429 with Retry-After

Files to Modify

File Changes
internal/api/ratelimit.go Add RateLimitInfo, ParseRateLimitHeaders(), UpdateFromHeaders()
internal/api/client.go:64-67 Extract and process rate limit headers after httpClient.Do()
internal/api/ratelimit_test.go Add tests for header parsing

Headers to Parse

Based on common API patterns (Basecamp may use some/all):

Retry-After: 30              (on 429 responses - confirmed in docs)
X-RateLimit-Limit: 50        (if present)
X-RateLimit-Remaining: 45    (if present)
X-RateLimit-Reset: 1609459200 (if present)

Acceptance Criteria

  • Parse Retry-After header on 429 responses (confirmed in API docs)
  • Parse X-RateLimit-* headers if present
  • Update rate limiter dynamically based on remaining count
  • When remaining requests are low, increase delay between requests
  • Gracefully handle missing headers (fall back to current defaults)
  • Thread-safe updates to rate limiter state
  • Log when rate limiting kicks in (debug level)

Testing

  • Unit tests for header parsing with various formats
  • Test Retry-After parsing (integer seconds)
  • Test dynamic adjustment of rate limiter
  • Test fallback behavior when headers are missing
  • Test thread safety with concurrent access

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions