# Create and Track a Reimbursement

Once a user's expenses are eligible, Sail can pay them back from their own HSA or FSA. A reimbursement draws directly from the user's `benefit_account` connection. Sail doesn't fund the payout, and neither does your application.

In this guide, you'll confirm the expenses are eligible and the account balance covers them, create a reimbursement, and track it to completion.

## Requirements

Before you start, make sure you have:

- A [Sail user](/docs/get-started/get-started-overview/create-a-user) created for your internal user, with at least one `benefit_account` connection and one `eligible` expense already in Sail
- An [API key](/docs/get-started/get-started-overview/api-key) with the `expenses` and `benefit_account` scopes
- A [webhook endpoint](/docs/get-started/get-started-overview/webhooks) configured to receive events, if you plan to track status by webhook instead of polling

## Step 1: Confirm the expenses are eligible

A reimbursement can only include expenses whose [Section 213(d) classification](/docs/concepts/expense-classification) has already resolved to `eligible`. Call [List Expenses](/reference/expenses/get-users-user-id-expenses) with `status` set to `eligible` to confirm which ones qualify.

<Tabs>
  <Tab title="Request">

  ```bash
  curl "https://live.savewithsail.com/api/v1/users/usr_abc123/expenses?status=eligible" \
    -H "Authorization: Bearer sk_live_..."
  ```

  </Tab>
  <Tab title="Response">

  ```json
  {
    "data": [
      {
        "id": "exp_9f2c",
        "name": "Walgreens",
        "amount": 27.02,
        "date": "2026-08-05",
        "status": "eligible"
      },
      {
        "id": "exp_d92a",
        "name": "CVS Pharmacy",
        "amount": 42.98,
        "date": "2026-08-06",
        "status": "eligible"
      }
    ],
    "pagination": {
      "limit": 25,
      "offset": 0,
      "total": 2
    }
  }
  ```

  </Tab>
</Tabs>

Note the `id` and `amount` of each expense you plan to include. You need the ids for the reimbursement request in Step 3, and the amounts to check against the account's balance in the next step.

## Step 2: Confirm the account balance covers the total

The funding account's cash balance must cover the sum of the expenses you're reimbursing, checked against that total, not against each expense individually. Call [Get Account](/reference/accounts/get-users-user-id-accounts-account-id) and compare its cash balance to the sum you noted in Step 1.

<Tabs>
  <Tab title="Request">

  ```bash
  curl https://live.savewithsail.com/api/v1/users/usr_abc123/accounts/acct_77b1 \
    -H "Authorization: Bearer sk_live_..."
  ```

  </Tab>
  <Tab title="Response">

  ```json
  {
    "id": "acct_77b1",
    "type": "hsa",
    "balance": {
      "cash": 1050.50,
      "invested": 500.00
    }
  }
  ```

  </Tab>
</Tabs>

`27.02 + 42.98 = 70.00`, well under this account's `1050.50` cash balance (`1050.50` means $1,050.50), so the reimbursement can proceed. See [Account Connections](/docs/concepts/account-connections) for how `benefit_account` connections and their balances work.

## Step 3: Create the reimbursement

With both preconditions met, create the reimbursement by calling [Create Reimbursement](/reference/reimbursements/post-users-user-id-reimbursements) with the expense ids to reimburse and the funding account id.

<Tabs>
  <Tab title="Request">

  ```bash
  curl -X POST https://live.savewithsail.com/api/v1/users/usr_abc123/reimbursements \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "expense_ids": ["exp_9f2c", "exp_d92a"],
      "account_id": "acct_77b1"
    }'
  ```

  </Tab>
  <Tab title="Response">

  ```json
  {
    "id": "reimb_8c1f",
    "status": "pending",
    "amount": 70.00,
    "account_id": "acct_77b1",
    "expenses": [
      { "id": "exp_9f2c", "name": "Walgreens", "amount": 27.02, "date": "2026-08-05" },
      { "id": "exp_d92a", "name": "CVS Pharmacy", "amount": 42.98, "date": "2026-08-06" }
    ],
    "receipt_url": null
  }
  ```

  </Tab>
</Tabs>

Save the `id` (`reimb_8c1f` in this example). Sail immediately begins contacting the account's administrator, so you don't call a separate endpoint to start processing.

An expense that isn't `eligible`, or that's already part of another in-flight reimbursement, returns a `409 conflict` instead of a new reimbursement.

## Step 4: Track the reimbursement to completion

A reimbursement moves through `pending`, `submitted`, `processing`, and `completed` on its way to a successful payout, or to `failed` if something goes wrong. See [Reimbursement](/docs/concepts/reimbursement) for what each status means and the two distinct failure causes. Track the transition either by listening for the `reimbursement.status_changed` webhook, or by polling [Get Reimbursement](/reference/reimbursements/get-users-user-id-reimbursements-reimbursement-id).

```json
{
  "id": "evt_7f3k9m",
  "event": "reimbursement.status_changed",
  "created_at": "2026-08-12T09:15:00Z",
  "user_id": "usr_abc123",
  "data": {
    "reimbursement_id": "reimb_8c1f",
    "previous_status": "submitted",
    "status": "processing",
    "failure_reason": null
  }
}
```

<Tabs>
  <Tab title="Request">

  ```bash
  curl https://live.savewithsail.com/api/v1/users/usr_abc123/reimbursements/reimb_8c1f \
    -H "Authorization: Bearer sk_live_..."
  ```

  </Tab>
  <Tab title="Response">

  ```json
  {
    "id": "reimb_8c1f",
    "status": "completed",
    "amount": 70.00,
    "account_id": "acct_77b1",
    "expenses": [
      { "id": "exp_9f2c", "name": "Walgreens", "amount": 27.02, "date": "2026-08-05" },
      { "id": "exp_d92a", "name": "CVS Pharmacy", "amount": 42.98, "date": "2026-08-06" }
    ],
    "receipt_url": "https://<RECEIPT_URL>",
    "submitted_at": "2026-08-10T16:02:00Z",
    "completed_at": "2026-08-12T09:15:00Z"
  }
  ```

  </Tab>
</Tabs>

Once `status` reaches `completed`, the included expenses move to a `reimbursed` status and `receipt_url` is populated. See [Receiving New Data](/docs/concepts/receiving-new-data) for how to verify and deduplicate any webhook delivery, `reimbursement.status_changed` included.

<Callout type="info">
  **If status becomes `failed`**

  Check the `failure_reason` on the same webhook or on [Get Reimbursement](/reference/reimbursements/get-users-user-id-reimbursements-reimbursement-id). See [Reimbursement](/docs/concepts/reimbursement) for the two distinct causes, failed to submit versus denied after submission, and how to read a failure reason.
</Callout>