Reimbursement claims
A reimbursement claim is a request from an Individual to be paid back from a Benefit for an expense they paid.
| Operation | Use it to |
|---|---|
| prepareClaimDocumentUpload | Get secure links to upload receipts and other claim documents. |
| submitClaim | Submit a claim for an Individual. |
| claims and claim | Find claims and read their status. |
| approveClaim | Approve a claim in full or in part. |
| denyClaim | Deny a claim. |
| requestClaimInformation | Ask the Individual for more information. |
| resubmitClaim | Send a claim back for review after an information request. |
| updateClaim | Correct the details of a claim before it is decided. |
A typical flow:
- Upload the claim's documents, then submit the claim.
- Decide the claim, or ask for more information and resubmit it.
- Follow reimbursement with the claim's
status, using the claims query.
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 theidfrom 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 Benefitid; First Dollar applies the claim to the member's Division enrollment. A Division's own BenefitidreturnsNOT_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 toAPPROVEDand then on through reimbursement. - Denies it with
denyClaim. The claim moves toREJECTED. - Asks for more information with
requestClaimInformation. The claim moves toINFORMATION_REQUESTED. The response,resubmitClaim, moves it toRESUBMITTED, ready for review again.
| Status | Meaning |
|---|---|
PENDING | Submitted and waiting for review. |
IN_REVIEW | Being reviewed. |
INFORMATION_REQUESTED | Waiting for the Individual to supply more information. |
RESUBMITTED | Resubmitted with more information and waiting for review. |
APPROVED | Approved. Reimbursement has not started. |
IN_ACCRUAL | Approved, and waiting for Benefit funds to accrue. It can be partly reimbursed in the meantime. |
PAYMENT_PENDING | Approved, and reimbursement is in progress. |
PAID | Approved, and reimbursement is complete. |
OUT_OF_POCKET | Approved as eligible, but the Benefit reimburses none of it, for example before a deductible is met. |
REJECTED | Denied. Nothing is reimbursed. |
CANCELLED | Cancelled. 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:
- Call
prepareClaimDocumentUploadto get one upload link for each file. - Upload each file to its link.
- Pass the returned
documentGroupIdtosubmitClaim. 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.
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 inUSDcents.4250is $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.serviceStartDateandserviceEndDate: optional. The start date defaults totransactionDate, 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.merchantorprovider: specify exactly one. See Merchants and care providers.description: optional.descriptionon any operation, andnoteon 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: queryclaimswith the Individual'sindividualIdsand the claim'sbenefitIds, and matchamount,transactionDate, andsubmittedAt. If it is not there, retry with the same key after no response, or a new key after anInternalServerError. - 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.
| Filter | Behavior |
|---|---|
individualIds | Only claims reimbursed to these Individuals. If any of them is not one of your members, the list is empty. |
organizationIds | Only claims whose enrollment belongs to these Organizations. A parent Organization does not match its Divisions' claims. |
benefitIds | Only claims reimbursed from these Benefits, including claims of Division members. |
statuses | Only 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
amountis the amount of the expense. In rare cases it isnull, with an error for that field, when a stored amount includes a fraction of a cent. Handle anullamount without failing the whole page.approvedAmountis the part of the expense approved as eligible. It isnulluntil 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 withstatus.
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
commentondenyClaimandrequestClaimInformationis required whenreasonisOTHER, and not accepted for any other reason.commentonpartialDenialis accepted for every reason, and required forOTHER.UNSPECIFIEDis not accepted as a denial reason. It can appear on older claims.noteis 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.
-
To add documents, call
prepareClaimDocumentUploadwith the claim'sidinstead ofindividualId. 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" }]}} -
Call
resubmitClaim. The claim moves toRESUBMITTED.mutation ResubmitClaim($input: ResubmitClaimInput!) {resubmitClaim(input: $input) {... on ResubmitClaimResult {claim {idstatus}}... on Error {codemessageretryable}}}{"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.
changedisfalsewhen 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:
code | Meaning |
|---|---|
NOT_FOUND | The claim, Individual, Benefit, or document group does not exist or belongs to another partner. |
FEATURE_NOT_ENABLED | Claims, or this operation, are not enabled for your account. |
FORBIDDEN | Your credential is not permitted to perform the operation. |
INVALID_CLAIM_STATUS | The claim's current status does not allow the operation. |
LINKED_BANK_ACCOUNT_REQUIRED | The Individual has no verified primary linked bank account that is ready to use. |
BAD_REQUEST | The 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
- Reimbursement Widget: an embeddable UI that lets members file their own claims.
- Claim Reimbursement file format: submit claims in a file instead of through the API.