Skip to main content

Reimbursement claims

A reimbursement claim is a request from an Individual to be paid back from a Benefit for an expense they paid.

OperationUse it to
prepareClaimDocumentUploadGet secure links to upload receipts and other claim documents.
submitClaimSubmit a claim for an Individual.
claims and claimFind claims and read their status.
approveClaimApprove a claim in full or in part.
denyClaimDeny a claim.
requestClaimInformationAsk the Individual for more information.
resubmitClaimSend a claim back for review after an information request.
updateClaimCorrect the details of a claim before it is decided.

A typical flow:

  1. Upload the claim's documents, then submit the claim.
  2. Decide the claim, or ask for more information and resubmit it.
  3. Follow reimbursement with the claim's status, using the claims query.
Enabling claims

Claims operations are not enabled by default. Contact [email protected] to enable them. Some operations, such as corrections and partial approval, are enabled separately. An operation that is not enabled returns FEATURE_NOT_ENABLED.

Before you start​

You need:

  • individualId: the account holder who paid the expense and who is reimbursed. This is the id from createIndividual or the individuals query.
  • benefitId: the Benefit to reimburse from. The Individual must be enrolled in it. For a member of a Division, use the parent Organization's Benefit id; First Dollar applies the claim to the member's Division enrollment. A Division's own Benefit id returns NOT_FOUND.

The Individual must also have a verified primary linked bank account. First Dollar reimburses the claim to that account, and you do not choose it in the request. The Individual links and verifies the account in their First Dollar account; the Partner API does not link bank accounts. If there is no such account, or the account must be linked again, submitClaim returns LINKED_BANK_ACCOUNT_REQUIRED.

The claim lifecycle​

A submitted claim is PENDING. A reviewer then does one of three things:

  • Approves it with approveClaim. The claim moves to APPROVED and then on through reimbursement.
  • Denies it with denyClaim. The claim moves to REJECTED.
  • Asks for more information with requestClaimInformation. The claim moves to INFORMATION_REQUESTED. The response, resubmitClaim, moves it to RESUBMITTED, ready for review again.
StatusMeaning
PENDINGSubmitted and waiting for review.
IN_REVIEWBeing reviewed.
INFORMATION_REQUESTEDWaiting for the Individual to supply more information.
RESUBMITTEDResubmitted with more information and waiting for review.
APPROVEDApproved. Reimbursement has not started.
IN_ACCRUALApproved, and waiting for Benefit funds to accrue. It can be partly reimbursed in the meantime.
PAYMENT_PENDINGApproved, and reimbursement is in progress.
PAIDApproved, and reimbursement is complete.
OUT_OF_POCKETApproved as eligible, but the Benefit reimburses none of it, for example before a deductible is met.
REJECTEDDenied. Nothing is reimbursed.
CANCELLEDCancelled. Nothing is reimbursed. The API does not cancel claims.

You can approve, deny, request information about, or correct a claim while it is PENDING, IN_REVIEW, INFORMATION_REQUESTED, or RESUBMITTED. In any other status, these operations return INVALID_CLAIM_STATUS. A claim can also change status outside the API, for example when an administrator reviews it. After approval, the Benefit's configuration sets the statuses that follow.

A decision on a RESUBMITTED claim that fails can leave the claim IN_REVIEW. You can still decide it.

New statuses can be added. Handle a status that you do not recognize without failing.

Upload claim documents​

Attach receipts, invoices, and other documents to a claim in three steps:

  1. Call prepareClaimDocumentUpload to get one upload link for each file.
  2. Upload each file to its link.
  3. Pass the returned documentGroupId to submitClaim. The document group must belong to the same Individual.

Documents are optional. But only a claim submitted with documents can get more of them later, so upload them before you submit.

1. Prepare the upload​

mutation PrepareClaimDocumentUpload($input: PrepareClaimDocumentUploadInput!) {
prepareClaimDocumentUpload(input: $input) {
... on PrepareClaimDocumentUploadResult {
documentGroupId
uploads {
fileName
uploadUrl
requestHeaders
expiresAt
}
}
... on Error {
code
message
retryable
}
}
}

Every claims operation returns its result or an Error: a BadRequestError or an InternalServerError. The other examples on this page select errors the same way.

Variables:

{
"input": {
"individualId": "<individualId>",
"files": [
{ "fileName": "receipt.pdf", "contentType": "PDF" },
{ "fileName": "invoice.jpg", "contentType": "JPEG" }
]
}
}

Each call accepts 1 to 10 files. A file name can have at most 255 characters and must not contain a path or control characters. The accepted formats are PDF, JPEG, PNG, HEIC, and HEIF.

uploads has one entry for each file, in the same order as files.

2. Upload the files​

Send each file's bytes in an HTTP PUT request to its uploadUrl, with every header in requestHeaders exactly as returned. With curl, add one -H for each header:

curl -X PUT "<uploadUrl>" \
-H "<header name>: <header value>" \
--data-binary @receipt.pdf

Upload each file before its expiresAt. If a link expires before you submit, prepare and upload all of the claim's files again: each call with individualId creates a new document group, and a claim takes only one. When you add files to a submitted claim, prepare only the expired file again.

Treat upload links as secrets

The headers contain the file's encryption key. Together, the URL and headers let anyone write the file. Do not log them or store them.

Submit a claim​

mutation SubmitClaim($input: SubmitClaimInput!) {
submitClaim(input: $input) {
... on SubmitClaimResult {
claim {
id
status
}
}
... on Error {
code
message
retryable
}
}
}

Variables:

{
"input": {
"individualId": "<individualId>",
"benefitId": "<benefitId>",
"amount": { "currency": "USD", "lowestDenominationAmount": 4250 },
"transactionDate": "2026-10-02",
"merchant": { "name": "Corner Pharmacy" },
"description": "Prescription refill",
"documentGroupId": "<documentGroupId>",
"idempotencyKey": "<your unique key>"
}
}

The new claim has status PENDING. The claim amount is held on the Individual's Benefit balance until the claim is decided.

Input rules:

  • amount: a positive amount in USD cents. 4250 is $42.50. It must not be more than the Individual's available balance for the Benefit, or the Benefit's plan limit. A Benefit that reimburses claims as its funds accrue accepts a claim larger than the available balance; that claim is not held on the balance.
  • transactionDate: the date the expense was paid. It must not be in the future.
  • serviceStartDate and serviceEndDate: optional. The start date defaults to transactionDate, and it must be in the Benefit's claim period. An end date requires a start date and must be on or after it.
  • claimantId: optional. The person who received the service: the account holder, or one of their active dependents who is enrolled in the same Benefit. Defaults to the account holder.
  • merchant or provider: specify exactly one. See Merchants and care providers.
  • description: optional. description on any operation, and note on a decision, are added to the claim's notes, which the Individual and administrators can read.

Idempotency​

idempotencyKey is required. Use a new key for each new claim.

  • No response, or an InternalServerError: the claim may exist. Look for it first: query claims with the Individual's individualIds and the claim's benefitIds, and match amount, transactionDate, and submittedAt. If it is not there, retry with the same key after no response, or a new key after an InternalServerError.
  • A retry with the same key returns the original response, including an error, once the first request has completed. A retry that arrives while the first request is still running, or after it stopped unexpectedly, can submit a second claim.
  • A BadRequestError: correct the cause, then retry with a new key.
  • A key reused with different input or different selected fields returns a top-level error.

Merchants and care providers​

Most claims name the merchant where the expense was paid:

"merchant": { "name": "Corner Pharmacy" }

Dependent care claims name a care provider instead. To record a new provider, give its name and the kind of care:

"provider": { "name": "Little Oaks Daycare", "serviceCategory": "DAYCARE" }

See ClaimProviderServiceCategory for the categories.

A provider belongs to the Individual. The claim returns it as provider { id name serviceCategory }. To use the same provider on another claim for the same Individual, pass only its id:

"provider": { "id": "<providerId>" }

Find claims​

The claims query returns your members' claims, newest first. See Claim for every field.

query Claims($where: ClaimsFilterInput, $after: String, $first: Int) {
claims(where: $where, after: $after, first: $first) {
... on ClaimsResults {
nodes {
id
individualId
benefitId
status
transactionDate
amount {
currency
lowestDenominationAmount
}
approvedAmount {
currency
lowestDenominationAmount
}
submittedAt
statusUpdatedAt
}
pageInfo {
hasNextPage
endCursor
}
}
... on Error {
code
message
retryable
}
}
}

Variables:

{
"where": {
"individualIds": ["<individualId>"],
"statuses": ["PENDING", "RESUBMITTED"]
}
}

All filters are optional. Filters that match nothing return an empty list, not an error.

FilterBehavior
individualIdsOnly claims reimbursed to these Individuals. If any of them is not one of your members, the list is empty.
organizationIdsOnly claims whose enrollment belongs to these Organizations. A parent Organization does not match its Divisions' claims.
benefitIdsOnly claims reimbursed from these Benefits, including claims of Division members.
statusesOnly claims in these statuses.

The list is cursor-paginated. See Pagination.

To read one claim, use the claim query with where: { id: "<claimId>" }.

Read claim amounts​

  • amount is the amount of the expense. In rare cases it is null, with an error for that field, when a stored amount includes a fraction of a cent. Handle a null amount without failing the whole page.
  • approvedAmount is the part of the expense approved as eligible. It is null until the claim is approved.
  • The amount reimbursed can be less than approvedAmount, for example when the Benefit pays a percentage or applies a deductible. Follow reimbursement with status.

These fields describe the claim. They are not a record of payments.

Decide a claim​

Approve​

Approve the full amount:

mutation ApproveClaim($input: ApproveClaimInput!) {
approveClaim(input: $input) {
... on ApproveClaimResult {
claim {
id
status
approvedAmount {
currency
lowestDenominationAmount
}
}
}
... on Error {
code
message
retryable
}
}
}
{
"input": {
"claimId": "<claimId>"
}
}

To approve part of the expense, set approvedAmount to the eligible amount and give the reason for the rest in partialDenial:

{
"input": {
"claimId": "<claimId>",
"approvedAmount": { "currency": "USD", "lowestDenominationAmount": 3000 },
"partialDenial": {
"reason": "INELIGIBLE_EXPENSE",
"comment": "Vitamins are not an eligible expense."
}
}
}

approvedAmount must be positive and no more than the claim's amount. partialDenial is required when approvedAmount is less than amount, and is not accepted otherwise.

The claim moves to APPROVED, and the Individual is notified.

Deny​

mutation DenyClaim($input: DenyClaimInput!) {
denyClaim(input: $input) {
... on DenyClaimResult {
claim {
id
status
denial {
reason
comment
}
}
}
... on Error {
code
message
retryable
}
}
}
{
"input": {
"claimId": "<claimId>",
"reason": "RECEIPT_MISSING_OR_INVALID"
}
}

The claim moves to REJECTED, and any amount held on the Individual's Benefit balance is released. The Individual is notified of the denial and its reason. See ClaimDenialReason for the reasons.

Request more information​

mutation RequestClaimInformation($input: RequestClaimInformationInput!) {
requestClaimInformation(input: $input) {
... on RequestClaimInformationResult {
claim {
id
status
informationRequest {
reason
comment
}
}
}
... on Error {
code
message
retryable
}
}
}
{
"input": {
"claimId": "<claimId>",
"reason": "OTHER",
"comment": "Please upload an itemized receipt."
}
}

The claim moves to INFORMATION_REQUESTED, and the Individual is notified. See ClaimInformationRequestedReason for the reasons.

Reasons and comments​

  • comment on denyClaim and requestClaimInformation is required when reason is OTHER, and not accepted for any other reason.
  • comment on partialDenial is accepted for every reason, and required for OTHER.
  • UNSPECIFIED is not accepted as a denial reason. It can appear on older claims.
  • note is optional on every decision.

Respond to an information request​

When a claim is INFORMATION_REQUESTED, supply the information and send the claim back for review.

  1. To add documents, call prepareClaimDocumentUpload with the claim's id instead of individualId. Upload the files as in Upload the files. The files are added to the claim's document group.

    {
    "input": {
    "claimId": "<claimId>",
    "files": [{ "fileName": "itemized-receipt.pdf", "contentType": "PDF" }]
    }
    }
  2. Call resubmitClaim. The claim moves to RESUBMITTED.

    mutation ResubmitClaim($input: ResubmitClaimInput!) {
    resubmitClaim(input: $input) {
    ... on ResubmitClaimResult {
    claim {
    id
    status
    }
    }
    ... on Error {
    code
    message
    retryable
    }
    }
    }
    {
    "input": {
    "claimId": "<claimId>",
    "description": "Added the itemized receipt."
    }
    }

resubmitClaim can also correct the same details as updateClaim. It needs only submission to be enabled for your account, so it can correct a claim even when updateClaim returns FEATURE_NOT_ENABLED.

Correct a claim​

Use updateClaim to correct a claim's details before it is decided. The claim's status does not change.

mutation UpdateClaim($input: UpdateClaimInput!) {
updateClaim(input: $input) {
... on UpdateClaimResult {
changed
claim {
id
transactionDate
merchant {
name
}
}
}
... on Error {
code
message
retryable
}
}
}
{
"input": {
"claimId": "<claimId>",
"transactionDate": "2026-10-01",
"merchant": { "name": "Corner Pharmacy #12" }
}
}
  • Omitted fields keep their current values. changed is false when nothing was different.
  • You cannot change the amount, the Individual, or the Benefit.
  • A claim with a merchant keeps a merchant, and a claim with a provider keeps a provider.
  • A corrected provider name or category applies only to this claim. The Individual's other claims that use the provider do not change.

Errors​

Claims operations return a BadRequestError with one of these codes:

codeMeaning
NOT_FOUNDThe claim, Individual, Benefit, or document group does not exist or belongs to another partner.
FEATURE_NOT_ENABLEDClaims, or this operation, are not enabled for your account.
FORBIDDENYour credential is not permitted to perform the operation.
INVALID_CLAIM_STATUSThe claim's current status does not allow the operation.
LINKED_BANK_ACCOUNT_REQUIREDThe Individual has no verified primary linked bank account that is ready to use.
BAD_REQUESTThe input is not valid, or a claim rule rejected it, such as a date outside the claim period. The message describes the problem.

Unexpected failures return InternalServerError. Before you retry a submitClaim with a new key, read Idempotency.

See also​