Rate limits & errors
The SDK must tolerate all of this without crashing. The normalized result for any non-200 or unfillable response is simply no-fill: render nothing, optionally retry with backoff.
Status codes & body shapes
| Name | Type | Required | Notes |
|---|---|---|---|
No fill | 200 | Optional | {"fill": false} (± reason) |
Missing slot | 400 | Optional | {"fill": false, "error": "slot_id or slot_name is required"} |
Handler rate limit | 429 | Optional | {"fill": false, "error": "rate limit exceeded"} + Retry-After: 60 |
Middleware rate/DDoS limit | 429 | Optional | {"error": "..."} (no fill key) + maybe Retry-After |
WAF / SQLi block | 403 | Optional | {"error": "Request blocked due to security policy"} — see WAF-safe encoding |
Malformed query | 400 | Optional | Bad UUID, missing api_key — body is plain text, not JSON |
Rate limit thresholds
Per client IP unless otherwise noted:
- 200 req / 10s — DDoS-tier middleware limit
- 1000 req/min per IP
- 100 req/min per
api_key(GET) - plus a per-IP
RATE_LIMIT_PER_MINUTE(default 60) in the serve handler itself
SDKs default to client-side throttling — a minimum interval between requests for the same slot, and a refresh interval floor of 30s (see Beacons & lifecycle) — to stay well under all of these.
The parsing rule: fail closed
Never assume the error body is JSON
On any status ≥ 400, or a body that doesn't parse as JSON, orfill != true, return the normalized NoFill result. Surface the HTTP status and Retry-After on the result object so callers can implement backoff — but the default render path treats everything unfillable as "show nothing." An ad request must never throw into the host app, crash a page, or block content.Every official SDK implements this identically: a network error, a timeout, a non-200 response, or an unparseable body all collapse to the same { filled: false } result your rendering code already handles for the ordinary no-fill case — there is no separate error path to write.