> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.truemed.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.truemed.com/_mcp/server.

# API calls

> Every call this integration makes, the payloads that carry a customer through signup, and what the eligibility read answers with.

## API calls, at a glance

| Call                                                                                                           | When                                 | What it does                                                                                                                                                                                                     |
| -------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Create Payment Token](/api-reference/payment-tokens/create-payment-token)                                     | Signup, once                         | Starts the survey and dual-card journey. Returns the URL you embed and a provision-request id.                                                                                                                   |
| [Payment Token Updated](/api-reference/payment-tokens/payment-token-updated-webhook) (webhook)                 | LMN decided                          | Tells you the token exists and carries the clinical outcome in `eligibility_status`. It arrives on approval and on rejection (then with zero HSA/FSA coverage). Your signal that the first order can be charged. |
| [Retrieve Provision Token Request](/api-reference/payment-tokens/retrieve-payment-token-provision-request)     | Optional                             | Polling fallback if you would rather not depend solely on the webhook.                                                                                                                                           |
| [Retrieve Eligibility by Payment Token](/api-reference/eligibility/retrieve-token-eligibility)                 | Menu render                          | Returns `eligibility_status`, `items` (the meals this customer may put on their HSA/FSA card) and `lmn_expires_at`.                                                                                              |
| [Retrieve Eligibility by Provision Request](/api-reference/eligibility/retrieve-provision-request-eligibility) | First order, before the token exists | The same response, keyed on the provision-request id from signup.                                                                                                                                                |
| [Retrieve Catalog Eligibility](/api-reference/eligibility/retrieve-catalog-eligibility)                        | Menu with no customer                | Your whole configured catalog, with no customer-specific fields.                                                                                                                                                 |
| [Create Payment Session](/api-reference/payment-sessions/create-payment-session)                               | Every charge                         | The whole transaction: classifies each line, splits across both cards, authorizes, echoes the expiry date.                                                                                                       |
| [Capture Session](/api-reference/payment-sessions/capture-payment-session)                                     | Optional                             | If you would rather authorize at selection and capture at fulfilment, this is how.                                                                                                                               |
| [Payment Session Complete](/api-reference/payment-sessions/payment-session-completed-webhook) (webhook)        | Every charge                         | Asynchronous outcome of the charge.                                                                                                                                                                              |
| [Create Refund](/api-reference/payment-sessions/create-refund)                                                 | As needed                            | Refunds return to the originating card, split the same way as the charge.                                                                                                                                        |

Every call in this table is live in production today and documented in the
[API reference](/api-reference).

## The four calls that carry a customer through signup

Four payloads, matching steps 1, 3, 5 and 6a of the
[first-order diagram](/guides/get-started/use-cases/medically-tailored-meals/first-order#how-the-first-order-flows).
Host tokens resolve to the environment you are reading this on; sandbox and production hosts are
listed in
[Before you begin](/guides/setup/api/payment-sessions/before-you-begin#environments).

### Step 1: Start the journey

[Create payment token](/api-reference/payment-tokens/create-payment-token):

```http
POST https://api.truemed.com/api/v1/payment_tokens/create
x-truemed-api-key: <your API key>
Content-Type: application/json

{
  "idempotency_key": "merchant-signup-84213",
  "customer_email": "jordan.reyes@example.com",
  "customer_name": "Jordan Reyes",
  "customer_state": "CA",
  "success_url": "https://www.example.com/hsa/complete",
  "failure_url": "https://www.example.com/hsa/cancelled",
  "metadata": "{\"merchant_customer_id\":\"84213\"}",
  "use_iframe": true
}
```

That is the whole of the customer information Truemed needs, and each field does one job:

* **`customer_email`**: required. Truemed uses it to create the patient record the LMN is written
  for, and it is where the receipt goes. **It is not how you look the customer up afterwards**; that is
  the token's job, and the distinction matters, because an email address can change while the person
  does not.
* **`customer_name`**: required. It goes on the LMN, and it pre-fills the name
  confirmation step in the survey so the customer confirms rather than retypes. It is read once, when
  the record is first created, so a later name change on your side does not travel through this call.
* **`customer_state`**: optional, and worth sending. It pre-fills the state-of-residence answer, which
  determines which practitioner can review the case. It is the only survey answer this call can
  pre-fill.
* **`metadata`**: an opaque string Truemed stores and hands back to you on the token webhook and on
  every payment session. **This is where your own customer ID goes.** There is no dedicated external-ID
  field on this endpoint today; if you would rather have a first-class one, say so and we will scope it.
* **`order_items`**: deliberately absent. Your channel qualifies against the whole catalog, so there is
  no basket to declare. (For other partners it is required.)
* **`use_iframe`**: `true`, which is what makes the returned URL embeddable. See
  [Embedding the flow](/guides/get-started/use-cases/medically-tailored-meals/embedding).

### Step 2: Read what comes back

```json
{
  "provision_token_request_id": "b1f0c7a2-5d3e-4a19-9c6b-2f8e41d07a55",
  "redirect_url": "https://app.truemed.com/survey/6c2d9e41-7b83-4f52-a0d1-3ea95c6b8f27"
}
```

Use `redirect_url` as the `src` of your iframe. That is step 2 of the first-order flow, the health survey
and the two cards, rendered inside a modal on your page.

> **Warning**
>
> **Persist `provision_token_request_id` on the customer record before you open the frame.** It is the
> join key. The decision comes back asynchronously, minutes or hours later, on a webhook that carries no
> email and no name, so this id (or your own id inside `metadata`) is how you know which customer it
> belongs to.

### Step 3: Receive the token

[payment\_token.updated](/api-reference/payment-tokens/payment-token-updated-webhook):

```http
POST https://<your webhook endpoint>
x-truemed-signature: t=1785938402,v0=8f4c2ad91e6b...
Content-Type: application/json
```

```json
{
  "webhook_delivery_id": "dlv_9a71c0e4b5d2483f8e6c1a70d94b2f53",
  "event_type": "payment_token.updated",
  "data": {
    "payment_token": "tm_token_4e2a91c8-70bd-4f36-9a15-c8b3d6e07f42",
    "provision_token_request_id": "b1f0c7a2-5d3e-4a19-9c6b-2f8e41d07a55",
    "payment_session_id": null,
    "failed_payment_session_id": null,
    "eligibility_status": "approved",
    "payment_methods": [
      { "last4": "4242", "expiration": "08/2029",
        "payment_method_updated_at": "2026-08-06T14:22:07.318000+00:00" },
      { "last4": "1881", "expiration": "03/2028",
        "payment_method_updated_at": "2026-08-06T14:22:07.402000+00:00" }
    ],
    "lmn_expires_at": "2027-08-06",
    "metadata": "{\"merchant_customer_id\":\"84213\"}"
  }
}
```

Acknowledge with a `2xx`; Truemed retries with backoff for up to seven days. The payload is a **full
snapshot of current state** rather than a delta, so the safe way to consume it is to upsert, overwriting
the token and card details you hold. That stays correct under a retry and under a genuine later update
alike.

* **`payment_token`**: the value you charge against. Its shape is `tm_token_<uuid4>`, 45 characters;
  treat it as an opaque string rather than parsing it. It is the only field on this payload guaranteed
  to be non-null.
* **`eligibility_status`**: the clinical outcome, one of `approved`, `pending`, `expired`, `rejected` or
  `never_requested`. On a first order it is `approved` or `rejected`. The other three can reach you on a
  later delivery for the same token, so branch on the value rather than treating it as a boolean. It is
  an additive optional field, so a handler written before it existed keeps working unchanged.
* **`payment_methods`**: last four and expiry for both stored cards, HSA/FSA first, so you can render
  "HSA •••• 4242 · regular •••• 1881" in account settings without a second call.
* **`lmn_expires_at`**: the 12-month LMN expiry described in
  [Subsequent orders](/guides/get-started/use-cases/medically-tailored-meals/subsequent-orders).
  It is the same date you get back on every payment session.
* The envelope shown is what a **signed** endpoint receives, with an HMAC-SHA256 over the timestamp and
  body in `x-truemed-signature`. We recommend it. An endpoint registered without a signing secret
  instead receives the inner `data` object at the top level, authenticated with `x-truemed-api-key`.

The rejected delivery is the same event, and three fields tell it apart. It is worth having side by side
with the payload above:

```json
{
  "webhook_delivery_id": "dlv_3c7e0b52d1a94f60b8ae1d7f2c05e934",
  "event_type": "payment_token.updated",
  "data": {
    "payment_token": "tm_token_3c7e0b52-d1a9-4f60-b8ae-1d7f2c05e934",
    "provision_token_request_id": "f81a4e60-90c7-4d2b-8ab3-71e5c9042fd7",
    "payment_session_id": null,
    "failed_payment_session_id": null,
    "eligibility_status": "rejected",
    "payment_methods": [
      { "last4": "1881", "expiration": "03/2028",
        "payment_method_updated_at": "2026-08-06T14:22:07.402000+00:00" }
    ],
    "lmn_expires_at": null,
    "metadata": "{\"merchant_customer_id\":\"84219\"}"
  }
}
```

The token is still minted, so the outcome can be correlated back to the `provision_token_request_id`
you persisted. It simply covers nothing, and every line of every charge routes to the regular card.

### Where the token lives on your side

One token per customer, stored as a field **on the customer or subscription record**, in the same place
you would keep a payment-method id. It is what every later
[create payment session](/api-reference/payment-sessions/create-payment-session) call is keyed on,
including the unattended weekly ones, so it needs to be readable from your recurring-billing job as well
as from a checkout session.

Two properties are worth designing around. The token is **long-lived and writable**: if the customer
later replaces a card, the token value stays the same and Truemed sends `payment_token.updated` again,
with the same `payment_token`, the same `provision_token_request_id` and refreshed `payment_methods`. So
handle a repeat delivery as an update to the record you already have. The token is also **durable**: it
survives LMN expiry. An expired LMN leaves the token valid and means the HSA/FSA portion stops
being eligible, so everything routes to the regular card until a new LMN is in place.

### Step 4: Read eligibility, before and after the decision

[Retrieve Eligibility by Provision Request](/api-reference/eligibility/retrieve-provision-request-eligibility):

```http
GET https://api.truemed.com/api/v1/eligibility/provision_request/b1f0c7a2-5d3e-4a19-9c6b-2f8e41d07a55
x-truemed-api-key: <your API key>
```

```json
{
  "eligibility_status": "pending",
  "items": ["salmon-teriyaki-bowl", "chicken-pesto-plate", "protein-breakfast-box"],
  "lmn_expires_at": null
}
```

This is the read behind step 3 of the first-order flow. The LMN is still with the practitioner, so
`items` is what the survey predicts it will cover and there is no expiry yet. Badge these as provisional.
The charge still waits for step 5's webhook.

**This is the same one call whether or not a person picks the meals.** If the customer chooses their
first box by hand, you use the response to badge the menu they are looking at. If your personalization
engine assembles the box for them with no manual selection, you use the identical response to decide
which lines are HSA/FSA-payable before you charge. The eligibility call answers the same question in
both cases.

Once the token is minted, [the same read keyed on the token](/api-reference/eligibility/retrieve-token-eligibility)
answers with the decided coverage. This is the call
[every later order](/guides/get-started/use-cases/medically-tailored-meals/subsequent-orders) opens with:

```http
GET https://api.truemed.com/api/v1/eligibility/token/tm_token_4e2a91c8-70bd-4f36-9a15-c8b3d6e07f42
x-truemed-api-key: <your API key>
```

```json
{
  "eligibility_status": "approved",
  "items": ["salmon-teriyaki-bowl", "chicken-pesto-plate", "protein-breakfast-box"],
  "lmn_expires_at": "2027-08-06"
}
```

If the practitioner declines, the same read answers `rejected`, the same value the webhook already
carried, available here whenever you want to confirm it:

```http
GET https://api.truemed.com/api/v1/eligibility/provision_request/b1f0c7a2-5d3e-4a19-9c6b-2f8e41d07a55
x-truemed-api-key: <your API key>
```

```json
{
  "eligibility_status": "rejected",
  "items": [],
  "lmn_expires_at": null
}
```

The token still arrives on `payment_token.updated` with zero HSA/FSA coverage, so the held order charges
fully to the regular card and ships. Use this outcome for messaging.

All three forms are live and share one response shape. Called bare, as
[`GET /api/v1/eligibility`](/api-reference/eligibility/retrieve-catalog-eligibility) with no key, it
returns your whole configured catalog with `eligibility_status` and `lmn_expires_at` both `null`: the
menu-with-no-customer read. The
[state table](/guides/get-started/use-cases/medically-tailored-meals/api-calls#five-states-five-different-things-to-do)
below spells out `items` and `lmn_expires_at` for every value.

## Five states, five different things to do

Every form of `GET /api/v1/eligibility`, whether bare, keyed on `/token/{token_id}`, or keyed on
`/provision_request/{provision_token_request_id}`, answers with the same three-field response shape:
`eligibility_status`, `items`, `lmn_expires_at`. The field tells you where the customer stands, and you
decide what your lifecycle messaging does about it. First, what each value carries:

| `eligibility_status`                                | `items`                                                                                                                             | `lmn_expires_at`                                                                                                                                |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pending`** (normally the provision-request read) | What the survey predicts the LMN will cover, provisional until the practitioner decides. Empty only while the survey is unfinished. | `null`, because there is no LMN yet. On a token read, `pending` means a requalification is in flight, and it carries the previous LMN's expiry. |
| **`approved`**                                      | The items the customer may put on the HSA/FSA card.                                                                                 | In the future: the date coverage ends.                                                                                                          |
| **`expired`**                                       | Empty.                                                                                                                              | In the past: the date coverage ended.                                                                                                           |
| **`rejected`**                                      | Empty.                                                                                                                              | `null` on a first order, where no LMN was ever issued; the old LMN's expiry when a lapsed subscriber was re-declined.                           |
| **`never_requested`**                               | Empty, unless your catalog needs no LMN at all, in which case the pre-approved items, since nothing depends on an LMN.              | `null`, because no LMN was ever requested for this customer on this channel.                                                                    |

The bare catalog read, `GET /api/v1/eligibility` with no key, returns every item configured for you with
`eligibility_status` and `lmn_expires_at` both `null`: there is no shopper to have either.

The states are not interchangeable, and one of them is a reason to hold back:

| State               | Useful to you?                                                                                                                             | Right behavior                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| **Pending**         | Yes. It is what lets you badge the first menu before the LMN is decided.                                                                   | Badge the predicted items and label them provisional. The first charge still waits for the webhook. |
| **Approved**        | Yes. The steady state, and the only one where the customer can pay with the HSA/FSA card.                                                  | Badge the listed items as HSA/FSA-payable and charge. Refresh the read when your menu changes.      |
| **Expired**         | Yes, high value. This is the renewal trigger, and the one to build lifecycle messaging on.                                                 | Nudge to requalify, and keep charging the regular card meanwhile.                                   |
| **Never requested** | Yes. It distinguishes "hasn't tried" from "tried and was declined", which calls for an acquisition prompt rather than a re-engagement one. | Offer the qualification link. The copy differs from the renewal nudge.                              |
| **Rejected**        | Yes, mainly as a suppression signal.                                                                                                       | Do not prompt. Charge the regular card silently.                                                    |

**Never requested is usually yours to know without asking, though not always.** The read is keyed on
ids only you hold: the token, or the provision-request id for the first order. If you are holding
neither for a customer, they have never started the journey, and there is nothing to call. What that
reasoning misses is that a customer can hold a token while no LMN was ever requested for them, so
holding an id is not proof that a decision was ever made. When you call the read in that situation,
Truemed answers `never_requested` rather than folding the case into `expired`, which would assert a
an LMN that never existed and point you at a renewal nudge when what the customer needs is a first-time
prompt.

**Rejected is the one worth handling carefully.** A customer whose LMN lapsed, who requalified and was
then declined, still has a live token and a working regular card, so everything keeps shipping and keeps
charging. What should not happen is another renewal nudge: a practitioner has already reviewed and
declined, and Truemed has already emailed them about it. Reading `eligibility_status`, rather than
inferring from an empty `items` list, is what keeps you from asking twice. A first-order rejection lands
in the same place from day zero: the token arrives covering nothing, and the subscription starts on the
regular card instead of dying at the gate.