Docs/Getting Started

Conventions

Base URL, request and response format, timestamps, money, identifiers, request IDs, pagination, idempotency and rate limits.

Base URL and versioning

https://api.physioaroundme.com/v1

The same API is also available at https://physioaroundme.com/api/v1. Both hosts serve sandbox and live traffic, and your API key decides the environment.

The major version is part of the path (/v1). Webhook payloads carry an api_version field (currently 2026-10-01) that identifies the payload format.

Requests and responses

  • Bodies are JSON, encoded as UTF-8. Send Content-Type: application/json on requests that have a body.
  • Unknown request fields are ignored, so double-check field names: a misspelled optional field is silently dropped.
  • Every resource has an object field (referral, session, report, provider, referral_link, commercial_terms, commission, list, and so on) that tells you what you're looking at.
  • Fields without a value are returned as null rather than left out.

Timestamps

Timestamps are ISO-8601 strings in UTC with millisecond precision, for example 2026-10-13T09:30:00.000Z. Query parameters that take a time (such as created_after, from and to) expect the same format.

Two headers use Unix time in seconds instead: X-RateLimit-Reset and the t value in webhook signatures (and X-Rehabify-Timestamp).

Money

Amounts are integers in kobo, in fields whose names end in _kobo. The currency is always given alongside the amount as "currency": "NGN".

NairaKobo value
₦15,000.001500000
₦1,425.00142500

Percentages are plain numbers. For example, "revenue_share_percent": 10 means 10%.

Identifiers

IdentifierExampleNotes
Referral idjd7f3k2m9x1q8w4e5r6t7y8u9i0o1p2aOpaque. Store it as a string.
tracking_codeRHB-4829173655RHB- followed by 10 digits. Older referrals may have RHB- followed by 5 digits. Case-insensitive. Safe to show to patients.
partner_referenceHMO-REF-2026-0091Your own reference, 1–100 characters. Unique per partner per environment.
Event idevt_01JA4T7Q9Y2V3W4X5Z6A7B8C9DWebhook event ID. Stable across retries.
Request IDreq_5f2c9a1b7d3e4f60a1b2c3d4Unique for every API request.

Endpoints that take a referral in the path (GET /partners/referrals/{id} and GET /partners/reports/{id}) accept any of the referral's id, tracking_code or your partner_reference. URL-encode a partner_reference that contains special characters.

Metadata

POST /partners/referrals accepts a metadata object for your own tags:

  • Up to 20 keys. Each key is a string of up to 40 characters.
  • Values must be strings of up to 500 characters.
  • Metadata is echoed back on the referral and included in webhook events.

Because metadata travels in webhooks, don't put personal or clinical information in it. Use it for things like plan names, campaign codes or internal IDs.

Request IDs and environment headers

Every response, including error responses, carries:

HeaderExampleMeaning
X-Request-Idreq_5f2c9a1b7d3e4f60a1b2c3d4Unique ID for this request. Error bodies repeat it as error.request_id. Quote it when you contact support.
X-Rehabify-Environmentsandbox or liveThe environment that handled the request.

Pagination

List endpoints return a list object:

{
  "object": "list",
  "data": [],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0xMC0xM1QwOTozMDowMC4wMDBaIn0"
}
ParameterDescription
limitNumber of items to return, from 1 to 100. Default 25.
cursorThe next_cursor value from the previous page. Treat it as an opaque string.

When has_more is true, pass next_cursor as cursor to get the next page. On the last page, has_more is false and next_cursor is null.

Cursor pagination is supported on GET /partners/referrals, GET /partners/referral-links and GET /partners/commissions. GET /partners/sessions and GET /partners/providers return a single page of up to limit items, with next_cursor always null. Narrow those queries with filters if has_more is true.

// list-all-referrals.mjs (Node.js 18+)
const BASE_URL = "https://api.physioaroundme.com/v1";
const headers = { Authorization: `Bearer ${process.env.REHABIFY_API_KEY}` };

let cursor = null;
do {
  const params = new URLSearchParams({ limit: "100", status: "in_treatment" });
  if (cursor) params.set("cursor", cursor);

  const res = await fetch(`${BASE_URL}/partners/referrals?${params}`, { headers });
  const page = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${page.error.code}: ${page.error.message}`);

  for (const referral of page.data) console.log(referral.tracking_code, referral.status);
  cursor = page.has_more ? page.next_cursor : null;
} while (cursor);

Idempotency

Network failures happen. To retry a POST safely without creating duplicates, send an Idempotency-Key header: any unique string of up to 255 characters. A UUID v4 works well.

Idempotency-Key: 9b2f6c1e-4d7a-4c3b-8e5f-2a1d0c9b8e7f

Rehabify remembers each key for 24 hours, separately for each environment.

You sendResult
A new keyThe request is processed normally.
The same key with the same bodyThe original response is replayed, with the header Idempotent-Replayed: true. Nothing is created twice.
The same key with a different body422 idempotency_key_reused
The same key while the first request is still processing409 idempotency_request_in_progress. Wait briefly, then retry.

Best practices:

  • Generate one key per logical operation, such as "refer patient X", store it with your pending record, and reuse it only to retry that exact request.
  • If you change the request body (for example, to fix a validation error), use a new key.
  • Retry 429, 500 and 503 responses, and network timeouts, with the same key.
  • All authenticated POST endpoints accept Idempotency-Key. It's strongly recommended for POST /partners/referrals.

Rate limits

Limits depend on the environment of the API key:

EnvironmentLimit
Sandbox (sk_test_)10 requests per second
Live (sk_live_)20 requests per second
Live, enterprise contracts100 requests per second

Every authenticated response includes these headers:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetWhen the window resets, in Unix seconds

If you exceed the limit, you get 429 rate_limit_exceeded with a Retry-After header (in seconds). Wait that long, then retry with exponential backoff and jitter:

// Retries 429, 500 and 503 responses, and network errors.
// Pass the same Idempotency-Key in options.headers on every attempt.
export async function fetchWithRetry(url, options, maxRetries = 4) {
  for (let attempt = 0; ; attempt++) {
    let res;
    try {
      res = await fetch(url, options);
    } catch (err) {
      if (attempt >= maxRetries) throw err; // network error
    }
    if (res && ![429, 500, 503].includes(res.status)) return res;
    if (attempt >= maxRetries) return res;

    const retryAfter = Number(res?.headers.get("retry-after"));
    const delayMs = retryAfter > 0
      ? retryAfter * 1000
      : Math.min(30_000, 500 * 2 ** attempt) * (0.5 + Math.random() / 2);
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
}

CORS

Partner endpoints (/partners/* and /sandbox/*) don't send CORS headers, because they must only be called from your servers. The public lead capture endpoint is the exception. It's CORS-enabled and needs no API key.

Conventions | Rehabify Docs | Physio Around Me by Rehabify Health