alpacahq/alpaca-broker-rate-limits-resilience
Make Alpaca API clients resilient — rate-limit header handling, HTTP 429 backoff, exponential retry, bounded concurrency/worker pools, pagination loops, batch sizing, and timeouts. Use when building robust REST clients, bulk/cron jobs, or reconciliation sweeps against Alpaca in any language.
npx skills add https://github.com/alpacahq/alpaca-skills --skill alpaca-broker-rate-limits-resilience
Alpaca's APIs are rate-limited and occasionally flaky under load. Any client that does more than a handful of calls — especially bulk jobs, backfills, and reconciliation sweeps — needs disciplined retry, backoff, and concurrency control. These patterns are transport-level and apply in any language.
> Read alpaca-broker-integration first.
Alpaca returns standard headers:
| Header | Meaning |
|--------|---------|
| X-RateLimit-Limit | requests allowed in the window |
| X-RateLimit-Remaining | requests left in the current window |
| X-RateLimit-Reset | unix timestamp (seconds) when the window resets |
Parse them on every response, not just on errors. Two uses:
Remaining drops below a threshold (e.g. ≤ 50), log a warning and/or slow down — you're about to get throttled.429, use Reset to wait exactly until the window opens.> Limits vary by endpoint and plan; market-data limits differ from broker limits. Don't hardcode a number — react to the headers.
MAX_ATTEMPTS = 10
INITIAL_DELAY_MS = 1000
for attempt in 1..MAX_ATTEMPTS:
res = http(request) # with a sane timeout (see §5)
remaining, reset_at = parse_rate_headers(res.headers)
if remaining <= 50: log_warn("approaching rate limit", reset_at)
if res.status == 429:
# wait until the window resets, plus a small buffer
wait = (reset_at - now()) if reset_at else INITIAL_DELAY_MS * 2^(attempt-1)
sleep(max(0, wait) + 1000) # +1s buffer past reset
continue
if res.status in (500, 502, 503, 504) or network_error:
sleep(INITIAL_DELAY_MS * 2^(attempt-1)) # exponential backoff
continue
return res # success or non-retryable 4xx
raise last_error
Key points:
429, wait until X-RateLimit-Reset + a ~1s buffer — don't blindly exponential-backoff when the API told you exactly when to retry.base * 2^(attempt-1)) for network errors and 5xx. With base 1s and 10 attempts the tail is minutes — fine for background jobs, too slow for user-facing calls (use fewer attempts there).400/403/422) — those won't fix themselves; surface them.Parallelism speeds bulk jobs but is the fastest way to hit limits. Use a fixed worker pool, not unbounded fan-out.
List endpoints page forward with a token — never assume one response is complete.
/v1/accounts/activities): page via the X-Next-Page-Token response header; loop until it's empty. Use page_size (≤100) and a direction./v2/stocks/bars): page via next_page_token in the body → pass back as page_token. Remember limit counts across all symbols and results sort by symbol-then-time, so a single page may contain only the first symbol(s) — keep paging.token = null
loop:
page = fetch(url + (token ? "&page_token="+token : "")) # via retry loop
accumulate(page.items)
token = page.next_token # header or body, per endpoint
if not token: break
alpaca-broker-sse-events.)429 → wait until X-RateLimit-Reset + buffer.alpaca-broker-reconciliation-idempotency.Related skills: safe re-runs of jobs → alpaca-broker-reconciliation-idempotency; the heal/poll jobs that use these patterns → alpaca-broker-reconciliation-idempotency; market-data pagination specifics → alpaca-broker-market-data.
Take alpacahq/alpaca-broker-rate-limits-resilience from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.