Concepts
Errors & billing
Authentication, authorization and billing are three separate outcomes here, and nothing that fails to return usable data costs anything. Both of those are specified behaviour with tests behind them, not a courtesy.
The order things happen in
Every request (synchronous, batch item, async job, or subscription poll) runs through the same eight stages in the same order. The order is what makes the guarantees hold: charging sits after the cache and before the run, and settlement sits after it.
- 01
validatefreeParameters are checked against the endpoint schema. A failure lists every bad path and never reaches a scraper.
- 02
preflightfreeAn endpoint this deployment is not set up to serve fails here, before the cache, so it fails identically whether or not something happens to be cached.
- 03
cachefreeA hit inside your cache_max_age returns immediately with creditsCharged 0. A cache read failure degrades to a live fetch, never to an error.
- 04
chargemay chargeTaken only after a miss, only when the endpoint costs more than zero, and only if the balance covers it. A balance can never go negative.
- 05
runfreeThe scraper executes. Async jobs run here too, with no request timeout above them.
- 06
settlefreeAnything that threw is refunded in full. Anything empty is refunded in full. An endpoint may charge less than its listed price, never more.
- 07
cache writefreeThe result is stored for the next caller, unless your team has cache writes or retention turned off.
- 08
logfreeExactly one request log row per request, success or failure. A failed log write never fails the request.
Every error code
The charged column reads no on every row. That is the point of the table. The origin column is the one to act on: a you error needs a change on your side, a platform error usually needs a retry, and a us error needs nothing from you at all.
| Code | HTTP | Origin | Retry | Charged | Meaning |
|---|---|---|---|---|---|
| missing_api_key | 401 | you | no | no | No x-api-key header was sent. |
| invalid_api_key | 401 | you | no | no | The key is not recognised. This is a typo, not a billing problem. |
| revoked_api_key | 401 | you | no | no | The key existed and was revoked. Issue a new one. |
| account_suspended | 403 | you | no | no | The account has been suspended. Contact support to restore access. |
| endpoint_not_permitted | 403 | you | no | no | The key is valid and the endpoint exists, but this key is restricted to a subset of endpoints and this is not one of them. Distinct from 401 (key not recognised) and 402 (out of credits): authentication, authorization and billing are three separate outcomes. |
| insufficient_credits | 402 | you | no | no | The key is valid and the balance is not. The only condition that returns 402. |
| daily_cap_exceeded | 429 | you | no | no | Your configured daily cap would be exceeded. Checked before any charge. |
| invalid_request | 400 | you | no | no | Parameters failed validation. Every failing path is listed, not just the first. |
| not_found | 404 | you | no | no | The route or resource does not exist on this API. Check the path. |
| upstream_not_found | 404 | you | no | no | The profile, video, or post does not exist or is not public. |
| empty_result | 200 | platform | no | no | The target exists and has nothing to return. A success, and free. |
| upstream_blocked | 502 | platform | yes | no | The platform refused the request. Ours to fix, not yours. |
| upstream_rate_limited | 429 | platform | yes | no | The platform throttled us. Back off and retry. |
| upstream_unavailable | 503 | platform | yes | no | The platform is unreachable. |
| upstream_timeout | 504 | platform | yes | no | The upstream request ran out of time. Use a job for work this long. |
| upstream_schema_drift | 502 | platform | no | no | The platform changed its response shape. We are alerted automatically. |
| not_configured | 501 | us | no | no | This deployment cannot serve that endpoint. Nothing you send will change it, so contact us. |
| too_many_targets | 400 | you | no | no | A batch exceeded the maximum target count. |
| job_not_found | 404 | you | no | no | No job with that id belongs to your team. |
| internal_error | 500 | us | no | no | Our fault. Refunded, logged, and alerted on. |
401 is not 402
A missing, unrecognised, or revoked key always returns 401. A 402 means the key is valid and the balance is not. Nothing else returns 402, so a 402 in your logs is never something you fix by checking for a typo.
This matters more than it sounds, and conflating the two is a common enough failure that it is worth naming. An API that answers 402 out of credits for a key it does not recognise sends you to top up your balance when what you actually have is a stray whitespace character in an environment variable, and your balance was never the problem.
What an error looks like
{
"success": false,
"error": {
"code": "upstream_blocked",
"message": "The platform blocked this request. It was not charged.",
"details": { }
}
}Empty is a success
A channel with no uploads, a video with no captions, a search with no hits: the target exists and the answer is nothing. You get a 200, an empty payload, and creditsCharged: 0. Treat empty as data, not as failure.
{
"success": true,
"data": { "items": [] },
"meta": {
"endpoint": "youtube.channel-videos",
"creditsCharged": 0,
"cached": false,
"durationMs": 1104,
"requestId": "req_01JB8Z..."
}
}The clauses this page implements
| P1 | You are never charged for a failed request. | SPEC §2.5 |
| P2 | You are never charged for an empty result. | SPEC §2.5 |
| P3 | Cache hits cost 0 credits. | SPEC §2.3 |
| P4 | An invalid API key is an auth error, not a billing error. | SPEC §3 |
| P5 | Every endpoint's price is machine-readable. | SPEC §2.6 |
| P6 | A balance can never go negative. | SPEC §1.1 |
| P7 | Per-tenant cache isolation is standard. | SPEC §2.3 |
| P8 | Hard daily spend caps. | SPEC §1.3 |