Documentation 29
Keys & rate limits · 4 min read
One key. Every provider's own auth slot.
No host here ever requires credentials — a SandboxAPIs key only raises your rate limit. It travels in the auth header your client already sends for that provider, so adoption stays a base-URL swap.
01 / How keys work
Any token is accepted. A recognized one is lifted.
Every sandbox host accepts any credential — that is what makes the drop-in work with the auth code you already have. Recognition is by lookup: when the token in the provider's auth slot is a SandboxAPIs key, the request is metered against that key's tier. Anything else — including your real provider tokens — simply stays anonymous; nothing is ever rejected for bad auth.
02 / Where the key rides
21 hosts, each in its own dialect.
The key goes exactly where that provider's real credential goes — same header, same scheme — so your client library's own auth option is the only thing you configure. Open the category you're integrating with; each section lists every host in it.
git host4 hosts
| Provider | The key rides as | curl |
|---|---|---|
| GitHub | Authorization: token <key> (Bearer works too) | -H "Authorization: token $KEY" |
| GitLab | PRIVATE-TOKEN: <key> (Bearer works too) | -H "PRIVATE-TOKEN: $KEY" |
| Bitbucket | HTTP Basic — key as the app-password half | -u "you:$KEY" |
| Azure DevOps | HTTP Basic — key as the PAT half (Bearer works too) | -u ":$KEY" |
issue tracker2 hosts
AI telemetry5 hosts
| Provider | The key rides as | curl |
|---|---|---|
| Anthropic | x-api-key: <key> | -H "x-api-key: $KEY" |
| Cursor | HTTP Basic — key as the USERNAME, empty password | -u "$KEY:" |
| Devin | Authorization: Bearer <key> | -H "Authorization: Bearer $KEY" |
| OpenAI | Authorization: Bearer <key> | -H "Authorization: Bearer $KEY" |
| Codex Analytics | Authorization: Bearer <key> | -H "Authorization: Bearer $KEY" |
messaging2 hosts
| Provider | The key rides as | curl |
|---|---|---|
| Slack | Authorization: Bearer <key> (where the xoxb-/xoxp- token rides) | -H "Authorization: Bearer $KEY" |
| Microsoft Teams | Authorization: Bearer <key> (where the Entra JWT rides — the header must be present) | -H "Authorization: Bearer $KEY" |
CI/CD2 hosts
observability3 hosts
| Provider | The key rides as | curl |
|---|---|---|
| Sentry | Authorization: Bearer <key> (what sentry-cli and sentry-mcp send) | -H "Authorization: Bearer $KEY" |
| PagerDuty | Authorization: Token token=<key> | -H "Authorization: Token token=$KEY" |
| Statuspage | OAuth token | -H "Authorization: OAuth $KEY" |
CRM/support3 hosts
| Provider | The key rides as | curl |
|---|---|---|
| Zendesk | HTTP Basic — key as the API-token half | -u "you@example.com/token:$KEY" |
| Salesforce | Authorization: Bearer <key> (where the OAuth access token rides) | -H "Authorization: Bearer $KEY" |
| HubSpot | Authorization: Bearer <key> (where the private-app token rides) | -H "Authorization: Bearer $KEY" |
# GitHub's slot, for example — /rate_limit shows the tier the key resolved to
export KEY=your-sandboxapis-key # claimed at sandboxapis.dev/login
curl -sD- -o /dev/null -H "Authorization: token $KEY" https://gh.sandboxapis.dev/rate_limit
# x-ratelimit-limit: 600 ← keyed: the free tier, in GitHub's own headers03 / The tiers
6 rungs published. 4 the limiter enforces today.
The whole ladder is below, priced rungs included, because a limits page that lists only the metered tiers is a limits page you have to leave to find out what comes next. The Enforced column is the honest part: the 4 rungs marked yes are what packages/gateway meters against right now. Solo joined them on 2026-08-31: it is self-serve on pricing, and a subscription writes its rate onto every live key on the account within seconds of the payment. Scale has not — its daily window exists in the limiter, but no key carries that tier yet and those customers are onboarded by hand, so the row stays “not yet” until one is running in production. Pricing has the terms; this page has the mechanics.
| Tier | Limit | Enforced | Bucket |
|---|---|---|---|
| Anonymous | 60 req/hour | Yes | Per IP — one bucket shared across all provider hosts |
| Free key | 600 req/hour | Yes | Per account, shared across all your keys and all provider hosts |
| Founding key | 6,000 req/hour | Yes | Per account, shared across all your keys and all provider hosts |
| Solo — $5/month | 6,000 req/hour | Yes | Per account, shared across all your keys and all provider hosts — founding's rate, bought rather than comped |
| Scale — $99/month | 250,000 req/day | Not yet | 50,000 req/hour burst. The daily window is built, but no key carries this tier yet — Scale is onboarded by hand |
| Enterprise | Custom | Not yet | Scoped per agreement, including workloads that fit neither an hourly nor a daily window |
At expiry the key falls back to the free limits of the day — it keeps working, and is never revoked. Anonymous access (60 requests/hour per IP across all sandbox hosts, no key) stays open to everyone, always — the waitlist gates the founding upgrade, not access.
This page is the technical statement of the limits — what they are, how they spend, and how each provider reports them. For what each rung costs, what it includes, and how to get on the list for one that isn't buyable yet, see pricing.
04 / Reading the budget
Provider-conventional headers, provider-shaped 429s.
Each host reports and enforces the budget in its provider's own dialect — GitHub's x-ratelimit-* trio and 403 over-limit body, GitLab's RateLimit-* 429, Linear's GraphQL RATELIMITED error, Anthropic's anthropic-ratelimit-requests-* trio — and providers whose real APIs send no rate-limit headers send none here either. Your client's existing throttling code just works; the quickstarts show each host's exact furniture. Reads also carry an ETag wherever the real provider serves one, and conditional requests with If-None-Match answer 304 Not Modified.
When the budget runs out, the refusal names the rung you are on and the next one up. The status, the body shape and the provider's own headers are unchanged — the sentence lives inside the message field the provider's envelope already has. On an anonymous caller's GitHub 403:
{
"message": "API rate limit exceeded. Sign up for a free API key to raise limits to 600 requests/hour — https://sandboxapis.dev/upgrade?from=anonymous",
"documentation_url": "https://sandboxapis.dev/upgrade?from=anonymous",
"status": "403"
}A key gets its own rung's sentence instead — a free key is told what Solo costs, a Solo key what Scale costs — and the same fact rides two headers so a client can read it without parsing prose: x-sandboxapis-tier names the rung that refused (anonymous, free, founding, solo, scale), and x-sandboxapis-upgrade carries the link, present only when there is a rung to offer. Both appear on over-limit refusals and nowhere else — never on a success, never on a coverage 404, never on a read-only refusal. Two dialects have no message field a real response would carry (Slack's {"ok":false,"error":"ratelimited"} and Teams'), so for those the sentence rides x-sandboxapis-reason, as it always has.
05 / MCP
One env var lifts the agent too.
The MCP server is a thin client over the same public hosts, so it shares the same tiers. Set SANDBOXAPIS_API_KEY and every request it makes carries your key — in each provider's own auth slot, automatically. Without it the server runs anonymously at 60 req/hour, and an over-limit refusal to that server says so in the one way its operator can act on: “This MCP server is running without a key (60 req/hour). Set SANDBOXAPIS_API_KEY to a free key for 600/hour” — the same anonymous rung, the same link, addressed to the reader who can fix it. Agents get the same fact as data: a refused tool result carries upgrade, and orient states the budget before the first call.
claude mcp add sandboxapis --env SANDBOXAPIS_API_KEY=your-key -- npx -y @sandboxapis/mcp