18+ Only  |  Gambling can be addictive — please gamble responsibly  |  Gambling Help: 1800 858 858  |  GambleAware

Developers / Errors

Errors

Every failure is returned as RFC 7807 application/problem+json, with a stable type URI you can branch on.

The shape

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.

One inconsistency worth coding around: 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.

Status codes

Code What happened What to do
401
Unauthorized
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.
402
Payment 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.
403
Forbidden
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.
404
Not Found
No such route, or the requested resource does not exist. Enumerate endpoints from /openapi.json rather than guessing paths.
422
Validation 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.
429
Too Many Requests
You exceeded your plan's requests-per-minute limit. Honour the Retry-After header. See rate limits.
500
Internal Server Error
An unexpected failure on our side. Safe to retry with exponential backoff. Persisting? Tell us and check status.

402 vs 429 — they are not the same problem

These are the two that get conflated, and retry logic that treats them alike will spin forever:

Handling them

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.

This site contains wagering-related analysis and is intended for Australian users aged 18+. Gambling involves risk. Please gamble responsibly.