{"openapi":"3.1.0","info":{"title":"FleetOps Public Customer API","description":"Programmatic access for FleetOps customers: estimate a lane, book transport, and track shipments and proof of delivery.\n\n**Authentication.** Every endpoint requires a per-customer API key, presented as `Authorization: Bearer fop_…` or in the `X-API-Key` header. A key only ever sees its own customer's data.\n\n**Response envelope.** Every 2xx JSON response is wrapped as `{ \"status\", \"message\", \"data\" }`. The schema shown for each response describes the `data` field; `status` echoes the HTTP status and `message` is a short human-readable note.\n\n**Caching.** Read endpoints return an `ETag` and honour `If-None-Match`, answering `304 Not Modified` when nothing changed — poll them cheaply.\n\n**Rate limiting.** Responses carry `X-RateLimit-Limit`/`X-RateLimit-Remaining`; exceeding the budget is `429`. Estimates cost more than reads and less than list scans.\n\n**Sandbox.** A `fop_test_…` key reads a fixed, documented catalogue of fixture shipments (references `SANDBOX-BOOKED`, `SANDBOX-PLANNED`, `SANDBOX-IN-TRANSIT`, `SANDBOX-REEFER`, `SANDBOX-HAZMAT`, `SANDBOX-DELIVERED`) instead of live orders, so a test suite is deterministic and never touches real operations. One extra fixture, `SANDBOX-PROGRESSING`, advances up the milestone ladder on a fixed 30-second-per-rung cadence (booked → planned → in_transit → delivered, then repeats) so you can watch a shipment move without waiting on real operations; its state is a pure function of the clock, so it is deterministic at any given instant. Writes are canned too: a sandbox booking comes back `pending_acceptance` under reference `SANDBOX-BOOKING` without creating an order or consuming credit, and a sandbox estimate returns a flat priced answer needing no contract — neither is persisted. The delivered fixture (`SANDBOX-DELIVERED`) also carries a canned proof-of-delivery bundle whose evidence downloads stream a placeholder image; every other fixture 404s its POD, exactly as a live shipment does until it is signed for.\n\n**Webhooks.** Optionally, register webhook subscriptions to have events pushed instead of polled — polling always remains sufficient. Every delivery is a POST with an `X-FleetOps-Signature: t=<unix>,v1=<hex>` header, where `<hex>` is HMAC-SHA256 over `\"{t}.{body}\"` keyed with the subscription's signing secret (returned once, at creation): recompute it and compare, and reject stale `t` values by your own clock. Delivery is at-least-once with exponential-backoff retries; deduplicate on the payload's `id`, which is stable across retries and replays. Failed-out deliveries stay visible on the deliveries listing and can be requeued via the replay endpoint. Webhooks are live-keys-only: the sandbox emits no events.","contact":{"name":"Markus Laubscher","email":"markus@grootwebdev.com"},"license":{"name":"MIT","identifier":"MIT"},"version":"1.0.0"},"servers":[{"url":"/","description":"The API host serving this document"}],"paths":{"/public/v1/account":{"get":{"tags":["account"],"summary":"`GET /public/v1/account` — the account behind the presented key. Lets an\nintegration confirm which customer its key is bound to and whether it is in\nsandbox mode. Proves the key→customer binding end-to-end; the customer id is\ntaken from the verified [`ApiKeyContext`], never from the request.","operationId":"public_account_handler","responses":{"200":{"description":"The account bound to the presented API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccount"}}}},"401":{"description":"Missing or invalid API key"}}}},"/public/v1/bookings":{"post":{"tags":["bookings"],"summary":"`POST /public/v1/bookings`. `201 Created` for a booking this request made,\n`200 OK` when an earlier request with the same `Idempotency-Key` already made\nit, `409 Conflict` when that key was used for a materially different request.","operationId":"create_booking_handler","parameters":[{"name":"Idempotency-Key","in":"header","description":"Required. A retry with the same key returns the original booking rather than creating a second one","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingRequest"}}},"required":true},"responses":{"200":{"description":"Idempotent replay of an earlier identical request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBooking"}}}},"201":{"description":"Booking received and awaiting acceptance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBooking"}}}},"400":{"description":"Missing Idempotency-Key header or an invalid body"},"401":{"description":"Missing or invalid API key"},"409":{"description":"Idempotency-Key reused for a materially different request"}}}},"/public/v1/bookings/{reference}":{"get":{"tags":["bookings"],"summary":"`GET /public/v1/bookings/{reference}` — acceptance state and requested windows.\nUnknown *and* someone else's references are both `404`, so a caller cannot use\nthis endpoint to discover that another customer's booking exists.","operationId":"get_booking_handler","parameters":[{"name":"reference","in":"path","description":"FleetOps booking reference","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Booking with its acceptance state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBooking"}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"404":{"description":"Unknown reference, or one belonging to another customer"}}}},"/public/v1/estimates":{"post":{"tags":["estimates"],"summary":"`POST /public/v1/estimates`. `201 Created` with the priced breakdown and its\nvalidity; `400` explains exactly what was missing (no contract, uncovered\nlane, a per-ton lane without `mass_kg`, …).","operationId":"create_estimate_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEstimateRequest"}}},"required":true},"responses":{"201":{"description":"Priced estimate with its validity date","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEstimate"}}}},"400":{"description":"No active contract, an uncovered lane, or a cargo detail the rate basis requires (e.g. a per-ton lane without mass_kg)"},"401":{"description":"Missing or invalid API key"},"429":{"description":"Rate limit exceeded"}}}},"/public/v1/estimates/{id}":{"get":{"tags":["estimates"],"summary":"`GET /public/v1/estimates/{id}` — re-read a priced estimate. Unknown and\nforeign ids are both `404` (object scoping, doc 10 §2).","operationId":"get_estimate_handler","parameters":[{"name":"id","in":"path","description":"Estimate id returned at creation","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The priced estimate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEstimate"}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"404":{"description":"Unknown id, or one belonging to another customer"}}}},"/public/v1/shipments":{"get":{"tags":["shipments"],"operationId":"list_shipments_handler","parameters":[{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"}},{"name":"customer_reference","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The customer's shipments, newest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicShipment"}}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"}}}},"/public/v1/shipments/{reference}":{"get":{"tags":["shipments"],"operationId":"get_shipment_handler","parameters":[{"name":"reference","in":"path","description":"FleetOps shipment reference","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Shipment detail with the milestone timeline","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicShipmentDetail"}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"404":{"description":"Unknown reference, or one belonging to another customer"}}}},"/public/v1/shipments/{reference}/pod":{"get":{"tags":["proof-of-delivery"],"operationId":"get_pod_bundle_handler","parameters":[{"name":"reference","in":"path","description":"FleetOps shipment reference","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The proof-of-delivery bundle for the shipment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPodBundle"}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"404":{"description":"No POD captured yet (poll until delivered), or a shipment belonging to another customer"}}}},"/public/v1/shipments/{reference}/pod/documents/{pod_id}/{slot}":{"get":{"tags":["proof-of-delivery"],"summary":"Stream one piece of POD evidence. `slot` is `signature` or `photo-{n}` — an\nunparseable slot is a 404, never an index into the key list.","operationId":"download_pod_document_handler","parameters":[{"name":"reference","in":"path","description":"FleetOps shipment reference","required":true,"schema":{"type":"string"}},{"name":"pod_id","in":"path","description":"Consignment POD id taken from the bundle","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"slot","in":"path","description":"Evidence slot: 'signature' or 'photo-{n}'","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evidence bytes, streamed as an attachment","content":{"application/octet-stream":{}}},"404":{"description":"Unknown slot or POD, or a shipment belonging to another customer"}}}},"/public/v1/webhooks":{"get":{"tags":["webhooks"],"summary":"`GET /public/v1/webhooks` — the key's subscriptions (without secrets).","operationId":"list_webhooks_handler","responses":{"200":{"description":"Subscriptions registered on this key","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicWebhook"}}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"400":{"description":"Sandbox key (webhooks are live-only)"},"401":{"description":"Missing or invalid API key"}}},"post":{"tags":["webhooks"],"summary":"`POST /public/v1/webhooks`. The response is the only time the signing\nsecret is ever revealed.","operationId":"create_webhook_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"201":{"description":"Subscription created; `secret` is shown this once only","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhook"}}}},"400":{"description":"Invalid url or event list, or a sandbox key (the sandbox emits no events)"},"401":{"description":"Missing or invalid API key"}}}},"/public/v1/webhooks/deliveries/{id}/replay":{"post":{"tags":["webhooks"],"summary":"`POST /public/v1/webhooks/deliveries/{id}/replay` — requeue a delivery (any\nstate, dead included) as a fresh pending attempt. The event keeps its `id`,\nso a receiver that already got it deduplicates the replay.","operationId":"replay_webhook_delivery_handler","parameters":[{"name":"id","in":"path","description":"Delivery id (from the deliveries listing)","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delivery requeued; the next dispatch sweep sends it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicWebhookDelivery"}}}},"404":{"description":"Unknown id, or one belonging to another key"}}}},"/public/v1/webhooks/{id}":{"delete":{"tags":["webhooks"],"summary":"`DELETE /public/v1/webhooks/{id}`. Unknown and someone else's ids are both\n`404` — a key cannot probe for other customers' subscriptions.","operationId":"delete_webhook_handler","parameters":[{"name":"id","in":"path","description":"Subscription id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Subscription removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicWebhook"}}}},"404":{"description":"Unknown id, or one belonging to another key"}}}},"/public/v1/webhooks/{id}/deliveries":{"get":{"tags":["webhooks"],"summary":"`GET /public/v1/webhooks/{id}/deliveries` — recent delivery attempts,\nnewest first: the dead-letter visibility view (doc 10 §4).","operationId":"list_webhook_deliveries_handler","parameters":[{"name":"id","in":"path","description":"Subscription id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Recent deliveries, newest first (max 100)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicWebhookDelivery"}}}}},"304":{"description":"Not modified — matched the caller's If-None-Match"},"404":{"description":"Unknown id, or one belonging to another key"}}}}},"components":{"schemas":{"CreateBookingRequest":{"type":"object","description":"`POST /public/v1/bookings` — a request for transport, not an accepted order\n(doc 10 §3). The customer is never named in the body: it is taken from the\nverified API key, so a key cannot book on another customer's account.\n\nSites are referenced by id and **must belong to the calling customer** —\nverified in the repository, inside the booking transaction.\n\nSerialized as-is into `public_idempotency_keys.request_body`, so field order\nand defaults are part of the idempotency contract: two requests are \"the same\nrequest\" exactly when this struct serializes identically.","properties":{"commodity":{"type":["string","null"]},"customer_reference":{"type":["string","null"],"description":"The customer's own reference for the booking; echoed back on every read."},"declared_value_cents":{"type":"integer","format":"int64","description":"Declared cargo value in cents — drives the GIT exposure and credit gate."},"dg_class":{"type":["string","null"]},"dropoff_site_id":{"type":["string","null"],"format":"uuid"},"estimate_id":{"type":["string","null"],"format":"uuid","description":"A prior estimate to book against (doc 10 §3). When given it must be one of\nthe caller's own, unexpired estimates; the order is linked to it so ops\naccept at the quoted price. It does **not** supply the booking's cargo or\nsites — those stay in this body, keeping the idempotency contract literal."},"is_abnormal":{"type":"boolean"},"is_cross_border":{"type":"boolean"},"is_hazardous":{"type":"boolean","description":"Special cargo is accepted as a *request* and always goes to ops review —\nwhich is automatic here, since every public booking starts pending."},"is_reefer":{"type":"boolean"},"notes":{"type":["string","null"]},"pickup_site_id":{"type":["string","null"],"format":"uuid"},"reefer_max_c":{"type":["number","null"],"format":"double"},"reefer_min_c":{"type":["number","null"],"format":"double"},"requested_delivery_at":{"type":["string","null"],"format":"date-time"},"requested_pickup_at":{"type":["string","null"],"format":"date-time"}}},"CreateEstimateRequest":{"type":"object","description":"`POST /public/v1/estimates` — lane + cargo in, contracted price out.","required":["origin","destination"],"properties":{"customer_reference":{"type":["string","null"],"description":"The customer's own reference (an RFQ number); echoed back on reads."},"destination":{"$ref":"#/components/schemas/LaneEndpoint"},"is_cross_border":{"type":"boolean"},"is_hazardous":{"type":"boolean"},"is_reefer":{"type":"boolean"},"mass_kg":{"type":["number","null"],"format":"double"},"origin":{"$ref":"#/components/schemas/LaneEndpoint"},"pallets":{"type":["integer","null"],"format":"int32"},"vehicle_class":{"type":["string","null"],"description":"Equipment hint, matched against the rate card's vehicle classes."}}},"CreateWebhookRequest":{"type":"object","description":"`POST /public/v1/webhooks` — register an endpoint for pushed events\n(doc 10 §4). `events` names entries from the event registry, e.g.\n`shipment.status_changed`.","required":["url","events"],"properties":{"events":{"type":"array","items":{"type":"string"}},"url":{"type":"string","description":"HTTPS endpoint to POST signed events to (plain HTTP is allowed for\nloopback addresses only, for local development)."}}},"CreatedWebhook":{"type":"object","description":"The creation response: the subscription plus its signing secret, shown this\nonce and never again — verify every delivery's `X-FleetOps-Signature` with it.","required":["id","url","events","secret","created_at"],"properties":{"created_at":{"type":"string","format":"date-time"},"events":{"type":"array","items":{"type":"string"}},"id":{"type":"string","format":"uuid"},"secret":{"type":"string"},"url":{"type":"string"}}},"LaneEndpoint":{"type":"object","description":"One end of the lane being estimated (doc 10 §3). `zone` is the customer's\nrate-card zone label — they negotiated those, so they know them; `site_id`\nis one of their own sites and supplies coordinates for routed distance.\nAt least one must be given.","properties":{"site_id":{"type":["string","null"],"format":"uuid"},"zone":{"type":["string","null"]}}},"OrderStatus":{"type":"string","enum":["quoted","pending_acceptance","booked","planned","in_transit","delivered","invoiced","closed","cancelled"]},"PodExceptionType":{"type":"string","enum":["shortage","damage","rejection","overage","other"]},"PodMethod":{"type":"string","enum":["signature_on_glass","paper_scan"]},"PricedLine":{"type":"object","description":"One priced component of an estimate. Serialized into the estimate's stored\nbreakdown, so the shape is part of the persisted record.","required":["description","basis","quantity","unit_price_cents","line_total_cents"],"properties":{"basis":{"$ref":"#/components/schemas/RateBasis"},"description":{"type":"string"},"line_total_cents":{"type":"integer","format":"int64"},"quantity":{"type":"number","format":"double"},"unit_price_cents":{"type":"integer","format":"int64"}}},"PublicAccount":{"type":"object","description":"The authenticated account behind an API key. Returned by\n`GET /public/v1/account` to let an integration confirm which customer its key\nis bound to and whether it is operating against sandbox fixtures.","required":["customer_id","name","is_sandbox"],"properties":{"customer_id":{"type":"string","format":"uuid"},"is_sandbox":{"type":"boolean"},"name":{"type":"string"}}},"PublicBooking":{"type":"object","description":"A booking as the customer sees it: our reference, their reference, and whether\nops have accepted it yet. Internal commercial fields (contract, corridor,\ncredit state) never appear here.","required":["reference","status","accepted","declared_value_cents","is_hazardous","is_reefer","is_abnormal","is_cross_border","created_at","updated_at"],"properties":{"accepted":{"type":"boolean","description":"False while the booking waits on the ops board (doc 10 §3)."},"commodity":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"customer_reference":{"type":["string","null"]},"declared_value_cents":{"type":"integer","format":"int64"},"dg_class":{"type":["string","null"]},"is_abnormal":{"type":"boolean"},"is_cross_border":{"type":"boolean"},"is_hazardous":{"type":"boolean"},"is_reefer":{"type":"boolean"},"reference":{"type":"string"},"requested_delivery_at":{"type":["string","null"],"format":"date-time"},"requested_pickup_at":{"type":["string","null"],"format":"date-time"},"status":{"$ref":"#/components/schemas/OrderStatus"},"updated_at":{"type":"string","format":"date-time"}}},"PublicEstimate":{"type":"object","description":"A priced estimate. The breakdown lines are the persisted record — what the\ncustomer was shown is exactly what is stored.","required":["id","origin_zone","destination_zone","lines","currency","subtotal_cents","vat_cents","total_cents","valid_until","created_at"],"properties":{"created_at":{"type":"string","format":"date-time"},"currency":{"type":"string"},"customer_reference":{"type":["string","null"]},"destination_zone":{"type":"string"},"escalation":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PublicEstimateEscalation"}]},"id":{"type":"string","format":"uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PricedLine"}},"origin_zone":{"type":"string"},"subtotal_cents":{"type":"integer","format":"int64"},"total_cents":{"type":"integer","format":"int64"},"transit":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PublicEstimateTransit"}]},"valid_until":{"type":"string","format":"date"},"vat_cents":{"type":"integer","format":"int64"},"vehicle_class":{"type":["string","null"]}}},"PublicEstimateEscalation":{"type":"object","description":"The diesel escalation as applied to this estimate — enough for the customer\nto see which published price the adjustment was struck from.","required":["price_month","current_diesel_cents"],"properties":{"current_diesel_cents":{"type":"integer","format":"int64"},"price_month":{"type":"string","format":"date"}}},"PublicEstimateTransit":{"type":"object","description":"Routed transit, best-effort: absent when neither end resolved to coordinates\nor the routing engine was unavailable (the price stands regardless).","required":["distance_km","drive_hours"],"properties":{"distance_km":{"type":"number","format":"double"},"drive_hours":{"type":"number","format":"double"}}},"PublicPodBundle":{"type":"object","description":"`GET /public/v1/shipments/{ref}/pod` (doc 10 §3). Absent until at least one\nconsignment has been signed for — the endpoint 404s rather than returning an\nempty bundle, so a client can poll it as a delivery signal.\n\n`complete` distinguishes a partially-delivered multi-consignment shipment from\none where every consignment is in; `delivered_at` is the most recent capture.","required":["reference","delivered_at","complete","consignments"],"properties":{"complete":{"type":"boolean"},"consignments":{"type":"array","items":{"$ref":"#/components/schemas/PublicPodConsignment"}},"delivered_at":{"type":"string","format":"date-time"},"reference":{"type":"string"}}},"PublicPodConsignment":{"type":"object","description":"The POD for one consignment of a shipment. An order can be carried as several\nconsignments across trips, so a shipment's proof of delivery is a *bundle* of\nthese, one per consignment that has been signed for.","required":["pod_id","consignee_name","method","captured_at","has_exceptions","exceptions","documents"],"properties":{"captured_at":{"type":"string","format":"date-time"},"consignee_name":{"type":"string"},"consignment_reference":{"type":["string","null"]},"documents":{"type":"array","items":{"$ref":"#/components/schemas/PublicPodDocument"}},"exceptions":{"type":"array","items":{"$ref":"#/components/schemas/PublicPodException"}},"has_exceptions":{"type":"boolean"},"method":{"$ref":"#/components/schemas/PodMethod"},"pod_id":{"type":"string","format":"uuid"}}},"PublicPodDocument":{"type":"object","description":"One piece of POD evidence, addressed by slot rather than by its vault object\nkey. `path` is the URL the customer GETs to fetch the bytes; it stays relative\nso the API's public hostname is not baked into stored responses.","required":["slot","content_type","path"],"properties":{"content_type":{"type":"string"},"path":{"type":"string"},"slot":{"type":"string"}}},"PublicPodException":{"type":"object","description":"A delivery exception on the customer's own consignment (shortage, damage,\nrejection). Carries no internal reason narrative beyond what was recorded\nagainst their freight.","required":["exception_type"],"properties":{"description":{"type":["string","null"]},"exception_type":{"$ref":"#/components/schemas/PodExceptionType"},"quantity":{"type":["integer","null"],"format":"int32"},"reason_code":{"type":["string","null"]}}},"PublicShipment":{"type":"object","description":"A shipment as it appears in the customer's tracking list — their reference,\ncurrent status, own origin/destination site names and cargo characteristics.\nNever carries vehicle, driver, or co-load information (doc 10 §5).","required":["reference","status","is_hazardous","is_reefer","is_abnormal","is_cross_border","updated_at"],"properties":{"commodity":{"type":["string","null"]},"customer_reference":{"type":["string","null"]},"destination":{"type":["string","null"]},"is_abnormal":{"type":"boolean"},"is_cross_border":{"type":"boolean"},"is_hazardous":{"type":"boolean"},"is_reefer":{"type":"boolean"},"origin":{"type":["string","null"]},"reference":{"type":"string"},"requested_delivery_at":{"type":["string","null"],"format":"date-time"},"requested_pickup_at":{"type":["string","null"],"format":"date-time"},"status":{"$ref":"#/components/schemas/OrderStatus"},"updated_at":{"type":"string","format":"date-time"}}},"PublicShipmentDetail":{"allOf":[{"$ref":"#/components/schemas/PublicShipment"},{"type":"object","required":["timeline"],"properties":{"progress":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ShipmentProgress"}]},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/ShipmentMilestone"}}}}],"description":"The single-shipment tracking view: the summary, the status-event timeline, and\nthe live coarse location + ETA while the shipment is moving (doc 10 §3, §5).\n`progress` is absent unless the shipment is on an in-progress trip."},"PublicWebhook":{"type":"object","description":"A registered webhook subscription. The signing secret is never included —\nit is returned exactly once, at creation, via [`CreatedWebhook`].","required":["id","url","events","is_active","created_at"],"properties":{"created_at":{"type":"string","format":"date-time"},"events":{"type":"array","items":{"type":"string"}},"id":{"type":"string","format":"uuid"},"is_active":{"type":"boolean"},"url":{"type":"string"}}},"PublicWebhookDelivery":{"type":"object","description":"One delivery attempt record — the per-subscription history that makes\nfailures and dead-letters visible, and the handle the replay endpoint takes.","required":["id","event_id","event_type","payload","status","attempts","next_attempt_at","created_at"],"properties":{"attempts":{"type":"integer","format":"int32"},"created_at":{"type":"string","format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time"},"event_id":{"type":"string","format":"uuid","description":"Stable across retries, replays and fan-out — the receiver's dedup key."},"event_type":{"type":"string"},"id":{"type":"string","format":"uuid"},"last_error":{"type":["string","null"]},"next_attempt_at":{"type":"string","format":"date-time"},"payload":{"type":"object"},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"}}},"RateBasis":{"type":"string","enum":["per_load","per_ton","per_pallet","per_km","per_hour","per_day","per_shipment"]},"ShipmentMilestone":{"type":"object","description":"One rung of the public milestone ladder for a shipment. `reached` is true for\nevery milestone up to and including the current one; `current` marks exactly\nthe shipment's present position.","required":["milestone","reached","current"],"properties":{"current":{"type":"boolean"},"milestone":{"type":"string"},"reached":{"type":"boolean"}}},"ShipmentProgress":{"type":"object","description":"The live progress of a moving shipment (doc 10 §5): a coarse, geofence-derived\nlocation and a routed ETA. Every field is best-effort and customer-safe — the\nraw vehicle position is never exposed, only the town/segment label the vehicle\nlast passed and the projected arrival time. Present whenever the shipment is on\nan in-progress trip, even if a fix or route is momentarily unavailable.","properties":{"coarse_location":{"type":["string","null"],"description":"The last located milestone, e.g. a depot or border name — never\ncoordinates. `None` when the trip has logged no located event yet."},"eta":{"type":["string","null"],"format":"date-time","description":"Projected arrival at the delivery site (doc 05 §6). `None` when routing is\nunavailable, the vehicle has no fix yet, or the destination isn't geofenced."},"last_event":{"type":["string","null"],"description":"A plain-language label for that milestone (departed, cleared border, …)."},"last_event_at":{"type":["string","null"],"format":"date-time"}}},"WebhookDeliveryStatus":{"type":"string","enum":["pending","delivered","dead"]}},"securitySchemes":{"api_key":{"type":"apiKey","in":"header","name":"X-API-Key"},"bearer_key":{"type":"http","scheme":"bearer","bearerFormat":"API key, prefixed fop_"}}},"security":[{"bearer_key":[]},{"api_key":[]}],"tags":[{"name":"account","description":"Confirm the account behind an API key"},{"name":"estimates","description":"Price a lane against the customer's contract"},{"name":"bookings","description":"Request transport and watch for acceptance"},{"name":"shipments","description":"Track the customer's own shipments"},{"name":"proof-of-delivery","description":"Retrieve signed proof of delivery"},{"name":"webhooks","description":"Have events pushed to your endpoint instead of polling"}]}