Sail

Reimbursement

A reimbursement pays a user back from their own HSA or FSA for expenses Sail has already classified as eligible. This page explains what a reimbursement needs before Sail creates one, how its status changes from creation to a successful payout, and the two distinct ways it can fail.

What a reimbursement is

A reimbursement is a request to pay a user back from their own benefit account for a set of expenses. Sail doesn’t fund it, and neither does your application. It draws from a benefit_account connection, the same connection type covered in Account Connections, and it only ever returns money the user already has in that account.

Scope required

Creating, getting, or listing reimbursements needs the benefit_account key scope. Creating one also needs the benefit_account product on the funding account’s connection.

Preconditions

Two things must be true before Sail accepts a request to create a reimbursement.

  • Every included expense must carry an eligible Section 213(d) classification. An expense that’s ineligible, still itemization_required, or lmn_required can’t be reimbursed until it resolves to eligible.
  • The funding account’s cash balance must cover the sum of the included expenses. Sail checks that total against the account’s available balance, not against each expense individually.

An expense can only belong to one reimbursement at a time. Including an expense that isn’t eligible, or one that’s already part of an in-flight reimbursement, returns a 409 conflict error instead of creating a new reimbursement.

Creating a reimbursement

A reimbursement request names the expenses to pay back and the account to pay them from: an array of expense IDs plus the funding account_id, matching the ReimbursementCreateRequest shape. Sail responds with a Reimbursement object, identified by an ID like reimb_8c1f, whose amount field reports the sum of the included expenses as a decimal USD value (for example, 1050.50 for $1,050.50).

The status lifecycle

A reimbursement moves through four statuses on its way to a successful payout:

  • pending: Sail has created the reimbursement and is about to contact the funding account’s administrator.
  • submitted: Sail has submitted the included expenses to the administrator.
  • processing: the administrator is processing the submitted claim.
  • completed: the administrator has finished processing the claim, and the money has moved out of the account back to the user. completed reflects money actually moving, not just the administrator approving the claim.

Once a reimbursement reaches completed, its expenses move to a reimbursed status, and a receipt document becomes available through Get Reimbursement’s receipt_url field, which stays empty until then.

Why a reimbursement fails

A failed status covers two distinct causes, both surfaced the same way today:

  • Failed to submit: something went wrong on Sail’s side before the claim reached the administrator. Sail doesn’t automatically retry, but you can resubmit the same expenses and account as a new reimbursement request.

  • Failed after submission: the administrator denied the claim. Sail returns a reason alongside the failure. Denial reasons aren’t a fixed set, since administrators’ review processes vary and are still evolving. For example, an administrator can ask for a Letter of Medical Necessity on an expense Sail classified as eligible, since each administrator applies its own documentation standards when it reviews a claim. A failed reason is context on that decision, not a fixed, retryable error code.

    LMN required after submission

    Support for resolving a Letter of Medical Necessity request through Sail is on the way. We’ll update these docs as soon as it’s live.

How you find out the result

Sail sends a reimbursement.status_changed webhook on every status transition, naming the reimbursement_id, the previous_status, the new status, and a failure_reason when the new status is failed. See Receiving New Data for how to verify and deduplicate any webhook delivery, reimbursements included.

The current status is also available any time from Get Reimbursement or List Reimbursements, so polling either endpoint on any cadence stays a valid alternative to only listening for the webhook.

Next steps