error.code below.
{
"success": false,
"error": {
"code": "rateLimited",
"message": "Too many requests. Retry after the number of seconds in Retry-After.",
"retryable": true,
"retryAfterSeconds": 12,
"requestId": "..."
}
}
| Field | What it is |
|---|---|
code | A stable code from the table below |
message | What went wrong, in plain words |
retryable | true when the same request may work later |
retryAfterSeconds | Present when you should wait a set time. The Retry-After header has the same value. |
requestId | The ID of this request. Include it when you contact support. |
Error codes
| Code | Status | Retryable | What it means |
|---|---|---|---|
invalidRequest | 400 | No | The input is not valid, or the source rejected a value you sent. The message names the problem. |
unauthorized | 401 | No | The API key is missing or not valid. |
insufficientCredits | 402 | No | Your balance is too low for this call. Top up. |
forbidden | 403 | No | This request is not allowed. |
notFound | 404 | No | The endpoint, or the thing you asked for, does not exist. |
unsupportedMediaType | 415 | No | Send the body as JSON with content-type: application/json. |
rateLimited | 429 | Yes | Too many requests. Wait for Retry-After. |
tooManyInFlight | 429 | Yes | Too many of your calls are running at once. Wait for one to finish. |
internalError | 500, 503 | Yes | Something went wrong on our side. Try again shortly. |
sourceChanged | 502 | No | The source answered in a shape we could not read. Retrying will not help yet. |
sourceUnavailable | 503 | Yes | The source is not answering right now. Try again in a few seconds. |
sourceRefused | 503 | Yes | The source is refusing requests right now. Try again in a few minutes. |
sourceTimeout | 504 | Yes | The source took too long to answer. Try again. |
Sign-in codes
These come only from the account sign-in flow, never from a data endpoint:| Code | Status | Retryable | What it means |
|---|---|---|---|
invalidCliSession | 400 | No | The sign-in session is not valid. |
invalidCliVerifier | 400 | No | The sign-in verifier is not valid. |
cliSessionNotFound | 404 | No | The sign-in session was not found or has expired. |
cliSessionExists | 409 | No | The sign-in session already exists. |
cliSessionCompleted | 409 | No | The sign-in session is no longer pending. |
Retrying
- Retry only when
retryableistrue. - If
retryAfterSecondsorRetry-Afteris present, wait that long first. Otherwise back off: for example 0.5 s, then 1 s, then 2 s. - Stop after a few tries.
async function call(path: string, input: unknown, attempts = 3) {
for (let attempt = 0; ; attempt++) {
const response = await fetch(`https://api.stophy.dev${path}`, {
method: "POST",
headers: {
authorization: `Bearer ${process.env.STOPHY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify(input),
});
const body = await response.json();
if (body.success) return body.data;
if (!body.error.retryable || attempt + 1 >= attempts) throw new Error(body.error.message);
const seconds = body.error.retryAfterSeconds ?? 0.5 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}
import os
import time
import requests
def call(path, payload, attempts=3):
for attempt in range(attempts):
response = requests.post(
f"https://api.stophy.dev{path}",
headers={"Authorization": f"Bearer {os.environ['STOPHY_API_KEY']}"},
json=payload,
timeout=30,
)
body = response.json()
if body["success"]:
return body["data"]
error = body["error"]
if not error["retryable"] or attempt + 1 >= attempts:
raise RuntimeError(error["message"])
time.sleep(error.get("retryAfterSeconds", 0.5 * 2**attempt))