Fleetora API, as shipped in the download
Fleetora REST API, Webhooks & MCP (1.0)
Fleetora ships a JSON REST API, signed webhooks and an MCP endpoint, so the register can answer to whatever else you run — a spreadsheet, a workflow tool, or an assistant.
Everything below reads the same engine the screens read. There is one definition of "overdue" in this product, and no surface computes a second one.
Authentication
Every request carries a bearer key:
Authorization: Bearer ask_xxxx
Mint keys in Settings → API & Webhooks. A key belongs to a user and can do exactly what that user can do in the browser — the same four-rung role ladder applies (viewer · driver · member · admin): adding a vehicle needs fleet.write (member or admin), logging a reading mileage.log and reporting a fault defect.report (driver and up), and a key whose holder lacks the permission is refused with 403 forbidden (from 1.0.3; before, the REST writes checked only the key's scope). Keys are stored as SHA-256 hashes and shown once.
Access: a key is created Read only or Read and write. A read-only key is refused on every non-GET request at the door, before any handler runs, with 403 forbidden. Give the read-only kind to anything that only needs to answer questions.
Endpoints
| Method | Path | What it does | ||
|---|---|---|---|---|
GET | /api/ping | Verify the key; returns the user and the key's scope | ||
GET | /api/vehicles | List vehicles. `?status=active\ | disposed\ | all` |
POST | /api/vehicles | Add a vehicle. reg required and unique | ||
GET | /api/vehicles/{id} | One vehicle with its documents, services and open-defect count | ||
GET | /api/expiring | The headline. Document expiries and service dues in one ranked list. ?state=…, ?include_disposed=1 | ||
POST | /api/vehicles/{id}/mileage | Record an odometer reading | ||
POST | /api/vehicles/{id}/defects | Report a fault | ||
GET | /api/openapi.json | OpenAPI 3.0 spec (imports as a custom connector) | ||
GET | /healthz | Unauthenticated liveness probe |
The one that matters
curl -s https://fleet.example.com/api/expiring \
-H "Authorization: Bearer ask_xxxx"
{
"as_of": "2026-08-22",
"expiring": [
{ "kind": "document", "id": 12, "vehicle_id": 2, "reg": "FL-2277",
"item": "Road-use tax or licence", "detail": "RUT-9910-B",
"state": "expired", "days": -6, "distance": null,
"due_on": "2026-08-16", "due_by": "date" },
{ "kind": "service", "id": 3, "vehicle_id": 1, "reg": "FL-1042",
"item": "Full service", "detail": "500 mi to go",
"state": "soon", "days": 200, "distance": 500,
"due_on": "2027-01-01", "due_by": "distance" }
]
}
state is one of expired · overdue · critical · soon · ok · unknown. unknown is a real answer, not a missing one — see Limits, honestly below.
Units
distance, odometer and every interval are whole numbers in the vehicle's own unit (mi or km, per vehicle). The API never converts between them, because converting would mean picking a unit the operator did not.
Webhooks
POSTed as JSON on each subscribed event, signed:
X-Fleetora-Event: defect.reported
X-Fleetora-Signature: sha256=<hmac_sha256(body, secret)>
| Event | Fires when |
|---|---|
vehicle.created | a vehicle is added, from any door |
vehicle.disposed | a vehicle is taken off the road |
service.completed | a scheduled service is marked done |
defect.reported | a fault is reported |
One delivery attempt with a 5-second timeout, logged either way. Build idempotent receivers.
Power Automate (and Logic Apps)
Import /api/openapi.json as a custom connector, set the bearer key once, and the endpoints appear as actions. GET /api/expiring is the natural trigger source for "tell the office what is due".
Limits, honestly
- The API acts at user level; scope is read-only or full, on top of the app's roles.
- One delivery attempt per webhook event (log + idempotent receivers, not a retry queue).
GET /api/vehiclescaps at 500 rows. This is a fleet register, not a telematics warehouse.unknownis not an error. A service tracked by distance whose vehicle has no odometer reading on file cannot be given a date without inventing a usage rate. Fleetora returns"state": "unknown"and the honest nulls rather than a plausible guess. Record a reading and the row takes its place in the ranking.- Fleetora reads no telematics device and talks to no licensing authority. Every date in it is a date somebody entered.
MCP — the agent endpoint
Fleetora speaks the Model Context Protocol at POST /mcp, so Claude, ChatGPT agents, n8n's AI nodes or your own code can use the register instead of merely reading it. It is the same product underneath: the same bearer key, the same roles, the same engine. An agent cannot do anything its key's user could not do in the browser — and it cannot decide what is overdue; Fleetora's engine does.
Transport is streamable HTTP, stateless: one JSON-RPC 2.0 request in, one JSON response out.
curl -s -X POST https://fleet.example.com/mcp \
-H "Authorization: Bearer ask_xxxx" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1"}}}'
Then tools/list to discover, tools/call to act:
curl -s -X POST https://fleet.example.com/mcp \
-H "Authorization: Bearer ask_xxxx" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"expiring_next","arguments":{"limit":10}}}'
Connect it to an assistant
Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.
Every client needs the same three facts, and nothing in the handshake is vendor-specific:
| The address | https://fleet.example.com/mcp |
| The key | header Authorization: Bearer ask_xxxx |
| The transport | MCP over streamable HTTP, stateless |
Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:
claude mcp add --transport http fleetora https://fleet.example.com/mcp \
--header "Authorization: Bearer ask_xxxx"
ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):
{
"type": "mcp",
"server_label": "fleetora",
"server_url": "https://fleet.example.com/mcp",
"authorization": "ask_xxxx",
"require_approval": "never"
}
Own Your AI reads a list of servers in this shape:
{
"mcpServers": [
{ "id": "fleetora", "name": "Fleetora", "url": "https://fleet.example.com/mcp",
"token": "ask_xxxx", "enabled": true }
]
}
**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.
A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:
curl -X POST https://fleet.example.com/mcp \
-H "Authorization: Bearer ask_xxxx" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Fleetora implements the protocol, not an integration with a particular vendor.
Tools
| Tool | Writes? | What it does |
|---|---|---|
expiring_next | no | What expires or falls due next across the whole fleet, ranked worst first |
list_vehicles | no | Vehicles with current odometer and who holds each one |
vehicle_record | no | One vehicle in full: documents, services with due dates and due readings, open defects, recent readings |
document_types | no | The document types this operator tracks — always list these before assuming one exists |
log_mileage | yes | Record an odometer reading; a reading lower than the last is refused |
report_defect | yes | Report a fault, with severity |
There is deliberately no dispose tool, no document-renewal tool and no send-mail tool. Taking a vehicle off the road, renewing compliance paperwork and emailing a driver stay human clicks in the app. An agent can tell you what is about to bite and log what a driver reports — it cannot retire a vehicle or claim a certificate was renewed.
What an agent must not assume
- Document types are the operator's, not a standard. Vehicle compliance is named differently in every country. Call
document_typesfirst; never assume "MOT" or any other regime exists here. - Distance is not time. A service due in 500 miles has no date until somebody records a reading. If
stateisunknown, say so — do not estimate. - The register knows only what was entered. There is no telematics feed and no authority lookup behind any of these numbers.