Errors
Every error has the same shape, a stable type and a plain-English message. Here's what each one means and how to fix it.
When something goes wrong, you get a non-2xx HTTP status and this body:
{
"error": {
"type": "insufficient_credits",
"message": "This call needs 3 credits and you have 1. Buy credits at /app/billing.",
"docs": "https://veqen-app.com/docs/errors#insufficient_credits"
}
}
typeis stable. Branch on it in your code.messageis written for humans. Log it, or show it to a developer.- Every response also has an
X-Request-Idheader. Include it if you contact support.
Failed requests are never charged.
Error types
missing_api_key
HTTP 401. The request had no API key. Add the header Authorization: Bearer vq_live_....
invalid_api_key
HTTP 401. The key is malformed, doesn't exist, or was revoked. Copy it again carefully, or create a new key in the dashboard.
account_suspended
HTTP 403. The account is suspended, usually after a payment dispute. Contact us.
insufficient_credits
HTTP 402. Your balance doesn't cover this call. Buy credits, turn on auto top-up, or wait for your free credits to reset on the 1st. Unconfirmed accounts get no free credits: confirm your email first.
invalid_request
HTTP 422. The JSON body is missing a field or has the wrong type. The message names the field, for example email: Field required.
idempotency_key_reused
HTTP 422 or 409. You reused an Idempotency-Key with a different request body (422), or sent the same key twice at the same moment (409). Use a fresh key for each distinct request. See idempotency.
rate_limited
HTTP 429. Too many requests per second. Wait for the number of seconds in the Retry-After header, then retry. See rate limits.
server_error
HTTP 500. Something broke on our side. Retry with exponential backoff. If you send an Idempotency-Key, retries are always safe.
A good retry policy
Retry on 429 and 5xx only. Never retry 4xx errors other than 429: the same request will fail the same way.
import time, requests
def check(payload, key, attempts=4):
for attempt in range(attempts):
r = requests.post(
"https://veqen-app.com/v1/signup/check",
headers={"Authorization": f"Bearer {key}", "Idempotency-Key": payload["request_id"]},
json={k: v for k, v in payload.items() if k != "request_id"},
timeout=10,
)
if r.status_code == 429 or r.status_code >= 500:
time.sleep(float(r.headers.get("Retry-After", 2**attempt)))
continue
return r
return r
async function check(body, key, requestId, attempts = 4) {
let res;
for (let attempt = 0; attempt < attempts; attempt++) {
res = await fetch("https://veqen-app.com/v1/signup/check", {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
"Idempotency-Key": requestId,
},
body: JSON.stringify(body),
});
if (res.status !== 429 && res.status < 500) return res;
const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
await new Promise((r) => setTimeout(r, wait * 1000));
}
return res;
}
Fail open, not closed
If Veqen is unreachable, don't block real people. Treat a timeout or 5xx like a review verdict: let them in, and require email confirmation.