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
controlTypeis 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
- Complete Authentication and obtain an admin-scoped access token.
- Create or identify the customer organization (see Admin API — Organizations) and have its
externalIdready. - 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 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. |
The constraints onCustomDimensionDefinitionallow null on every field. Findity will accept a definition with nodescriptionorcontrolTypeand the row will be unusable until you patch it. Treat the four "Recommended" fields as required in practice.
cURL
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
{
"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. |
cURL
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.
clearBeforeUpdate=trueremoves all existing values before inserting. Send your complete value set in a single request — anything not in the payload will be deleted.
Example response
JSON
{
"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
{ "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 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. |
The approver must be a real user on the organization. If no user with the suppliedpersonExternalSourceIdexists, the request returns400 "failed to update custom field list"and no assignment is created.
cURL
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
{
"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
{
"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": "[email protected]",
"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.
- Fetch dimension definitions from your ERP →
PUT /customdimensions/ - For each list dimension, fetch values from your ERP →
PUT /customdimensions/{definitionExternalId}/list/?clearBeforeUpdate=true - 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
{ "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
Add, update, and remove employees from an organization.
Define approval workflows and routing rules across departments and dimensions.
Provision and configure the organizations that hold your dimensions.
A worked example combining dimension definitions, list values, and approvers end-to-end.
Updated about 7 hours ago
