Errors & availability

Which failures are facts, which are transient, and which you should retry. Availability is typed so you never have to guess from a status code.

Availability is a field, not a status code

HTTP status is too coarse to branch on. “This account is private” and “this account never existed” both arrive as 404 elsewhere, and they call for completely different handling. So every response carries an availability block:

{ "availability": { "status": "private", "reason": "account_is_private" } }
status Meaning Retry? Charged
available You have the data Yes, unless cached
not_found Deleted, or never existed No No
private Exists, not publicly visible No No
unsupported We do not cover this platform or resource No No
unavailable Temporarily unreachable upstream Yes, backoff No
upstream_unavailable Our provider is failing Yes, backoff No

Branch on availability.status, not the HTTP code. The status code tells your HTTP client what to do; this field tells your application what happened.

The retry rule

The first four rows are facts about the world. Retrying a deleted post does not undelete it — it burns time and, on providers that charge for the lookup, money. The last two are transient and worth retrying with exponential backoff.

TERMINAL = {"not_found", "private", "unsupported"}

def handle(resp):
    status = resp["availability"]["status"]
    if status == "available":
        return resp
    if status in TERMINAL:
        mark_gone(resp)     # record it; stop asking
        return None
    raise Transient(status)  # retry with backoff

HTTP status codes

Code Meaning Charged
200 Request succeeded — check availability for what you got Depends
400 Malformed request, unparseable URL No
401 Missing, malformed or revoked key No
402 Credits exhausted and overage disabled No
409 Idempotency-Key reused with a different request No
429 Rate limited — see Retry-After No
5xx Our fault No

A 200 with availability.status of not_found is not a contradiction: the request worked, and the answer is that the resource is gone. That answer is free.

Cancelled and timed-out requests

If your client gives up on a request — an AbortController, a timeout shorter than ours, a dropped connection, a killed process — the response never reaches you, but the work may already have been done on our side.

A cancelled request can still be billed. If we had already fetched from the source when you disconnected, that fetch is charged: we paid the provider, and your socket closing does not refund us. If you cancelled before we called upstream, nothing is charged.

The data is not lost. The result is still written to cache, so retrying the same resource is a cache hit and costs 0 credits. Cancelling and retrying costs you one fetch, not two.

Use an Idempotency-Key if you retry. With the same key, the retry returns the original stored response with no second charge at all — and it removes the one genuinely awkward part of a cancellation, which is that you cannot tell from the client whether you were billed:

key = str(uuid.uuid4())

for attempt in range(3):
    try:
        r = client.get("/v1/profile", params={"url": url},
                       headers={"Idempotency-Key": key}, timeout=10)
        break
    except (httpx.TimeoutException, httpx.ReadError):
        continue        # same key — the retry cannot double-charge

Do not generate a new key per attempt. A fresh key on each retry is a fresh request, and each one that reaches upstream is billed.

Error body

{
  "error": {
    "code": "unparseable_url",
    "message": "That URL is not a supported TikTok, Instagram or YouTube resource.",
    "request_id": "req_9c2a0e4482118893"
  }
}

Always log request_id. It is on every response, success or failure, and it is the fastest way for us to find what happened.

Rate limiting

429 carries Retry-After in seconds. Respect it — retrying sooner will not succeed and counts against your next window. Rate limits are per project and scale with your plan; see authentication.

Last updated 27 August 2026