API reference

Version 1.0.0-experimental. Read-only access to your organisation's own records with scoped API keys, and signed webhooks for events.

Experimental, and off by default. The API answers only when the deployment operator has enabled it and your organisation's administrator has switched it on in the API & Integrations Center (Settings → API & Integrations). It has no write endpoints. It has not yet been used by a customer's system in production; treat it as a preview and tell us what you build on it.

Authentication

Every request carries an API key in the Authorization header. A browser session is never accepted, so the API cannot be called with someone's signed-in cookie.

Authorization: Bearer ark_live_<keyId>_<secret>
  • Keys are created by an organisation administrator, are shown once, and are stored only as a SHA-256 hash.
  • A key expires within your organisation's maximum lifetime (default 365 days, never more than 365). Rotation issues a new key and keeps the old one working for a grace period you choose (up to 7 days).
  • A key never does more than the administrator who created it can do now. Deactivate them, change their role, or end the organisation's subscription to a product, and the key loses that access on its next request.
  • ark_live_ keys work only on a production deployment and ark_test_ keys only on a test deployment.

Scopes

ScopeApplicationGrants
rcm.claims:readArkCloud RCMClaim status, amounts and dates. No patient demographics, no denial narrative.
rehab.appointments:readArkCloud RehabAppointment times, status and the ids of the patient, episode and clinician. No notes, no reasons.

Endpoints

The machine-readable description is at /api/v1/openapi.json (OpenAPI 3.0). Administrators can try each endpoint from the API explorer in the API & Integrations Center.

ArkCloud Health platform

GET/api/v1/me

Identifies the calling key: its organisation, environment, scopes and expiry. Useful as a connectivity check.

Scope: any valid key · Stability: experimental

Example
GET /api/v1/me

{
  "data": {
    "credentialId": "3c1d…",
    "tenantId": "9b2e…",
    "environment": "live",
    "scopes": [
      "rcm.claims:read"
    ],
    "expiresAt": "2027-01-01T00:00:00.000Z"
  }
}

GET/api/v1/events

The events an endpoint can subscribe to, with the exact fields each one carries.

Scope: any valid key · Stability: experimental

Example
GET /api/v1/events

{
  "data": [
    {
      "type": "claim.submitted",
      "application": "rcm",
      "description": "A claim's 837P was uploaded to the clearinghouse mailbox.",
      "fields": {
        "claimId": "string"
      }
    }
  ]
}

ArkCloud RCM

GET/api/v1/rcm/claims

Claims in the organisation, oldest change first, so a client can poll with `updatedSince` and page with `cursor`.

Scope: rcm.claims:read · Stability: experimental

  • status (query) — Filter by claim status.
  • updatedSince (query) — Only claims changed at or after this time.
  • limit (query) — Page size, 1-100. Default 25.
  • cursor (query) — `nextCursor` from the previous page.
Example
GET /api/v1/rcm/claims?status=SUBMITTED&limit=1

{
  "data": [
    {
      "id": "6f1c2b9e-0d4f-4b8e-9a51-3b7d2c1e0a11",
      "accountId": "0b7f3e21-5c9a-4d0e-8f6b-1a2c3d4e5f60",
      "claimNumber": "CLM-000123",
      "claimType": "PROFESSIONAL",
      "status": "SUBMITTED",
      "payerSequence": "PRIMARY",
      "payerName": "Example Health Plan",
      "payerId": "EXMPL",
      "payerClaimNumber": null,
      "serviceFromDate": "2026-09-29T00:00:00.000Z",
      "serviceToDate": null,
      "billedAmount": "180.00",
      "allowedAmount": null,
      "paidAmount": null,
      "patientResponsibility": null,
      "denialCode": null,
      "submittedAt": "2026-09-30T14:02:11.000Z",
      "adjudicatedAt": null,
      "paidAt": null,
      "createdAt": "2026-09-30T13:58:40.000Z",
      "updatedAt": "2026-09-30T14:02:11.000Z"
    }
  ],
  "pagination": {
    "nextCursor": "MjAyNi0wOS0zMFQxNDowMjoxMS4wMDBafDZmMWMy…",
    "limit": 1
  }
}

GET/api/v1/rcm/claims/{id}

One claim by id.

Scope: rcm.claims:read · Stability: experimental

  • id (path, required) — Claim id.
Example
GET /api/v1/rcm/claims/6f1c2b9e-0d4f-4b8e-9a51-3b7d2c1e0a11

{
  "data": {
    "id": "6f1c2b9e-0d4f-4b8e-9a51-3b7d2c1e0a11",
    "accountId": "0b7f3e21-5c9a-4d0e-8f6b-1a2c3d4e5f60",
    "claimNumber": "CLM-000123",
    "claimType": "PROFESSIONAL",
    "status": "SUBMITTED",
    "payerSequence": "PRIMARY",
    "payerName": "Example Health Plan",
    "payerId": "EXMPL",
    "payerClaimNumber": null,
    "serviceFromDate": "2026-09-29T00:00:00.000Z",
    "serviceToDate": null,
    "billedAmount": "180.00",
    "allowedAmount": null,
    "paidAmount": null,
    "patientResponsibility": null,
    "denialCode": null,
    "submittedAt": "2026-09-30T14:02:11.000Z",
    "adjudicatedAt": null,
    "paidAt": null,
    "createdAt": "2026-09-30T13:58:40.000Z",
    "updatedAt": "2026-09-30T14:02:11.000Z"
  }
}

ArkCloud Rehab

GET/api/v1/rehab/appointments

Visits whose start time falls in [from, to). The window may be at most 31 days; it defaults to the next 7 days.

Scope: rehab.appointments:read · Stability: experimental

  • from (query) — Window start (inclusive). Default: now.
  • to (query) — Window end (exclusive). Default: from + 7 days.
  • status (query) — Filter by appointment status.
  • limit (query) — Page size, 1-100. Default 25.
  • cursor (query) — `nextCursor` from the previous page.
Example
GET /api/v1/rehab/appointments?from=2026-10-06T00:00:00Z&to=2026-10-07T00:00:00Z

{
  "data": [
    {
      "id": "a1d0c3f4-77b2-4c39-9e0a-5b6c7d8e9f01",
      "patientId": "c2e4f6a8-1b3d-4f5a-8c7e-9d0b1a2c3e4f",
      "episodeId": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9",
      "providerId": "u_8f3c",
      "encounterId": null,
      "startsAt": "2026-10-06T15:00:00.000Z",
      "endsAt": "2026-10-06T15:45:00.000Z",
      "status": "SCHEDULED",
      "telehealth": false,
      "checkedInAt": null,
      "completedAt": null,
      "cancelledAt": null,
      "createdAt": "2026-10-01T09:12:00.000Z",
      "updatedAt": "2026-10-01T09:12:00.000Z"
    }
  ],
  "pagination": {
    "nextCursor": null,
    "limit": 25
  }
}

GET/api/v1/rehab/appointments/{id}

One appointment by id.

Scope: rehab.appointments:read · Stability: experimental

  • id (path, required) — Appointment id.
Example
GET /api/v1/rehab/appointments/a1d0c3f4-77b2-4c39-9e0a-5b6c7d8e9f01

{
  "data": {
    "id": "a1d0c3f4-77b2-4c39-9e0a-5b6c7d8e9f01",
    "patientId": "c2e4f6a8-1b3d-4f5a-8c7e-9d0b1a2c3e4f",
    "episodeId": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9",
    "providerId": "u_8f3c",
    "encounterId": null,
    "startsAt": "2026-10-06T15:00:00.000Z",
    "endsAt": "2026-10-06T15:45:00.000Z",
    "status": "SCHEDULED",
    "telehealth": false,
    "checkedInAt": null,
    "completedAt": null,
    "cancelledAt": null,
    "createdAt": "2026-10-01T09:12:00.000Z",
    "updatedAt": "2026-10-01T09:12:00.000Z"
  }
}

Examples

cURL

curl -H "Authorization: Bearer $ARKCLOUD_API_KEY" \
  "https://<your ArkCloud address>/api/v1/rcm/claims?status=SUBMITTED&limit=25"

JavaScript / TypeScript

const res = await fetch(`${base}/api/v1/rehab/appointments?from=${from}&to=${to}`, {
  headers: { Authorization: `Bearer ${process.env.ARKCLOUD_API_KEY}` },
});
if (res.status === 429) await sleep(Number(res.headers.get("Retry-After")) * 1000);
const { data, pagination } = await res.json();
// pass pagination.nextCursor back as ?cursor= until it is null

Python

import os, requests

r = requests.get(
    f"{base}/api/v1/rcm/claims",
    params={"updatedSince": "2026-10-01T00:00:00Z", "limit": 100},
    headers={"Authorization": f"Bearer {os.environ['ARKCLOUD_API_KEY']}"},
    timeout=30,
)
r.raise_for_status()
body = r.json()  # {"data": [...], "pagination": {"nextCursor": ..., "limit": 100}}

Pagination and filtering

List endpoints return { data, pagination: { nextCursor, limit } }. Pass nextCursor back as ?cursor= until it is null. limit is 1 to 100 (default 25). Claims are ordered by last change, oldest first, so updatedSince plus the cursor is a complete incremental sync. Appointments are filtered by a start-time window of at most 31 days.

Errors

Errors are { error: { code, message, requestId } }. Every response carries X-Request-Id; quote it when reporting a problem. A record in another organisation, or one that does not exist, is the same 404 NOT_FOUND.

503API_UNAVAILABLEThe API is not enabled on this deployment.
401UNAUTHENTICATEDNo key, or not in the form Authorization: Bearer ark_<env>_...
401INVALID_CREDENTIALThe key is not recognised by this deployment.
401CREDENTIAL_REVOKEDThe key was revoked.
401CREDENTIAL_EXPIREDThe key passed its expiry date.
401CREDENTIAL_OWNER_INACTIVEThe administrator who created the key has left the organisation or been deactivated.
403API_DISABLED_FOR_ORGANIZATIONThe organisation's administrator has switched API access off.
403INSUFFICIENT_SCOPEThe key was not granted the scope this endpoint requires.
403PRODUCT_NOT_AVAILABLEThe organisation, or the key's creator, cannot use this product.
403PERMISSION_DENIEDThe key's creator's role no longer allows this.
404NOT_FOUNDNo such record in this organisation.
400INVALID_PARAMETERA query or path parameter is malformed; the message names it.
429RATE_LIMITEDToo many requests for this key this minute. Retry after the Retry-After header.
500INTERNAL_ERRORUnexpected failure. Quote the X-Request-Id when reporting it.

Rate limits

Per key, per minute: your organisation's setting (default 120, at most 600). Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 carries Retry-After in seconds. Repeated failed authentication from one address is also limited.

Webhooks

An administrator registers an HTTPS endpoint, chooses events, and verifies it: ArkCloud sends a signed webhook.ping and the endpoint must answer 2xx. Private, loopback and cloud-metadata addresses are refused when the endpoint is saved and again before every delivery, and redirects are not followed.

Each delivery is a POST of { id, type, apiVersion, createdAt, tenantId, data }. Events carry identifiers, statuses, codes, amounts and times - never names, dates of birth, contact details or note text. Fetch more through the API with a scoped key if your system needs it.

EventWhendata fields
appointment.scheduledA visit was booked.appointmentId, patientId, episodeId, providerId, startsAt, endsAt, telehealth
appointment.completedA visit was completed; its encounter, if one was started, is completed with it.appointmentId, patientId, encounterId, completedAt
appointment.cancelledA visit was cancelled or recorded as a no-show.appointmentId, patientId, status
documentation.signedA clinical note was signed.documentId, patientId, episodeId, encounterId, documentType, version, signedAt
charge.postedA delivered service was posted to the patient account as a charge.chargeId, serviceEventId, accountId
claim.createdA claim was assembled from posted charges.claimId, accountId, claimNumber, billedAmount
claim.submittedA claim's 837P was uploaded to the clearinghouse mailbox.claimId, claimNumber, interchangeControlNumber
remittance.postedAn 835 remittance was posted to a claim.claimId, remittanceId, paymentAmount, adjustmentAmount, patientResponsibility, partiallyPosted
eligibility.checkedAn eligibility (270/271) check completed.eligibilityCheckId, patientId, coverageActive, coverageChanged
eligibility.coverage_changedAn eligibility check found coverage had become active or inactive.eligibilityCheckId, patientId, previousCoverageActive, currentCoverageActive
payment.succeededA card payment was captured and posted.paymentId, accountId, amount
payment.failedA card payment failed.paymentId, accountId, amount, errorCode
payment.refundedA card payment was refunded, in part or in full.paymentId, accountId, refundAmount, isFullRefund
denial.createdA claim denial was recorded.denialId, claimId, carcCode, category, amount

Verifying a delivery. X-ArkCloud-Signature is v1=<hex HMAC-SHA256(secret, "<X-ArkCloud-Timestamp>.<raw body>")>. While a secret is being rotated it carries two v1= values, one per secret; accept either. Refuse a timestamp more than five minutes old, and deduplicate on X-ArkCloud-Event-Id.

Order and repeats. Deliveries can arrive out of order (a retried event arrives after later ones) and, rarely, more than once. Order by the envelope's createdAt, and treat an event id you have already processed as done.

TypeScript

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (do not re-serialise it).
export function verifyArkCloudWebhook(rawBody: string, headers: Headers, secret: string): boolean {
  const ts = headers.get("X-ArkCloud-Timestamp") ?? "";
  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay window
  const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest();
  return (headers.get("X-ArkCloud-Signature") ?? "")
    .split(",")
    .map((part) => part.trim().match(/^v1=([0-9a-f]{64})$/)?.[1])
    .some((hex) => hex !== undefined && timingSafeEqual(Buffer.from(hex, "hex"), expected));
}
// Then deduplicate on X-ArkCloud-Event-Id: a delivery can arrive more than once.

Python

import hashlib, hmac, time

def verify_arkcloud_webhook(raw_body: bytes, headers, secret: str) -> bool:
    ts = headers.get("X-ArkCloud-Timestamp", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:  # replay window
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    sigs = [p.strip()[3:] for p in headers.get("X-ArkCloud-Signature", "").split(",") if p.strip().startswith("v1=")]
    return any(hmac.compare_digest(s, expected) for s in sigs)

A failed delivery is retried with growing delays (30 seconds, doubling, at most 6 hours) up to your organisation's attempt limit (default 8). An endpoint that fails 20 deliveries in a row, or answers 410 Gone, is switched off until an administrator resumes it. The delivery history and a manual retry are in the API & Integrations Center.

Versioning and deprecation

The version is in the path (/api/v1). Within v1, fields may be added and new endpoints and events may appear; nothing is removed or renamed. While v1 is experimental, a breaking change is announced in this reference at least 30 days ahead. Once it is generally available, a breaking change means v2, and v1 keeps working for at least 12 months after v2 is published.

What is not here

The web application's own /api endpoints (described for reference at /api/docs) serve a signed-in browser session and are not a supported contract. There are no write endpoints, SDKs, OAuth client credentials or bulk-import endpoint. Claims, remittances and acknowledgements move as X12 files through a clearinghouse mailbox, and other connections are described with their real status under integrations. To ask for something the API does not cover: [email protected].