We do not handle life-threatening emergencies: call 112.

+34 611 673 280

DevelopersAPI v1

Guides

Rate limits

How many requests per minute each key allows, the X-RateLimit headers, what happens when you exceed the limit (429 and Retry-After) and how to retry properly.

Last reviewed: October 1, 2026

Per-key limit

Each API key has a requests-per-minute limit. The default is 60 requests per minute, and it can be adjusted per key to match your volume: if you need more, ask us to raise it and tell us what for.

  • The limit is per key, not per IP address or per organisation: if you have two keys, each has its own.
  • It's counted in calendar-minute windows: the counter resets at the start of every minute.
  • Every request with a valid key and the required scope counts, including those that end in a validation error. Requests rejected with 401 or 403 don't count.
  • The webhooks we send you don't count: they aren't your requests.

Headers

Every authenticated response includes:

HeaderMeaning
X-RateLimit-LimitRequests per minute your key allows.
X-RateLimit-RemainingRequests you have left in the current minute.
HTTP
HTTP/1.1 200 OK
X-Serad-Environment: production
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57

When you exceed the limit

If you go over, the API responds 429 Too Many Requests with the Retry-After header (seconds until the next minute starts) and an RFC 9457 body:

HTTP
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

{
  "type": "/errors/provider-api/rate-limited",
  "title": "Demasiadas peticiones",
  "status": 429,
  "detail": "Se ha superado el límite de la clave de API. Reintenta en 23 s",
  "instance": "/api/integrations/v1/visits",
  "timestamp": "2026-10-01T09:10:37.000Z"
}

The rejected request has not been executed: if it was a create request, the visit hasn't been created. Wait as long as Retry-After says and repeat it (with the same Idempotency-Key).

Retry strategy

Not every error should be retried. The rule:

ResponseRetry?How
429YesWait exactly Retry-After seconds.
500, 502, 503, 504, network error, timeoutYesExponential backoff with random jitter: 1 s, 2 s, 4 s, 8 s… up to a cap (for example, 5 attempts or 1 minute).
409 idempotency-in-progressYesA few seconds, with the same Idempotency-Key.
400, 401, 403, 404, 409 (others)NoIt's a problem with the request: fix it. Retrying would give the same result.

And always:

  • Retry create requests with the same Idempotency-Key (idempotency), or you could create duplicate visits.
  • Add random jitter to the waits: if many of your processes fail at the same time, they shouldn't all retry in the same second.
  • Set a cap. If it's still failing after the retries, log the error, alert a person and, if the visit is urgent, have them call the dispatch centre (+34 611 673 280).
// Retry with exponential backoff, jitter and respect for Retry-After.
async function seradFetch(url: string, init: RequestInit, maxAttempts = 5): Promise<Response> {
  for (let attempt = 1; ; attempt++) {
    let response: Response | null = null;
    try {
      response = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
    } catch {
      // Network error or timeout: retried below.
    }

    const retryable = !response || response.status === 429 || response.status >= 500;
    if (!retryable || attempt >= maxAttempts) {
      if (!response) throw new Error('The Salud en Ruta API is not responding');
      return response;
    }

    const retryAfter = Number(response?.headers.get('Retry-After'));
    const backoff = Math.min(60_000, 1000 * 2 ** (attempt - 1));
    const waitMs = response?.status === 429 && retryAfter > 0 ? retryAfter * 1000 : backoff * (0.5 + Math.random());
    await new Promise((resolve) => setTimeout(resolve, waitMs));
  }
}

Something missing or not matching what you see? Email our technical team: info@saludenruta.es.