Getting Started
Build embedded expense management into your product by integrating the Findity Expense API β including dynamic forms, tax logic, approval workflows, and expense reporting.
Integration overview
The Findity Expense API lets you embed full expense management functionality β receipt expenses, mileage claims, per diem / subsistence allowances, and food benefits β directly into your application. Instead of implementing complex tax rules and country-specific business logic yourself, you use Findity's Fields system, which delivers dynamic form definitions that encapsulate all validation, tax calculations, and regulatory compliance.
A typical integration involves these layers:
βββββββββββββββββββββββββββββββββββββββ
β Your Application β
ββββββββββββββββ¬βββββββββββββββββββββββ€
β End-User β Admin / Approver β
β Views β Views β
ββββββββββββββββ΄βββββββββββββββββββββββ€
β Findity Expense API β
β (Fields Β· Expenses Β· Reports Β· β
β Approvals Β· Content Β· Cards) β
βββββββββββββββββββββββββββββββββββββββ€
β Findity Authentication β
β (OAuth 2.0 token management) β
βββββββββββββββββββββββββββββββββββββββ
Why Fields? The Fields system is the core of a Findity integration. Rather than hardcoding expense form structures, you fetch field definitions from the API and render them dynamically. This means your UI automatically adapts when organizations configure new categories, dimensions, or tax rules β with zero code changes on your side.
Prerequisites
- Complete the Getting started with the Findity API guide β this walks you through obtaining your
client_idandclient_secret, choosing your environment (stage or production), and making your first authenticated request.
Step 1: Authenticate as user
All API calls require a Bearer token. Obtain one using the OAuth 2.0 client credentials flow, then include it as a Bearer token in the Authorization header of every request.
For end-user context (acting on behalf of a specific user), use the authorization grant flow to exchange a user ID for a user-scoped token.
Always use
authorization_codegrant type when making requests on behalf of end users. Theclient_credentialsgrant gives you application-level access, which is appropriate for admin operations but won't return user-specific data from endpoints like/me.
β See the Authentication guide for step-by-step instructions on obtaining API keys, getting tokens, refreshing tokens, and impersonating users. For endpoint details, see the Authentication reference.
Step 2: Establish user context
Once authenticated with a user token, fetch the current user's profile and organizations. This gives you the foundation for all expense operations.
Get the logged-in user:
GET /v1/expense/me?include=preferences,meta.capabilities
This returns the user's identity, preferences, and what they're permitted to do (e.g., canImpersonate, canViewAdmin).
List the user's organizations:
GET /v1/expense/me/organizations?include=settings,meta.permissions
Each organization includes a meta.permissions object that tells you exactly what the user can do within that organization:
| Permission | Description |
|---|---|
canCreateExpense | User can create new expenses |
canCreateReport | User can create expense reports |
canSendExpenses | User can submit individual expenses |
canSendReports | User can submit expense reports |
canApprove | User is an approver in this organization |
canAddCard | User can connect payment cards |
Use these permissions to control which UI elements you show. For example, hide the "Approve" tab if
canApproveisfalse, or disable the "New expense" button ifcanCreateExpenseisfalse.
β See the User and Organizations references for full details.
Step 3: Understand the Fields system
The Fields system is what sets a Findity integration apart from a raw REST API integration. Instead of constructing expense payloads manually, you:
- Fetch field definitions β the API returns a structured array of fields, each with a
controlType,value,visible,mandatory, and validation state - Render the fields dynamically β build your UI from the field definitions rather than hardcoding form layouts
- Send field updates back β when a user changes a value on a field marked
requiresUpdate: true, send the entire fields payload back to get recalculated fields (e.g., selecting a country may reveal new tax-related fields) - Save using the fields format β submit the final fields payload to create or update the expense
Initializing fields for a new expense
The API supports four expense types, each with its own field structure:
| Expense type | Description | type value |
|---|---|---|
| Receipt expense | Standard purchases with receipts | ReceiptVerification |
| Mileage | Distance-based travel claims | CarSpecification |
| Per diem / Subsistence | Daily allowances for business travel | SubsistenceAllowance |
| Food benefits | Meal benefit reporting | FoodBenefitsSpecification |
Initialize fields for a chosen expense type:
GET /v1/expense/me/organizations/{id}/expensetypes/{expenseType}/fields
This returns a complete form definition for the chosen expense type, including:
- Standard fields β category, description, amount, currency, date, VAT, payment method
- Custom dimensions β organization-specific fields like cost center or project (
custom: true) - Nested field groups β complex structures like representation/entertainment details or travel routes
- List endpoints β URLs to fetch selectable values (
urlproperty on LIST fields)
Key field properties to handle
| Property | Type | What it means for your UI |
|---|---|---|
controlType | string | How to render: TEXT, DOUBLE, DATE, LIST, SWITCH, FIELD_GROUP, CONTENT_HOLDER, MILEAGE_ROUTE, INFORMATION, etc. |
visible | boolean | Only render fields where visible: true |
mandatory | boolean | Mark as required in your UI |
requiresUpdate | boolean | When the user changes this field, call the PUT fields endpoint to get recalculated field definitions |
disabled | boolean | Render as read-only |
value | any | Current field value β type depends on controlType |
error / errorText | string | Display validation errors inline |
url | string | For LIST fields, fetch selectable options from this relative URL |
data | object | Pre-loaded list item for the current value, so you don't need an extra API call |
The update loop
When a user changes a field with requiresUpdate: true, you must send the full fields payload back to the API:
PUT /v1/expense/me/organizations/{id}/expensetypes/{expenseType}/fields
The API returns an updated fields definition β new fields may appear, others may hide, values may be recalculated, and validation errors may be added or cleared. This is how Findity's tax and business logic works without you implementing any of it.
Always process the full response after a
requiresUpdateround-trip. Don't assume only the changed field is affected β selecting a new category can change which custom dimensions are visible, which representation fields appear, or which tax rules apply.
Performance tip: Only trigger the update round-trip when the user modifies a field with
requiresUpdate: true. For all other fields, update the value locally and send the full payload only on save.
β See the Fields reference for complete endpoint documentation.
Step 4: Build end-user views
End users need to create expenses, track their status, and manage expense reports. Use the processStatus filter to segment expenses into views.
Expense drafts
Draft expenses are expenses the user has created but not yet submitted.
GET /v1/expense/expenses?organizationId={orgId}&processStatus=DRAFT&include=meta.abbreviation,meta.capabilities
From this view, users should typically be able to:
- Create new expenses β initialize fields for the chosen expense type, render the dynamic form, and save with
POST /v1/expense/expenses?format=fields - Edit existing drafts β load the expense fields with the
expenseRecordIdquery parameter, render the form, and update withPUT /v1/expense/expenses/{id}?format=fields - Attach receipts β upload images or PDFs via
POST /v1/expense/content(withaction=scanfor automatic OCR), then reference the returnedidin theverification.receiptAttachmentfield - Delete drafts β
DELETE /v1/expense/expenses/{id}
Receipt scanning: When you upload content with
action=scan, the response includes ascanResultwith extracted data, e.g.amount,taxAmount,currency,purchaseDate, and suggestedcategoryIds. Apply these to the fields by passing theapplyScanResultquery parameter on the fields PUT endpoint.
Submitted expenses
Once submitted, expenses move through the approval pipeline. List them with:
GET /v1/expense/expenses?organizationId={orgId}&processStatus=PROCESSING&include=meta.abbreviation
This view is read-only for the end user. Show the status, amount, and category β the meta.abbreviation object provides pre-formatted display strings like reimbursementDescription and dateDescription.
Approved expenses
GET /v1/expense/expenses?organizationId={orgId}&processStatus=PROCESSED&include=meta.abbreviation
These are completed expenses that have been sent or paid. Display as a history view.
Declined expenses
GET /v1/expense/expenses?organizationId={orgId}&processStatus=REJECTED&include=meta.abbreviation
Rejected expenses can be edited and resubmitted. When loading the fields for a rejected expense, check for the rejectionComment field (an INFORMATION type with informationType: ERROR) to display the approver's reason for rejection.
Counters for badges: Use
GET /v1/expense/me/countersto fetch notification counts per organization β includingexpenses,approvals,rejections, andnotificationsβ to show badge counts on your navigation tabs.
Step 5: Handle expense reports
Most organizations require expenses to be grouped into reports before submission. The report workflow wraps multiple expenses into a single submission unit.
Creating and managing reports
POST /v1/expense/expensereports?format=fields&organizationId={orgId}
Use the fields format for reports too β initialize report fields with:
GET /v1/expense/me/organizations/{id}/expensereports/fields
This returns fields for the report name, description, recipients, and any report-level custom dimensions.
Adding expenses to a report
You can add expenses to a report in two ways:
- At creation β include an array of expense record IDs in the report payload
- During editing β update the
expenseRecordsfield in the report's fields payload
The expenseRecords field uses controlType: LIST with multiSelect: true. Use the filter canBeAddedToReport when listing expenses to show only eligible records:
GET /v1/expense/expenses?organizationId={orgId}&filter=canBeAddedToReport
Submitting a report
PUT /v1/expense/expensereports/{id}?action=send
Before sending, check the meta.capabilities.canBeSentIn flag. If validation errors exist, the API returns them in the response so you can guide the user to fix issues.
Listing reports by status
Use the same processStatus filter as expenses:
GET /v1/expense/expensereports?organizationId={orgId}&processStatus=DRAFT&include=meta.abbreviation,meta.capabilities,expenseRecords
Report preview: Each report includes a
previewUrlin its metadata. Use this to offer users a PDF-style preview of the report before submission.
β See the Expense Reports reference for full details.
Step 6: Build admin and approver views
Users with the canApprove permission in an organization can review, approve, or reject submitted expenses and reports.
Pending approvals
List all reports awaiting the current user's approval:
GET /v1/expense/me/organizations/{id}/approvals?include=meta.abbreviation,expenseRecords.meta.abbreviation
You can also group approvals by person for a cleaner overview:
GET /v1/expense/me/organizations/{id}/approvals/groups/person
This returns each person's name, profile picture, total amount, and number of pending approvals β ideal for a summary dashboard.
Reviewing individual expenses
To show the approver a detailed, read-only view of an expense:
GET /v1/expense/me/organizations/{id}/approvals/expense/{expenseId}/fields?include=meta.capabilities,meta.summary
This returns the full fields structure with all values filled in and disabled: true on every field β the approver can see all details but cannot edit them.
Approving or rejecting
Approve or reject individual expense records within a report:
PUT /v1/expense/me/organizations/{id}/approvals/{expenseReportId}
To approve the entire report:
{
"approvalChainId": "the-approval-chain-id"
}To reject specific expenses with comments:
{
"rejectedExpenseRecords": [
{
"id": "expense-record-id",
"rejectComment": "Receipt is missing β please re-upload"
}
],
"approvalChainId": "the-approval-chain-id"
}The
approvalChainIdis returned on the expense report object. It identifies where in the approval chain the current approver sits β this is important for organizations with multi-level approval workflows.
Approval history
Approved and processed reports are available through the standard report listing filtered by status:
GET /v1/expense/expensereports?organizationId={orgId}&processStatus=PROCESSED&include=meta.abbreviation
β See the Approvals reference for full details.
Step 7: Manage receipts and attachments
The Content API handles all file uploads β receipt images, PDF documents, and guest list attachments.
Upload receipts before initializing the expense fields. That way, you can pass the
scanResultto pre-fill form fields, giving users a faster expense creation experience.
Upload a receipt with OCR scanning:
curl -X POST https://stage-api.findity.com/api/v1/expense/content?action=scan&organizationId={orgId} \
-H "Authorization: Bearer {token}" \
-H "Content-Type: image/jpeg" \
-H "Content-Name: receipt.jpg" \
--data-binary @receipt.jpgThe response includes the content id (to reference in expense fields) and a scanResult with extracted data.
Retrieve receipt images at specific sizes:
GET /v1/expense/content/{id}/image?size=400x400
β See the Content reference for full details.
Integration best practices
Cache list data appropriately
Fields with controlType: LIST include a url property pointing to the list values endpoint. Cache these lists (categories, currencies, payment types) per organization and refresh periodically β they don't change often, and caching reduces API calls significantly.
Handle the fields update loop efficiently
Only trigger the fields update round-trip (PUT on the fields endpoint) when the user changes a field marked requiresUpdate: true. For all other changes, store the value locally and send the complete payload only when saving. This minimizes API calls and keeps the UI responsive.
Use meta.abbreviation for display
The meta.abbreviation object on expenses and reports provides pre-formatted, locale-aware strings for dates, amounts, and descriptions. Use these for list views instead of formatting raw values yourself β this ensures consistency with Findity's formatting rules.
Respect capabilities everywhere
Always check meta.capabilities before rendering action buttons. For example, canBeSentIn tells you if an expense can be submitted directly, canBeSplit tells you if splitting is available, and canBeMerged indicates whether a card transaction expense can be merged with another expense. Disabling unavailable actions prevents confusing error responses.
Test with the stage environment first
Use https://stage-api.findity.com/api/ during development. The stage environment has the same API surface as production but lets you experiment freely without affecting real data.
Handle validation errors gracefully
When using format=fields, validation errors are returned inline with the fields structure. Check the error and errorText properties on each field to display validation feedback exactly where users need it, rather than showing generic error messages.
Leverage OCR scan results
Upload receipts with action=scan before initializing expense fields. Pass the scanResult to the fields initialization to pre-fill form data, reducing manual data entry and improving user experience.
Use include parameters strategically
Only request the data you need via include parameters (e.g., include=meta.capabilities,meta.abbreviation). This reduces payload size and improves response times, especially for list endpoints.
Implement proper error handling
The API uses standard HTTP status codes and returns structured error responses with code and description. Map common error codes (EXPENSE_RECORD_NOT_FOUND, INVALID_ORGANIZATION_ID, etc.) to user-friendly messages in your application.
Next steps
Dive into each area of the API for detailed endpoint documentation, request/response schemas, and examples:
OAuth 2.0 token management β client credentials, authorization grants, and token refresh.
User profiles, organization settings, permissions, and preferences.
Create, read, update, delete, and submit expenses in both JSON and fields format.
Group expenses into reports, manage report lifecycle, and submit for approval.
Dynamic form definitions with built-in tax logic, validation, and business rules.
Review, approve, or reject expense reports as an approver.
Upload receipts and attachments with optional OCR scanning.
Connect payment cards, list transactions, and create expenses from card activity.
Updated 14 days ago
