Skip to content

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

curl https://raceranger-api-2wm0cky.ew.gateway.dev/v1/events \
  -H "Authorization: Bearer rr_o_..."
{
  "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: true header.
  • 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-Redacted header.

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:

{
  "error": {
    "code": "forbidden",
    "message": "Missing required scope: write:events"
  }
}
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.