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 and obtain an admin-scoped access token.
  2. Create or identify the customer organization (see 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 below.

Key concepts

ConceptDescription
Dimension definitionThe field's metadata: name, level, control type, preset rules. Created or updated as a unit.
List valueOne selectable option on a list-type dimension (e.g. "Marketing" on Cost center).
externalSourceIdYour system's identifier for the dimension. Findity uses it to match definitions across syncs — same id means update, new id means create.
controlTypeHow the dimension is rendered: LIST_SEARCHABLE (typeahead from a predefined list) or TEXT (free-text input).
levelWhere the dimension appears: EXPENSE (per line) or EXPENSE_REPORT (on the report header).
presetLocationAuto-fill source: NONE, EMPLOYEE, DEPARTMENT, or CATEGORY. Combined with userOverridable, this controls whether the value can be changed.
clearBeforeUpdateQuery 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
OperationMethodEndpoint
Get dimension definitionsGET/organizations/externalid/{externalId}/customdimensions/
Create or update definitionsPUT/organizations/externalid/{externalId}/customdimensions/
Get list valuesGET/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/
Create or update list valuesPUT/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/
Get approvers for valuesGET/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/
Create or update approversPUT/organizations/externalid/{externalId}/customdimensions/{definitionExternalId}/list/approvers/

Required headers

Include these on every request:

HeaderValue
Content-Typeapplication/json
AuthorizationBearer {access_token}
X-Findity-ApiVersion3

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

FieldTypeRequiredDescription
externalSourceIdstringRecommendedYour 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.
descriptionstringRecommendedDisplay name shown to users (e.g. "Cost center", "Project").
levelstringRecommendedEXPENSE (on each line) or EXPENSE_REPORT (on the report header).
controlTypestringRecommendedLIST_SEARCHABLE or TEXT.
presetLocationstringOptionalNONE (default), EMPLOYEE, DEPARTMENT, or CATEGORY.
userOverridablebooleanOptionalWhen presetLocation is not NONE, controls whether the user can change the preset.
visibleIfNoPresetbooleanOptionalWhen presetLocation is not NONE, controls whether the field is visible when no preset value is available.
⚠️

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.

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

ParameterTypeDefaultDescription
clearBeforeUpdatebooleanfalseWhen 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.
cascadebooleanfalseWhen true, applies the same set of values to every sub-organization under the target organization.

Request body — list value fields

FieldTypeRequiredDescription
idstringYesYour unique identifier for this value.
valuestringYesDisplay text shown to users.
sortOrderintegerOptionalPosition in the list. Lower numbers appear first.
startDatestring (ISO 8601)OptionalDate from which this value is available for selection.
endDatestring (ISO 8601)OptionalDate 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=true removes 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:

FieldTypeRequiredDescription
personExternalSourceIdstringYesThe externalSourceId of the user who should approve. Use List users to find it.
sortOrderintegerOptionalPosition in the approval chain (0 = first approver, 1 = second). Defaults to 0.
deletebooleanOptionalWhen 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 supplied personExternalSourceId exists, the request returns 400 "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:

presetLocationBehavior
NONENo auto-fill. The user selects or enters a value manually.
EMPLOYEEAuto-fills from the employee's profile.
DEPARTMENTAuto-fills from the employee's department.
CATEGORYAuto-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

{ "result": "error", "description": "<human-readable message>", "reason": "<optional machine-readable hint>" }
StatusreasonDescription
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.
400reference-check-errorThe change would break a referential constraint (e.g. removing a value that's currently referenced on a draft expense). Resolve the references and retry.
401Access token is missing, expired, or invalid. Refresh the token and retry.
403The token does not have admin scope for this organization. Verify the API client has the right permissions.
404The organization or dimension definition isn't found. Confirm {externalId} and {definitionExternalId} are correct.
429Rate limit exceeded. Apply exponential backoff before retrying.
500Server 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