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
| Property | Type | Description |
|---|---|---|
property | string | Unique identifier for this field. Use it as the key when sending data back to the server. |
name | string | Translated display label. Show this to the user. |
controlType | string | Determines which UI component to render. See Control types. |
value | any | Current value. Type depends on controlType — can be string, number, boolean, array, or null. |
visible | boolean | Whether to show this field. Only render fields where visible is true. |
mandatory | boolean | Whether the field is required. |
disabled | boolean | Whether the field is read-only. Used in approval flows where fields are shown but not editable. |
requiresUpdate | boolean | If true, changing this field's value requires a PUT back to the server to get updated field definitions. See The update cycle. |
error | string | Error code for this field, present when validation fails. |
errorText | string | Human-readable error message to display under the field. |
url | string | For LIST and LIST_SEARCHABLE fields — the relative URL to fetch selectable values. |
data | object | Pre-loaded list item for LIST fields, containing id, name, and optionally iconUrl and section. Avoids an extra API call to display the current selection. |
custom | boolean | true if this is a custom dimension field configured by the organization, false for standard fields. |
scannable | boolean | true if this field can be populated by receipt scanning. |
allowFutureDate | boolean | For DATE and DATE_TIME fields — whether to allow dates after today. |
fields | array | Nested child fields when controlType is FIELD_GROUP. |
commands | array | Actions the user can perform on a FIELD_GROUP, such as clearing a travel route or removing a stop. |
deleteCommand | object | For removable field groups (e.g., travel stops in per diem), the command to delete this group. |
summary | array | Compact representation of a FIELD_GROUP's content, useful for collapsed views. |
numberOfDecimals | integer | For DOUBLE fields — the maximum number of decimal places to display. |
informationType | string | For INFORMATION fields — one of normal, information, warning, error, success. |
dismissible | boolean | For INFORMATION fields — whether the user can dismiss the message. |
multiSelect | boolean | For LIST fields — whether multiple values can be selected. |
listItemType | string | For LIST fields with multiSelect=true — indicates the type of items in the list. |
accessorIconUrl | string | URL for an icon indicating access to commands, typically on FIELD_GROUP fields. |
accessorText | string | Text 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:
| controlType | UI component | Value type | Notes |
|---|---|---|---|
TEXT | Single-line text input | string | |
MULTI_LINE_TEXT | Multi-line text area | string | Used for comments and descriptions. |
DOUBLE | Decimal number input | number | Use numberOfDecimals to format display. |
INTEGER | Integer number input | integer | |
DATE | Date picker | string (ISO 8601) | Check allowFutureDate. |
DATE_TIME | Date and time picker | string (ISO 8601) | Check allowFutureDate. |
LOCAL_DATE_TIME | Date and time picker (no timezone) | string (yyyy-MM-dd HH:mm) | Used in per diem travel routes. Value is local time. |
SWITCH | Toggle / checkbox | boolean | |
LIST | Dropdown / picker | string (selected ID) | Fetch options from url. May have multiSelect. |
LIST_SEARCHABLE | Searchable dropdown | string (selected ID) | Fetch options from url with search support. |
CONTENT_HOLDER | File/image attachment | string (content ID) | For receipt images and attachments. |
GUEST_LIST | Guest list editor | array of {name, company} | Used in entertainment/representation expenses. |
MILEAGE_ROUTE | Route map editor | array of {place, latitude, longitude} | For mileage expenses. |
FIELD_GROUP | Container / section | null | Contains child fields. May have commands, summary, and deleteCommand. |
INFORMATION | Info banner / callout | string | Display-only. Check informationType for styling. |
COMMAND | Action button | null | Triggers a server action via commands array. |
RECIPIENT_LIST | Email list editor | array of {email} | For expense report recipients. |
URL | Link / preview | string (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
requiresUpdate works- Initialize — Call
GETon the fields endpoint to get the initial field structure. - User edits a field — If the edited field has
requiresUpdate: true, callPUTon the same endpoint with the full fields array (including the updated value). - 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.
- 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:
| Property | Description |
|---|---|
command | Query string to append to the PUT request URL (e.g., action=addStop&afterIndex=2). |
text | Button label to display. |
confirmationRequired | Whether to show a confirmation dialog before executing. |
confirmation | Text 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:
- Iterate the
fieldsarray in order — The server controls field order. Render fields top-to-bottom as they appear. - Check
visible— Skip fields wherevisibleisfalse, but keep them in state. - Check
disabled— Render disabled fields as read-only. - Render by
controlType— Map eachcontrolTypeto your platform's UI component. - Handle
FIELD_GROUPrecursively — AFIELD_GROUPcontains nestedfields. Render it as a section or expandable container, then render its children. - Show errors — If
erroris not null, displayerrorTextas inline validation feedback. - Show
INFORMATIONfields — Render these as banners or callouts styled byinformationType. - Load list data — For
LISTandLIST_SEARCHABLEfields, use thedataproperty for the initial display. When the user opens the picker, fetch the full list fromurl.
Handling summary on field groups
summary on field groupsSome 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
deletable field groupsIn 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=fieldswith 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
erroranderrorTextpopulated. Re-render the form with these errors shown inline. - Top-level
validationErrors: An array of{errorText, errorCode, checkType}objects.checkTypecan beBLOCKER(cannot submit),ERROR(save issue), orWARNING(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.
- Call
GET /me/organizations/{id}/expensetypes/ReceiptVerification/fieldsto get the field structure. - Build your generic field renderer that handles
TEXT,DOUBLE,DATE,LIST,SWITCH,CONTENT_HOLDER, andFIELD_GROUP. - Implement the
requiresUpdatecycle withPUT /me/organizations/{id}/expensetypes/ReceiptVerification/fields. - 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.
- Call
GET /me/organizations/{id}/expensetypes/CarSpecification/fields. - Add
MILEAGE_ROUTErendering and command handling (clear travel route, round trip toggle). - Handle the
FIELD_GROUPfor 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.
- Call
GET /me/organizations/{id}/expensetypes/SubsistenceAllowance/fields. - Add
LOCAL_DATE_TIMErendering. - Implement add/remove stop commands (dynamic
FIELD_GROUPmanipulation). - Handle the food and accommodation deduction groups.
4. Food benefit expenses (FoodBenefitsSpecification)
A simpler expense type with breakfast/lunch/dinner toggles.
- Call
GET /me/organizations/{id}/expensetypes/FoodBenefitsSpecification/fields. - No new control types needed — reuses
SWITCHandDATE.
5. Expense report fields
Adds RECIPIENT_LIST, URL (for preview), and multi-select LIST (for expense record selection).
- Call
GET /me/organizations/{id}/expensereports/fieldsto initialize. - Add
RECIPIENT_LISTandURLrendering. - Handle expense record list management.
6. Card fields
Read-only card details and editable card settings.
- Call
GET /me/organizations/{id}/cards/{cardId}/fieldsfor read-only view. - Call
GET /me/organizations/{id}/cards/{cardId}/editfieldsfor editable view. - 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.
- Call
GET /me/settings/fieldsto fetch settings. - Handle
COMMANDfields withclientCommand(edit email, edit phone, change password). - Handle temporary approver management (addTemporaryApprover/removeTemporaryApprover commands).
- Save with
PUT /me?format=fieldsfor persistent changes.
8. Approval fields
Read-only fields for the approval flow. All fields have disabled: true.
- Call
GET /me/organizations/{id}/approvals/expense/{expenseId}/fieldsfor single expense approval. - Call
GET /me/organizations/{id}/approvals/expensereports/{reportId}/fieldsfor report approval. - Reuse your existing field renderer — the
disabledproperty handles the read-only state.
Fields endpoints reference
| Endpoint | Method | Description |
|---|---|---|
/me/organizations/{id}/expensetypes/{type}/fields | GET | Initialize fields for a new or existing expense. Pass expenseRecordId to load an existing expense. |
/me/organizations/{id}/expensetypes/{type}/fields | PUT | Transient update — send the full fields array when a requiresUpdate field changes. |
/me/organizations/{id}/expensereports/fields | GET | Initialize fields for a new or existing expense report. Pass expenseReportId to load existing. |
/me/organizations/{id}/expensereports/fields | PUT | Transient update for expense report fields. |
/me/organizations/{id}/approvals/expense/{expenseId}/fields | GET | Get read-only fields for expense approval. |
/me/organizations/{id}/approvals/expensereports/{reportId}/fields | GET | Get read-only fields for expense report approval. |
/me/organizations/{id}/cards/{cardId}/fields | GET | Get read-only card details in fields format. |
/me/organizations/{id}/cards/{cardId}/editfields | GET | Get editable card details in fields format. |
/me/organizations/{id}/cards/{cardId}/editfields | PUT | Update card details. |
/me/settings/fields | GET | Get user settings in fields format. |
/me/settings/fields | PUT | Transient update for user settings (e.g., add/remove temporary approver). |
/me?format=fields | PUT | Persist user settings changes. |
/me/organizations/{id}/cards/{cardId}/transactions/{transactionId}/fields | GET | Get card transaction details in fields format (read-only). |
Updated about 5 hours ago
