Production access is free and self-serve with an account. · Synthetic demo · no account needed

FinchNode

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.

Node.js
// 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));
  }
}
Python
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/sessions only with the same Idempotency-Key and the same body.
  • Don't retry sandbox controls blindly. Each call changes the synthetic patient.
  • Don't retry other 4xx responses 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=100 instead of many small pages.
  • Read the change feed instead of re-reading whole categories.
  • Use webhooks instead of polling sessions.