Developers / Errors
Every failure is returned as RFC 7807 application/problem+json, with a stable type URI you can branch on.
Errors never come back as bare strings or HTML. The body is always the same four fields:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
{
"type": "https://puntersedge.online/errors/rate-limit",
"title": "Rate limit exceeded",
"status": 429,
"detail": {
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 30 requests/minute on free plan.",
"upgrade_url": "https://puntersedge.online/api-platform",
"retry_after_seconds": 60
}
}
Branch on type or status, not on title. The type URI is stable; wording is not.
detail is a
string for most errors, an array of field errors for 422, and an
object for 429. Type-check it before you render it, or a validation error
will crash your error handler.
| Code | What happened | What to do |
|---|---|---|
401Unauthorized |
The X-API-Key header is missing, or the key is not valid. |
Send your key as X-API-Key. Sandbox endpoints under /v1/demo/ need no key at all. |
402Payment Required |
The monthly credit allowance for your plan is exhausted. | Wait for the monthly reset or upgrade. This is about credits, not request rate — a 402 will not clear by retrying. |
403Forbidden |
The key is valid but not permitted to use this endpoint — for example a racing-only plan calling a sports endpoint, or an IP outside your whitelist. | Check your plan's endpoint set and any IP whitelist on the key. |
404Not Found |
No such route, or the requested resource does not exist. | Enumerate endpoints from /openapi.json rather than guessing paths. |
422Validation Error |
A parameter is missing, malformed or out of range. | detail is an array of field errors for this code — unlike other errors, where it is a string. Read each entry's loc to find the offending parameter. |
429Too Many Requests |
You exceeded your plan's requests-per-minute limit. | Honour the Retry-After header. See rate limits. |
500Internal Server Error |
An unexpected failure on our side. | Safe to retry with exponential backoff. Persisting? Tell us and check status. |
These are the two that get conflated, and retry logic that treats them alike will spin forever:
import time, requests
def call(url, key, attempts=4):
for i in range(attempts):
r = requests.get(url, headers={"X-API-Key": key}, timeout=30)
if r.status_code == 429: # too fast — wait it out
time.sleep(int(r.headers.get("Retry-After", 60)))
continue
if r.status_code == 402: # out of credits — retrying cannot help
raise RuntimeError(r.json()["detail"])
if r.status_code >= 500: # our fault — back off
time.sleep(2 ** i)
continue
r.raise_for_status()
return r.json()
raise RuntimeError("exhausted retries")
The official Python SDK already does this, raising typed exceptions (AuthenticationError, RateLimitError, NotFoundError, ServerError) with automatic retry on 5xx.