Fitnescity Health Developers
Public API documentation

Use Full API Integration—the default approach—to keep diagnostic testing search, customer identity, consent when required, ordering, questionnaires, scheduling, and results in your product. Start in Staging (Sandbox) for API Partner testing before switching hosts and credentials for live traffic.

Default test host
https://api-staging.engineeringfitnescity.com
Request access
Webhooks

Use webhooks for appointment lifecycle changes and keep results polling separate.

Appointment lifecycle updates can be pushed to an API Partner-owned HTTPS endpoint. Explicitly authorized endpoints can also receive terminal API Partner evidence events. Results and evidence resources remain readable through their GET endpoints for reconciliation.

How webhook delivery works

  1. 1. Register an API Partner-owned callback URL with POST /v1/webhook-endpoints.
  2. 2. Persist the signing secret that is returned once at creation time.
  3. 3. Fitnescity Health queues webhook deliveries asynchronously when appointment state changes or an opted-in API Partner evidence resource reaches a terminal state.
  4. 4. Your endpoint verifies the signature, stores the event id and sequence, then updates your own member-facing appointment state.
  5. 5. If you ever need reconciliation, use GET /v1/appointments as the polling source of truth.
Create endpoint example
curl -X POST "https://api-staging.engineeringfitnescity.com/v1/webhook-endpoints" \
  -H "x-api-key: <staging-prefix>.<staging-secret>" \
  -H "Content-Type: application/json" \
  -d '{"callback_url":"https://api-partner.example.com/webhooks/fitnescity","subscribed_events":["appointment.results_ready","appointment.form_available","customer_consent_evidence.accepted","physician_order_evidence.accepted"],"partner_evidence_webhooks_enabled":true}'

Staging lifecycle certification

Lifecycle testing is support-assisted in staging. After registering your webhook endpoint and creating an appointment, send support the appointment ID, initial slot, replacement slot, and the scenarios you need: center confirmation, changed-time confirmation, and physician-order approval.

  • Center confirmation emits appointment.confirmed.
  • A direct confirmed-to-confirmed location time change emits appointment.rescheduled.
  • Physician-order approval can be completed as part of the support-assisted certification run. Physician rejection is not currently an API Partner self-service staging action.

Payload example

Every delivery includes a stable envelope plus an appointment lifecycle object under data.object. Persist the envelope id for idempotency and use payload_version to pin your parser.

{
  "id": "4d9d4b14-2c48-4a71-8d5e-8d7771f8f5d2",
  "type": "appointment.confirmed",
  "occurred_at": "2026-08-28T12:00:00.000Z",
  "payload_version": "2026-08-28",
  "sequence": 4,
  "data": {
    "object": {
      "appointment_id": "9e879f77-ccaa-4d3f-85d8-92a6b9bd9fc0",
      "order_id": "3a694a8c-5373-4b5b-bf0b-aec5604c9fb4",
      "test_type": "DEXA",
      "status": "confirmed",
      "results_status": "waiting_results",
      "scheduled_for": "2026-08-30T14:00:00.000Z",
      "previous_status": "requested",
      "previous_results_status": "waiting_results",
      "previous_scheduled_for": null,
      "original_scheduled_for": "2026-08-30T14:00:00.000Z"
    }
  }
}

Retry policy

  • Return any 2xx response once your consumer has durably accepted the event.
  • Fitnescity Health retries on 408, 409, 425, 429, 500, 502, 503, 504 and on network/connectivity failures.
  • Delivery uses up to 6 total attempts with backoff intervals of roughly 60, 120, 240, 480, and 960 seconds between retries.
  • Deduplicate by the stable envelope id. Retry attempts receive a fresh delivery timestamp and signature, but the event body remains immutable.
  • Non-retryable non-2xx responses are treated as terminal delivery failures, so reconcile with GET /v1/appointments if your consumer misses an event.

Signed-form availability

This event is emitted after the form is available for retrieval. Its data identifies the form_type, artifact_type, and retrieval_status; the PDF itself is never attached to the webhook.

{
  "id": "58409c43-c5f3-4910-a69a-85e022237987",
  "type": "appointment.form_available",
  "occurred_at": "2026-04-10T15:42:00.000Z",
  "payload_version": "2026-08-28",
  "sequence": 4,
  "data": {
    "object": {
      "appointment_id": "9e879f77-ccaa-4d3f-85d8-92a6b9bd9fc0",
      "order_id": "3a694a8c-5373-4b5b-bf0b-aec5604c9fb4",
      "test_type": "DEXA",
      "form_type": "DOCTORORDER",
      "artifact_type": "combined",
      "retrieval_status": "available"
    }
  }
}
In addition to webhooks, appointment-related emails sent to members will also carry API Partner branding. See the Communications page for details on co-branded email notifications.

API Partner evidence terminal events

These events are delivered only when the webhook endpoint sets partner_evidence_webhooks_enabled=true and subscribes to the event. Keep evidence GETs as the source of truth; webhook payloads do not include document bytes or upload capabilities.

customer_consent_evidence.accepted

API Partner-provided consent PDF evidence passed validation and can be selected on the order.

customer_consent_evidence.rejected

Customer-consent evidence was rejected; read the evidence resource for its safe reason code.

customer_consent_evidence.failed

Customer-consent evidence processing failed; reconcile the evidence resource before retrying.

physician_order_evidence.accepted

API Partner-provided physician-order PDF evidence passed validation for its appointment.

physician_order_evidence.rejected

Physician-order evidence was rejected; read the evidence resource for its safe reason code.

physician_order_evidence.failed

Physician-order evidence processing failed; reconcile the evidence resource before retrying.

Appointment events

appointment.created

The appointment record now exists, but scheduling has not progressed beyond the initial created state yet.

appointment.requested

A scheduling request has been submitted and the appointment is now awaiting confirmation from the test location. For appointments created through Full API Integration, this maps to pending_confirmation on appointment reads.

appointment.confirmed

The requested slot has been confirmed and the appointment now has a confirmed scheduled datetime.

appointment.rescheduled

A previously confirmed appointment changed to a different confirmed scheduled datetime.

appointment.canceled

The appointment was canceled and should be treated as no longer scheduled for the member.

appointment.no_show

The member did not attend the scheduled appointment at the test location.

appointment.results_in_progress

Downstream result processing has started, but finalized result payloads are not ready to retrieve yet.

appointment.results_ready

Finalized results are ready and can now be retrieved through GET /v1/results with sibling result_units metadata.

appointment.form_available

A signed form is available. Re-read the appointment, then use its signed-form endpoint to request a temporary download URL.

Headers to verify

  • X-Fitnescity-Event-Id
  • X-Fitnescity-Sequence
  • X-Fitnescity-Payload-Version
  • X-Fitnescity-Timestamp
  • X-Fitnescity-Signature

Signatures use HMAC-SHA256 over ${timestamp}.${rawBody} with the exact raw request body. X-Fitnescity-Timestamp is decimal Unix epoch seconds for that delivery attempt; reject timestamps more than five minutes from your local clock before checking the signature. Persist the signing secret securely on the API Partner side because it is shown only once at endpoint creation time.

import crypto from 'node:crypto';

export function verifyFitnescityWebhook({
  rawBody,
  timestamp,
  signature,
  signingSecret,
}: {
  rawBody: string;
  timestamp: string;
  signature: string;
  signingSecret: string;
}) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', signingSecret)
    .update(signedPayload, 'utf8')
    .digest('hex');

  const provided = Buffer.from(signature, 'hex');
  const expected = Buffer.from(expectedSignature, 'hex');

  if (provided.length !== expected.length) {
    return false;
  }

  return crypto.timingSafeEqual(provided, expected);
}

Reconciliation fallback

If your webhook consumer is unavailable, or if you need to reconcile after deployment changes, poll the appointment resource by updated timestamp. Reconcile API Partner-provided consent PDF evidence with GET /v1/api-first/consent-evidence/{evidenceId} and API Partner-provided physician-order PDF evidence with GET /v1/api-first/physician-order-evidence/{evidenceId}.

curl -X GET "https://api-staging.engineeringfitnescity.com/v1/appointments?updated_after=2026-04-10T00:00:00.000Z&limit=25" \
  -H "x-api-key: <staging-prefix>.<staging-secret>"