Patient-authorized EHR integration
Rate limits
Each API key gets 300 requests a minute; when you go over, wait for Retry-After and try again.
Know the limits
| Limit | Scope |
|---|---|
| 300 requests a minute | Each API key, across REST and MCP |
| 300 requests a minute | All delegated agent credentials of one app and environment, together |
| 600 requests a minute | Each network address, checked before authentication, per server |
| 120 requests a minute | Each network address on the demo API, by default |
GET /health isn't limited.
Read the headers
Authenticated responses carry your remaining budget. Some error responses may not.
| Header | Meaning |
|---|---|
RateLimit-Limit |
The limit for this key. |
RateLimit-Remaining |
Requests left in this window. |
RateLimit-Reset |
When the window resets, in Unix seconds. On the demo API, it's seconds from now. |
Handle a 429
The REST API returns 429 with code rate_limited and a Retry-After header in seconds. Wait at least that long, then retry.
// For GET requests. Retries 429, 5xx, and network errors, and honors Retry-After.
async function getWithRetry(url, headers, attempts = 5) {
for (let attempt = 1; ; attempt += 1) {
const res = await fetch(url, { headers, signal: AbortSignal.timeout(30_000) }).catch((error) => error);
const failed = res instanceof Error || res.status === 429 || res.status >= 500;
if (!failed) return res;
if (attempt === attempts) {
if (res instanceof Error) throw res;
return res;
}
const retryAfter = res instanceof Error ? 0 : Number(res.headers.get('Retry-After')) || 0;
const backoff = Math.min(30, 2 ** attempt) + Math.random();
await new Promise((resolve) => setTimeout(resolve, Math.max(retryAfter, backoff) * 1000));
}
}import random
import time
import requests
def get_with_retry(url, headers, attempts=5):
"""For GET requests. Retries 429, 5xx, and network errors, and honors Retry-After."""
for attempt in range(1, attempts + 1):
try:
res = requests.get(url, headers=headers, timeout=30)
except requests.RequestException:
if attempt == attempts:
raise
res = None
if res is not None:
retryable = res.status_code == 429 or res.status_code >= 500
if not retryable or attempt == attempts:
return res
retry_after = float(res.headers.get("Retry-After", 0) or 0) if res is not None else 0
time.sleep(max(retry_after, min(30, 2 ** attempt) + random.random()))Use these for reads only:
- Retry
POST /connect/sessionsonly with the sameIdempotency-Keyand the same body. - Don't retry sandbox controls blindly. Each call changes the synthetic patient.
- Don't retry other
4xxresponses blindly. Handle them by code;403 authority_changed, for example, asks you to retry the read once.
Over MCP, a key or address limit fails the whole request with a JSON-RPC error and HTTP 429. A throttled tool returns retryAfterSeconds in its error metadata.
Stay under the limit
- Page with
limit=100instead of many small pages. - Read the change feed instead of re-reading whole categories.
- Use webhooks instead of polling sessions.