Palamond Docs
The API

Errors and Limits

The error envelope, the codes to branch on, rate limits, and idempotent retries.

The error envelope

Every non-2xx response carries the same JSON envelope:

{
  "error": {
    "code": "consent_required",
    "message": "Required agreements are unmet. Accept them, then retry.",
    "details": { "agreements": [{ "url": "...", "version": "2", "sourceUrl": "..." }] }
  }
}
  • code is stable and machine-readable: branch on it, not on the message.
  • message is human-readable and safe to show to the patient.
  • details carries structured context for the codes that have it.

Business failures are real HTTP statuses. You will never get a 200 that is secretly a failure.

The codes

CodeStatusMeaning
unauthorized401Missing, malformed, or expired bearer token.
forbidden403Valid token, but not allowed (for example, not a patient session).
invalid_request400Validation failed, or the platform rejected the request as invalid.
not_found404No such endpoint or resource in the patient's record.
unsupported_resource404The resource type is not part of the record plane.
method_not_allowed405Not writable here; the message names the workflow endpoint that owns the change.
conflict409Lost a race or hit a forbidding state: slot taken, task closed, already started.
consent_required409Required agreements are unmet; details.agreements lists them.
rate_limited429Too many requests; honor Retry-After.
upstream_error502The platform behind the API failed to answer. Retry is reasonable.
internal500An unexpected error on our side.

Two 409s carry actionable details:

  • consent_required - agreements are unmet; details.agreements lists them. The clean flow checks requirements upfront with agreements.requirements and records acceptances before acting, so this code is the server-enforced backstop, not something the patient routinely sees. Nothing was changed by the refused call, so retrying after agreements.accept is safe.
  • conflict - read details for what happened (details.reason, details.notBefore, details.appointment depending on the endpoint) and route the user accordingly.

Handling errors in the SDK

The SDK throws on any non-2xx response and ships getApiError to recover the envelope from the thrown error:

import { getApiError } from '@palamond/sdk';

try {
  await palamond.assessments.start({ healthcareService });
} catch (err) {
  const api = getApiError(err);
  if (api?.code === 'consent_required') {
    // api.details.agreements lists what to accept.
  } else {
    throw err;
  }
}

getApiError returns undefined for anything that is not a Palamond API failure (network errors, plain FHIR outcomes), so rethrow those. The guides use it wherever a 409 is part of the normal flow.

Rate limits

Requests are limited per session over a sliding minute window. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 adds Retry-After in seconds. Back off and retry after that.

Idempotent retries

Workflow mutations accept an Idempotency-Key header; every mutation method takes it as an option. Retrying the same key on the same endpoint within 24 hours replays the recorded response instead of repeating the action, so a timed-out reschedule retried moves the visit once, not twice.

await palamond.appointments.reschedule(appointmentId, body, { idempotencyKey: crypto.randomUUID() });

Use a fresh key per logical action and reuse it for retries of that action. Reads and the record plane do not need one.

On this page