# Expense claims

> File staff expense claims, attach receipts, and approve your own claims through the AccountFUN public API — final approval creates a supplier bill.

Source: https://www.accountfun.com/docs/api/expense-claims/

The expense-claim endpoints let staff file out-of-pocket expenses through an integration or an AI assistant, attach the receipt, and — when the connected user can approve — approve their own claim. The **final** approval creates a supplier bill and posts it to the ledger, exactly like approving in AccountFUN. A **base-currency** claim’s bill is posted straight away; a **foreign-currency** claim’s bill is left as a **draft** so finance can set the exchange rate before posting.

Reading and creating claims requires `expense_claims:read` / `expense_claims:write`; **approving** posts to the books, so it needs its own scope, `expense_claims:approve`, and the authorizing user’s role must have the **Approve Expense Claims** permission (the built-in Admin and Accountant roles have it by default; Standard, Sales and Purchase don’t). See [Authentication](https://www.accountfun.com/docs/api/authentication/).

Approving is limited to your own claims

Through the API or an AI assistant you can only approve claims **you filed yourself**. Approving another member’s claim stays an action inside AccountFUN. The usual separation-of-duties rule also applies, so most members still can’t approve their own claim unless they are an admin or the only approver in the organization — see [Expense claims](https://www.accountfun.com/docs/guides/expense-claims/).

## Endpoints

| Method | Path                                      | Scope                    | Description                                                                       |
| ------ | ----------------------------------------- | ------------------------ | --------------------------------------------------------------------------------- |
| `GET`  | `/api/v1/expense-claims`                  | `expense_claims:read`    | List claims (paginated). A role without Approve Expense Claims sees only its own. |
| `POST` | `/api/v1/expense-claims`                  | `expense_claims:write`   | Create a draft claim.                                                             |
| `POST` | `/api/v1/expense-claims/{id}/approve`     | `expense_claims:approve` | Approve **your own** claim (a draft is submitted first).                          |
| `GET`  | `/api/v1/expense-claims/{id}/attachments` | `expense_claims:read`    | List the receipts attached to a claim.                                            |
| `POST` | `/api/v1/expense-claims/{id}/attachments` | `expense_claims:write`   | Attach receipts to a claim.                                                       |

Related lookups you will usually need: [contacts](https://www.accountfun.com/docs/api/contacts/) (the supplier) and the [chart of accounts](https://www.accountfun.com/docs/api/chart-of-accounts/) (the expense account). AI assistants file and approve claims through the [MCP server](https://www.accountfun.com/docs/api/mcp/).

***

## List expense claims

Returns a paginated list for the token’s organization, newest first. Summary fields only. A role without the **Approve Expense Claims** permission is scoped to the claims it filed itself; the `claimantId` filter is ignored for those roles.

### Request

```bash
curl "https://app.accountfun.com/api/v1/expense-claims?status=SUBMITTED" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Query parameters

| Parameter    | Type    | Default | Description                                                                          |
| ------------ | ------- | ------- | ------------------------------------------------------------------------------------ |
| `status`     | string  | —       | Filter by status: `DRAFT`, `SUBMITTED`, `APPROVED`, `PAID`, `DECLINED`, or `VOIDED`. |
| `claimantId` | string  | —       | Only claims for a specific member. Ignored by a role restricted to its own claims.   |
| `contactId`  | string  | —       | Only claims spent with a specific supplier.                                          |
| `page`       | integer | `1`     | Page number (1-based, max `10000`).                                                  |
| `limit`      | integer | `25`    | Page size (max `200`).                                                               |

### Response — `200 OK`

```json
{
  "expenseClaims": [
    {
      "id": "9b1e4c2a-...",
      "claimNumber": "EC-0007",
      "claimantId": "...",
      "status": "SUBMITTED",
      "description": "Taxi to client meeting",
      "spentAt": "2026-09-15",
      "total": "42.5000",
      "currency": "HKD",
      "billId": null,
      "createdAt": "2026-10-07T03:12:44.120Z",
      "contact": { "id": "...", "name": "City Taxi" }
    }
  ],
  "pagination": { "page": 1, "limit": 25, "total": 1, "totalPages": 1 }
}
```

***

## Create a claim

Creates a **draft** claim. It is not posted to the books: the ledger only moves when the claim reaches **final approval** and AccountFUN creates the linked supplier bill (posted immediately for a base-currency claim, or left as a draft for a foreign-currency one).

A staff role always files for itself — a `claimantId` other than the token user is ignored. A role with **Approve Expense Claims** may set `claimantId` to another member to file on their behalf, in which case that claim can only be approved inside AccountFUN.

### Request

```bash
curl -X POST https://app.accountfun.com/api/v1/expense-claims \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "claimantId": "00000000-0000-0000-0000-000000000001",
    "description": "Taxi to client meeting",
    "spentAt": "2026-09-15",
    "contactId": "00000000-0000-0000-0000-0000000000c1",
    "accountId": "00000000-0000-0000-0000-0000000000a1",
    "amount": "42.50",
    "taxMode": "inclusive"
  }'
```

### Body parameters

| Field         | Type   | Required | Description                                                                                                                          |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `claimantId`  | string | Yes      | The member the claim is for. A staff role may only claim for itself.                                                                 |
| `description` | string | Yes      | What the expense was for. Max 2000 characters.                                                                                       |
| `spentAt`     | date   | Yes      | The date the expense was incurred, `"YYYY-MM-DD"`.                                                                                   |
| `contactId`   | string | Yes      | The supplier the money was spent with. Must be an active contact in the token’s organization.                                        |
| `accountId`   | string | Yes      | The expense account to code the claim to. Must be an account in the token’s organization.                                            |
| `amount`      | string | Yes      | The amount, greater than zero.                                                                                                       |
| `taxMode`     | string | Yes      | `exclusive` (amount excludes tax) or `inclusive` (amount includes tax).                                                              |
| `taxRateId`   | string | No       | An active tax rate from [`GET /api/v1/tax-rates`](https://www.accountfun.com/docs/api/tax-rates/). Omit, `null`, or `""` for no tax. |
| `currency`    | string | No       | 3-letter ISO 4217 code. **Defaults to the organization’s base currency.** Must be enabled.                                           |

`spentAt` may also be sent as an ISO 8601 datetime; the calendar date **as written** is used.

No exchange rate on the claim

A claim never carries an exchange rate. A foreign-currency claim’s bill is created on approval and finance sets the rate there; see [Approve a claim](#approve-a-claim).

### Response — `201 Created`

The created claim in the same shape as the API detail object, including its `claimNumber` and computed `subtotal` / `taxAmount` / `total`.

```json
{
  "expenseClaim": {
    "id": "9b1e4c2a-...",
    "claimNumber": "EC-0007",
    "claimantId": "...",
    "status": "DRAFT",
    "description": "Taxi to client meeting",
    "spentAt": "2026-09-15",
    "amount": "42.5000",
    "taxMode": "inclusive",
    "subtotal": "42.5000",
    "taxAmount": "0.0000",
    "total": "42.5000",
    "currency": "HKD",
    "billId": null,
    "contact": { "id": "...", "name": "City Taxi" },
    "account": { "id": "...", "code": "493", "name": "Travel - National" },
    "taxRate": null,
    "...": "..."
  }
}
```

### Errors

| Status | Cause                                                                                                                                                                                         |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid body — the `details` object names each bad field, including any id that isn’t a valid id or an amount that isn’t positive.                                                            |
| `400`  | The supplier is deactivated, the currency isn’t enabled, or the account can’t be used for an expense claim (a bank, receivable, asset or system control account — choose an expense account). |
| `403`  | The supplier, account or tax rate doesn’t belong to this organization, the claimant isn’t a member, or the spent-at date falls on or before the organization’s lock date.                     |
| `403`  | The role lacks the **Create Expense Claims** permission.                                                                                                                                      |

***

## Approve a claim

Approves the caller’s **own** claim. For a **draft** claim the API submits it to its approval chain and records the decision in the same atomic call; for a claim already **submitted**, it records the decision only. When that approval completes the chain, AccountFUN creates the linked supplier bill — posted straight away for a base-currency claim. A **foreign-currency** claim is posted with the system exchange rate, or left as a **draft** for finance to check or amend the rate before posting, when the organization turns off its **Post foreign-currency claims automatically** setting (see [Settings](https://www.accountfun.com/docs/guides/settings/)).

Requires the `expense_claims:approve` scope and the **Approve Expense Claims** permission. No body.

```bash
curl -X POST https://app.accountfun.com/api/v1/expense-claims/9b1e4c2a-.../approve \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Response — `200 OK`

```json
{
  "expenseClaimId": "9b1e4c2a-...",
  "final": true,
  "outcome": "completed",
  "billId": "7c0e5a1f-...",
  "journalEntryId": "...",
  "postingDeferred": false
}
```

| Field             | Meaning                                                                                                                                                                                                                                 |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `final`           | `true` when the decision completed the chain and created the bill.                                                                                                                                                                      |
| `outcome`         | `completed` (the claim is approved) or `advanced` (it moved to the next approval level).                                                                                                                                                |
| `billId`          | Present only when the decision finalized the claim.                                                                                                                                                                                     |
| `journalEntryId`  | The posted ledger entry — present when the bill was posted (base-currency claims, or foreign-currency claims when the organization auto-posts them). **Absent** when the bill was left as a draft for finance to set the exchange rate. |
| `postingDeferred` | `true` when the bill was created as a **draft** for finance to set the exchange rate (a foreign-currency claim the organization does not auto-post). The claim is still `APPROVED`.                                                     |

### Errors

| Status | Cause                                                                                                                                               |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `403`  | `insufficient_scope` — the token doesn’t carry `expense_claims:approve`, or the user’s role doesn’t have the **Approve Expense Claims** permission. |
| `403`  | The claim was filed by another member — API/MCP approval is limited to your own claims. Approve it in AccountFUN.                                   |
| `403`  | Separation of duties — you can’t approve your own claim unless you’re an admin or the organization’s only approver.                                 |
| `404`  | No claim with that id in the token’s organization.                                                                                                  |
| `409`  | The claim isn’t in a decidable state, or the organization’s Accounts Payable / tax system accounts are missing.                                     |

***

## Attach receipts

Attach one or more files to a claim. File types, size limits and virus scanning follow the same rules as [attaching receipts to a bill](https://www.accountfun.com/docs/api/bills/#attach-receipts): PDF, PNG, JPEG, XLSX, XLS, DOCX, DOC, CSV; 5 MB per file, 25 MB per request, 10 attachments per claim, and the organization’s storage quota. Only the claimant can change a claim’s receipts.

### Multipart upload

```bash
curl -X POST https://app.accountfun.com/api/v1/expense-claims/9b1e4c2a-.../attachments \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@taxi-receipt.pdf"
```

### JSON upload (base64)

```bash
curl -X POST https://app.accountfun.com/api/v1/expense-claims/9b1e4c2a-.../attachments \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"files\": [{\"fileName\": \"taxi-receipt.jpg\", \"contentBase64\": \"$(base64 -w0 taxi-receipt.jpg)\"}]}"
```

### Response — `201 Created`

```json
{
  "attachments": [
    {
      "id": "...",
      "originalName": "taxi-receipt.pdf",
      "mimeType": "application/pdf",
      "fileSize": 48213,
      "createdAt": "2026-10-07T03:13:10.004Z"
    }
  ]
}
```

Rejected files come back in a `warnings` array alongside the stored ones.

### List attachments

```bash
curl https://app.accountfun.com/api/v1/expense-claims/9b1e4c2a-.../attachments \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Returns `{ "attachments": [ … ] }` oldest first, each with `id`, `originalName`, `mimeType`, `fileSize`, `scanStatus`, and `createdAt`.

***

## Claim statuses

| Status      | Meaning                                                                                                                                                    |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DRAFT`     | Saved, not submitted.                                                                                                                                      |
| `SUBMITTED` | Moving through the approval chain.                                                                                                                         |
| `APPROVED`  | Final approval reached — a linked supplier bill was created (posted immediately for a base-currency claim, or left as a draft for a foreign-currency one). |
| `PAID`      | The linked bill has been paid.                                                                                                                             |
| `DECLINED`  | An approver rejected it.                                                                                                                                   |
| `VOIDED`    | Cancelled.                                                                                                                                                 |

Over the API you can create a claim and approve your own; editing, declining, voiding and reimbursing are done inside AccountFUN.
