API integrations
Public beta
The HTTP API is in public beta. Endpoints and authentication are
stable. The production endpoint is now active on the API Gateway;
the hostname will move to api.raceranger.com once DNS is live.
Update your base URL when that happens — no other change is required.
RaceRanger exposes a read-and-write HTTP API for organisations that want to push athlete data in, pull live race results out, or subscribe to webhook deliveries when something changes during a race.
The API is intended for organisation tooling, federation integrations, and reporting pipelines. The mobile app itself does not use this API — it talks to Firebase directly.
Base URLs
| Environment | Current URL | Planned URL |
|---|---|---|
| Production | Active — see your API key welcome email for the current URL | https://api.raceranger.com |
| Testing | https://raceranger-api-2wm0cky.ew.gateway.dev |
https://api-test.raceranger.com |
All paths are versioned under /v1. The current version is v1; any
breaking changes will ship under a new prefix.
In this section
- Quickstart — zero to a verified live edit: mint a key, make a write, see it land in the app.
- Authentication — API key tiers, scopes, how to mint a key from inside the app.
- Webhooks — subscribing to live event deliveries, verifying HMAC signatures, retries.
- Reference — full OpenAPI reference for every endpoint, including request and response schemas, and recent changes.
Quick example
{
"data": [
{
"id": "evt_abc",
"name": "Spring Sprint 2026",
"sport": "triathlon",
"dateFrom": "2026-05-04T07:00:00.000Z",
"isOver": false
}
]
}
Rate limits
Default per-key limits are 120 requests/minute and 1000
requests/hour. Every successful response includes
X-RateLimit-Limit-Minute and X-RateLimit-Limit-Hour headers. When a
key exceeds its limit the API returns HTTP 429 with a Retry-After
header — wait the requested number of seconds before retrying.
The limits count clock minutes and hours. Refused requests count too, so retrying in a loop uses up your hourly allowance. A coarser per-IP limit also applies to every request, before the key is checked.
Idempotency
Every create and update — POST and PATCH, including bulk imports and
webhook subscriptions — needs an Idempotency-Key header. Deleting a
webhook and rotating its secret don't. Pick a stable client-generated key
between 8 and 128 characters. Keys belong to the API key that sends them.
- Reusing a key with the same request within 24 hours returns the cached
response, marked with an
Idempotent-Replayed: trueheader. - Reusing it with a different body, or on a different endpoint, returns
400. - If the first request with that key is still running, the retry returns
409— try again a moment later. - Only successful responses are kept. If a request fails, retrying it with the same key runs it again.
- Webhook signing secrets are never replayed: a replayed response leaves
them out and lists the omitted fields in the
Idempotent-Redactedheader.
Caching
GET responses carry a Cache-Control header: 10 seconds for a race
that is active, 5 minutes once the event is over, and 60 seconds
otherwise. If your HTTP client or a proxy honours it, a change can take
that long to show. Use webhooks to hear about changes as
they happen.
Errors
Errors follow a single shape:
| Status | code |
Meaning |
|---|---|---|
400 |
bad_request |
Validation failed, the body has an unknown field, or the Idempotency-Key was used for a different request. |
401 |
unauthorized |
Missing, invalid, expired or revoked API key. |
402 |
payment_required |
Creating an event: your organisation has no event credits of the requested eventSize left. |
403 |
forbidden |
The key lacks the scope or can't reach the resource, or the change isn't allowed — for example, the event is over or the race has started. |
404 |
not_found |
The resource does not exist. |
409 |
conflict |
A request with the same Idempotency-Key is still in progress — retry shortly. |
429 |
rate_limited |
Rate limit exceeded. |
503 |
service_unavailable |
The API briefly can't check rate limits — retry after the Retry-After seconds. |
Malformed JSON returns an HTML error
If you send Content-Type: application/json with a body that isn't
valid JSON, the request returns 400 Bad Request with an HTML body
instead of our usual JSON error. The platform parses the body before
the API sees the request. Validate your JSON client-side, or always
JSON.stringify(...) before sending.