Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/alpacahq-alpaca-broker-rate-limits-resilience/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 22% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-12 | ✗→✓ | ▲ Improved | -13% | 0% |
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_errorKey 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: breakalpaca-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.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→pass | 26,548 | 18,218 | -31% | 1 | 1 | 0% | 5,432 | 6,607 | +22% | 0 | 0 | — |
case-03 | pass→pass | 8,457 | 7,101 | -16% | 1 | 1 | 0% | 2,064 | 3,071 | +49% | 0 | 0 | — |
case-01 | fail→pass | 26,423 | 26,718 | +1% | 1 | 1 | 0% | 5,639 | 6,245 | +11% | 0 | 0 | — |
case-04 | pass→pass | 12,196 | 11,559 | -5% | 1 | 1 | 0% | 2,495 | 3,381 | +36% | 0 | 0 | — |
case-05 | pass→pass | 13,354 | 11,739 | -12% | 1 | 1 | 0% | 2,732 | 3,584 | +31% | 0 | 0 | — |
case-06 | pass→pass | 5,323 | 1,576 | -70% | 1 | 1 | 0% | 993 | 1,805 | +82% | 0 | 0 | — |
case-07 | pass→pass | 13,081 | 4,816 | -63% | 1 | 1 | 0% | 2,462 | 2,304 | -6% | 0 | 0 | — |
case-08 | fail→pass | 9,538 | 4,069 | -57% | 1 | 1 | 0% | 1,878 | 2,356 | +25% | 0 | 0 | — |
case-09 | pass→pass | 18,558 | 8,813 | -53% | 1 | 1 | 0% | 2,845 | 3,134 | +10% | 0 | 0 | — |
case-10 | fail→pass | 13,555 | 9,394 | -31% | 1 | 1 | 0% | 2,551 | 3,398 | +33% | 0 | 0 | — |
case-11 | pass→pass | 11,042 | 7,283 | -34% | 1 | 1 | 0% | 2,167 | 2,902 | +34% | 0 | 0 | — |
case-12 | fail→pass | 10,292 | 1,831 | -82% | 1 | 1 | 0% | 2,099 | 1,816 | -13% | 0 | 0 | — |
case-13 | pass→pass | 13,790 | 9,014 | -35% | 1 | 1 | 0% | 2,801 | 3,336 | +19% | 0 | 0 | — |
case-14 | pass→pass | 11,529 | 7,656 | -34% | 1 | 1 | 0% | 2,086 | 2,864 | +37% | 0 | 0 | — |
case-15 | pass→pass | 15,855 | 6,036 | -62% | 1 | 1 | 0% | 2,703 | 2,560 | -5% | 0 | 0 | — |
case-16 | pass→pass | 13,780 | 9,533 | -31% | 1 | 1 | 0% | 2,705 | 3,348 | +24% | 0 | 0 | — |
case-21 | pass→pass | 10,923 | 6,854 | -37% | 1 | 1 | 0% | 2,099 | 2,919 | +39% | 0 | 0 | — |
case-17 | pass→pass | 12,045 | 6,316 | -48% | 1 | 1 | 0% | 2,317 | 2,623 | +13% | 0 | 0 | — |
case-18 | pass→pass | 11,788 | 2,175 | -82% | 1 | 1 | 0% | 1,945 | 1,813 | -7% | 0 | 0 | — |
case-19 | pass→pass | 10,234 | 6,221 | -39% | 1 | 1 | 0% | 1,959 | 2,647 | +35% | 0 | 0 | — |
case-20 | pass→pass | 11,646 | 2,904 | -75% | 1 | 1 | 0% | 2,037 | 1,993 | -2% | 0 | 0 | — |
case-22 | pass→pass | 17,330 | 10,112 | -42% | 1 | 1 | 0% | 3,275 | 3,497 | +7% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.