Skip to content
TRIM FITTv1 · sandbox
↑↓ to move↵ to openesc to close
Contents

Reference

Orders

Price a basket, place a referral, and follow it. An order is a request for clinical review, not a charge and not a dispatch.

An order is a referral

The order lands as pending_approval, unpaid, with no pharmacy assigned. A clinician reviews it, writes the prescription and routes it. Nothing in this API charges a card or sends anything to a pharmacy.

How an order is made#

  1. Quote. Send one patient, one clinic if your key holds several, and the products and quantities. The response fixes the lines, the amount and a 30 minute expiry.
  2. Order. Send the quote id. The quote is consumed in the same transaction that creates the order, so it can fund exactly one order.
  3. Follow. Read one order, or list orders changed since a timestamp. There are no webhooks yet, so polling is the supported path.
  4. Cancel, while it is still in review. A cancellation is accepted only before approval and only while unpaid.

Create a quote#

POST/v1/quotesScopes: orders:write, catalog:read, catalog:read:pricing

The Idempotency-Key header is required. Use a new key for each intended quote. A retry with the same key and the same body returns the first quote.

http
POST /api/partner/v1/quotes
Authorization: Bearer tfk_test_…
Idempotency-Key: quote-2026-11-07-0001
Content-Type: application/json

{
  "patient_id": "0b7c…",
  "clinic_id": "dd47…",
  "items": [
    { "product_id": "0999dff2-506e-4d0d-a17a-c4772d1be9d4", "quantity": 2 }
  ]
}
FieldRequiredNotes
patient_idYesThe id returned by POST /v1/patients. The patient must be linked to the clinic the quote is for.
clinic_idNoOnly when the key holds more than one clinic. It must be one of the key's grants. With one clinic, omit it.
itemsYesOne to 20 lines. Each line is a product_id and an integer quantity from 1 to 20. A repeated product is refused.

A product is orderable only when it is active for the clinic in the catalogue and has an agreed price. If any line is not, the whole quote is refused with 400 validation_failed and nothing is created. Check the catalogue first: a product with priced: false cannot be quoted.

The response is 201 for a new quote and 200 for a replay:

json
{
  "ok": true,
  "request_id": "…",
  "quote": {
    "id": "c0f1…",
    "status": "open",
    "currency": "USD",
    "patient_id": "0b7c…",
    "clinic_id": "dd47…",
    "subtotal_cents": 75200,
    "total_cents": 75200,
    "expires_at": "2026-11-07T15:30:00.000Z",
    "created_at": "2026-11-07T15:00:00.000Z",
    "order_id": null,
    "lines": [
      {
        "product_id": "0999dff2-506e-4d0d-a17a-c4772d1be9d4",
        "name": "*International - Semax Nasal Spray - 10mg, 15mL",
        "quantity": 2,
        "unit": "each",
        "unit_amount_cents": 37600,
        "line_amount_cents": 75200
      }
    ]
  },
  "idempotent": false
}

Place the order#

POST/v1/ordersScope: orders:write

The body carries the quote id and nothing else. The quote already names the patient, the clinic, the lines and the amount, so this endpoint never re-prices.

http
POST /api/partner/v1/orders
Authorization: Bearer tfk_test_…
Idempotency-Key: order-2026-11-07-0001
Content-Type: application/json

{ "quote_id": "c0f1…" }

The response is 201 for a new order and 200 for a replay:

json
{
  "ok": true,
  "request_id": "…",
  "order": {
    "id": "8a3d…",
    "order_number": "TF-2026-00042",
    "status": "pending_approval",
    "origin": "partner",
    "patient_id": "0b7c…",
    "clinic_id": "dd47…",
    "quote_id": "c0f1…",
    "total_cents": 75200,
    "currency": "USD",
    "payment_status": "pending",
    "item_count": 1,
    "created_at": "2026-11-07T15:01:00.000Z",
    "updated_at": "2026-11-07T15:01:00.000Z",
    "sent_at": null,
    "shipped_at": null,
    "delivered_at": null,
    "tracking_number": null,
    "carrier": null,
    "ship_status": null,
    "pharmacy_name": null
  },
  "idempotent": false
}

One quote, one order

A quote can fund exactly one order, even if the second request uses a different key. A reused quote returns 409 quote_consumed. Create a new quote for a second order.

Read an order#

GET/v1/orders/{id}Scope: orders:read, plus orders:read:clinical for lines

The shell carries the number, status, origin, dates, totals, payment status, tracking and pharmacy. Add orders:read:clinical to receive the lines as well.

json
{
  "ok": true,
  "request_id": "…",
  "order": {
    "id": "8a3d…",
    "order_number": "TF-2026-00042",
    "status": "pending_approval",
    "origin": "partner",
    "patient_id": "0b7c…",
    "clinic_id": "dd47…",
    "quote_id": "c0f1…",
    "total_cents": 75200,
    "currency": "USD",
    "payment_status": "pending",
    "item_count": 1,
    "created_at": "2026-11-07T15:01:00.000Z",
    "updated_at": "2026-11-07T15:01:00.000Z",
    "sent_at": null,
    "shipped_at": null,
    "delivered_at": null,
    "tracking_number": null,
    "carrier": null,
    "ship_status": null,
    "pharmacy_name": null,
    "items": [
      {
        "id": "…",
        "product_id": "0999dff2-506e-4d0d-a17a-c4772d1be9d4",
        "medication_name": "*International - Semax Nasal Spray - 10mg, 15mL",
        "dosage": "As directed",
        "quantity": 2,
        "unit": "each",
        "refills": 0,
        "days_supply": null,
        "line_type": "medication",
        "unit_amount_cents": 37600,
        "line_amount_cents": 75200
      }
    ]
  }
}

Without orders:read:clinical, the items key is absent, not an empty array.

A key can read every order in its clinics, including one a staff member created. Cancellation is narrower: only a partner-created order can be withdrawn through this API.

List and poll#

GET/v1/ordersScope: orders:read

Newest first, keyset paginated. updated_since is the polling filter: send the last time you checked and receive everything that changed after it. The cursor is where the previous page ended.

QueryNotes
limit1 to 100. Default 25.
statusOne of draft, pending_approval, approved, sent_to_pharmacy, shipped, delivered, cancelled, on_hold, a2z_cleared.
updated_sinceISO 8601 instant with a zone, for example 2026-11-07T00:00:00Z. Orders with updated_at at or after it are returned.
cursorThe next_cursor from the previous response. Treat it as opaque. An unusable cursor is 400, never a silent restart.
json
{
  "ok": true,
  "request_id": "…",
  "orders": [ { "id": "8a3d…", "order_number": "TF-2026-00042", "status": "pending_approval", "…": "…" } ],
  "truncated": false,
  "next_cursor": null
}

The list is shell only, even when the key holds orders:read:clinical. Read one order to get its lines.

Cancel#

POST/v1/orders/{id}/cancelScope: orders:write

Accepted only for a partner-created order while it is draft or pending_approval and unpaid. After approval, after a payment, after a pharmacy has it, or for a staff-created order, the answer is 409 order_not_cancellable; contact the clinic.

json
{
  "ok": true,
  "request_id": "…",
  "order": {
    "id": "8a3d…",
    "order_number": "TF-2026-00042",
    "status": "cancelled",
    "already_cancelled": false
  }
}

Cancelling twice is 200 with already_cancelled: true. The event and audit rows are written once, for the transition.

Retries and idempotency#

SituationResult
Same key, same bodyThe first result, with idempotent: true. No second row.
Same key, different body409 idempotency_conflict.
Different key, same quoteThe first order stands; the second is 409 quote_consumed.
Key whose request was refusedThe key is removed in the same transaction, so you can retry it after fixing the quote or creating a new one.

Keys are 8 to 128 visible characters with no whitespace. A UUID, a request id or a date-stamped sequence all work. Use a new key for a new intention, not a new key for a retry of the same intention.

Prices and expiry#

A quote is valid for 30 minutes. The amount in the quote is the clinic-agreed amount from the catalogue, fixed at the moment the quote is created.

At order time the platform checks that every line is still active and still priced the same. If the price moved, the order is refused with 409 quote_price_changedrather than billing a different number. Create a new quote and show the new amount.

An expired quote returns 409 quote_expired. A quote that was already used returns 409 quote_consumed.

Order amounts are part of the order. orders:read carries the total, and orders:read:clinical carries the line breakdown. The catalogue's catalog:read:pricing gates the catalogue price, not an order.

Errors#

StatusCodeMeaning
400validation_failedThe body, query, idempotency key or one of the quote lines is not valid, or a key holding several clinics did not name one.
403forbidden_scopeThe key lacks a required scope, or the named clinic is not in its grants.
403orders_not_enabledProduction only. The signed BAA and DPA, or the operator's order switch, are not in place.
404not_foundThe patient is not linked to the clinic, or the quote or order is not in the key's clinics. The same answer for every reason.
409idempotency_conflictThe same key was used for a different body.
409quote_expiredThe 30 minute window passed. Create a new quote.
409quote_consumedThe quote already funded an order.
409quote_price_changedAn agreed price or the formulary status moved. Create a new quote.
409order_not_cancellableThe order is approved, paid, already with a pharmacy, or was created by staff rather than by the partner.
500internal_errorOur bug. Report it with the request id.
503service_unavailableThe credential or the order gate could not be checked. Retry with backoff.

What is not returned#

  • Patient name, email, phone or address. Use the patient endpoints and the PHI scope.
  • The prescription sig, diagnosis codes, internal notes, override reasons and pharmacy cost. Those are clinical or commercial data the order API does not serve.
  • Card data, Stripe ids and payment failure messages.
  • Orders from a clinic the key does not hold.

Sandbox and production#

Orders are available in the sandbox now. Production is gated three ways: the key must carry the scope, the BAA and DPA must be signed and recorded, and an operator must enable orders for the account after the lawful-supply review. A production key without all three gets 403 orders_not_enabled.

The lawful-supply review is real

The sandbox accepts the sandbox catalogue without a state-by-state supply decision. Live orders do not, and the switch is the point at which that decision is recorded. Do not ask an operator to enable live orders before counsel and the clinical lead have answered which products are lawful in which states.