Rate limits
Partners endpoints rate-limit at 100 requests per minute per API key. Every response carries headers describing the current quota state; 429 responses include Retry-After.
Limits
| Endpoint group | Default |
|---|---|
| /v1/partners/* | 100 requests / minute / API key |
| /v1/surveys | 60 requests / minute (when shipped) |
| /v1/uploads/presign | 60 requests / minute (when shipped) |
| /v1/health, /v1/openapi | Not rate-limited |
Implemented as a Postgres-backed sliding window with 1-second resolution. Rate is per API key — multiple tokens for the same organization have independent quotas.
Response headers
Every Partners-tagged response (success and error) carries:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Requests allowed per window (e.g. 100). |
| X-RateLimit-Remaining | Requests remaining in the current window. |
| X-RateLimit-Reset | Unix-seconds when the current window resets. |
| X-Request-Id | Correlation id for support; matches error.request_id. |
| Retry-After | Seconds to wait before retrying. 429 only. |
Handling 429
When you exceed the limit, you get:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715040060
Retry-After: 42
{
"error": {
"type": "rate_limit_error",
"code": "RATE_LIMITED",
"message": "Rate limit exceeded for this API key.",
"doc_url": "https://developer.tendralhealth.com/docs/errors#RATE_LIMITED",
"request_id": "req_018f5a2e7c127c8da123cafed010001"
}
}Honor Retry-After. Don't retry tighter — that just keeps the limit pegged. A simple, correct client recipe:
async function callWithRateLimit(url, headers) {
for (let attempt = 0; attempt < 5; attempt++) {
const r = await fetch(url, { headers })
if (r.status !== 429) return r
const retryAfter = parseInt(r.headers.get('retry-after') ?? '60', 10)
await sleep(retryAfter * 1000 + Math.random() * 1000)
}
throw new Error('Exceeded retry budget')
}Need a higher limit?
100/min is the default. If a legitimate use case needs more, talk to your Tendral contact — limits are per-key and we can raise them on request.
