Developer API
Build on Referee.Observer data. The public REST API exposes match officials, matches, discipline ratings and appointments. Webhooks push domain events to your systems in real time.
Authentication
The API uses bearer API keys. Every request must send an Authorization header:
Authorization: Bearer ro_your_api_key_here- Keys are issued from the admin panel at
/admin/api-keys. Each key belongs to an organization and is shown only once at creation — store it securely. - Keys look like
ro_<48 hex characters>. The server stores only a SHA-256 hash, so a leaked database never exposes usable keys. - Scopes / permissions: keys carry a permissions array (default
["read"]). The v1 endpoints are read-only. - A missing or malformed header returns
401withWWW-Authenticate: Bearer. A revoked or unknown key also returns401.
Rate limits
Each key is limited to a rolling 24-hour quota — by default 1000 requests/day (configurable per key). Every authenticated response carries rate-limit headers:
X-RateLimit-Limit: 1000 # quota for the 24h window
X-RateLimit-Remaining: 994 # requests left
X-RateLimit-Reset: 1772150400 # unix epoch seconds when the window resetsWhen the quota is exhausted the API responds 429 Too Many Requests with a Retry-After header (seconds until the window resets), alongside the same X-RateLimit-* headers.
API reference
The interactive reference below is generated from the live OpenAPI spec at /api/openapi. Use the Authorize button to paste your key and try requests.
Loading API reference…
Webhooks
Webhooks are configured per organization. When a subscribed event fires, we POST a signed JSON body to your endpoint. The request carries these headers:
X-Webhook-Event: match.created # the event name
X-Webhook-Signature: <hmac-sha256-hex> # HMAC-SHA256 of the raw body
X-Delivery-ID: <uuid> # unique per delivery
Content-Type: application/json
User-Agent: RefereeObserver-Webhooks/1.0The body always has the shape { event, delivery_id, payload }. Failed deliveries (non-2xx, timeout) are retried with exponential backoff (1m, 5m, 30m, 2h, then a final 6h attempt).
Event catalog
match.created
A new match was imported or created for one of your organization’s competitions.
{
"event": "match.created",
"delivery_id": "d3b07384-...-a1b2c3",
"payload": {
"match_id": 50123,
"competition_id": 42,
"home_team": "Olympique Lyonnais",
"away_team": "Paris Saint-Germain",
"match_date": "2026-03-14"
}
}assignment.made
An official appointment was confirmed for one of your matches.
{
"event": "assignment.made",
"delivery_id": "e7c91a02-...-f4d5e6",
"payload": {
"match_id": 50123,
"official_id": 123,
"role": "referee"
}
}official.updated
An official’s profile linked to your organization was edited.
{
"event": "official.updated",
"delivery_id": "a1f2b3c4-...-998877",
"payload": {
"official_id": 123
}
}Verifying the signature
Recompute an HMAC-SHA256 over the raw request body using your webhook’s secret and compare it (constant-time) to X-Webhook-Signature.
Node.js
import crypto from "crypto";
// Express-style handler. Use the RAW request body (bytes as received).
function verifyWebhook(rawBody, headers, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
const received = headers["x-webhook-signature"] || "";
// constant-time compare
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python
import hmac, hashlib
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")Code examples
curl
curl -s https://www.referee.observer/api/v1/officials?country=FRA&limit=5 \
-H "Authorization: Bearer ro_your_api_key_here"JavaScript (fetch)
const res = await fetch(
"https://www.referee.observer/api/v1/officials?country=FRA&limit=5",
{ headers: { Authorization: "Bearer ro_your_api_key_here" } }
);
console.log("remaining:", res.headers.get("X-RateLimit-Remaining"));
const { api_version, data, pagination } = await res.json();
console.log(api_version, pagination, data);Python (requests)
import requests
resp = requests.get(
"https://www.referee.observer/api/v1/officials",
params={"country": "FRA", "limit": 5},
headers={"Authorization": "Bearer ro_your_api_key_here"},
)
resp.raise_for_status()
print("remaining:", resp.headers.get("X-RateLimit-Remaining"))
body = resp.json()
print(body["api_version"], body["pagination"])
for official in body["data"]:
print(official["full_name"], official["nationality"])