Costs and your ERP
Most integrations with Fleet exist for one reason: fleet costs have to end up in the ERP. Fuel, maintenance, tyres, insurance and licences, tolls and fines, all coded to the right accounts, departments and projects, without double entry and without double counting. This page explains the model; the Odoo and Business Central recipes put it to work.
Who owns what
Section titled “Who owns what”Fleet holds the codes. The ERP holds the accounting rules.
- Fleet records what happened: which vehicle, which supplier, how many litres, which parts, which department or project it was for, the VAT on the receipt and how it was paid.
- Your ERP decides how to book it: which G/L account, which tax code, which journal, what the approval limits are.
So Fleet never stores G/L accounts as accounting truth. It stores dimension codes your ERP gives it, and external references that tie Fleet objects to ERP records. Your integration translates.
Two ways costs flow
Section titled “Two ways costs flow”| Request-first (Fleet requests) | Record-first (cost ledger) | |
|---|---|---|
| When | Spend needs approval or a PO before it happens: services, repairs, tyres, parts | The cost already happened: fuel-card transactions, receipts, claims, renewals |
| Flow | Fleet request → approved in Fleet → your ERP raises the PR / PO / bill → ERP writes the outcome back | Fleet records the cost → your ERP pulls it from the export queue and posts it |
| Actual cost | The ERP bill (Fleet records it from your write-back) | The Fleet record |
| Endpoints | /v1/fleet_requests, …/financial_events, webhooks fleet_request.* |
/v1/cost_entries, /v1/cost_exports, webhooks cost_entry.* |
Most organizations use both. Neither double counts: when your ERP bill for a fleet request arrives, the ledger
entries for the linked work order are marked superseded and the bill becomes the cost.
The cost ledger
Section titled “The cost ledger”Every cost in Fleet appears once in /v1/cost_entries, as ERP-ready lines:
{ "id": "6f1c2b0e-8a43-4c1e-9d55-0c2b7f3e9a10", "object": "cost_entry", "status": "posted", "revision": 1, "category": "fuel", "subcategory": "diesel", "incurred_on": "2026-10-01", "posting_date": "2026-10-01", "amount": "7600.00", "currency": "KES", "tax": { "amount": "1048.28", "rate": 16 }, "quantity": "40", "unit": "litre", "unit_price": "190.00", "vehicle_id": "…", "branch_id": "…", "vendor_id": "…", "settlement": "fuel_card", "payee": { "type": "vendor", "id": "…" }, "references": { "receipt_number": "R-55102", "tax_invoice_number": "KRAMW004202610010123" }, "dimensions": { "department": "OPS", "project": "P-104" }, "source": { "type": "fuel_log", "id": "…", "origin": "api", "url": "/v1/fuel_logs/…" }}- Categories are
fuel,ev_charging,maintenance(one line each for parts, labour and service on a completed work order),tires,renewalsandexpenses. Totals match Fleet’s Operating Cost Summary report, so what you post reconciles with what fleet managers see. - Settlement says who is owed:
vendor_bill(a payable),employee_reimbursement(an approved claim),prepaid_card(an allocation against a prepaid card account, not a new payable),fuel_card,cash,stock_issue(parts from inventory),internal_labor,financing(lease or loan payments). - Dimensions combine branch defaults, vehicle defaults and the record’s own codes, most specific last.
- Entries are never deleted. A change raises
revision. A deleted or no-longer-qualifying record becomesvoid; an ERP bill replacing a work order makes itsuperseded. Reverse what you posted in both cases.
Exporting exactly once
Section titled “Exporting exactly once”GET /v1/cost_exports?system=business_central is a queue of what your ERP has not seen in its current form:
pending_new: never exported. Post it.pending_update: changed since you exported it.last_exportholds what you posted; post the difference or reverse and re-post.pending_void: you exported it and it has since been voided or superseded. Reverse it.
After posting, acknowledge with the revision you posted and your document id:
curl https://api.get-fleet.com/v1/cost_exports \ -H "Authorization: Bearer $FLEET_API_KEY" -H "Content-Type: application/json" \ -d '{"system": "business_central", "entries": [{"id": "6f1c…", "revision": 1, "external_id": "PI-10442"}]}'If the entry changed while you were posting it, the acknowledgement is still recorded for the revision you posted
and comes back stale: the entry stays in the queue as pending_update (or pending_void) with last_export
showing what you posted. Nothing is lost between reading and acknowledging, and nothing is posted twice. Each ERP has its own queue;
records your ERP itself created through the API (they carry its external reference) are never offered back to it.
Mapping
Section titled “Mapping”Set these up once, from your ERP, with accounting:write:
-
Dimensions and their values. Create a dimension per ERP dimension (department, project, cost centre, donor, activity…), then push the value list with
PUT /v1/accounting_dimensions/{key}/values. Senddeactivate_missing: trueto make it a full sync. Values can have a parent (sub-activity under activity) and validity dates. Fleet only accepts active codes on records. -
Default codes on branches (
PUT /v1/branches/{id}/dimensions) and vehicles (dimensionson the vehicle), so people rarely have to pick them. -
External references for the objects your ERP needs ids for: vehicles (asset or analytic account), vendors (partner / vendor no.), users (employee), expense types, and cost categories (
maintenance.parts,fuel,renewals.insurance…), whosemetadatacan carry the account and tax code your integration should use:PUT /v1/external_references{ "data": [{ "object_type": "cost_category", "object_key": "fuel", "system": "odoo", "external_id": "611000","metadata": { "account": "611000", "tax": "VAT 16% (purchases)" } },{ "object_type": "vehicle", "object_key": "6f1c…", "system": "odoo", "external_id": "77" }] }Objects then carry
external_ids({"odoo": "77"}), and every list takesexternal_id=odoo:77.
GET /v1/fleet_requests/{id}/readiness?system=odoo runs these checks for one request before anyone approves it:
missing amounts or categories, required dimensions, and unmapped vehicles, suppliers or categories.
Tax and payment
Section titled “Tax and payment”Fuel logs, expenses and EV charging sessions carry tax_amount, tax_rate, tax_invoice_number (for example the
KRA eTIMS invoice number), supplier_invoice_number, payment_method and payment_reference. They flow into the
ledger as tax and references, so input VAT can be claimed from the ERP side.
Periods and currencies
Section titled “Periods and currencies”- Soft period close. When your ERP closes a period, set
closed_throughwithPATCH /v1/accounting_settings. A cost dated in a closed period that changes later keeps itsincurred_onbut gets aposting_datein the first open day, so you never have to reopen a closed period. Post byposting_date. - Currencies are explicit. Each entry is in its transaction currency (a fuel log at a Ugandan branch is in UGX).
GET /v1/cost_summariesconverts with Fleet’s monthly rates and, where a rate is missing, says so (missing_fx: true) instead of guessing.
Creating costs from the ERP side
Section titled “Creating costs from the ERP side”If the ERP is where a cost is first captured (a supplier invoice for tyres, an insurance renewal), create it in
Fleet so vehicle cost reports are complete: POST /v1/expenses, /v1/vehicle_renewals, /v1/fuel_logs or
/v1/work_orders. Send external_system and external_id: re-sending the same pair returns the existing record
instead of a duplicate, and the ledger knows not to export it back to you.