Dynamic fields

Use server-driven field definitions to dynamically render forms for expenses, expense reports, cards, and user settings.

The Findity Expense API uses a server-driven UI pattern called Fields. Instead of hardcoding form layouts in your client, you fetch field definitions from the API and render forms dynamically. The server controls which fields to show, in what order, with what validation — and your client renders them.

Why use Fields

Building expense management forms is complex. Different organizations configure different categories, dimensions, tax rules, and approval flows. The Fields system handles this complexity on the server side so your client stays simple.

Consistent behavior across platforms — Every client (iOS, Android, web) renders the same fields in the same order with the same validation. When the server adds a new field or changes visibility rules, all clients pick it up without a code change.

Organization-specific configuration — Each organization can have different expense categories, custom dimensions, representation rules, and country-specific per diem logic. The Fields endpoints return exactly the right fields for the current user's organization — you don't need to implement this logic yourself.

Reduced client-side complexity — Your client becomes a renderer. The server decides what to show, whether a field is mandatory, and when changing one field should trigger a re-fetch of the field structure. This eliminates hundreds of conditional branches in your client code.

How Fields work

Every Fields endpoint returns a JSON structure with a fields array, optional meta (summary, capabilities), and optional validationErrors. Each field object describes one form element.

The field object

PropertyTypeDescription
propertystringUnique identifier for this field. Use it as the key when sending data back to the server.
namestringTranslated display label. Show this to the user.
controlTypestringDetermines which UI component to render. See Control types.
valueanyCurrent value. Type depends on controlType — can be string, number, boolean, array, or null.
visiblebooleanWhether to show this field. Only render fields where visible is true.
mandatorybooleanWhether the field is required.
disabledbooleanWhether the field is read-only. Used in approval flows where fields are shown but not editable.
requiresUpdatebooleanIf true, changing this field's value requires a PUT back to the server to get updated field definitions. See The update cycle.
errorstringError code for this field, present when validation fails.
errorTextstringHuman-readable error message to display under the field.
urlstringFor LIST and LIST_SEARCHABLE fields — the relative URL to fetch selectable values.
dataobjectPre-loaded list item for LIST fields, containing id, name, and optionally iconUrl and section. Avoids an extra API call to display the current selection.
custombooleantrue if this is a custom dimension field configured by the organization, false for standard fields.
scannablebooleantrue if this field can be populated by receipt scanning.
allowFutureDatebooleanFor DATE and DATE_TIME fields — whether to allow dates after today.
fieldsarrayNested child fields when controlType is FIELD_GROUP.
commandsarrayActions the user can perform on a FIELD_GROUP, such as clearing a travel route or removing a stop.
deleteCommandobjectFor removable field groups (e.g., travel stops in per diem), the command to delete this group.
summaryarrayCompact representation of a FIELD_GROUP's content, useful for collapsed views.
numberOfDecimalsintegerFor DOUBLE fields — the maximum number of decimal places to display.
informationTypestringFor INFORMATION fields — one of normal, information, warning, error, success.
dismissiblebooleanFor INFORMATION fields — whether the user can dismiss the message.
multiSelectbooleanFor LIST fields — whether multiple values can be selected.
listItemTypestringFor LIST fields with multiSelect=true — indicates the type of items in the list.
accessorIconUrlstringURL for an icon indicating access to commands, typically on FIELD_GROUP fields.
accessorTextstringText that can replace all fields in a FIELD_GROUP, used as an accessor display.

Control types

Map each controlType to a UI component in your client:

controlTypeUI componentValue typeNotes
TEXTSingle-line text inputstring
MULTI_LINE_TEXTMulti-line text areastringUsed for comments and descriptions.
DOUBLEDecimal number inputnumberUse numberOfDecimals to format display.
INTEGERInteger number inputinteger
DATEDate pickerstring (ISO 8601)Check allowFutureDate.
DATE_TIMEDate and time pickerstring (ISO 8601)Check allowFutureDate.
LOCAL_DATE_TIMEDate and time picker (no timezone)string (yyyy-MM-dd HH:mm)Used in per diem travel routes. Value is local time.
SWITCHToggle / checkboxboolean
LISTDropdown / pickerstring (selected ID)Fetch options from url. May have multiSelect.
LIST_SEARCHABLESearchable dropdownstring (selected ID)Fetch options from url with search support.
CONTENT_HOLDERFile/image attachmentstring (content ID)For receipt images and attachments.
GUEST_LISTGuest list editorarray of {name, company}Used in entertainment/representation expenses.
MILEAGE_ROUTERoute map editorarray of {place, latitude, longitude}For mileage expenses.
FIELD_GROUPContainer / sectionnullContains child fields. May have commands, summary, and deleteCommand.
INFORMATIONInfo banner / calloutstringDisplay-only. Check informationType for styling.
COMMANDAction buttonnullTriggers a server action via commands array.
RECIPIENT_LISTEmail list editorarray of {email}For expense report recipients.
URLLink / previewstring (URL)For expense report preview links.

Hidden and system fields

Many fields in the response have visible: false. These are system fields (like id, status, reportStatus) that carry state between the client and server. Include them in your PUT requests but do not render them in the UI.

⚠️

Always send the complete fields array back to the server on PUT requests — including hidden fields and fields you didn't modify. The server uses the full state to compute the response.

The update cycle

The Fields system uses a request-response cycle to keep the form state consistent. Some fields are interdependent — for example, changing the expense category can change which custom dimensions are visible, or changing a per diem destination triggers recalculation of deduction periods.

How requiresUpdate works

  1. Initialize — Call GET on the fields endpoint to get the initial field structure.
  2. User edits a field — If the edited field has requiresUpdate: true, call PUT on the same endpoint with the full fields array (including the updated value).
  3. Server returns updated fields — The response contains the new field structure. Replace your local state with this response. Fields may have changed visibility, added new options, or updated other values.
  4. Repeat — Continue until the user saves or submits.
┌────────┐          ┌────────┐
│ Client │          │ Server │
└───┬────┘          └───┬────┘
    │  GET /fields      │
    │──────────────────>│
    │  Fields response  │
    │<──────────────────│
    │                   │
    │  User changes a   │
    │  requiresUpdate   │
    │  field            │
    │                   │
    │  PUT /fields      │
    │  (full fields)    │
    │──────────────────>│
    │  Updated fields   │
    │<──────────────────│
    │                   │
    │  User saves       │
    │                   │
    │  POST /expenses   │
    │  or PUT /expenses │
    │──────────────────>│
    │  Saved expense    │
    │<──────────────────│
💡

Only call PUT when the user changes a field with requiresUpdate: true. For fields without this flag, update the value locally without making an API call.

Commands and actions

FIELD_GROUP and COMMAND fields can include a commands array. Each command has:

PropertyDescription
commandQuery string to append to the PUT request URL (e.g., action=addStop&afterIndex=2).
textButton label to display.
confirmationRequiredWhether to show a confirmation dialog before executing.
confirmationText for the confirmation dialog.

To execute a command, send a PUT request with the command's query parameters appended to the fields endpoint URL.

Note: Some fields in user settings may include client-side action hints in the response, but these are not part of the formal Fields schema and should be handled as implementation-specific extensions.

Rendering fields

Follow these rules when building your field renderer:

  1. Iterate the fields array in order — The server controls field order. Render fields top-to-bottom as they appear.
  2. Check visible — Skip fields where visible is false, but keep them in state.
  3. Check disabled — Render disabled fields as read-only.
  4. Render by controlType — Map each controlType to your platform's UI component.
  5. Handle FIELD_GROUP recursively — A FIELD_GROUP contains nested fields. Render it as a section or expandable container, then render its children.
  6. Show errors — If error is not null, display errorText as inline validation feedback.
  7. Show INFORMATION fields — Render these as banners or callouts styled by informationType.
  8. Load list data — For LIST and LIST_SEARCHABLE fields, use the data property for the initial display. When the user opens the picker, fetch the full list from url.

Handling summary on field groups

Some FIELD_GROUP fields include a summary array — a flat list of key-value pairs that summarize the group's contents. Use this to display a compact preview when the group is collapsed (e.g., showing "Stockholm → Gothenburg, 468 km" for a mileage route).

Handling deletable field groups

In per diem travel routes, intermediate stops are represented as FIELD_GROUP fields with deletable: true and a deleteCommand. Render a delete action on these groups that executes the command via PUT.

Saving fields

When the user finishes editing:

Use the fields format for creating and updating expenses:

  • Create: POST /expenses?format=fields&organizationId={id}&expenseType={type} with the fields payload.
  • Update: PUT /expenses/{expenseId}?format=fields with the fields payload.

Alternatively, use the standard JSON format (POST /expenses or PUT /expenses/{id}) if you extract values from the fields into the standard expense object.

Handling validation errors

When a save fails due to validation, the response may contain:

  • Field-level errors: Individual fields will have error and errorText populated. Re-render the form with these errors shown inline.
  • Top-level validationErrors: An array of {errorText, errorCode, checkType} objects. checkType can be BLOCKER (cannot submit), ERROR (save issue), or WARNING (can still submit).

Recommended integration order

Integrate the Fields endpoints in this order, starting with the simplest case and building up:

1. Receipt expenses (ReceiptVerification)

Start here — this is the most common expense type and has the most straightforward field structure.

  1. Call GET /me/organizations/{id}/expensetypes/ReceiptVerification/fields to get the field structure.
  2. Build your generic field renderer that handles TEXT, DOUBLE, DATE, LIST, SWITCH, CONTENT_HOLDER, and FIELD_GROUP.
  3. Implement the requiresUpdate cycle with PUT /me/organizations/{id}/expensetypes/ReceiptVerification/fields.
  4. Save the expense with POST /expenses?format=fields.

2. Mileage expenses (CarSpecification)

Adds the MILEAGE_ROUTE control type and the route travel group with commands.

  1. Call GET /me/organizations/{id}/expensetypes/CarSpecification/fields.
  2. Add MILEAGE_ROUTE rendering and command handling (clear travel route, round trip toggle).
  3. Handle the FIELD_GROUP for travel route with its nested distance fields.

3. Per diem expenses (SubsistenceAllowance)

The most complex expense type. Adds LOCAL_DATE_TIME, dynamic travel stops (addStop/removeStop commands), and food/accommodation deductions.

  1. Call GET /me/organizations/{id}/expensetypes/SubsistenceAllowance/fields.
  2. Add LOCAL_DATE_TIME rendering.
  3. Implement add/remove stop commands (dynamic FIELD_GROUP manipulation).
  4. Handle the food and accommodation deduction groups.

4. Food benefit expenses (FoodBenefitsSpecification)

A simpler expense type with breakfast/lunch/dinner toggles.

  1. Call GET /me/organizations/{id}/expensetypes/FoodBenefitsSpecification/fields.
  2. No new control types needed — reuses SWITCH and DATE.

5. Expense report fields

Adds RECIPIENT_LIST, URL (for preview), and multi-select LIST (for expense record selection).

  1. Call GET /me/organizations/{id}/expensereports/fields to initialize.
  2. Add RECIPIENT_LIST and URL rendering.
  3. Handle expense record list management.

6. Card fields

Read-only card details and editable card settings.

  1. Call GET /me/organizations/{id}/cards/{cardId}/fields for read-only view.
  2. Call GET /me/organizations/{id}/cards/{cardId}/editfields for editable view.
  3. Save changes with PUT /me/organizations/{id}/cards/{cardId}/editfields.

7. User settings fields

Personal settings, bank details, experience settings, app settings, notification settings, and temporary approvers.

  1. Call GET /me/settings/fields to fetch settings.
  2. Handle COMMAND fields with clientCommand (edit email, edit phone, change password).
  3. Handle temporary approver management (addTemporaryApprover/removeTemporaryApprover commands).
  4. Save with PUT /me?format=fields for persistent changes.

8. Approval fields

Read-only fields for the approval flow. All fields have disabled: true.

  1. Call GET /me/organizations/{id}/approvals/expense/{expenseId}/fields for single expense approval.
  2. Call GET /me/organizations/{id}/approvals/expensereports/{reportId}/fields for report approval.
  3. Reuse your existing field renderer — the disabled property handles the read-only state.

Fields endpoints reference

EndpointMethodDescription
/me/organizations/{id}/expensetypes/{type}/fieldsGETInitialize fields for a new or existing expense. Pass expenseRecordId to load an existing expense.
/me/organizations/{id}/expensetypes/{type}/fieldsPUTTransient update — send the full fields array when a requiresUpdate field changes.
/me/organizations/{id}/expensereports/fieldsGETInitialize fields for a new or existing expense report. Pass expenseReportId to load existing.
/me/organizations/{id}/expensereports/fieldsPUTTransient update for expense report fields.
/me/organizations/{id}/approvals/expense/{expenseId}/fieldsGETGet read-only fields for expense approval.
/me/organizations/{id}/approvals/expensereports/{reportId}/fieldsGETGet read-only fields for expense report approval.
/me/organizations/{id}/cards/{cardId}/fieldsGETGet read-only card details in fields format.
/me/organizations/{id}/cards/{cardId}/editfieldsGETGet editable card details in fields format.
/me/organizations/{id}/cards/{cardId}/editfieldsPUTUpdate card details.
/me/settings/fieldsGETGet user settings in fields format.
/me/settings/fieldsPUTTransient update for user settings (e.g., add/remove temporary approver).
/me?format=fieldsPUTPersist user settings changes.
/me/organizations/{id}/cards/{cardId}/transactions/{transactionId}/fieldsGETGet card transaction details in fields format (read-only).