{"openapi":"3.1.0","info":{"title":"Fleetiqo API","version":"2026-09-01","summary":"Fleet, rental, customer and cost data for Fleetiqo accounts.","description":"The Fleetiqo REST API. One envelope, one error shape, cursor pagination, ETags,\nidempotent writes and rate-limit headers on every response.\n\n## Authentication\n\nSend an API key as a bearer token:\n\n```\ncurl https://dashboard.fleetiqo.com/api/v1/vehicles \\\n  -H \"Authorization: Bearer fio_live_ab12cd34…\"\n```\n\nKeys are created in **Settings → API keys**. The secret is shown once, at\ncreation, and stored only as a SHA-256 hash — we cannot recover it for you, so\nif it is lost, revoke it and mint a new one. Each key carries scopes; a key\nwithout the scope an endpoint needs gets **403 `insufficient_scope`** naming\nthe scope it is missing.\n\nA key can be issued in the **test** environment, which is accepted on every read\nand refused on every write. That is how you exercise an integration against real\ndata with no risk of changing it.\n\n## Envelope\n\nSuccess is always:\n\n```json\n{ \"data\": …, \"meta\": { \"request_id\": \"req_…\", \"api_version\": \"2026-09-01\", … } }\n```\n\nFailure is always:\n\n```json\n{ \"error\": { \"code\": \"validation_failed\", \"message\": \"…\", \"field\": \"amount\" }, \"request_id\": \"req_…\" }\n```\n\n`code` is stable and safe to branch on. `message` is for humans and may be\nreworded at any time — do not match on it.\n\n## Pagination\n\nCursor by default: read `meta.pagination.next_cursor` and pass it back as\n`?cursor=`. Cursor paging is stable while rows are being inserted. Pass\n`?page=` instead if you need a total and can accept that rows shift under you.\n`limit` is 1–100 and is capped, not rejected, above 100.\n\n## Conditional requests and concurrency\n\nEvery GET returns a strong `ETag`. Send it back as `If-None-Match` to get a\n304 with no body. Send it as `If-Match` on a PATCH or DELETE and the write is\nrefused with **412** if the resource changed since you read it — which is how\ntwo people editing the same row stop silently overwriting each other.\n\n## Idempotency\n\nEvery write accepts `Idempotency-Key`. The same key with the same body replays\nthe original response (`Idempotent-Replay: true`); the same key with a\ndifferent body is a **409**. Keys live for 24 hours. Retrying a POST is safe.\n\n## Rate limits\n\n120 requests/minute per key. Every response carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset`; a 429 also carries\n`Retry-After`. Back off on `Remaining`, do not wait for the 429.\n\n## Money and dates\n\nMoney is always a **2-decimal string** with an explicit `currency`, never a\nJSON number — a float cannot hold every value a DECIMAL(10,2) column can, and\nthe precision is gone the moment a client parses it. Date-only fields are\n`YYYY-MM-DD` calendar dates; timestamps are RFC 3339 UTC. Date-only *filters*\nare interpreted in `?timezone=`, then the account timezone, then UTC, and the\nzone that was applied is echoed in `meta.timezone`.","contact":{"name":"Fleetiqo support","url":"https://fleetiqo.com/contact"},"license":{"name":"Proprietary","identifier":"LicenseRef-Fleetiqo"}},"servers":[{"url":"https://dashboard.fleetiqo.com/api","description":"Production"}],"tags":[{"name":"Vehicles","description":"The fleet."},{"name":"Rentals","description":"Trips, from any channel."},{"name":"Customers","description":"Renters, with notes, tags and a block list."},{"name":"Expenses","description":"Costs against a vehicle."},{"name":"Maintenance","description":"Service history with line items."},{"name":"Documents","description":"Vehicle paperwork and its expiry dates."},{"name":"Income","description":"Non-rental income."},{"name":"Mileage","description":"Odometer history with provenance."},{"name":"Account","description":"Who the credential belongs to."},{"name":"Events","description":"Polling alternative to webhooks, and the event catalogue."},{"name":"Batch","description":"One operation across many rows."},{"name":"Exports","description":"Server-side CSV and JSON exports."},{"name":"Imports","description":"CSV imports with a dry run."}],"paths":{"/v1":{"get":{"operationId":"getApiRoot","summary":"Service discovery","description":"The API root. Unauthenticated on purpose: an evaluator has to be able to see what exists, what the conventions are and where the spec lives before signing up.","tags":["Account"],"security":[],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"api_root","name":"Fleetiqo API","status":"available","openapi_url":"https://dashboard.fleetiqo.com/api/v1/openapi.json","conventions":{"rate_limit_per_minute":120,"idempotency_header":"Idempotency-Key"}},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"OpenAPI 3.1 document","description":"The machine-readable spec, generated from the route definitions so it cannot drift from the implementation. Unauthenticated and CORS-open, so Swagger UI, Postman and code generators can read it directly.","tags":["Account"],"security":[],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"openapi":"3.1.0","info":{"title":"Fleetiqo API"}},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/vehicles":{"get":{"operationId":"listVehicles","summary":"List vehicles","description":"Returns the vehicles on the account. Filterable by status, live rental status, make, model, year, VIN, plate and free-text search.","tags":["Vehicles"],"security":[{"ApiKeyAuth":["vehicles:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"status","in":"query","description":"active (default), archived, deleted or all.","required":false,"schema":{"type":"string","enum":["active","archived","deleted","all"]}},{"name":"rental_status","in":"query","description":"Live availability: available, rented or maintenance.","required":false,"schema":{"type":"string","enum":["available","rented","maintenance"]}},{"name":"make","in":"query","description":"Substring match on make.","required":false,"schema":{"type":"string"}},{"name":"model","in":"query","description":"Substring match on model.","required":false,"schema":{"type":"string"}},{"name":"year","in":"query","description":"Exact model year.","required":false,"schema":{"type":"integer"}},{"name":"vin","in":"query","description":"Exact VIN.","required":false,"schema":{"type":"string"}},{"name":"license_plate","in":"query","description":"Exact plate.","required":false,"schema":{"type":"string"}},{"name":"is_favorite","in":"query","description":"Only favourites.","required":false,"schema":{"type":"boolean"}},{"name":"search","in":"query","description":"Free text across make, model, VIN, plate and Turo name.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"vehicle","id":142,"vin":"7FARW2H58NE012345","make":"Honda","model":"CR-V","trim":"EX-L","year":2022,"color":"Blue","license_plate":"ABC-1234","status":"active","rental_status":"available","sort_order":3,"is_favorite":false,"current_mileage_mi":24500,"distance_unit":"mi","powertrain":"gas","engine_power_hp":"190","engine_displacement":"1.5L","purchase":{"type":"retail","price_usd":"28500.00","price_cad":"28500.00","fx_applied":false,"ownership_start_date":"2024-03-12"},"market_value":{"currency":"USD","blended":"24100.00","retail":"25400.00","trade_in":"22300.00","private_party":"24600.00","updated_at":"2026-08-01T04:11:02.000Z"},"insurance":{"provider":"Progressive","policy_number":"PA-99881","monthly_cost":"142.00","deductible":"1000.00","renewal_date":"2027-01-15"},"recalls_last_checked":"2026-08-30T07:15:00.000Z","factory_maintenance_schedule":null,"vehicle_token":"v_8f2b1c9d4e","turo":{"listing_id":"1234567","vehicle_id":null,"link":null,"name":"Blue CR-V"},"image_url":null,"created_at":"2024-03-12T18:22:00.000Z","updated_at":"2026-08-30T07:15:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createVehicle","summary":"Create a vehicle","description":"Adds a vehicle. A duplicate VIN or plate returns 409 `conflict` rather than a 500.","tags":["Vehicles"],"security":[{"ApiKeyAuth":["vehicles:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Vehicle"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"vehicle","id":142,"vin":"7FARW2H58NE012345","make":"Honda","model":"CR-V","trim":"EX-L","year":2022,"color":"Blue","license_plate":"ABC-1234","status":"active","rental_status":"available","sort_order":3,"is_favorite":false,"current_mileage_mi":24500,"distance_unit":"mi","powertrain":"gas","engine_power_hp":"190","engine_displacement":"1.5L","purchase":{"type":"retail","price_usd":"28500.00","price_cad":"28500.00","fx_applied":false,"ownership_start_date":"2024-03-12"},"market_value":{"currency":"USD","blended":"24100.00","retail":"25400.00","trade_in":"22300.00","private_party":"24600.00","updated_at":"2026-08-01T04:11:02.000Z"},"insurance":{"provider":"Progressive","policy_number":"PA-99881","monthly_cost":"142.00","deductible":"1000.00","renewal_date":"2027-01-15"},"recalls_last_checked":"2026-08-30T07:15:00.000Z","factory_maintenance_schedule":null,"vehicle_token":"v_8f2b1c9d4e","turo":{"listing_id":"1234567","vehicle_id":null,"link":null,"name":"Blue CR-V"},"image_url":null,"created_at":"2024-03-12T18:22:00.000Z","updated_at":"2026-08-30T07:15:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"make":"Honda","model":"CR-V","year":2022,"vin":"7FARW2H58NE012345","license_plate":"ABC-1234","current_mileage_mi":24500,"purchase_price":"28500.00","purchase_date":"2024-03-12","purchase_type":"retail"}}}}}},"/v1/vehicles/{id}":{"get":{"operationId":"getVehicle","summary":"Retrieve a vehicle","description":"One vehicle. `expand` returns the whole detail aggregate in a single round trip — the dashboard screen this replaces fired eight sequential GETs on mount.","tags":["Vehicles"],"security":[{"ApiKeyAuth":["vehicles:read"]}],"parameters":[{"name":"id","in":"path","description":"The vehicle's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"expand","in":"query","description":"Comma-separated: rentals, expenses, maintenance, documents, income, mileage, totals.","required":false,"schema":{"type":"string"},"example":"expenses,maintenance,totals"},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Vehicle"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"vehicle","id":142,"vin":"7FARW2H58NE012345","make":"Honda","model":"CR-V","trim":"EX-L","year":2022,"color":"Blue","license_plate":"ABC-1234","status":"active","rental_status":"available","sort_order":3,"is_favorite":false,"current_mileage_mi":24500,"distance_unit":"mi","powertrain":"gas","engine_power_hp":"190","engine_displacement":"1.5L","purchase":{"type":"retail","price_usd":"28500.00","price_cad":"28500.00","fx_applied":false,"ownership_start_date":"2024-03-12"},"market_value":{"currency":"USD","blended":"24100.00","retail":"25400.00","trade_in":"22300.00","private_party":"24600.00","updated_at":"2026-08-01T04:11:02.000Z"},"insurance":{"provider":"Progressive","policy_number":"PA-99881","monthly_cost":"142.00","deductible":"1000.00","renewal_date":"2027-01-15"},"recalls_last_checked":"2026-08-30T07:15:00.000Z","factory_maintenance_schedule":null,"vehicle_token":"v_8f2b1c9d4e","turo":{"listing_id":"1234567","vehicle_id":null,"link":null,"name":"Blue CR-V"},"image_url":null,"created_at":"2024-03-12T18:22:00.000Z","updated_at":"2026-08-30T07:15:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateVehicle","summary":"Update a vehicle","description":"Partial update; returns the full updated resource so a client never has to re-GET. Send `If-Match` with the ETag from your last read for optimistic concurrency — a stale value returns 412 instead of silently overwriting another editor.","tags":["Vehicles"],"security":[{"ApiKeyAuth":["vehicles:write"]}],"parameters":[{"name":"id","in":"path","description":"The vehicle's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Vehicle"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"vehicle","id":142,"vin":"7FARW2H58NE012345","make":"Honda","model":"CR-V","trim":"EX-L","year":2022,"color":"Blue","license_plate":"ABC-1234","status":"active","rental_status":"available","sort_order":3,"is_favorite":false,"current_mileage_mi":24500,"distance_unit":"mi","powertrain":"gas","engine_power_hp":"190","engine_displacement":"1.5L","purchase":{"type":"retail","price_usd":"28500.00","price_cad":"28500.00","fx_applied":false,"ownership_start_date":"2024-03-12"},"market_value":{"currency":"USD","blended":"24100.00","retail":"25400.00","trade_in":"22300.00","private_party":"24600.00","updated_at":"2026-08-01T04:11:02.000Z"},"insurance":{"provider":"Progressive","policy_number":"PA-99881","monthly_cost":"142.00","deductible":"1000.00","renewal_date":"2027-01-15"},"recalls_last_checked":"2026-08-30T07:15:00.000Z","factory_maintenance_schedule":null,"vehicle_token":"v_8f2b1c9d4e","turo":{"listing_id":"1234567","vehicle_id":null,"link":null,"name":"Blue CR-V"},"image_url":null,"created_at":"2024-03-12T18:22:00.000Z","updated_at":"2026-08-30T07:15:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"rental_status":"maintenance","sort_order":1,"market_value_retail":"25400.00"}}}}},"delete":{"operationId":"deleteVehicle","summary":"Delete a vehicle","description":"Soft delete — the vehicle moves to trash and its rentals, expenses and maintenance are retained. There is no API for a permanent delete, because that cascade erases the cost basis analytics is built from.","tags":["Vehicles"],"security":[{"ApiKeyAuth":["vehicles:write"]}],"parameters":[{"name":"id","in":"path","description":"The vehicle's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":142,"deleted":true,"restorable":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/rentals":{"get":{"operationId":"listRentals","summary":"List rentals","description":"`status` filters on the derived status (upcoming / active / completed / cancelled) — `rentals` has no status column, so this is the single definition the whole API uses.","tags":["Rentals"],"security":[{"ApiKeyAuth":["rentals:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"start_date","in":"query","description":"Inclusive lower bound on the rental start date. A `YYYY-MM-DD` value is a calendar date, not an instant.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"end_date","in":"query","description":"Inclusive upper bound on the rental start date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-12-31"},{"name":"vehicle_id","in":"query","description":"Only rentals on this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"customer_id","in":"query","description":"Only rentals for this customer.","required":false,"schema":{"type":"integer"}},{"name":"source","in":"query","description":"Channel: turo, outside, getaround, hyrecar, booking.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"upcoming, active, completed or cancelled.","required":false,"schema":{"type":"string","enum":["upcoming","active","completed","cancelled"]}},{"name":"search","in":"query","description":"Free text across renter name, email, phone and Turo reservation id.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Rental"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"rental","id":9001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"customer_id":305,"source":"turo","start_date":"2026-05-25","end_date":"2026-05-28","start_time":"14:00:00","end_time":"14:00:00","timezone":"America/New_York","amount":"420.50","currency":"USD","exchange_rate":"1.000000","fees":{"late_return_fee":"0.00","over_mileage_fee":"0.00","cleaning_fee":"0.00","fuel_fee":"0.00","damage_fee":"0.00"},"security_deposit":"0.00","deposit_status":"none","payment_status":"paid","mileage_start_mi":24500,"mileage_end_mi":24812,"renter":{"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199"},"notes":null,"is_cancelled":false,"cancelled_at":null,"cancellation_reason":null,"turo_reservation_id":"R-XYZ-12345","booking_reservation_id":null,"status":"completed","created_at":"2026-05-20T09:12:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createRental","summary":"Create a rental","description":"Records a rental. Emits `rental.created`.","tags":["Rentals"],"security":[{"ApiKeyAuth":["rentals:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Rental"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"rental","id":9001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"customer_id":305,"source":"turo","start_date":"2026-05-25","end_date":"2026-05-28","start_time":"14:00:00","end_time":"14:00:00","timezone":"America/New_York","amount":"420.50","currency":"USD","exchange_rate":"1.000000","fees":{"late_return_fee":"0.00","over_mileage_fee":"0.00","cleaning_fee":"0.00","fuel_fee":"0.00","damage_fee":"0.00"},"security_deposit":"0.00","deposit_status":"none","payment_status":"paid","mileage_start_mi":24500,"mileage_end_mi":24812,"renter":{"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199"},"notes":null,"is_cancelled":false,"cancelled_at":null,"cancellation_reason":null,"turo_reservation_id":"R-XYZ-12345","booking_reservation_id":null,"status":"completed","created_at":"2026-05-20T09:12:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"start_date":"2026-05-25","end_date":"2026-05-28","source":"outside","amount":"420.50","renter_name":"Jane Doe","renter_email":"jane@example.com","timezone":"America/New_York"}}}}}},"/v1/rentals/{id}":{"get":{"operationId":"getRental","summary":"Retrieve a rental","description":"`expand=turo` adds the Turo trip detail (rating, guest-message link); `expand=customer` adds the linked customer.","tags":["Rentals"],"security":[{"ApiKeyAuth":["rentals:read"]}],"parameters":[{"name":"id","in":"path","description":"The rental's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"expand","in":"query","description":"turo, customer.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Rental"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"rental","id":9001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"customer_id":305,"source":"turo","start_date":"2026-05-25","end_date":"2026-05-28","start_time":"14:00:00","end_time":"14:00:00","timezone":"America/New_York","amount":"420.50","currency":"USD","exchange_rate":"1.000000","fees":{"late_return_fee":"0.00","over_mileage_fee":"0.00","cleaning_fee":"0.00","fuel_fee":"0.00","damage_fee":"0.00"},"security_deposit":"0.00","deposit_status":"none","payment_status":"paid","mileage_start_mi":24500,"mileage_end_mi":24812,"renter":{"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199"},"notes":null,"is_cancelled":false,"cancelled_at":null,"cancellation_reason":null,"turo_reservation_id":"R-XYZ-12345","booking_reservation_id":null,"status":"completed","created_at":"2026-05-20T09:12:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateRental","summary":"Update a rental","description":"Partial update; returns the updated resource. Setting `is_cancelled` also emits `rental.cancelled`.","tags":["Rentals"],"security":[{"ApiKeyAuth":["rentals:write"]}],"parameters":[{"name":"id","in":"path","description":"The rental's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Rental"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"rental","id":9001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"customer_id":305,"source":"turo","start_date":"2026-05-25","end_date":"2026-05-28","start_time":"14:00:00","end_time":"14:00:00","timezone":"America/New_York","amount":"420.50","currency":"USD","exchange_rate":"1.000000","fees":{"late_return_fee":"0.00","over_mileage_fee":"0.00","cleaning_fee":"0.00","fuel_fee":"0.00","damage_fee":"0.00"},"security_deposit":"0.00","deposit_status":"none","payment_status":"paid","mileage_start_mi":24500,"mileage_end_mi":24812,"renter":{"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199"},"notes":null,"is_cancelled":false,"cancelled_at":null,"cancellation_reason":null,"turo_reservation_id":"R-XYZ-12345","booking_reservation_id":null,"status":"completed","created_at":"2026-05-20T09:12:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"payment_status":"paid","mileage_end_mi":24812}}}}},"delete":{"operationId":"deleteRental","summary":"Delete a rental","description":"Permanently deletes the rental and emits `rental.deleted`.","tags":["Rentals"],"security":[{"ApiKeyAuth":["rentals:write"]}],"parameters":[{"name":"id","in":"path","description":"The rental's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":9001,"deleted":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/customers":{"get":{"operationId":"listCustomers","summary":"List customers","description":"Reads the real customers table and pages it in SQL. Rental counts and lifetime spend are added with one grouped query over the page, never one query per row.","tags":["Customers"],"security":[{"ApiKeyAuth":["customers:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"search","in":"query","description":"Free text across name, email and phone.","required":false,"schema":{"type":"string"}},{"name":"is_blocked","in":"query","description":"Only blocked (or only unblocked) customers.","required":false,"schema":{"type":"boolean"}},{"name":"tag","in":"query","description":"Substring match on the tag list.","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","description":"manual, rental, turo or booking.","required":false,"schema":{"type":"string"}},{"name":"include_merged","in":"query","description":"Include records that were merged into another. Off by default.","required":false,"schema":{"type":"boolean"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"customer","id":305,"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199","notes":"Repeat guest, always early.","tags":["VIP"],"is_blocked":false,"blocked_reason":null,"license":{"number":"D1234567","state":"NY","expiry":"2029-04-30"},"date_of_birth":null,"address":null,"source":"rental","merged_into_id":null,"rental_count":7,"total_spend":"2940.00","created_at":"2025-01-08T11:00:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createCustomer","summary":"Create a customer","description":"A matching phone or email on an existing customer returns 409 with the existing id, rather than creating a duplicate.","tags":["Customers"],"security":[{"ApiKeyAuth":["customers:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Customer"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"customer","id":305,"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199","notes":"Repeat guest, always early.","tags":["VIP"],"is_blocked":false,"blocked_reason":null,"license":{"number":"D1234567","state":"NY","expiry":"2029-04-30"},"date_of_birth":null,"address":null,"source":"rental","merged_into_id":null,"rental_count":7,"total_spend":"2940.00","created_at":"2025-01-08T11:00:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199","tags":["VIP"]}}}}}},"/v1/customers/{id}":{"get":{"operationId":"getCustomer","summary":"Retrieve a customer","description":"`expand=rentals` includes their rental history.","tags":["Customers"],"security":[{"ApiKeyAuth":["customers:read"]}],"parameters":[{"name":"id","in":"path","description":"The customer's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"expand","in":"query","description":"rentals.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Customer"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"customer","id":305,"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199","notes":"Repeat guest, always early.","tags":["VIP"],"is_blocked":false,"blocked_reason":null,"license":{"number":"D1234567","state":"NY","expiry":"2029-04-30"},"date_of_birth":null,"address":null,"source":"rental","merged_into_id":null,"rental_count":7,"total_spend":"2940.00","created_at":"2025-01-08T11:00:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateCustomer","summary":"Update a customer","description":"Partial update. Setting `merged_into_id` performs a reversible soft merge.","tags":["Customers"],"security":[{"ApiKeyAuth":["customers:write"]}],"parameters":[{"name":"id","in":"path","description":"The customer's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Customer"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"customer","id":305,"name":"Jane Doe","email":"jane@example.com","phone":"+1 555 010 0199","notes":"Repeat guest, always early.","tags":["VIP"],"is_blocked":false,"blocked_reason":null,"license":{"number":"D1234567","state":"NY","expiry":"2029-04-30"},"date_of_birth":null,"address":null,"source":"rental","merged_into_id":null,"rental_count":7,"total_spend":"2940.00","created_at":"2025-01-08T11:00:00.000Z","updated_at":"2026-05-28T18:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Repeat guest, always early.","tags":["VIP","local"]}}}}},"delete":{"operationId":"deleteCustomer","summary":"Delete a customer","description":"Their rentals are retained and simply unlinked, so no revenue history is lost.","tags":["Customers"],"security":[{"ApiKeyAuth":["customers:write"]}],"parameters":[{"name":"id","in":"path","description":"The customer's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":305,"deleted":true,"rentals_retained":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/expenses":{"get":{"operationId":"listExpenses","summary":"List expenses","description":"Filter by vehicle, type, category, recurrence, amount range and date range. `meta.totals` carries the DECIMAL sum for the same filter.","tags":["Expenses"],"security":[{"ApiKeyAuth":["expenses:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"start_date","in":"query","description":"Inclusive lower bound on the expense date. A `YYYY-MM-DD` value is a calendar date, not an instant.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"end_date","in":"query","description":"Inclusive upper bound on the expense date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-12-31"},{"name":"vehicle_id","in":"query","description":"Only this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"expense_type","in":"query","description":"Maintenance, Insurance, Fuel, Registration, Cleaning, Parking, Tolls, Depreciation, Other.","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","description":"Parallel category column; the product treats it as a synonym of expense_type today.","required":false,"schema":{"type":"string"}},{"name":"is_recurring","in":"query","description":"Only rows flagged recurring.","required":false,"schema":{"type":"boolean"}},{"name":"min_amount","in":"query","description":"Inclusive lower bound.","required":false,"schema":{"type":"string"}},{"name":"max_amount","in":"query","description":"Inclusive upper bound.","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","description":"Substring match on description.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Expense"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"expense","id":5512,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"expense_type":"Fuel","category":"Fuel","description":"Top-up before delivery","amount":"58.40","currency":"USD","expense_date":"2026-05-24","is_recurring":false,"invoice_image":null,"created_at":"2026-05-24T16:02:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createExpense","summary":"Create an expense","description":"Records an expense against a vehicle.","tags":["Expenses"],"security":[{"ApiKeyAuth":["expenses:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Expense"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"expense","id":5512,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"expense_type":"Fuel","category":"Fuel","description":"Top-up before delivery","amount":"58.40","currency":"USD","expense_date":"2026-05-24","is_recurring":false,"invoice_image":null,"created_at":"2026-05-24T16:02:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"expense_type":"Fuel","amount":"58.40","expense_date":"2026-05-24","description":"Top-up before delivery"}}}}}},"/v1/expenses/{id}":{"get":{"operationId":"getExpense","summary":"Retrieve an expense","description":"One expense.","tags":["Expenses"],"security":[{"ApiKeyAuth":["expenses:read"]}],"parameters":[{"name":"id","in":"path","description":"The expense's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Expense"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"expense","id":5512,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"expense_type":"Fuel","category":"Fuel","description":"Top-up before delivery","amount":"58.40","currency":"USD","expense_date":"2026-05-24","is_recurring":false,"invoice_image":null,"created_at":"2026-05-24T16:02:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateExpense","summary":"Update an expense","description":"Partial update; returns the updated resource.","tags":["Expenses"],"security":[{"ApiKeyAuth":["expenses:write"]}],"parameters":[{"name":"id","in":"path","description":"The expense's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Expense"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"expense","id":5512,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"expense_type":"Fuel","category":"Fuel","description":"Top-up before delivery","amount":"58.40","currency":"USD","expense_date":"2026-05-24","is_recurring":false,"invoice_image":null,"created_at":"2026-05-24T16:02:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":"61.00","category":"Fuel"}}}}},"delete":{"operationId":"deleteExpense","summary":"Delete an expense","description":"Permanently deletes the expense.","tags":["Expenses"],"security":[{"ApiKeyAuth":["expenses:write"]}],"parameters":[{"name":"id","in":"path","description":"The expense's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":5512,"deleted":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/maintenance":{"get":{"operationId":"listMaintenance","summary":"List maintenance records","description":"Service history with line items. `limit` is capped at 100 (the dashboard endpoint this replaces accepted `limit=100000`).","tags":["Maintenance"],"security":[{"ApiKeyAuth":["maintenance:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"start_date","in":"query","description":"Inclusive lower bound on the service date. A `YYYY-MM-DD` value is a calendar date, not an instant.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"end_date","in":"query","description":"Inclusive upper bound on the service date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-12-31"},{"name":"vehicle_id","in":"query","description":"Only this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"maintenance_type","in":"query","description":"Substring match on the service type.","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","description":"Substring match on description.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Maintenance"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"maintenance","id":412,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"maintenance_type":"Oil Change","description":"Synthetic oil + filter","cost":"78.50","currency":"USD","mileage_mi":25000,"performed_at":"2026-05-24T10:30:00.000Z","invoice_image":null,"items":[{"id":900,"item_type":"Oil","description":"0W-20 synthetic","cost":"48.50"}],"created_at":"2026-05-24T10:35:00.000Z","updated_at":"2026-05-24T10:35:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createMaintenance","summary":"Create a maintenance record","description":"At least one line item is required; the record cost is the sum of its items.","tags":["Maintenance"],"security":[{"ApiKeyAuth":["maintenance:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Maintenance"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"maintenance","id":412,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"maintenance_type":"Oil Change","description":"Synthetic oil + filter","cost":"78.50","currency":"USD","mileage_mi":25000,"performed_at":"2026-05-24T10:30:00.000Z","invoice_image":null,"items":[{"id":900,"item_type":"Oil","description":"0W-20 synthetic","cost":"48.50"}],"created_at":"2026-05-24T10:35:00.000Z","updated_at":"2026-05-24T10:35:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"maintenance_type":"Oil Change","performed_at":"2026-05-24T10:30:00Z","mileage_mi":25000,"items":[{"item_type":"Oil","description":"0W-20 synthetic","cost":"48.50"}]}}}}}},"/v1/maintenance/{id}":{"get":{"operationId":"getMaintenance","summary":"Retrieve a maintenance record","description":"One record with its line items.","tags":["Maintenance"],"security":[{"ApiKeyAuth":["maintenance:read"]}],"parameters":[{"name":"id","in":"path","description":"The maintenance record's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Maintenance"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"maintenance","id":412,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"maintenance_type":"Oil Change","description":"Synthetic oil + filter","cost":"78.50","currency":"USD","mileage_mi":25000,"performed_at":"2026-05-24T10:30:00.000Z","invoice_image":null,"items":[{"id":900,"item_type":"Oil","description":"0W-20 synthetic","cost":"48.50"}],"created_at":"2026-05-24T10:35:00.000Z","updated_at":"2026-05-24T10:35:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateMaintenance","summary":"Update a maintenance record","description":"Sending `items` replaces the whole line-item set.","tags":["Maintenance"],"security":[{"ApiKeyAuth":["maintenance:write"]}],"parameters":[{"name":"id","in":"path","description":"The maintenance record's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Maintenance"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"maintenance","id":412,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"maintenance_type":"Oil Change","description":"Synthetic oil + filter","cost":"78.50","currency":"USD","mileage_mi":25000,"performed_at":"2026-05-24T10:30:00.000Z","invoice_image":null,"items":[{"id":900,"item_type":"Oil","description":"0W-20 synthetic","cost":"48.50"}],"created_at":"2026-05-24T10:35:00.000Z","updated_at":"2026-05-24T10:35:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"item_type":"Oil","cost":"48.50"},{"item_type":"Tire rotation","cost":"40.00"}]}}}}},"delete":{"operationId":"deleteMaintenance","summary":"Delete a maintenance record","description":"Permanently deletes the record and its line items.","tags":["Maintenance"],"security":[{"ApiKeyAuth":["maintenance:write"]}],"parameters":[{"name":"id","in":"path","description":"The maintenance record's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":412,"deleted":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/documents":{"get":{"operationId":"listDocuments","summary":"List vehicle documents","description":"`expiring_within_days` is the compliance query: everything with an expiry inside the window.","tags":["Documents"],"security":[{"ApiKeyAuth":["documents:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"vehicle_id","in":"query","description":"Only this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"document_type","in":"query","description":"Exact document type.","required":false,"schema":{"type":"string"}},{"name":"expiring_within_days","in":"query","description":"Documents expiring within N days.","required":false,"schema":{"type":"integer"},"example":30},{"name":"expires_before","in":"query","description":"Documents expiring on or before this calendar date.","required":false,"schema":{"type":"string","format":"date"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"document","id":1001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"document_type":"registration","name":"NY registration 2026","file_path":"documents/142/registration-2026.pdf","file_size":214233,"expires_at":"2027-01-31","days_until_expiry":149,"uploaded_at":"2026-02-01T09:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createDocument","summary":"Register a document","description":"Registers a stored file against a vehicle and emits `document.uploaded`. Upload the bytes first; this records the metadata and the expiry the reminder cron reads.","tags":["Documents"],"security":[{"ApiKeyAuth":["documents:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Document"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"document","id":1001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"document_type":"registration","name":"NY registration 2026","file_path":"documents/142/registration-2026.pdf","file_size":214233,"expires_at":"2027-01-31","days_until_expiry":149,"uploaded_at":"2026-02-01T09:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"document_type":"registration","name":"NY registration 2026","file_path":"documents/142/registration-2026.pdf","expires_at":"2027-01-31"}}}}}},"/v1/documents/{id}":{"get":{"operationId":"getDocument","summary":"Retrieve a document","description":"One document.","tags":["Documents"],"security":[{"ApiKeyAuth":["documents:read"]}],"parameters":[{"name":"id","in":"path","description":"The document's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Document"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"document","id":1001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"document_type":"registration","name":"NY registration 2026","file_path":"documents/142/registration-2026.pdf","file_size":214233,"expires_at":"2027-01-31","days_until_expiry":149,"uploaded_at":"2026-02-01T09:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateDocument","summary":"Update a document","description":"Rename, retype, or set/clear the expiry.","tags":["Documents"],"security":[{"ApiKeyAuth":["documents:write"]}],"parameters":[{"name":"id","in":"path","description":"The document's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Document"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"document","id":1001,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"document_type":"registration","name":"NY registration 2026","file_path":"documents/142/registration-2026.pdf","file_size":214233,"expires_at":"2027-01-31","days_until_expiry":149,"uploaded_at":"2026-02-01T09:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"expires_at":"2028-01-31"}}}}},"delete":{"operationId":"deleteDocument","summary":"Delete a document","description":"Removes the metadata row and emits `document.deleted`.","tags":["Documents"],"security":[{"ApiKeyAuth":["documents:write"]}],"parameters":[{"name":"id","in":"path","description":"The document's id.","required":true,"schema":{"type":"integer"},"example":142},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":1001,"deleted":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/income":{"get":{"operationId":"listIncome","summary":"List one-time income","description":"Non-rental income with the cost and profit columns that were stored but never returned.","tags":["Income"],"security":[{"ApiKeyAuth":["income:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"start_date","in":"query","description":"Inclusive lower bound on the income date. A `YYYY-MM-DD` value is a calendar date, not an instant.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"end_date","in":"query","description":"Inclusive upper bound on the income date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-12-31"},{"name":"vehicle_id","in":"query","description":"Only this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"income_type","in":"query","description":"Exact income type.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"received or pending.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Income"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"income","id":77,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"income_type":"Delivery fee","description":"Airport delivery","amount_received":"75.00","cost_incurred":"20.00","profit":"55.00","currency":"USD","status":"received","income_date":"2026-05-25","created_at":"2026-05-25T12:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createIncome","summary":"Create an income entry","description":"`profit` is derived as `amount_received - cost_incurred` in decimal arithmetic.","tags":["Income"],"security":[{"ApiKeyAuth":["income:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Income"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"income","id":77,"vehicle_id":142,"vehicle":{"id":142,"label":"2022 Honda CR-V","make":"Honda","model":"CR-V","year":2022,"license_plate":"ABC-1234"},"income_type":"Delivery fee","description":"Airport delivery","amount_received":"75.00","cost_incurred":"20.00","profit":"55.00","currency":"USD","status":"received","income_date":"2026-05-25","created_at":"2026-05-25T12:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"income_type":"Delivery fee","amount_received":"75.00","cost_incurred":"20.00","income_date":"2026-05-25"}}}}}},"/v1/mileage":{"get":{"operationId":"listMileage","summary":"List odometer readings","description":"Includes provenance (`source`, `source_id`, `notes`) so a reading from Tesla can be told from one typed in by hand.","tags":["Mileage"],"security":[{"ApiKeyAuth":["mileage:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"start_date","in":"query","description":"Inclusive lower bound on the reading date. A `YYYY-MM-DD` value is a calendar date, not an instant.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"end_date","in":"query","description":"Inclusive upper bound on the reading date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-12-31"},{"name":"vehicle_id","in":"query","description":"Only this vehicle.","required":false,"schema":{"type":"integer"}},{"name":"source","in":"query","description":"Exact provenance, e.g. tesla, rental, manual, api.","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MileageReading"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"mileage_reading","id":8801,"vehicle_id":142,"mileage_mi":24812,"recorded_date":"2026-05-28","source":"rental","source_id":9001,"notes":"Odometer at return","created_at":"2026-05-28T18:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createMileageReading","summary":"Append an odometer reading","description":"Also rolls the vehicle's current odometer forward when the reading is higher, and emits `vehicle.mileage_updated`.","tags":["Mileage"],"security":[{"ApiKeyAuth":["mileage:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/MileageReading"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"mileage_reading","id":8801,"vehicle_id":142,"mileage_mi":24812,"recorded_date":"2026-05-28","source":"rental","source_id":9001,"notes":"Odometer at return","created_at":"2026-05-28T18:00:00.000Z","vehicle_odometer_updated":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vehicle_id":142,"mileage_mi":24812,"recorded_date":"2026-05-28","source":"manual","notes":"Odometer at return"}}}}}},"/v1/account":{"get":{"operationId":"getAccount","summary":"Retrieve the account","description":"Who this credential belongs to, its granted scopes, the account locale settings every other response is rendered against, and the fleet counts.","tags":["Account"],"security":[{"ApiKeyAuth":["read"]}],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"account","id":122,"email":"host@example.com","name":"Patrick O.","locale":{"timezone":"Europe/Paris","currency":"USD","language":"en","distance_unit":"mi"},"credential":{"type":"api_key","key_prefix":"fio_live_ab12cd34","environment":"live","scopes":["read"]},"counts":{"vehicles":12,"rentals":431,"customers":208}},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/events":{"get":{"operationId":"listEvents","summary":"List account events","description":"The polling counterpart to webhooks: every event emitted for this account, newest first. Integrations that cannot receive an inbound HTTP call (Zapier polling triggers, a laptop behind NAT) read this instead.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:read"]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1–100. Values above 100 are capped rather than rejected; the applied value is echoed in `meta.pagination.limit`.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":50},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's `meta.pagination.next_cursor`. Cursor paging is stable while rows are being inserted; offset paging is not.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Offset page number (1-based). Mutually exclusive with `cursor`. Using `page` also returns a `total`, at the cost of a COUNT.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"sort","in":"query","description":"Field to sort by. Allowlisted per resource; an unsupported value returns `unsupported_sort` with the supported list.","required":false,"schema":{"type":"string"}},{"name":"order","in":"query","description":"Sort direction.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"fields","in":"query","description":"Sparse fieldset: comma-separated top-level fields to return. `id` is always included.","required":false,"schema":{"type":"string"},"example":"make,model,year"},{"name":"ids","in":"query","description":"Multi-get: comma-separated ids, max 100. Returns them in the order given, plus a `meta.missing` list — so a client never fans out one request per row.","required":false,"schema":{"type":"string"},"example":"12,44,91"},{"name":"timezone","in":"query","description":"IANA timezone that date-only filters are interpreted in. Defaults to the account timezone, then UTC. Echoed in `meta.timezone`.","required":false,"schema":{"type":"string"},"example":"Europe/Paris"},{"name":"event_type","in":"query","description":"Exact event type, e.g. rental.created.","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","description":"Only events created after this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"event","id":"evt_4f2a9c1b8e7d","type":"rental.created","created_at":"2026-05-20T09:12:00.000Z","data":{"rental":{"id":9001,"vehicle_id":142}}}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/webhook-event-types":{"get":{"operationId":"listWebhookEventTypes","summary":"List webhook event types","description":"The event catalogue, each entry marked `live` (emitted today) or `planned` (documented, not yet emitted). Only `live` types can be subscribed to.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:read"]}],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"webhook_event_type","id":"rental.created","resource":"rental","status":"live","description":"A rental was created."}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/webhooks/endpoints":{"get":{"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","description":"The delivery URLs registered on this account, with their health counters.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:read"]}],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"object":"webhook_endpoint","id":12,"name":"Zapier","url":"https://hooks.zapier.com/hooks/catch/1/abcd/","events":["rental.created","rental.completed"],"is_active":true,"consecutive_failures":0,"last_success_at":"2026-09-03T18:02:00.000Z","created_at":"2026-06-01T10:00:00.000Z"}],"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"post":{"operationId":"createWebhookEndpoint","summary":"Register a webhook endpoint","description":"Registers a delivery URL. The `signing_secret` is returned ONCE, here — verify every delivery's `X-Fleetiqo-Signature` with it. Events that are documented but not yet emitted are refused, so a connector cannot subscribe to something that will never arrive.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"201":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"webhook_endpoint","id":12,"name":"Zapier","url":"https://hooks.zapier.com/hooks/catch/1/abcd/","events":["rental.created","rental.completed"],"is_active":true,"signing_secret":"whsec_…shown once…","created_at":"2026-09-04T10:00:00.000Z"},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Zapier","url":"https://hooks.zapier.com/hooks/catch/1/abcd/","events":["rental.created","rental.completed"]}}}}}},"/v1/webhooks/endpoints/{id}":{"get":{"operationId":"getWebhookEndpoint","summary":"Retrieve a webhook endpoint","description":"One endpoint. The signing secret is never returned here — only at creation.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:read"]}],"parameters":[{"name":"id","in":"path","description":"The endpoint's id.","required":true,"schema":{"type":"integer"},"example":12},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"webhook_endpoint","id":12,"name":"Zapier","is_active":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}},"patch":{"operationId":"updateWebhookEndpoint","summary":"Update a webhook endpoint","description":"Change the URL, the subscribed events, or re-enable an endpoint that auto-disabled after repeated failures (which also resets its failure counter).","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:write"]}],"parameters":[{"name":"id","in":"path","description":"The endpoint's id.","required":true,"schema":{"type":"integer"},"example":12},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"object":"webhook_endpoint","id":12,"is_active":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"events":["rental.created","rental.completed","maintenance.overdue"],"is_active":true}}}}},"delete":{"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"Removes the endpoint and stops all delivery to it. This is what a connector calls when a user disconnects it.","tags":["Events"],"security":[{"ApiKeyAuth":["webhooks:write"]}],"parameters":[{"name":"id","in":"path","description":"The endpoint's id.","required":true,"schema":{"type":"integer"},"example":12},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":12,"deleted":true},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/batch":{"post":{"operationId":"batchWrite","summary":"Bulk update or delete","description":"Applies one operation to up to 100 rows of one resource in a single transaction. Bulk endpoints previously existed only for vehicles and rentals, and only for status changes — this covers expenses, maintenance, income, documents, customers, rentals and vehicles, for both edit and delete.","tags":["Batch"],"security":[{"ApiKeyAuth":["write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"resource":"expenses","operation":"update","requested":3,"affected":3,"skipped":[]},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"resource":"expenses","operation":"update","ids":[5512,5513,5514],"data":{"category":"Fuel"}}}}}}},"/v1/exports/{resource}":{"get":{"operationId":"exportResource","summary":"Export a resource","description":"Server-side export of the full filtered dataset as CSV or JSON — not just the page the browser happens to have loaded. Accepts the same filters as the matching list endpoint. Cells are RFC-4180 quoted and formula-guarded.","tags":["Exports"],"security":[{"ApiKeyAuth":["exports:read"]}],"parameters":[{"name":"resource","in":"path","description":"vehicles, rentals, customers, expenses, maintenance, documents, income or mileage.","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"csv (default) or json.","required":false,"schema":{"type":"string","enum":["csv","json"]}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":"id,vin,make,model,year\n142,7FARW2H58NE012345,Honda,CR-V,2022","meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/exports/account":{"get":{"operationId":"exportAccount","summary":"Export the whole account","description":"Every fleet record this API can reach, in one JSON document, with a manifest naming what is included and what is deliberately not. This is the machine-readable data-portability export.","tags":["Exports"],"security":[{"ApiKeyAuth":["exports:read"]}],"parameters":[{"name":"format","in":"query","description":"json (default) or csv (a ZIP-less concatenation is not offered; use per-resource CSV).","required":false,"schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag from a previous read. A match returns 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"manifest":{"generated_at":"2026-09-04T10:00:00.000Z","resources":["vehicles","rentals"],"record_counts":{"vehicles":12}}},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"304":{"description":"Not modified — your ETag is still current."},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}},"/v1/imports/{resource}":{"post":{"operationId":"importResource","summary":"Import a CSV","description":"multipart/form-data CSV import for vehicles, customers or expenses. `dry_run=true` parses, maps, validates and duplicate-checks every row and reports exactly what a commit WOULD do, without writing anything — the same two-modes-one-code-path design as the rentals importer, so a preview can never disagree with the import that follows it.","tags":["Imports"],"security":[{"ApiKeyAuth":["imports:write"]}],"parameters":[{"name":"resource","in":"path","description":"vehicles, customers or expenses.","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-generated key (a UUID is ideal). Replays the original response on retry; a different body under the same key is a 409.","schema":{"type":"string","maxLength":255}},{"name":"If-Match","in":"header","required":false,"description":"ETag from your last read. A stale value returns 412 instead of overwriting a concurrent edit.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","headers":{"ETag":{"description":"Strong entity tag; send back as If-None-Match or If-Match.","schema":{"type":"string"}},"X-Request-Id":{"description":"Correlation id, also present in the body as meta.request_id.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"description":"Endpoint-specific payload."},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"dry_run":true,"total":30,"valid":28,"invalid":1,"duplicates":1,"headers":["VIN","Make","Model","Year"],"mapping":{"VIN":"vin","Make":"make","Model":"model","Year":"year"},"rows":[{"row":4,"status":"error","message":"year: must be between 1900 and 2100"}]},"meta":{"request_id":"req_9fk2j3l1","api_version":"2026-09-01"}}}}},"400":{"description":"The request was malformed or failed validation. `error.details.issues` names every offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"401":{"description":"Missing, malformed, unknown, revoked or expired credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"403":{"description":"The key lacks the required scope, or is a test key attempting a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"404":{"description":"No such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"409":{"description":"A uniqueness constraint, or an Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"412":{"description":"The If-Match ETag is stale; re-read, merge and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"precondition_failed","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"429":{"description":"Rate limit exceeded. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}},"500":{"description":"Unexpected failure. Quote `request_id` when reporting it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Human-readable explanation."},"request_id":"req_9fk2j3l1"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"fio_live_… / fio_test_…","description":"An API key from Settings → API keys, sent as a bearer token. Scopes are attached to the key, not negotiated per request."}},"schemas":{"Meta":{"type":"object","description":"Present on every success response.","properties":{"request_id":{"type":"string","description":"Correlation id; also returned as X-Request-Id."},"api_version":{"type":"string"},"currency":{"type":"string","description":"ISO-4217 code every money field in this response is denominated in."},"timezone":{"type":"string","description":"IANA zone the date filters were interpreted in."},"pagination":{"type":"object","properties":{"mode":{"type":"string","enum":["cursor","offset"]},"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"page":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}}}},"Error":{"type":"object","required":["error","request_id"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_request","validation_failed","invalid_cursor","invalid_date_range","unsupported_sort","unsupported_filter","unauthorized","invalid_api_key","expired_api_key","revoked_api_key","insufficient_scope","forbidden","test_key_write_denied","subscription_required","not_found","method_not_allowed","conflict","idempotency_key_reused","version_conflict","precondition_failed","payload_too_large","unsupported_media_type","unprocessable_entity","rate_limited","internal_error","service_unavailable"]},"message":{"type":"string"},"field":{"type":"string","description":"The offending input, when the failure is attributable to one."},"details":{"description":"Structured context — for validation failures, the per-field issue list."}}},"request_id":{"type":"string"},"documentation_url":{"type":"string"}}},"Money":{"type":"string","description":"A monetary amount with exactly 2 decimal places, as a string. Never a JSON number: a float cannot round-trip every DECIMAL(10,2) value.","pattern":"^-?\\d+\\.\\d{2}$","examples":["420.50","0.00"]},"Vehicle":{"type":"object","description":"A vehicle in the fleet.","additionalProperties":true,"properties":{"object":{"type":"string","const":"vehicle"},"id":{"type":"integer"}}},"Rental":{"type":"object","description":"A rental from any channel (manual, Turo, booking site).","additionalProperties":true,"properties":{"object":{"type":"string","const":"rental"},"id":{"type":"integer"}}},"Customer":{"type":"object","description":"A renter.","additionalProperties":true,"properties":{"object":{"type":"string","const":"customer"},"id":{"type":"integer"}}},"Expense":{"type":"object","description":"A cost recorded against a vehicle.","additionalProperties":true,"properties":{"object":{"type":"string","const":"expense"},"id":{"type":"integer"}}},"Maintenance":{"type":"object","description":"A service record with line items.","additionalProperties":true,"properties":{"object":{"type":"string","const":"maintenance"},"id":{"type":"integer"}}},"Document":{"type":"object","description":"Vehicle paperwork, optionally with an expiry.","additionalProperties":true,"properties":{"object":{"type":"string","const":"document"},"id":{"type":"integer"}}},"Income":{"type":"object","description":"Non-rental income against a vehicle.","additionalProperties":true,"properties":{"object":{"type":"string","const":"income"},"id":{"type":"integer"}}},"MileageReading":{"type":"object","description":"One odometer reading with its provenance.","additionalProperties":true,"properties":{"object":{"type":"string","const":"mileagereading"},"id":{"type":"integer"}}}}},"x-scopes":{"read":"Read every resource this key can see.","write":"Read and write every resource (full access).","vehicles:read":"List and read vehicles.","vehicles:write":"Create, update, archive and delete vehicles.","rentals:read":"List and read rentals.","rentals:write":"Create, update and delete rentals.","customers:read":"List and read customers.","customers:write":"Create, update and delete customers.","expenses:read":"List and read expenses.","expenses:write":"Create, update and delete expenses.","maintenance:read":"List and read maintenance records.","maintenance:write":"Create, update and delete maintenance records.","documents:read":"List document metadata and request download links.","documents:write":"Attach and delete vehicle documents.","income:read":"List and read one-time income entries.","income:write":"Create, update and delete income entries.","mileage:read":"Read odometer history.","mileage:write":"Append odometer readings.","webhooks:read":"List webhook endpoints and delivery attempts.","webhooks:write":"Create, update and delete webhook endpoints.","exports:read":"Run bulk exports and the full account export.","imports:write":"Run CSV imports."}}