Changelog

2026-08-23 — engine displacement on vehicles

Additive contract field; no shape change.

  • engineCapacityCc on vehicles. Vehicle create and update requests accept an optional engine displacement as a whole number of cubic centimetres, and both the vehicle detail and list responses return it. Omitting it on a full-replace update clears the stored value, so echo it back when you mean to keep it.

2026-08-17 — locked now reports document locks

Corrected value semantics on an existing response field; no shape change.

  • locked on travel-entry detail and list responses previously reported only the legacy pre-2023 import lock and stayed false for documents locked in the app, including the automatic lock applied at payout. It now carries the same derived lock state the entry.updated webhook already reports in isLocked: true whenever the document is closed to writes. Read it as “this entry cannot be modified”, not as a marker of a specific lock origin.

2026-07-13 — place ids, manual FX rates, per-diem overrides, exchange-rate direction

Additive, backward-compatible contract fields plus a new exchange-rate discriminator.

  • Place ids on travel entries. Create and update requests accept departureLocationPlaceId and destinationPlaceId (Google Places place_id), and the travel-entry response returns them. Echo the stored values back on a full-replace update to preserve place identity when you edit destination text.
  • Destination country on travel entries. Requests accept an optional destinationCountryCode (ISO 3166-1 alpha-2). When supplied it is stored directly and the server-side geocoding fallback is skipped.
  • Manual exchange rates. Expense and per-diem requests accept an optional manualExchangeRate, and travel-entry requests accept manualAdvanceExchangeRate for the advance. Each is honored only when the FX provider publishes no rate for the referential date; supplying one while an official rate exists is rejected with EXCHANGE_RATE_UNAVAILABLE.
  • Per-diem overrides. The per-diem request accepts overrideAmount and overrideReason, and the per-diem read models return them. To preserve an existing override on a full-replace update, echo the values back; omitting them clears the override and reverts to the statutory payout.
  • Exchange-rate direction. GET /api/v1/reference/exchange-rates now carries a direction discriminator: ForeignPerBase (HNB / ECB) means divide the rate to convert to the base currency, BasePerForeign (CNB) means multiply.

2026-07-08 — advance-payment date, CZ/SK exchange-rate provider

Two additive, backward-compatible changes.

  • Advance-payment date on travel entries. Create and update requests now accept an optional advancePaymentDate (yyyy-MM-dd), and the travel-entry response returns it. When omitted on create the server defaults it to the entry date. It sets the FX date for the advance and, for Czech tenants, the per-diem FX referential date.
  • Exchange-rate provider for CZ/SK. GET /api/v1/reference/exchange-rates now documents the full provider set: HNB for HR, ECB for SI and SK, CNB for CZ. The source field can be "CNB", and baseCurrency is the provider’s native base (EUR for HNB/ECB, CZK for CNB). Behavior is unchanged; the docs now match runtime.

2026-06-11 — per-meal counts, FX provenance, exchange-rate-unavailable code

Three additive surface changes. All are backward compatible: existing payloads keep working unchanged, and the new response fields are additions you can ignore until you need them.

  • Per-meal counts on per-diem create/update. Per-diem requests now accept breakfastsProvided, lunchesProvided, and dinnersProvided (per-meal integer counts) plus an optional per-day mealsProvidedAllocation. They are the canonical write model: the server recomputes the reduction from these counts. The existing v1 aggregate mealsProvided is still accepted; when the per-meal fields are absent the server maps the aggregate as before, so legacy clients are unaffected. Responses keep returning the derived mealsProvided (lunch + dinner, the HR Pravilnik shape) alongside the per-meal counts, so read paths are unchanged.
  • FX provenance on responses. Expense and per-diem responses gain exchangeRateSource, and the travel-entry response gains advancePaymentExchangeRateSource, recording where the applied rate came from: a provider code ("HNB", "ECB") for an official rate, "MANUAL" for a user-supplied rate, or null for a same-currency row (no conversion) or a legacy row saved before provenance tracking. Read-only; no request change.
  • New error code EXCHANGE_RATE_UNAVAILABLE (HTTP 400). Fires when the tenant’s FX provider publishes no rate for a currency on the requested date and no historical rate exists to fall back on. It is a validation-family failure with the standard field-keyed errors body, so you can discriminate the FX-unavailable case specifically and prompt for a manual rate. Added to the errors catalog.

2026-05-23 — per-diem itinerary times stored as true UTC (fix)

Per-diem times persisted via POST /api/v1/travel-entries/{id}/per-diems/from-itinerary are now stored as true UTC instants. Previously each leg’s departure/arrival was written using its wall-clock value tagged as UTC without shifting (an 08:00 local leg was stored as 08:00Z instead of 07:00Z/06:00Z), so itinerary-generated rows disagreed with manually-added rows by 1–2 hours and displayed late. The request and response schemas are unchanged, but the departureTime/arrivalTime values this endpoint returns (and that later reads of the same rows return) now reflect the corrected UTC instant. The stateless calculator preview still echoes wall-clock times. Per-diem amounts are unaffected.

2026-05-23 — per-diem calculator + whole-trip persist

The per-diem calculation surface is consolidated and gains whole-trip persist parity with the locco web app.

  • New POST /api/v1/utilities/per-diems/calculate — a stateless calculator. Send an itinerary (one leg = a 2-stop itinerary) and get the computed rows back, with itinerary-level redistribution applied. No entry context, nothing persisted.
  • New POST /api/v1/travel-entries/{id}/per-diems/from-itinerary — rebuild a travel entry’s per-diems from an itinerary in one call. The server re-runs its own engine over the stops (submitted amounts are never trusted), applies per-leg meals keyed by (countryCode, departureTime) (the pairs the calculator returns), then atomically replaces the entry’s per-diems. This reproduces itinerary-level redistribution and domestic-unit upgrades that posting rows one at a time cannot.
  • Removed POST /api/v1/travel-entries/{id}/per-diems/compute and POST /api/v1/per-diems/calculate-simple. Both are subsumed by utilities/per-diems/calculate — a single leg is just a 2-stop itinerary. Move integrations to the new calculator.

2026-05-22 — Slovenian market support

Initial support for Slovenian tenants. Update your client from the refreshed OpenAPI snapshot at /api-reference/:

  • Slovenian municipalities — new GET /api/v1/reference/slovenian-municipalities returns the SURS municipality list for place dropdowns on Slovenian travel forms. Callable from any tenant.
  • Employee tax residency — employee records gain a residency field (Resident / NonResident) for Slovenian per-diem and payroll-tax reporting. Defaults to Resident.

2026-04-28 — v1.0 launching

The first stable release of the locco partner API. Every surface is wired, typed, and covered by the OpenAPI snapshot at /api-reference/:

  • Travel entries — full CRUD plus the workflow transitions (submit, approve, reject, cancel, request-changes), with atomic creates that accept nested expenses, per-diems, cost-center allocations, and attachments in a single call.
  • Attachments — direct upload to a travel entry via POST /api/v1/travel-entries/{id}/attachments. Multipart, up to 5 files per request.
  • Webhooks — subscription registration with one-time signing-secret exposure, delivery history per subscription, and retry endpoint for failed or exhausted deliveries.
  • Payouts — list ready-for-payout entries, create batches, list history, delete a batch.
  • Employees, Departments, Vehicles, Cost centers — CRUD + list, with custom-field values on employees.
  • Reference — Croatian municipalities, HNB exchange rates, per-diem rate table, the company’s enabled-country and enabled-currency allow-lists.
  • Company — profile read.

Enum values for Status, TravelType, PayoutMethod, and the rest are documented in the OpenAPI schema as enum: [...] allow-lists, so generated clients pin against the canonical strings ("PendingApproval", "BankTransfer", etc.) rather than opaque values.