> ## 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.

# Dimensions

Configure custom dimensions, populate list values, and assign approvers for an organization.

This guide walks through setting up dimensions on a Findity organization. You'll define the dimension fields your business uses (cost center, project, department, etc.), populate them with the list values users can select, and optionally route expense approvals based on the value chosen.

## Overview

A **dimension** is a custom field attached to expenses or expense reports that maps to your organization's accounting structure. Dimensions are fully configurable — there are no fixed categories. The typical lifecycle is: (1) define the dimension, (2) populate its list values if it's a list type, and (3) optionally assign approvers to specific values so expense routing follows the value chosen.

Each dimension has two parts:

* A **definition** — the field's name, type, and behavior (visible on every expense vs. on the report header, free-text vs. list, auto-filled or not).
* A set of **list values** — the specific options users can pick from, when the dimension's `controlType` is a list.

Because dimensions are user-selectable, they also drive **approval routing**. Assign approvers to specific dimension values — for example, route every "Marketing" cost center expense to the marketing manager — and Findity will compose the approval chain when the user submits.

## Prerequisites

1. Complete [Authentication](/docs/accessing) and obtain an admin-scoped access token.
2. Create or identify the customer organization (see [Admin API — Organizations](/docs/admin-api-organizations)) and have its `externalId` ready.
3. Decide which dimensions the organization needs and what list values each one should hold. You can also sync these from your ERP — see [Common integration patterns](#common-integration-patterns) below.

## Key concepts

| Concept | Description |
| - | - |
| **Dimension definition** | The field's metadata: name, level, control type, preset rules. Created or updated as a unit. |
| **List value** | One selectable option on a list-type dimension (e.g. `"Marketing"` on `Cost center`). |
| **`externalSourceId`** | Your system's identifier for the dimension. Findity uses it to match definitions across syncs — same id means update, new id means create. |
| **`controlType`** | How the dimension is rendered: `LIST_SEARCHABLE` (typeahead from a predefined list) or `TEXT` (free-text input). |
| **`level`** | Where the dimension appears: `EXPENSE` (per line) or `EXPENSE_REPORT` (on the report header). |
| **`presetLocation`** | Auto-fill source: `NONE`, `EMPLOYEE`, `DEPARTMENT`, or `CATEGORY`. Combined with `userOverridable`, this controls whether the value can be changed. |
| **`clearBeforeUpdate`** | Query parameter on the list-values PUT. When `true`, removes all existing values before inserting the new set — use for a full ERP sync. |

## API endpoints

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

```
https://stage-expense.findity.com/api/admin
```

| Operation | Method | Endpoint |
| - | - | - |
| Get dimension definitions | `GET` | `/organizations/externalid/{externalId}/customdimensions/` |
| Create or update definitions | `PUT` | `/organizations/externalid/{externalId}/customdimensions/` |
| Get list values | `GET` | `/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/` |
| Create or update list values | `PUT` | `/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/` |
| Get approvers for values | `GET` | `/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/` |
| Create or update approvers | `PUT` | `/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/` |

### Required headers

Include these on every request:

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `Authorization` | `Bearer {access_token}` |
| `X-Findity-ApiVersion` | `3` |

## Step 1 — Create or update dimension definitions

Send a `PUT` with an array of definition objects. The endpoint is an upsert: definitions with an existing `externalSourceId` are updated, new ones are created.

`PUT /organizations/externalid/{externalId}/customdimensions/`

### Request body — definition fields

| Field | Type | Required | Description |
| - | - | - | - |
| `externalSourceId` | string | Recommended | Your unique identifier for this dimension. Used to match across syncs. The backend will create the row without it, but the dimension will be unusable for upserts. |
| `description` | string | Recommended | Display name shown to users (e.g. "Cost center", "Project"). |
| `level` | string | Recommended | `EXPENSE` (on each line) or `EXPENSE_REPORT` (on the report header). |
| `controlType` | string | Recommended | `LIST_SEARCHABLE` or `TEXT`. |
| `presetLocation` | string | Optional | `NONE` (default), `EMPLOYEE`, `DEPARTMENT`, or `CATEGORY`. |
| `userOverridable` | boolean | Optional | When `presetLocation` is not `NONE`, controls whether the user can change the preset. |
| `visibleIfNoPreset` | boolean | Optional | When `presetLocation` is not `NONE`, controls whether the field is visible when no preset value is available. |

<Warning>
  **The constraints on `CustomDimensionDefinition` allow null on every field. Findity will accept a definition with no `description` or `controlType` and the row will be unusable until you patch it. Treat the four "Recommended" fields as required in practice.**
</Warning>

```sh cURL theme={"system"}
curl -X PUT \
  "https://stage-expense.findity.com/api/admin/organizations/externalid/org-acme-123/customdimensions/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Findity-ApiVersion: 3" \
  -d '[
    {
      "externalSourceId": "dim-costcenter",
      "description": "Cost center",
      "level": "EXPENSE",
      "controlType": "LIST_SEARCHABLE",
      "presetLocation": "DEPARTMENT",
      "userOverridable": true,
      "visibleIfNoPreset": true
    },
    {
      "externalSourceId": "dim-project",
      "description": "Project",
      "level": "EXPENSE",
      "controlType": "LIST_SEARCHABLE",
      "presetLocation": "NONE"
    },
    {
      "externalSourceId": "dim-invoice-ref",
      "description": "Invoice reference",
      "level": "EXPENSE_REPORT",
      "controlType": "TEXT",
      "presetLocation": "NONE"
    }
  ]'
```

This creates three dimensions: a `Cost center` searchable list auto-filled from the user's department but overridable, a `Project` searchable list with no preset, and an `Invoice reference` free-text field on the report header.

**Example response**

```json JSON theme={"system"}
{
  "result": "success",
  "description": "custom dimension definition updated",
  "dimensions": [
    {
      "id": "d238c3f4555c48f697375af96cd49196",
      "externalSourceId": "dim-costcenter",
      "description": "Cost center",
      "level": "EXPENSE",
      "controlType": "LIST_SEARCHABLE",
      "presetLocation": "DEPARTMENT",
      "userOverridable": true,
      "visibleIfNoPreset": true
    },
    {
      "id": "ed8e02a47c29499f88c7c6b867bebcce",
      "externalSourceId": "dim-project",
      "description": "Project",
      "level": "EXPENSE",
      "controlType": "LIST_SEARCHABLE",
      "presetLocation": "NONE"
    },
    {
      "id": "4e5c12d878f942288baefd438b4f91bd",
      "externalSourceId": "dim-invoice-ref",
      "description": "Invoice reference",
      "level": "EXPENSE_REPORT",
      "controlType": "TEXT",
      "presetLocation": "NONE"
    }
  ]
}
```

## Step 2 — Populate list values

Dimensions with `controlType: LIST_SEARCHABLE` need list values for users to select. Send a `PUT` with the values **wrapped in a `values` array** — a bare top-level array will fail with a 400.

`PUT /organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/`

### Query parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `clearBeforeUpdate` | boolean | `false` | When `true`, removes all existing values before inserting the new set. Use for a full sync from your ERP. When `false`, existing values are preserved and the payload is upserted. |
| `cascade` | boolean | `false` | When `true`, applies the same set of values to every sub-organization under the target organization. |

### Request body — list value fields

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | string | Yes | Your unique identifier for this value. |
| `value` | string | Yes | Display text shown to users. |
| `sortOrder` | integer | Optional | Position in the list. Lower numbers appear first. |
| `startDate` | string (ISO 8601) | Optional | Date from which this value is available for selection. |
| `endDate` | string (ISO 8601) | Optional | Date after which this value is no longer available. |

```sh cURL theme={"system"}
curl -X PUT \
  "https://stage-expense.findity.com/api/admin/organizations/externalid/org-acme-123/customdimensions/dim-project/list/?clearBeforeUpdate=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Findity-ApiVersion: 3" \
  -d '{
    "values": [
      {
        "id": "proj-001",
        "value": "Website redesign",
        "sortOrder": 1,
        "startDate": "2026-01-01",
        "endDate":   "2026-12-31"
      },
      {
        "id": "proj-002",
        "value": "Mobile app v2",
        "sortOrder": 2,
        "startDate": "2026-03-01"
      },
      {
        "id": "proj-003",
        "value": "Internal tools",
        "sortOrder": 3
      }
    ]
  }'
```

In this example, `Website redesign` is available only during 2026; `Mobile app v2` becomes available from March 2026 onward with no end date; `Internal tools` has no date restrictions. `clearBeforeUpdate=true` replaces all existing values with these three.

<Note>
  `clearBeforeUpdate=true` removes **all** existing values before inserting. Send your complete value set in a single request — anything not in the payload will be deleted.
</Note>

**Example response**

```json JSON theme={"system"}
{
  "listKey": "CUSTOM_DIMENSIONS_ed8e02a47c29499f88c7c6b867bebcce_8a5c8bee9e3a4a24019e3ae1732b008e",
  "lastUpdated": "2026-05-22T07:15:54Z"
}
```

The response returns the internal `listKey` for the dimension's value list and the timestamp of the update. It does not echo the submitted values — to read them back, use the corresponding `GET` endpoint.

## Step 3 — Assign approvers to list values

Assign approvers to specific list values so expense reports route automatically based on the dimension value chosen. Same `values` wrapper as Step 2; each value carries an `approvers` array.

`PUT /organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/`

### Request body — approver fields

Top-level shape:

```json JSON theme={"system"}
{ "values": [ { "id": "<list-value-id>", "approvers": [ { ... }, ... ] } ] }
```

Each approver object:

| Field | Type | Required | Description |
| - | - | - | - |
| `personExternalSourceId` | string | Yes | The `externalSourceId` of the user who should approve. Use [List users](/docs/admin-api-users) to find it. |
| `sortOrder` | integer | Optional | Position in the approval chain (`0` = first approver, `1` = second). Defaults to `0`. |
| `delete` | boolean | Optional | When `true`, removes this approver assignment instead of upserting it. Defaults to `false`. |

<Note>
  The approver must be a real user on the organization. If no user with the supplied `personExternalSourceId`
  exists, the request returns `400 "failed to update custom field list"` and no assignment is created.
</Note>

```sh cURL theme={"system"}
curl -X PUT \
  "https://stage-expense.findity.com/api/admin/organizations/externalid/org-acme-123/customdimensions/dim-project/list/approvers/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Findity-ApiVersion: 3" \
  -d '{
    "values": [
      {
        "id": "proj-001",
        "approvers": [
          { "personExternalSourceId": "emp-jane-smith", "sortOrder": 0 }
        ]
      },
      {
        "id": "proj-002",
        "approvers": [
          { "personExternalSourceId": "emp-john-doe",   "sortOrder": 0 },
          { "personExternalSourceId": "emp-carol-park", "sortOrder": 1 }
        ]
      }
    ]
  }'
```

This assigns Jane Smith as the sole approver for `Website redesign`, and a two-step chain for `Mobile app v2`: John Doe first, then Carol Park.

**Example response**

```json JSON theme={"system"}
{
  "listKey": "CUSTOM_DIMENSIONS_ed8e02a47c29499f88c7c6b867bebcce_8a5c8bee9e3a4a24019e3ae1732b008e",
  "lastUpdated": "2026-05-22T07:15:54Z"
}
```

The response returns the internal `listKey` and an update timestamp. To confirm the assignments persisted and read them back, call the corresponding GET on the same path:

`GET /organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/`

```json JSON theme={"system"}
{
  "listKey": "CUSTOM_DIMENSIONS_ed8e02a47c29499f88c7c6b867bebcce_8a5c8bee9e3a4a24019e3ae1732b008e",
  "lastUpdated": "2026-05-22T07:15:54Z",
  "totalValueCount": 2,
  "values": [
    {
      "id": "proj-001",
      "value": "Website redesign",
      "sortOrder": 0,
      "approvers": [
        {
          "id": "2c85656820284f1183a3b9865e1864ee",
          "externalSourceId": "emp-jane-smith",
          "sortOrder": 0,
          "personId": "8ab28c4e797a953501797e6b7b4318df",
          "personName": "Jane Smith",
          "personEmail": "jane.smith@acme.com",
          "personExternalSourceId": "emp-jane-smith"
        }
      ]
    },
    {
      "id": "proj-002",
      "value": "Mobile app v2",
      "sortOrder": 1,
      "approvers": [
        { "/* ... */": "John Doe entry" },
        { "/* ... */": "Carol Park entry" }
      ]
    }
  ]
}
```

## Understanding presets

Presets auto-fill dimension values from existing data so users don't have to pick. The three preset fields work together:

| `presetLocation` | Behavior |
| - | - |
| `NONE` | No auto-fill. The user selects or enters a value manually. |
| `EMPLOYEE` | Auto-fills from the employee's profile. |
| `DEPARTMENT` | Auto-fills from the employee's department. |
| `CATEGORY` | Auto-fills from the expense category. |

When a preset is configured:

* **`userOverridable: true`** — auto-filled but the user can change it. Use this when the preset is a sensible default but exceptions are common.
* **`userOverridable: false`** — auto-filled and locked. Use this to enforce accounting rules.
* **`visibleIfNoPreset: true`** — if no preset value exists for the user / department / category, the dimension still appears and the user enters a value manually.
* **`visibleIfNoPreset: false`** — if no preset value exists, the dimension is hidden entirely.

## Common integration patterns

**Full sync from your ERP.** Use `clearBeforeUpdate=true` to mirror the ERP's current state exactly — deleted values in the ERP are also removed from Findity. Recommended for nightly batch syncs.

1. Fetch dimension definitions from your ERP → `PUT /customdimensions/`
2. For each list dimension, fetch values from your ERP → `PUT /customdimensions/{definitionExternalId}/list/?clearBeforeUpdate=true`
3. Fetch approver mappings → `PUT /customdimensions/{definitionExternalId}/list/approvers/`

**Incremental updates.** Use the default `clearBeforeUpdate=false` to add or update individual values without touching the rest. Send only the changed values in the request body. Useful when a new project is created, a value's display name changes, or you want to set an `endDate` to deactivate a value without removing its history.

**Multi-organization rollouts.** Set `cascade=true` as a query parameter on the list-values `PUT` to push the same value set to every sub-organization under the target. Use this when you manage a shared catalogue (cost centers, projects) centrally and want subsidiaries to inherit it.

## Error handling

Findity returns a JSON envelope on errors:

```json JSON theme={"system"}
{ "result": "error", "description": "<human-readable message>", "reason": "<optional machine-readable hint>" }
```

| Status | `reason` | Description |
| - | - | - |
| `400` | (none) | Invalid JSON body or missing wrapper. The most common cause on the list-values and approver endpoints is sending a bare top-level array — wrap it in `{ "values": [...] }`. |
| `400` | (none) | Approver `personExternalSourceId` does not match a user on the organization. Confirm the user exists before retrying. |
| `400` | `reference-check-error` | The change would break a referential constraint (e.g. removing a value that's currently referenced on a draft expense). Resolve the references and retry. |
| `401` | — | Access token is missing, expired, or invalid. Refresh the token and retry. |
| `403` | — | The token does not have admin scope for this organization. Verify the API client has the right permissions. |
| `404` | — | The organization or dimension definition isn't found. Confirm `{externalId}` and `{definitionExternalId}` are correct. |
| `429` | — | Rate limit exceeded. Apply exponential backoff before retrying. |
| `500` | — | Server error. Safe to retry with backoff; if the error persists, contact support with the response body and the request `X-Request-ID` if you have one. |

## Next steps

<Cards columns={2}>
  <Card title="Users" href="/docs/admin-api-users" icon="fa-users">
    Add, update, and remove employees from an organization.
  </Card>

  <Card title="Approval structures" href="/docs/admin-api-approval-structures" icon="fa-check-double">
    Define approval workflows and routing rules across departments and dimensions.
  </Card>

  <Card title="Organizations" href="/docs/admin-api-organizations" icon="fa-building">
    Provision and configure the organizations that hold your dimensions.
  </Card>

  <Card title="Approval on dimensions" href="/docs/admin-api-approval-on-dimensions" icon="fa-sitemap">
    A worked example combining dimension definitions, list values, and approvers end-to-end.
  </Card>
</Cards>
