SharpRelayPinnacle data
HomeAPI referenceDashboard

Errors & retries

Every failure — a bad parameter, an auth problem, a capacity event — uses exactly one JSON envelope. If your error handling covers this shape, it covers the entire API.

The error envelope

HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: req_9f12…
X-SharpRelay-Origin: https://api.worksinprod.dev

{
  "error": {
    "code": "invalid_parameters",
    "message": "One or more request parameters are invalid.",
    "request_id": "req_9f12…"
  }
}
FieldMeaning
codeStable, machine-readable identifier — branch on this, never on message
messageHuman-readable explanation; may change between releases
request_idUnique ID of this request (also in the X-Request-Id header) — quote it for support

Data-plane error codes

These are the stable failures returned by the 12 data endpoints. Account, checkout, and credential-delivery operations have additional route-specific 4xx codes in the API reference; they use the same envelope.

StatusCodeMeaningRetryable?
400invalid_parametersA query parameter is missing or malformedNo — fix the request
401invalid_api_keyMissing, malformed, or revoked keyNo
403insufficient_scopeThe key does not have data:readNo — use a correctly scoped key
403account_suspended / plan_inactiveThe account or plan cannot access dataNo — resolve the account state
404not_foundThe requested resource or result does not existNo
429rate_limitedPer-second rate limit hitYes — after Retry-After (1s)
429quota_exceededUTC-daily guard or plan-period allowance spentYes — after Retry-After (first applicable reset)
500internal_errorUnexpected gateway failureYes — with backoff; report if persistent
502service_errorSharpRelay could not safely complete the data responseYes — with backoff
503temporarily_unavailableAccounting, capacity, or dependency unavailableYes — honor Retry-After when present, otherwise back off

Errors and credit charges

Authentication, local parameter validation, and failures before a data request is dispatched are not charged. If work reached the contracted data source, a normalized 400, 404, 502, or 503 response can cost one credit. Metering headers are present only when a response was metered: X-Credits-Cost is authoritative, and X-Result-Rows is zero for a metered error. Their absence means no buyer credit was charged.

Historical Request Health

The authenticated buyer dashboard keeps request diagnosis beside usage and quota information. Request Health shows daily success rate, dominant normalized error codes, recent failed request IDs, owned endpoint names, latency, and the exact credit charge for each recorded failure. Automatic guidance explains the safest correction, while an active support note can add account-specific advice.

GET /v1/account/request-health?days=30 exposes the same tenant-isolated data for automation. Detailed failures are retained for 30 days; daily and issue aggregates support longer trends. The history never stores or returns query values, bodies, authorization headers, IP addresses, or private-source details. Authentication failures cannot be assigned safely to an account and are therefore not included. Use the request ID from the live error response when contacting support about one of those failures.

Recommended retry strategy

Retry only the retryable codes above, with exponential backoff and jitter, capped at a handful of attempts:

def call_with_retry(req, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            resp = req()
        except NetworkError:
            resp = None
        if resp is not None and resp.status < 500 and resp.status != 429:
            return resp            # success or a non-retryable error
        if attempt == max_attempts - 1:
            raise
        wait = min(2 ** attempt, 30) * (0.5 + random())
        retry_after = resp.headers.get("Retry-After") if resp else None
        sleep(float(retry_after) if retry_after else wait)
Rules of thumb

Honor Retry-After whenever present — it reflects the actual safe retry time, not a guess. Add jitter so parallel workers don't retry in lockstep. For data-plane calls, never retry 4xx other than 429 — they fail deterministically.

Credential-delivery retries are exact replays

Do not apply a generic “create again” retry to credential mutations. If a key-rotation response is interrupted, retry with the exact old API key and the exact original rot_… Idempotency-Key during its 15-minute window. If a checkout claim or lost-key recovery response is interrupted, retry with the exact same fragment bearer during its 10-minute delivery window. Changing either secret starts no safe recovery and can leave you without the only copy. Treat a received replacement as sensitive even when your client later times out.

Client-side safety net

Build a local circuit breaker for your own protection: if you see repeated quota_exceeded or rate_limited responses, slow your polling loop down or drop to a cheaper endpoint class — see Quotas & metering and Polling & performance.