Fields - Mileage expenses

Create and manage mileage expense claims using the dynamic Fields system in the Expense API.

Create mileage expense claims (car specifications) using the dynamic Fields system. This guide covers initializing fields, handling the route and vehicle type selection, managing trip details, and saving mileage expenses.

Overview

Mileage expenses use the CarSpecification expense type. They allow users to claim reimbursement for business travel based on distance driven, vehicle type, and optional extras like passengers, road tolls, and trailer usage.

The Fields system handles the complexity of mileage calculations — including country-specific reimbursement rates, vehicle type multipliers, and distance computation from routes. Your client renders the fields and lets the server do the math.

Key concepts

ConceptDescription
RouteAn ordered list of places (with coordinates) that defines the travel path. The server calculates distance automatically.
Vehicle typeThe type of vehicle used (e.g., private car, company car, motorcycle). Affects reimbursement rates.
Round tripA toggle that doubles the calculated distance.
Manual distanceAllows the user to override the server-calculated distance with a manually entered value.
Trip detailsOptional extras: passengers, road tolls, gravel distance, trailer, caravan, etc.
CommutingA variant of mileage for commuting expenses, with year/month/day selection instead of route-based travel.

API endpoints

All mileage field operations use the standard Fields endpoints with expenseType=CarSpecification:

OperationMethodEndpoint
Initialize fieldsGET/v1/expense/me/organizations/{id}/expensetypes/CarSpecification/fields
Update fieldsPUT/v1/expense/me/organizations/{id}/expensetypes/CarSpecification/fields
Create expensePOST/v1/expense/expenses?format=fields&organizationId={id}&expenseType=CarSpecification
Update expensePUT/v1/expense/expenses/{expenseId}?format=fields
Get car typesGET/v1/expense/me/organizations/{id}/categories/{categoryId}/cartypes
Get categoriesGET/v1/expense/me/organizations/{id}/expensetypes/CarSpecification/categories

Step 1: Initialize fields

Fetch the field structure for a new mileage expense:

curl -X GET "https://stage-api.findity.com/api/v1/expense/me/organizations/{orgId}/expensetypes/CarSpecification/fields" \
  -H "Authorization: Bearer {access_token}"

To load an existing mileage expense for editing, pass the expense record ID:

curl -X GET "https://stage-api.findity.com/api/v1/expense/me/organizations/{orgId}/expensetypes/CarSpecification/fields?expenseRecordId={expenseId}" \
  -H "Authorization: Bearer {access_token}"

Field structure overview

The response contains these key fields:

Field propertycontrolTypeDescription
idTEXTExpense ID (hidden, system field)
categoryIdLISTMileage category — requiresUpdate: true
verification.commentMULTI_LINE_TEXTUser comment
expenseReportIdLISTAssign to an expense report
verification.travelDateDATE_TIMEDate and time of travel
verification.destinationTEXTAuto-generated from route (hidden, read-only)
verification.carTypeLISTVehicle type — requiresUpdate: true
verification.travelRouteFIELD_GROUPRoute container with nested fields
verification.travelTimeTEXTTravel time (hidden, system field)
Custom dimension fieldsLISTOrganization-specific dimensions (e.g., cost center, project)

Step 2: Handle the route

The travel route is a FIELD_GROUP containing several nested fields:

Nested fieldcontrolTypeDescription
verification.manualDistanceSWITCHToggle manual distance entry — requiresUpdate: true
verification.routeMILEAGE_ROUTEArray of route points with place, latitude, longitude
verification.distanceDOUBLECalculated distance (or manual entry when manualDistance is true)
verification.roundTripSWITCHDoubles the distance — requiresUpdate: true
verification.numberOfPassengersINTEGERNumber of extra passengers
verification.tollCostDOUBLERoad toll amount
verification.distanceOnGravelDOUBLEDistance driven on gravel roads
verification.distanceInTransportDOUBLEDistance with vehicle on transport
verification.distanceWithTrailerDOUBLEDistance with trailer attached
verification.distanceWithCaravanDOUBLEDistance with caravan attached
verification.distanceWithHeavyCaravanDOUBLEDistance with heavy caravan
verification.distanceWithDogDOUBLEDistance with dog in vehicle
verification.travelTimeTEXTCalculated travel time — requiresUpdate: true
📘

Not all trip detail fields are visible by default. Their visibility depends on the organization's configuration and country-specific rules. Only render fields where visible: true.

Route data format

The MILEAGE_ROUTE field holds an array of waypoints:

[
  { "place": "Stockholm, Sweden", "latitude": "59.3293481", "longitude": "18.0682306" },
  { "place": "Gothenburg, Sweden", "latitude": "57.7086375", "longitude": "11.9747055" }
]

The first entry is the origin, the last is the destination, and any entries in between are via points. When the route changes and requiresUpdate is true, the server recalculates the distance and generates a map image.

Adding via points

Routes can include intermediate stops. A route with two via points:

[
  { "place": "Ludvika", "latitude": "60.1523510", "longitude": "15.1919879" },
  { "place": "Falun", "latitude": "60.609036", "longitude": "15.629638" },
  { "place": "Borlänge", "latitude": "60.489796", "longitude": "15.434664" },
  { "place": "Stockholm", "latitude": "59.3293481", "longitude": "18.0682306" }
]

Route commands

The travel route field group includes a commands array for clearing the route:

{
  "commands": [
    {
      "text": "Clear travel route",
      "confirmationRequired": true,
      "confirmation": "Are you sure you want to clear travel routes?",
      "command": "action=removeTravelRoute"
    }
  ]
}

Execute by appending the command to the PUT URL:

PUT /v1/expense/me/organizations/{id}/expensetypes/CarSpecification/fields?action=removeTravelRoute

Map image

After a route update, the server generates a map image. The URL is stored in the hidden field virtual.mileageMap:

{ "property": "virtual.mileageMap", "visible": false, "value": "https://expense.findity.com/api/resources/ff8080818cd9867f..." },
{ "property": "virtual.mileageMapChecksum", "visible": false, "value": "236c7fd6d8462091c3f36a33756cfa44b386433b" }

Use this URL to display a visual route preview in your UI. The server also provides a virtual.mileageMapChecksum field for cache validation. Both fields are hidden and read-only.

Step 3: Vehicle type and category

Both the category (categoryId) and vehicle type (verification.carType) have requiresUpdate: true. Changing either triggers a server round-trip that may update reimbursement calculations and visible fields.

Fetching vehicle types

Vehicle types are scoped to the selected category:

GET /v1/expense/me/organizations/{id}/categories/{categoryId}/cartypes

Common vehicle types include:

ValueDescription
PRIVATE_CARPrivate car
COMPANY_CARCompany car
COMPANY_CAR_DIESELCompany car (diesel)
COMPANY_CAR_ELECTRICITYCompany car (electric)
MOTOR_CYCLEMotorcycle
MOTOR_CYCLE_GT_125CCMotorcycle > 125cc
MOTOR_CYCLE_LTE_125CCMotorcycle ≤ 125cc
MOPEDMoped
CYCLE_OR_WALKINGBicycle or walking
PUBLIC_TRANSPORTATIONPublic transportation
💡

The full list of vehicle types depends on the organization's country and configuration. Always fetch from the API rather than hardcoding values.

Step 4: The update cycle

Mileage expenses have several fields with requiresUpdate: true. When the user changes any of these, send the full fields array back to the server:

PUT /v1/expense/me/organizations/{id}/expensetypes/CarSpecification/fields

Fields that trigger an update:

FieldWhat changes on update
categoryIdVisible fields, vehicle type options, reimbursement rates
verification.carTypeReimbursement rate, trip detail visibility
verification.manualDistanceToggles between calculated and manual distance input
verification.roundTripDoubles or halves the distance
verification.routeRecalculates distance and generates a new map
verification.fuelTypeUpdates toll cost calculations (where applicable)
verification.bompengUpdates road toll data (Norwegian organizations)

Example update request

curl -X PUT "https://stage-api.findity.com/api/v1/expense/me/organizations/{orgId}/expensetypes/CarSpecification/fields" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      { "property": "id", "value": null },
      { "property": "categoryId", "value": "b219079277d14c77a0c85497504cf90b" },
      { "property": "verification.carType", "value": "PRIVATE_CAR" },
      { "property": "verification.travelDate", "value": "2024-01-05T12:46:11Z" },
      { "property": "verification.travelRoute", "fields": [
        { "property": "verification.manualDistance", "value": false },
        { "property": "verification.route", "value": [
          { "place": "Stockholm, Sweden", "latitude": "59.3293481", "longitude": "18.0682306" },
          { "place": "Ludvika, Sweden", "latitude": "60.1523510", "longitude": "15.1919879" }
        ]},
        { "property": "verification.distance", "value": 434 },
        { "property": "verification.roundTrip", "value": false }
      ]}
    ]
  }'
⚠️

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

Step 5: Save the expense

Once the user is finished, save the mileage expense:

Using fields format

POST /v1/expense/expenses?format=fields&organizationId={id}&expenseType=CarSpecification

Send the full fields payload as the request body. The server validates all fields and returns the created expense.

Using standard JSON format

Alternatively, extract values from the fields and use the standard expense format:

curl -X POST "https://stage-api.findity.com/api/v1/expense/expenses" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "ff808181963979e20196397a2098004b",
    "categoryId": "b2d50112c6fa4411b75208c23c21b323",
    "verification": {
      "type": "CarSpecification",
      "carType": "PRIVATE_CAR",
      "travelDate": "2024-01-31T23:00:00Z",
      "description": "Trip to Gothenburg",
      "manualDistance": false,
      "roundTrip": false,
      "distance": 468,
      "distanceUnit": "KM",
      "route": [
        { "place": "Stockholm, Sweden", "latitude": "59.3293481", "longitude": "18.0682306" },
        { "place": "Gothenburg, Sweden", "latitude": "57.7086375", "longitude": "11.9747055" }
      ],
      "passengers": [],
      "tripDetails": {
        "carRegistrationNumber": "AB12345",
        "numberOfPassengers": 2,
        "tollCost": 10,
        "distanceOnGravel": 0,
        "distanceInTransport": 0,
        "distanceWithTrailer": 0,
        "distanceWithCaravan": 0,
        "distanceWithHeavyCaravan": 0,
        "distanceWithDog": 0
      }
    }
  }'

Example response

{
  "id": "67c18882083149599859054bcebb4ca0",
  "organizationId": "ff808181963979e20196397a2098004b",
  "personId": "ff808181963979e20196397a119f002d",
  "status": "NORMAL",
  "processStatus": "DRAFT",
  "categoryId": "b2d50112c6fa4411b75208c23c21b323",
  "reimbursementCurrency": "SEK",
  "reimbursementAmount": 2340.00,
  "dateCreated": "2024-01-31T10:52:48Z",
  "lastUpdated": "2024-01-31T10:52:48Z",
  "verification": {
    "type": "CarSpecification",
    "carType": "PRIVATE_CAR",
    "travelDate": "2024-01-31T23:00:00Z",
    "description": "Trip to Gothenburg",
    "destination": "Stockholm, Sweden - Gothenburg, Sweden",
    "distance": 468,
    "distanceUnit": "KM",
    "manualDistance": false,
    "roundTrip": false,
    "route": [
      { "place": "Stockholm, Sweden", "latitude": "59.3293481", "longitude": "18.0682306" },
      { "place": "Gothenburg, Sweden", "latitude": "57.7086375", "longitude": "11.9747055" }
    ],
    "passengers": [],
    "tripDetails": {
      "carRegistrationNumber": "AB12345",
      "numberOfPassengers": 2,
      "tollCost": 10,
      "distanceOnGravel": 0,
      "distanceInTransport": 0,
      "distanceWithTrailer": 0,
      "distanceWithCaravan": 0,
      "distanceWithHeavyCaravan": 0,
      "distanceWithDog": 0
    }
  }
}

Commuting variant

Some organizations configure mileage categories for commuting. Commuting expenses replace the route-based travel with a year/month/day selection:

FieldcontrolTypeDescription
verification.commuting.yearLIST_SEARCHABLEYear to report commuting for
verification.commuting.monthLIST_SEARCHABLEMonth to report — requiresUpdate: true
verification.commuting.daysLIST_SEARCHABLENumber of commuting days

Fetch the available values from these endpoints:

GET /v1/expense/me/organizations/{id}/commuting/years
GET /v1/expense/me/organizations/{id}/commuting/months?year=2025
GET /v1/expense/me/organizations/{id}/commuting/days?year=2025&month=6

The route field group is still present in commuting mode but with a simplified structure — the vehicle type is typically locked (e.g., PRIVATE_CAR with disabled: true).

Named passengers

Some organizations require named passengers instead of just a count. When enabled, the passengers array contains individual entries:

{
  "passengers": [
    { "name": "Albert", "distance": 100 },
    { "name": "Herbert", "distance": 200 },
    { "name": "Kyle", "distance": 150 }
  ]
}

Each passenger has a name and the distance they traveled. This is used for per-passenger reimbursement calculations in some countries.

Fleet management integration

Organizations with fleet management integrations can link mileage expenses to tracked vehicle trips:

{
  "verification": {
    "relatedTrips": [
      { "fleetManagementTripId": "12345678" },
      { "fleetManagementTripId": "12345679" }
    ]
  }
}

Fetch available trips from:

GET /v1/expense/me/organizations/{id}/categories/{categoryId}/fleetmanagementtrips

In the fields format, fleet management trips appear as a LIST field with multiSelect: true at verification.fleetManagementTripIds.

Toll cost data

For Norwegian organizations using AutoPASS or similar toll systems, toll cost data can be included:

{
  "verification": {
    "tollCostFuelType": "HYBRID",
    "tollCostData": {
      "price": 100,
      "discountedPrice": 90,
      "returnPrice": 200,
      "returnDiscountedPrice": 180
    }
  }
}

The tollCostFuelType affects the toll rate calculation and supports: PETROL, DIESEL, ELECTRIC, HYBRID, and HYDROGEN.

Common integration patterns

Route suggestions with place search

Use the route suggestions endpoint to provide autocomplete when users type a place name:

GET /v1/expense/route/suggestions?place=Stoc

Returns a list of matching places. After the user selects origin and destination, call the directions endpoint to get coordinates and calculated distance:

POST /v1/expense/route/directions

With a body of [{ "place": "Stockholm" }, { "place": "Gothenburg" }].

Handling manual distance override

When the user toggles verification.manualDistance to true, the verification.distance field becomes editable and the verification.route field is no longer required. Send a PUT to update the fields — the server will adjust field visibility accordingly. When toggled back to false, the distance is recalculated from the route.

Round trip handling

When verification.roundTrip is toggled to true, the server doubles the calculated distance. This is a requiresUpdate field — send a PUT to get the updated distance value. The route itself stays the same; only the distance is doubled.

Rendering the route summary

The travel route FIELD_GROUP includes a summary array for displaying a compact preview:

[
  { "type": "image" },
  { "type": "property_value", "property": "Distance", "value": "468 km" }
]

Use the image type to display the map from virtual.mileageMap, and property_value entries to show key metrics like distance.

Error handling

Common errors when working with mileage expenses:

Status codeError codeDescription
400NOT_A_CAR_SPECIFICATION_CATEGORYThe category ID provided is not a mileage category. Fetch categories for CarSpecification expense type.
400INVALID_JSON_BODYFailed to parse the request body or no known properties received.
400INVALID_JSON_PROPERTY_VALUEMissing or invalid type on verification — ensure type is set to CarSpecification.
400TOO_FEW_ROUTE_PLACESRoute requires at least 2 places (origin and destination).
404EXPENSE_RECORD_NOT_FOUNDThe expense ID does not exist or is not accessible by the current user.
404CATEGORY_NOT_FOUNDThe specified category does not exist in the organization.
404FLEET_MANAGEMENT_ACCOUNT_NOT_FOUNDFleet management account not configured for the organization.
404FLEET_MANAGEMENT_VEHICLE_NOT_FOUNDNo vehicle found for the current user in fleet management.

Next steps