> ## Documentation Index
> Fetch the complete documentation index at: https://developer.findity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Expense categories

Manage the expense categories of an organization through the Findity Admin API.

This guide shows you how to list, read, create, update, and delete an organization's expense categories, and how a category is mapped to accounts and salary types.

## Overview

An expense category is the label an expense is filed under, such as *Hotel*, *Mileage* or *Domestic* per diem. Each category also carries the rules that apply to expenses in it: default tax percent, the accounting and salary accounts used on export, and the kind of expense it creates.

The category lifecycle is:

1. **Read the categories** an organization already has — list them, or fetch one by its `id`.
2. **Create categories** your customer needs on top of the ones the organization started with.
3. **Keep them in sync** — update names, tax settings and account mappings, and delete categories that are no longer used.

Every call works on one organization.

## Prerequisites

1. Complete [Authentication](https://developer.findity.com/docs/accessing.md) and obtain an admin-scoped access token.

## Key concepts

| Concept | Description |
| - | - |
| **Expense category** | A category an expense can be filed under. Identified by its `id`, a 32-character hex string. |
| **Category type** | `categoryType` says what kind of category it is, for example `OtherExpenseCategory` for receipt-based categories, `SubsistenceAllowanceCategory` for per diem, `MileageExpenseCategory` for mileage and `RepresentationExpenseCategory` for representation. A category's type is fixed when it is created. |
| **Expense type** | `verificationClassType` is the expense type the category belongs to, for example `ReceiptVerification`, `SubsistenceAllowance` or `CarSpecification`. |
| **Category template** | `categoryTemplateId` is the template the category is based on. A new category needs one. |
| **Account mapping** | `accountingNumberId` is the `id` of the account the expense is recorded against. See [Charts of accounts](https://developer.findity.com/docs/admin-api-charts-of-accounts.md). |
| **Salary type** | `salaryCodeId` is the salary type used for payroll integration, if any. |

## API endpoints

All endpoints use the Admin API base URL. Examples in this guide use the stage host — switch to the production host (`api.findity.com`) only when you're ready to go live.

```text theme={"system"}
https://stage-api.findity.com/api/v1/admin
```

| Operation | Method | Endpoint |
| - | - | - |
| List expense categories | `GET` | `/organizations/{organizationId}/expenseservice/categories` |
| Get a single expense category | `GET` | `/organizations/{organizationId}/expenseservice/categories/{categoryId}` |
| Create an expense category | `POST` | `/organizations/{organizationId}/expenseservice/categories` |
| Update an expense category | `PUT` | `/organizations/{organizationId}/expenseservice/categories/{categoryId}` |
| Delete an expense category | `DELETE` | `/organizations/{organizationId}/expenseservice/categories/{categoryId}` |

### Required headers

| Header | Value |
| - | - |
| `Authorization` | `Bearer {access_token}` |
| `Content-Type` | `application/json` — on requests that have a body (`POST` and `PUT`) |

## Step 1 — List expense categories

Retrieve the categories of an organization. The response is wrapped in a `{meta, data}` envelope.

`GET /organizations/{organizationId}/expenseservice/categories`

### Query parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `sort` | string | `name:asc` | Comma-separated `property:direction` pairs, for example `name:desc`. Sortable properties are `name` and `dateCreated`. |
| `max` | integer | `10` | Maximum number of categories to return, 1–1000. A value outside that range returns `400 INVALID_PARAMETER`. |
| `offset` | integer | `0` | Number of categories to skip. Must be 0 or more. |

```sh cURL theme={"system"}
curl -X GET \
  "https://stage-api.findity.com/api/v1/admin/organizations/8a5c8bee9e3a4a24019e3ae1732b008e/expenseservice/categories?max=3" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example response** (status `200 OK`)

```json JSON theme={"system"}
{
  "meta": { "total": 16, "count": 3 },
  "data": [
    {
      "id": "a65a666f52424b44a480d1949aaae6aa",
      "name": "Abroad purchase",
      "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e954fe902ab",
      "type": "AbroadPurchaseExpenseCategory",
      "presetValues": [],
      "categoryTemplateId": "263",
      "dateCreated": "2026-11-13T13:45:55Z",
      "defaultTaxPercent": 5.0,
      "nonDeductableVat": false,
      "accountingNumberId": "8a5c896da1d5203f01a1d5285f67005b",
      "accountingNumbers": [],
      "mccCodes": [],
      "expenseCategoryFilter": "NONE",
      "showAccounting": false,
      "showRepresentation": false,
      "settings": {},
      "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e6045ded90038",
      "verificationClassType": "ReceiptVerification",
      "categoryType": "AbroadPurchaseExpenseCategory"
    },
    {
      "id": "f9b3b0f2f5a14f3c8c7a1f4a2f6d7e11",
      "name": "Biljetter (tåg, buss, flyg)",
      "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e90b94b0298",
      "type": "OtherExpenseCategory",
      "presetValues": [],
      "categoryTemplateId": "63",
      "dateCreated": "2026-11-13T13:45:55Z",
      "defaultTaxPercent": 6.0,
      "nonDeductableVat": false,
      "accountingNumberId": "8a5c896da1d5203f01a1d5285f68005f",
      "accountingNumbers": [],
      "salaryCodeId": "8a5c896da1d5203f01a1d5285f7b0090",
      "mccCodes": [],
      "showAccounting": false,
      "showRepresentation": false,
      "settings": {},
      "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e6045c0a10020",
      "verificationClassType": "ReceiptVerification",
      "categoryType": "OtherExpenseCategory"
    },
    {
      "id": "f36a3b3fe1c747ecaa82dc93535ba816",
      "name": "Domestic",
      "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e969dcd02df",
      "type": "SubsistenceAllowanceCategory",
      "presetValues": [],
      "categoryTemplateId": "343",
      "dateCreated": "2026-11-13T13:45:55Z",
      "nonDeductableVat": false,
      "accountingNumbers": [],
      "mccCodes": [],
      "expenseCategoryFilter": "NONE",
      "showAccounting": false,
      "showRepresentation": false,
      "settings": {},
      "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e604588430003",
      "verificationClassType": "SubsistenceAllowance",
      "categoryType": "SubsistenceAllowanceCategory"
    }
  ]
}
```

`meta.total` is the total number of categories in the organization; `meta.count` is the number of items returned in this response.

> [!NOTE]
>
> An organization gets its categories from the template it was created from. An organization created from a template that has no categories returns `{ "meta": { "total": 0, "count": 0 }, "data": [] }`.

### Response fields

Every endpoint that returns a category uses the same fields. Fields that have no value are left out of the response, so a per diem category such as *Domestic* has no `defaultTaxPercent` and no `accountingNumberId`.

| Field | Type | Description |
| - | - | - |
| `id` | string | The category's id. |
| `name` | string | Display name of the category. Names come from the template the organization was created from, so they differ between organizations. |
| `categoryType` | string | The category type: one of `SubsistenceAllowanceCategory`, `RepresentationExpenseCategory`, `OtherExpenseCategory`, `MileageExpenseCategory`, `FuelBenefitsExpenseCategory`, `FoodBenefitsExpenseCategory`, `ExpenseCategory`, `CongestionBenefitExpenseCategory`, `BenefitExpenseCategory` or `AbroadPurchaseExpenseCategory`. |
| `type` | string | Repeats the value of `categoryType`. |
| `verificationClassType` | string | The expense type the category belongs to, for example `ReceiptVerification`. |
| `categoryTemplateId` | string | The category template the category is based on. |
| `dateCreated` | string | When the category was created, as an ISO 8601 date-time in UTC. |
| `iconUrl`, `invertedIconUrl` | string | URLs of the category icon and of its inverted variant. |
| `defaultTaxPercent` | number | Default tax percentage. |
| `deviantTaxPercent` | number | Deviant tax percentage. |
| `nonDeductableVat` | boolean | Whether the VAT is non-deductable. |
| `accountingNumberId` | string | The `id` of the primary account. |
| `accountingNumber` | object | The primary account (`id`, `name`, `number`, `type`, `active`, `visible`). Present when the category has an account. |
| `accountingNumbers` | array | Accounts associated with the category. |
| `accountNumberMapping` | string | Account number mapping string. |
| `expenseCodeId` | string | The id of the expense code. |
| `salaryCodeId` | string | The id of the salary type. |
| `taxAccountingNumberId`, `taxSalaryCodeId`, `taxExpenseCodeId` | string | The ids of the account, salary type and expense code used for the tax amount. |
| `budgetAmount`, `budgetName`, `budgetStartMonth` | number, string, integer | Budget amount, budget name, and the month (1–12) the budget period starts. |
| `mccCodes` | array of strings | Merchant category codes associated with the category. |
| `presetValues` | array | Preset dimension values for the category. |
| `expenseCategoryFilter` | string | Filter that decides which expenses belong to the category. |
| `showAccounting` | boolean | Whether accounting fields are shown for the category. |
| `showRepresentation` | boolean | Whether representation fields are shown for the category. |
| `settings` | object | Additional category settings as a free-form object. |

## Step 2 — Get a single category

Retrieve one category by its `id`. The response is the category itself, without the `{meta, data}` envelope.

`GET /organizations/{organizationId}/expenseservice/categories/{categoryId}`

```sh cURL theme={"system"}
curl -X GET \
  "https://stage-api.findity.com/api/v1/admin/organizations/8a5c8bee9e3a4a24019e3ae1732b008e/expenseservice/categories/f36a3b3fe1c747ecaa82dc93535ba816" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example response** (status `200 OK`)

```json JSON theme={"system"}
{
  "id": "f36a3b3fe1c747ecaa82dc93535ba816",
  "name": "Domestic",
  "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e969dcd02df",
  "type": "SubsistenceAllowanceCategory",
  "presetValues": [],
  "categoryTemplateId": "343",
  "dateCreated": "2026-11-13T13:45:55Z",
  "nonDeductableVat": false,
  "accountingNumbers": [],
  "mccCodes": [],
  "expenseCategoryFilter": "NONE",
  "showAccounting": false,
  "showRepresentation": false,
  "settings": {},
  "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e604588430003",
  "verificationClassType": "SubsistenceAllowance",
  "categoryType": "SubsistenceAllowanceCategory"
}
```

An unknown `categoryId` returns `404 CATEGORY_NOT_FOUND`.

## Step 3 — Create a category

Add a category to the organization. Returns `201 Created` with the new category, including its `id`.

`POST /organizations/{organizationId}/expenseservice/categories`

### Request body fields

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Display name of the category. |
| `classType` | string | Yes | The category type to create, as the package `com.findity.economy.accounting` followed by the type name from `categoryType`, for example `com.findity.economy.accounting.OtherExpenseCategory`. Fixed after creation. |
| `categoryTemplateId` | string | Yes | The template the category is based on. Take the `categoryTemplateId` of an existing category of the same type. |
| `defaultTaxPercent` | number | No | Default tax percentage. |
| `deviantTaxPercent` | number | No | Deviant tax percentage. |
| `nonDeductableVat` | boolean | No | Whether the VAT is non-deductable. |
| `accountingNumberId` | string | No | The `id` of the account the category is mapped to. See [Charts of accounts](https://developer.findity.com/docs/admin-api-charts-of-accounts.md). |
| `salaryCodeId` | string | No | The id of the salary type. |
| `taxAccountingNumberId`, `taxSalaryCodeId`, `taxExpenseCodeId`, `expenseCodeId` | string | No | Account, salary type and expense code ids, as in the response fields. |
| `accountNumberMapping` | string | No | Account number mapping string. |
| `budgetAmount`, `budgetName`, `budgetStartMonth` | number, string, integer | No | Budget settings. `budgetStartMonth` is 1–12. |
| `expenseCategoryFilter` | string | No | Filter that decides which expenses belong to the category. |
| `mccCodes` | array of strings | No | Merchant category codes. |
| `presetValues` | array | No | Preset dimension values. |
| `settings` | object | No | Additional category settings. |

> [!WARNING]
>
> **Don't send `id` when you create a category: Findity generates it, and a request with an `id` is rejected with `400 INVALID_ID`.**

```sh cURL theme={"system"}
curl -X POST \
  "https://stage-api.findity.com/api/v1/admin/organizations/8a5c8bee9e3a4a24019e3ae1732b008e/expenseservice/categories" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Conference fees",
    "classType": "com.findity.economy.accounting.OtherExpenseCategory",
    "categoryTemplateId": "63",
    "defaultTaxPercent": 25.0
  }'
```

**Example response** (status `201 Created`)

```json JSON theme={"system"}
{
  "id": "5b2e8f1a7c3d4e9fa1b6c0d4e7f82a93",
  "name": "Conference fees",
  "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e90b94b0298",
  "type": "OtherExpenseCategory",
  "presetValues": [],
  "categoryTemplateId": "63",
  "dateCreated": "2026-11-13T14:05:12Z",
  "defaultTaxPercent": 25.0,
  "nonDeductableVat": false,
  "accountingNumbers": [],
  "mccCodes": [],
  "expenseCategoryFilter": "NONE",
  "showRepresentation": false,
  "settings": {},
  "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e60459cbe000e",
  "verificationClassType": "ReceiptVerification",
  "categoryType": "OtherExpenseCategory"
}
```

A request that is missing `name`, `classType` or `categoryTemplateId`, or that has an unsupported `classType`, returns `400` without a `code`.

## Step 4 — Update a category

Change a category. Only the fields you send are changed. The request body **must include the category's `id`**, and it must match the `categoryId` in the URL — otherwise the call returns `400 INVALID_ID`. The category's type can't be changed: a `classType` in an update is ignored.

`PUT /organizations/{organizationId}/expenseservice/categories/{categoryId}`

The body takes the same optional fields as in [Step 3](#step-3--create-a-category).

```sh cURL theme={"system"}
curl -X PUT \
  "https://stage-api.findity.com/api/v1/admin/organizations/8a5c8bee9e3a4a24019e3ae1732b008e/expenseservice/categories/5b2e8f1a7c3d4e9fa1b6c0d4e7f82a93" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "5b2e8f1a7c3d4e9fa1b6c0d4e7f82a93",
    "name": "Conference and seminar fees",
    "defaultTaxPercent": 12.0
  }'
```

**Example response** (status `200 OK`)

```json JSON theme={"system"}
{
  "id": "5b2e8f1a7c3d4e9fa1b6c0d4e7f82a93",
  "name": "Conference and seminar fees",
  "iconUrl": "https://stage-api.findity.com/api/resources/8ab28c5088104f7401881e90b94b0298",
  "type": "OtherExpenseCategory",
  "presetValues": [],
  "categoryTemplateId": "63",
  "dateCreated": "2026-11-13T14:05:12Z",
  "defaultTaxPercent": 12.0,
  "nonDeductableVat": false,
  "accountingNumbers": [],
  "mccCodes": [],
  "expenseCategoryFilter": "NONE",
  "showRepresentation": false,
  "settings": {},
  "invertedIconUrl": "https://stage-api.findity.com/api/resources/8ab28c4e6e603c61016e60459cbe000e",
  "verificationClassType": "ReceiptVerification",
  "categoryType": "OtherExpenseCategory"
}
```

To map the category to an account, send the `id` of the account as `accountingNumberId`. The response then carries the account in `accountingNumber`, and `accountingNumbers` lists it.

## Step 5 — Delete a category

Remove a category from the organization. Returns `204 No Content` with an empty response body on success.

`DELETE /organizations/{organizationId}/expenseservice/categories/{categoryId}`

> [!NOTE]
>
> A category that expenses already use can't be deleted. The call returns `400 CATEGORY_IN_USE`.

```sh cURL theme={"system"}
curl -X DELETE \
  "https://stage-api.findity.com/api/v1/admin/organizations/8a5c8bee9e3a4a24019e3ae1732b008e/expenseservice/categories/5b2e8f1a7c3d4e9fa1b6c0d4e7f82a93" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example response** (status `204 No Content`)

The response body is empty. After the deletion the category no longer appears in the list, and `GET` on it returns `404 CATEGORY_NOT_FOUND`.

## Common integration patterns

**Find the categories of one type.** The list has no filter. Read the full list and keep the items whose `categoryType` (or `verificationClassType`) matches, for example every `SubsistenceAllowanceCategory` to find the per diem categories.

**Map categories to accounts.** Keep the chart of accounts and the categories in line after an accounting change:

1. `GET /organizations/{organizationId}/accounts` to read the account ids.
2. `GET /organizations/{organizationId}/expenseservice/categories?max=1000` and check each category's `accountingNumberId`. Per diem and mileage categories have none.
3. `PUT` the categories whose mapping must change, with the new `accountingNumberId`.

To send many of these calls in one request, see [Bulk requests](https://developer.findity.com/docs/admin-api-bulk-requests.md).

**Add a category of an existing type.** Read the list, pick a category of the type you want (for example `OtherExpenseCategory`), and use its `categoryTemplateId` in `POST` with the matching `classType`.

## Error handling

Errors return a JSON envelope:

```json JSON theme={"system"}
{ "result": "error", "requestId": "<uuid>", "description": "<human-readable message>", "code": "<machine-readable code>" }
```

The `requestId` value is useful to include when reporting a problem to Findity support. Some `400` errors have no `code`.

| Status | `code` | Description |
| - | - | - |
| `400` | `INVALID_PARAMETER` | On the list, `max` is not an integer between 1 and 1000, or `offset` is not an integer of 0 or more. |
| `400` | `INVALID_JSON_BODY` | The body can't be parsed as JSON. |
| `400` | `NO_KNOWN_JSON_PROPERTIES` | On update, the body has no field the endpoint accepts. |
| `400` | `INVALID_ID` | On create, the body contains an `id`. On update, the body has no `id`, or its `id` doesn't match the `categoryId` in the URL. |
| `400` | `CATEGORY_IN_USE` | On delete, expenses use the category. |
| `400` | — | On create, `name`, `classType` or `categoryTemplateId` is missing, or `classType` is not supported. |
| `401` | — | Access token is missing, expired, or invalid. Refresh and retry. |
| `404` | `ORGANIZATION_NOT_FOUND` | The organization isn't found, you have no admin access to it, or the value is not the organization's internal `id`. Confirm the `organizationId`. |
| `404` | `CATEGORY_NOT_FOUND` | The category isn't found in this organization. Confirm the `categoryId`. |
| `429` | — | Rate limit exceeded. Apply exponential backoff. |
| `500` | — | Server error. Safe to retry with backoff; if it persists, contact support with the `requestId` from the response. |
