From 2f09ff21a3fc89e7e2dd2478ca7babcbaa3c3534 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 19:22:56 +0800 Subject: [PATCH 1/6] Document Storefront order payments, address change requests, stock and booking slots - Storefront Order: payment (and meta.payment) with its states and adjustments, meta.booking, cancel_reason, tip amounts, address_change_request, and the per-item meta the store sets (note, packed_at, unavailable, substitute_for, added_by_edit). - New Request Address Change (POST orders/{id}/address-change-request). - Product: track_inventory, inventory and in_stock. - New List Product Booking Slots (GET products/{id}/slots). - Checkout initialize: 422 cart_items_unavailable and booking_slot_unavailable. - Notifications: order_updated, and what order_canceled now says. --- .../Before Cash Pickup Checkout.request.yaml | 2 + .../Before \342\235\227.request.yaml" | 5 + .../Notifications/.resources/object.yaml | 4 +- .../Orders/.resources/object.yaml | 91 +++++++++++++++++-- .../Orders/Request Address Change.params.yaml | 8 ++ .../Request Address Change.request.yaml | 42 +++++++++ .../Products/.resources/object.yaml | 20 ++++ ...ist Product Booking Slots.queryParams.yaml | 6 ++ .../List Product Booking Slots.request.yaml | 31 +++++++ 9 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 postman/collections/Fleetbase Storefront API/Orders/Request Address Change.params.yaml create mode 100644 postman/collections/Fleetbase Storefront API/Orders/Request Address Change.request.yaml create mode 100644 postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.queryParams.yaml create mode 100644 postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.request.yaml diff --git a/postman/collections/Fleetbase Storefront API/Checkout/Before Cash Pickup Checkout.request.yaml b/postman/collections/Fleetbase Storefront API/Checkout/Before Cash Pickup Checkout.request.yaml index 0de5735..da8bf33 100644 --- a/postman/collections/Fleetbase Storefront API/Checkout/Before Cash Pickup Checkout.request.yaml +++ b/postman/collections/Fleetbase Storefront API/Checkout/Before Cash Pickup Checkout.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- Initializes a cash (cash-on-delivery) pickup checkout. A pickup checkout with the cash gateway needs no delivery service quote, so the `service_quote` parameter is omitted. The response returns the checkout id and token; no gateway client data is prepared for cash. + + Like every checkout, it holds stock for products that track inventory and can be refused with `422` `cart_items_unavailable` or `booking_slot_unavailable` (see `Before`). url: "{{base_url}}/{{api_prefix}}/{{namespace}}/checkouts/before" method: GET queryParams: diff --git "a/postman/collections/Fleetbase Storefront API/Checkout/Before \342\235\227.request.yaml" "b/postman/collections/Fleetbase Storefront API/Checkout/Before \342\235\227.request.yaml" index 35474e4..cb368d6 100644 --- "a/postman/collections/Fleetbase Storefront API/Checkout/Before \342\235\227.request.yaml" +++ "b/postman/collections/Fleetbase Storefront API/Checkout/Before \342\235\227.request.yaml" @@ -2,6 +2,11 @@ $kind: http-request description: |- Initializes a checkout preview for a cart, customer, gateway, and delivery or pickup option. The response prepares gateway-specific client data or a checkout token before the order is captured. + Stock and booked slots are checked when the checkout is created (cash, Stripe and QPay alike, and again when `Update Stripe Payment Intent` creates or re-prices a checkout). For products that track inventory, each line's quantity is held at the line's store location or food truck for 15 minutes and taken when the order is captured; an abandoned checkout gives it back. A refusal deletes the checkout: + + - `422` `{ "errors": ["Some items in your cart are no longer available."], "code": "cart_items_unavailable", "items": [{ "id", "product_id", "name", "available" }] }` when a tracked product doesn't have enough stock at the line's location. `id` is the cart line and `available` is what is left to sell there. + - `422` `{ "errors": ["The time you picked for was just booked. Please pick another time."], "code": "booking_slot_unavailable" }` when a booked service's slot (see `List Product Booking Slots`) has no places left. + Promotions are priced when the checkout is created: automatic promotions and any promo codes (from `promo_codes` or applied to the cart) are subtracted from the amount charged, and each use is reserved for the checkout. A code that cannot be applied refuses the checkout with `400`. url: "{{base_url}}/{{api_prefix}}/{{namespace}}/checkouts/before" method: GET diff --git a/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml b/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml index 498e670..9d4d577 100644 --- a/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml +++ b/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml @@ -1,7 +1,7 @@ $kind: object name: Notification description: |- - An inbox item. `type` identifies what the notification is about (for example `order_accepted`, `order_completed`, `promotional` or `campaign`), and `data` carries the identifiers the app needs to deep link. Recipient details are never included. + An inbox item. `type` identifies what the notification is about (for example `order_accepted`, `order_updated`, `order_completed`, `promotional` or `campaign`), and `data` carries the identifiers the app needs to deep link. Recipient details are never included. example: | { "id": "0f4c9a52-8d3f-4a6e-9f0e-2b1f7f2f8c11", @@ -30,7 +30,7 @@ fields: description: "Notification title." - name: body type: string - description: "Notification message." + description: "Notification message. For `order_updated` (the store edited the order) it says what changed and what that means for the customer's money: refunded, returned as store credit with the code to use, or a balance to pay with its payment link. For `order_canceled` it gives the store's reason, when there is one, and whether the payment was refunded or returned as store credit." - name: image type: string description: "Optional image URL." diff --git a/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml b/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml index be3f4ab..95c0fac 100644 --- a/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml +++ b/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml @@ -6,23 +6,81 @@ example: | { "id": "order_9Kx2mQ1", "public_id": "order_9Kx2mQ1", - "status": "created", + "status": "preparing", "type": "storefront", - "payload": null, + "payload": { + "id": "payload_4Rt7nB2", + "entities": [ + { + "id": "entity_6Lp2wQ9", + "name": "Cold Brew", + "meta": { "product_id": "product_9Lb2mQ1", "quantity": 2, "note": "No ice, please", "packed_at": "2026-10-13T09:41:00+00:00", "unavailable": false } + }, + { + "id": "entity_3Hd8vK5", + "name": "Iced Latte", + "meta": { "product_id": "product_7Qa1zX4", "quantity": 1, "substitute_for": "entity_8Wn5cT1", "added_by_edit": true } + } + ] + }, "customer": null, "meta": { "checkout_id": "checkout_123", "storefront": "Acme Market", - "total": 5150, + "total": 4600, "discount": 500, "promotions": [ { "promotion": "promotion_7Hq2xL9", "name": "Welcome $5 off", "type": "fixed_amount", "code": "WELCOME5", "amount": 500, "delivery_amount": 0 } ], - "currency": "USD" + "currency": "USD", + "gateway": "stripe", + "payment": { + "state": "partially_refunded", + "gateway": "stripe", + "method_label": "Card (Stripe)", + "total": 4600, + "amount_paid": 5150, + "amount_refunded": 550, + "amount_credited": 0, + "balance_due": 0, + "cod_amount": 0, + "currency": "USD", + "payment_link": null, + "store_credit_code": null, + "supports_partial_refund": true, + "adjustments": [ + { "type": "refund", "amount": 550, "gateway": "stripe", "reference": "re_example123", "reason": "Iced Mocha was unavailable", "at": "2026-10-13T09:40:00+00:00" } + ] + }, + "address_change_request": { + "status": "open", + "message": "Please deliver to my office instead.", + "place": "place_2Vb6nM8", + "address": "1 Raffles Place, Singapore 048616", + "at": "2026-10-13T09:45:00+00:00" + } + }, + "payment": { + "state": "partially_refunded", + "gateway": "stripe", + "method_label": "Card (Stripe)", + "total": 4600, + "amount_paid": 5150, + "amount_refunded": 550, + "amount_credited": 0, + "balance_due": 0, + "cod_amount": 0, + "currency": "USD", + "payment_link": null, + "store_credit_code": null, + "supports_partial_refund": true, + "adjustments": [ + { "type": "refund", "amount": 550, "gateway": "stripe", "reference": "re_example123", "reason": "Iced Mocha was unavailable", "at": "2026-10-13T09:40:00+00:00" } + ] }, "notes": null, - "created_at": "2026-05-07T09:30:00Z", - "updated_at": "2026-05-07T09:30:00Z" + "created_at": "2026-10-13T09:30:00Z", + "updated_at": "2026-10-13T09:45:00Z" } fields: - name: id @@ -55,3 +113,24 @@ fields: - name: updated_at type: timestamp description: "Time the order was last updated." + - name: payment + type: object + description: "The order's payment, computed when the order is returned (customer order lists). Same shape as `meta.payment`." + - name: meta.payment + type: object + description: "The order's payment as one picture, stored on the order and kept current by the server at capture, cash collection, edits and cancellation, so it is present whichever resource returns the order. `{ state, gateway, method_label, total, amount_paid, amount_refunded, amount_credited, balance_due, cod_amount, currency, payment_link, store_credit_code, supports_partial_refund, adjustments }`. `state` is `paid`, `to_collect` (cash on delivery not collected yet), `unpaid`, `refunded`, `partially_refunded` or `balance_due`. `payment_link` is the link to pay a balance due (Stripe Payment Link or QPay invoice); `store_credit_code` is the single-use code issued when a difference was returned as store credit. `adjustments` lists each change after checkout: `{ type, amount, gateway, reference, reason, at }` (and `by` when made by a store user), where `type` is `refund`, `store_credit`, `charge` or `cod_update`. Amounts are in the currency's smallest unit. A cash-on-delivery order stays `to_collect` with `amount_paid` 0 until it is completed." + - name: meta.booking + type: object + description: "Booking orders only: the booked slot `{ start, end, duration_minutes }` (ISO 8601), alongside `is_booking` and `booking_at`." + - name: meta.cancel_reason + type: string + description: "Why the store canceled the order, when it gave a reason." + - name: meta.tip_amount + type: integer + description: "Tip for the store, in the currency's smallest unit (`meta.delivery_tip_amount` for the driver's tip)." + - name: meta.address_change_request + type: object + description: "Set by `Request Address Change`: `{ status, message, place, address, at }`. `status` is `open` until the store updates the address (`resolved`) or dismisses the request (`dismissed`), which also adds `closed_at`; `place` and `address` are the saved place the customer suggested, or null." + - name: payload.entities[].meta + type: object + description: "Per item, besides the cart line data (`product_id`, `quantity`, ...): `note` (the store's note on the item), `packed_at` (when the store packed it), `unavailable` (the store marked it unavailable), `substitute_for` (the entity id this item replaces) and `added_by_edit` (added when the store edited the order)." diff --git a/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.params.yaml b/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.params.yaml new file mode 100644 index 0000000..77de287 --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.params.yaml @@ -0,0 +1,8 @@ +$kind: params +fields: + - name: message + type: string + description: "What is wrong with the address, up to 1000 characters." + - name: place + type: string + description: "Public ID of one of the customer's saved places to suggest as the new drop-off. An unknown place, or one that isn't the customer's, returns `404`." diff --git a/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.request.yaml b/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.request.yaml new file mode 100644 index 0000000..ce1746b --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Orders/Request Address Change.request.yaml @@ -0,0 +1,42 @@ +$kind: http-request +description: |- + Tells the store that the order's drop-off address is wrong. The store sees an alert on the order, and the message is posted in the customer's chat with the store (a message with `kind` `address_change_requested`). The store then updates the address or dismisses the request. Requires the `Customer-Token` of the customer who placed the order. + + Send a `message`, a `place` (one of the customer's saved places, see `customers/places`), or both. The request is recorded on the order as `meta.address_change_request`. + + Response: `{ "status": "ok", "address_change_request": { "status": "open", "message", "place", "address", "at" } }`. `place` and `address` are the suggested saved place's id and address, or null. + + Errors: `401` without a customer token; `404` for an order not placed through this storefront (`Order not found.`) or an unknown `place` (`Place not found.`); `403` for another customer's order; `409` with `"reason": "chat_unavailable"` when the chat with the store can't be opened (the customer has no user account, or the order finished without one); `422` when the order is completed, canceled or expired, or is a pickup order (`The address of this order can no longer change.`), and when `message` is longer than 1000 characters. + + In a contract run this addresses `{{address_change_order_id}}` when the environment sets one (an open delivery order of the run's customer). Otherwise it addresses an unknown order and asserts the `404`. +url: "{{base_url}}/{{api_prefix}}/{{namespace}}/orders/{{address_change_order_id}}/address-change-request" +method: POST +headers: + - key: Customer-Token + value: "{{customer_token}}" + - key: Content-Type + value: application/json +body: + type: json + content: |- + { + "message": "The building number is wrong, it's 18 not 81.", + "place": "{{place_id}}" + } +scripts: + - type: beforeRequest + code: |- + // Needs an open delivery order of the run's customer, which a contract run doesn't + // create (its one captured order is a pickup). Without one it addresses an order + // that doesn't exist and asserts the 404 refusal instead. + if (!pm.environment.get('address_change_order_id')) { + pm.variables.set('address_change_order_id', 'order_missing'); + pm.variables.set('expected_status', 404); + } + + // `place` is optional: send none rather than an unresolved "{{place_id}}". + if (!pm.environment.get('place_id')) { + pm.request.body.update(JSON.stringify({ message: "The building number is wrong, it's 18 not 81." }, null, 2)); + } + language: text/javascript +order: 2500 diff --git a/postman/collections/Fleetbase Storefront API/Products/.resources/object.yaml b/postman/collections/Fleetbase Storefront API/Products/.resources/object.yaml index 8f97d54..8d4e3ef 100644 --- a/postman/collections/Fleetbase Storefront API/Products/.resources/object.yaml +++ b/postman/collections/Fleetbase Storefront API/Products/.resources/object.yaml @@ -17,6 +17,17 @@ example: | "is_service": false, "is_bookable": false, "is_available": true, + "track_inventory": true, + "inventory": { + "tracked": true, + "in_stock": true, + "available_total": 3, + "low": true, + "locations": [ + { "id": "inventory_4Kd9wP2", "location": "store_location_7Nx2bQ5", "location_type": "store_location", "quantity": 5, "reserved": 2, "available": 3, "low_stock_threshold": 5, "allow_backorder": false, "low": true } + ] + }, + "in_stock": true, "tags": ["coffee"], "status": "active", "meta": {}, @@ -59,6 +70,15 @@ fields: - name: is_available type: boolean description: "Whether the product is available for purchase." + - name: track_inventory + type: boolean + description: "Whether the store tracks stock for this product. Off by default; products that don't track stock are never limited by it." + - name: inventory + type: object + description: "Stock as clients show it: `{ tracked, in_stock, available_total, low, locations }`. Untracked products return `{ \"tracked\": false, \"in_stock\": true, \"available_total\": null, \"low\": false, \"locations\": [] }`. Each location is `{ id, location, location_type, quantity, reserved, available, low_stock_threshold, allow_backorder, low }`: `location` is the store location or food truck public ID and `location_type` is `store_location` or `food_truck`; `quantity` is on hand, `reserved` is held by checkouts in progress (for 15 minutes), `available` is what can still be sold, and `low` is true while some stock is available but no more than `low_stock_threshold`. `in_stock` is true when any location has stock available or allows backorders." + - name: in_stock + type: boolean + description: "Whether the product can be sold now: false when `is_available` is false, or when it tracks stock and no location has any available (or allows backorders). Use it for out of stock labels; `inventory.available_total` for \"only N left\"." - name: tags type: array of strings description: "Product tags." diff --git a/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.queryParams.yaml b/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.queryParams.yaml new file mode 100644 index 0000000..87452cd --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.queryParams.yaml @@ -0,0 +1,6 @@ +$kind: params +fields: + - name: date + type: string + required: true + description: "The day to list, `YYYY-MM-DD`, in the store's time zone." diff --git a/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.request.yaml b/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.request.yaml new file mode 100644 index 0000000..d2d761b --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Products/List Product Booking Slots.request.yaml @@ -0,0 +1,31 @@ +$kind: http-request +description: |- + Returns the times a bookable service can be booked on a day. Slots are the service's hours for that weekday (else the store's first location's hours), cut into slots of the service's length, each with how many places are left. Times are in the store's time zone (else the company's, else the server's). Bookings of the service take `quantity` places in their slot; canceled, expired and failed orders give their places back. + + Response: `{ date, service: { duration_minutes, capacity, deposit_percent, travel_fee, provider_tag }, slots: [{ start, end, time, past, available, remaining, providers, capacity }] }`. `start` and `end` are ISO 8601 with the store's offset and `time` is the store's local `HH:mm`. `past` is true once the slot has started; `available` is true when the slot is still ahead and has places left; `remaining` (also reported as `providers`) is the places left out of `capacity`. `duration_minutes` defaults to 60 and `capacity` to 1. `deposit_percent` and `travel_fee` are reported as configured on the service but are not charged at checkout. + + Book a slot by adding the service to the cart with its `start` as the line's `scheduled_at`. If a slot the service's hours define fills up before checkout, initializing the checkout returns `422` with `"code": "booking_slot_unavailable"`. + + Errors: `404` (`Bookable service not found.`) when the product isn't bookable or isn't in this store or network; `422` when `date` is missing or not `YYYY-MM-DD`. + + In a contract run this addresses `{{service_product_id}}` when the environment sets one (a bookable service with hours). Otherwise it addresses the run's product, which isn't bookable, and asserts the `404`. +url: "{{base_url}}/{{api_prefix}}/{{namespace}}/products/{{service_product_id}}/slots" +method: GET +queryParams: + date: "{{slot_date}}" +scripts: + - type: beforeRequest + code: |- + // The day to list: the environment's {{slot_date}}, else today (UTC). + if (!pm.environment.get('slot_date')) { + pm.variables.set('slot_date', new Date().toISOString().slice(0, 10)); + } + + // A contract run has no bookable service unless the environment names one; the + // run's own product is a plain item, so the request documents the 404 instead. + if (!pm.environment.get('service_product_id')) { + pm.variables.set('service_product_id', pm.environment.get('product_id') || 'product_missing'); + pm.variables.set('expected_status', 404); + } + language: text/javascript +order: 3500 From d809948f92808d4105a3d0ea3e9bca5093030212 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 19:23:03 +0800 Subject: [PATCH 2/6] Document Fleet-Ops driver offers, guarded order edits and order events From fleetbase/fleetops#394 (release v0.6.72): - New Accept an Order and Decline an Order (POST orders/{id}/accept|decline). - Cancel an Order takes a reason. - Update an Order and Update a Payload: the status lock policy and its 422 locked response, entities_mode=replace, cod_amount at the top level and 0. - Entities: the 422 when the order's items are locked. - Order: the offer field; Orders folder: offers and the order.changed, order.driver_unassigned and order.offer_* realtime events. - The collection script honours expected_status, as the Storefront one does, so the offer requests can assert their 404 when a run has no offered order. --- .../Fleetbase API/.resources/definition.yaml | 11 +++- .../Entities/Create an Entity.request.yaml | 2 + .../Entities/Delete a Entity.request.yaml | 5 +- .../Entities/Update a Entity.request.yaml | 2 + .../examples/Forbidden.example.yaml | 27 +++++++++ .../examples/OK.example.yaml | 58 ++++++++++++++++++ .../examples/OK.example.yaml | 59 +++++++++++++++++++ .../Unprocessable Content.example.yaml | 27 +++++++++ .../Unprocessable Content.example.yaml | 37 ++++++++++++ .../Orders/.resources/definition.yaml | 12 ++++ .../Orders/.resources/object.yaml | 4 ++ .../Orders/Accept an Order.params.yaml | 5 ++ .../Orders/Accept an Order.request.yaml | 40 +++++++++++++ .../Orders/Cancel an Order.params.yaml | 5 ++ .../Orders/Cancel an Order.request.yaml | 13 +++- .../Orders/Decline an Order.params.yaml | 8 +++ .../Orders/Decline an Order.request.yaml | 39 ++++++++++++ .../Orders/Update an Order.params.yaml | 10 +++- .../Orders/Update an Order.request.yaml | 13 ++++ .../Payloads/Update a Payload.params.yaml | 9 ++- .../Payloads/Update a Payload.request.yaml | 4 ++ 21 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml create mode 100644 postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml create mode 100644 postman/collections/Fleetbase API/Orders/.resources/Update an Order.resources/examples/Unprocessable Content.example.yaml create mode 100644 postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml create mode 100644 postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml create mode 100644 postman/collections/Fleetbase API/Orders/Cancel an Order.params.yaml create mode 100644 postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml create mode 100644 postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index 09fcd52..3ff6a92 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -84,7 +84,16 @@ scripts: // started returning a document would still be caught. const contentType = pm.response.headers.get('Content-Type') || ''; const isDocument = /^(application\/pdf|image\/|application\/octet-stream|text\/csv)/i.test(contentType); - const expected = pm.request.method === "POST" ? [200, 201, 202] : [200, 201, 202, 204]; + // A request whose CONTRACT is a refusal opts out of the 2xx expectation by setting + // `expected_status` in its beforeRequest script (as the Storefront collection does), + // e.g. answering a driver offer when the run has no order offered to a driver. The + // override is consumed here: "local" variables are scoped to the RUN, so left set it + // would hold every later request against the same status. + const expectedOverride = Number(pm.variables.get("expected_status")) || null; + pm.variables.unset("expected_status"); + const expected = expectedOverride + ? [expectedOverride] + : (pm.request.method === "POST" ? [200, 201, 202] : [200, 201, 202, 204]); if (isDocument) { // Still asserted, not excused: a document response must carry actual bytes. diff --git a/postman/collections/Fleetbase API/Entities/Create an Entity.request.yaml b/postman/collections/Fleetbase API/Entities/Create an Entity.request.yaml index 9c7b91c..7481a8c 100644 --- a/postman/collections/Fleetbase API/Entities/Create an Entity.request.yaml +++ b/postman/collections/Fleetbase API/Entities/Create an Entity.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- Creates an entity such as a parcel, package, or item. Attach it to a payload and optionally set a destination or waypoint within that payload route. + + When the entity belongs to an order's payload, the order's status decides whether its items can still change: once a driver has the order, or the order is finished, items are locked; once it is ready, items can only be removed. A locked change returns `422` `{ "error": "...", "locked": { "items": "" } }`, and an accepted change broadcasts `order.changed`. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). url: "{{base_url}}/{{namespace}}/entities" method: POST headers: diff --git a/postman/collections/Fleetbase API/Entities/Delete a Entity.request.yaml b/postman/collections/Fleetbase API/Entities/Delete a Entity.request.yaml index eb79034..20f87ff 100644 --- a/postman/collections/Fleetbase API/Entities/Delete a Entity.request.yaml +++ b/postman/collections/Fleetbase API/Entities/Delete a Entity.request.yaml @@ -1,5 +1,8 @@ $kind: http-request -description: Delete an Entity. +description: |- + Delete an Entity. + + When the entity belongs to an order's payload, the order's status decides whether its items can still change: once a driver has the order, or the order is finished, items are locked; once it is ready, items can only be removed. A locked change returns `422` `{ "error": "...", "locked": { "items": "" } }`, and an accepted change broadcasts `order.changed`. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). url: "{{base_url}}/{{namespace}}/entities/:id" method: DELETE headers: diff --git a/postman/collections/Fleetbase API/Entities/Update a Entity.request.yaml b/postman/collections/Fleetbase API/Entities/Update a Entity.request.yaml index 6dee422..ba70ba8 100644 --- a/postman/collections/Fleetbase API/Entities/Update a Entity.request.yaml +++ b/postman/collections/Fleetbase API/Entities/Update a Entity.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- Updates an entity's descriptive fields, payload assignment, destination, dimensions, weight, pricing, or metadata. + + When the entity belongs to an order's payload, the order's status decides whether its items can still change: once a driver has the order, or the order is finished, items are locked; once it is ready, items can only be removed. A locked change returns `422` `{ "error": "...", "locked": { "items": "" } }`, and an accepted change broadcasts `order.changed`. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). url: "{{base_url}}/{{namespace}}/entities/:id" method: PUT headers: diff --git a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml new file mode 100644 index 0000000..2e6fa66 --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml @@ -0,0 +1,27 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/orders/:id/accept" + method: POST + headers: + Content-Type: application/json + Accept: application/json + pathVariables: + id: "order_7YqM3KpL2n" + body: + type: json + content: |- + { + "driver": "driver_8Tn4Wd1Zx6" + } +response: + statusCode: 403 + statusText: Forbidden + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "This order is not offered to you." + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml new file mode 100644 index 0000000..506c9fa --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml @@ -0,0 +1,58 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/orders/:id/accept" + method: POST + headers: + Content-Type: application/json + Accept: application/json + pathVariables: + id: "order_7YqM3KpL2n" + body: + type: json + content: |- + { + "driver": "driver_5Hk2Qp9Lm3" + } +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "id": "order_7YqM3KpL2n", + "internal_id": "ORD-1001", + "order_config": "order_config_transport", + "customer": null, + "payload": { "id": "payload_x9Z2Lm8QpA", "waypoints": [], "entities": [], "meta": {} }, + "facilitator": null, + "driver_assigned": { "id": "driver_5Hk2Qp9Lm3", "name": "Ravi Kumar" }, + "vehicle_assigned": null, + "tracking_number": { "id": "tracking_3Nf8Qw2YxM", "tracking_number": "FB-1001", "status": "dispatched", "status_code": "dispatched", "type": "order" }, + "tracking_statuses": [], + "files": [], + "purchase_rate": null, + "notes": "Deliver through receiving bay.", + "type": "transport", + "status": "dispatched", + "pod_method": null, + "pod_required": false, + "dispatched": true, + "started": false, + "adhoc": false, + "adhoc_distance": 0, + "distance": 0, + "time": 0, + "meta": { + "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": "2026-10-13T08:32:00Z", "declined_at": null, "status": "accepted" } + }, + "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": "2026-10-13T08:32:00Z", "declined_at": null, "status": "accepted" }, + "dispatched_at": "2026-10-13T08:30:00.000000Z", + "started_at": null, + "scheduled_at": null, + "updated_at": "2026-10-13T08:32:00.000000Z", + "created_at": "2026-10-13T08:20:00.000000Z" + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml new file mode 100644 index 0000000..384770c --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml @@ -0,0 +1,59 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/orders/:id/decline" + method: POST + headers: + Content-Type: application/json + Accept: application/json + pathVariables: + id: "order_7YqM3KpL2n" + body: + type: json + content: |- + { + "driver": "driver_5Hk2Qp9Lm3", + "reason": "Vehicle has a flat tyre." + } +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "id": "order_7YqM3KpL2n", + "internal_id": "ORD-1001", + "order_config": "order_config_transport", + "customer": null, + "payload": { "id": "payload_x9Z2Lm8QpA", "waypoints": [], "entities": [], "meta": {} }, + "facilitator": null, + "driver_assigned": null, + "vehicle_assigned": null, + "tracking_number": { "id": "tracking_3Nf8Qw2YxM", "tracking_number": "FB-1001", "status": "dispatched", "status_code": "dispatched", "type": "order" }, + "tracking_statuses": [], + "files": [], + "purchase_rate": null, + "notes": "Deliver through receiving bay.", + "type": "transport", + "status": "dispatched", + "pod_method": null, + "pod_required": false, + "dispatched": true, + "started": false, + "adhoc": false, + "adhoc_distance": 0, + "distance": 0, + "time": 0, + "meta": { + "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": null, "declined_at": "2026-10-13T08:32:00Z", "status": "declined", "reason": "Vehicle has a flat tyre." } + }, + "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": null, "declined_at": "2026-10-13T08:32:00Z", "status": "declined", "reason": "Vehicle has a flat tyre." }, + "dispatched_at": "2026-10-13T08:30:00.000000Z", + "started_at": null, + "scheduled_at": null, + "updated_at": "2026-10-13T08:32:00.000000Z", + "created_at": "2026-10-13T08:20:00.000000Z" + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml new file mode 100644 index 0000000..a3dd3ba --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml @@ -0,0 +1,27 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/orders/:id/decline" + method: POST + headers: + Content-Type: application/json + Accept: application/json + pathVariables: + id: "order_7YqM3KpL2n" + body: + type: json + content: |- + { + "driver": "driver_5Hk2Qp9Lm3" + } +response: + statusCode: 422 + statusText: Unprocessable Content + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "This order has already been started and cannot be declined." + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Update an Order.resources/examples/Unprocessable Content.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Update an Order.resources/examples/Unprocessable Content.example.yaml new file mode 100644 index 0000000..38e058a --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/.resources/Update an Order.resources/examples/Unprocessable Content.example.yaml @@ -0,0 +1,37 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/orders/:id" + method: PUT + headers: + Content-Type: application/json + Accept: application/json + pathVariables: + id: "order_7YqM3KpL2n" + body: + type: json + content: |- + { + "scheduled_at": "2026-10-13T15:00:00Z", + "payload": { + "entities_mode": "replace", + "entities": [ + { "id": "entity_4Fr8Kq2Lp1", "name": "Cold Brew", "quantity": 3 } + ] + } + } +response: + statusCode: 422 + statusText: Unprocessable Content + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "Items cannot be changed once a driver has the order. The schedule cannot be changed once a driver has the order.", + "locked": { + "items": "Items cannot be changed once a driver has the order.", + "schedule": "The schedule cannot be changed once a driver has the order." + } + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/definition.yaml b/postman/collections/Fleetbase API/Orders/.resources/definition.yaml index be97f22..8108d2c 100644 --- a/postman/collections/Fleetbase API/Orders/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/Orders/.resources/definition.yaml @@ -1,4 +1,16 @@ $kind: collection description: |- Orders represent work to be planned, dispatched, tracked, and completed. Use order endpoints to create delivery or service jobs, assign resources, manage status, capture proof of delivery, and record activity. + + ## Driver offers + + Assigning a driver records an offer on the order (`offer`). The driver answers it with `Accept an Order` or `Decline an Order`; an offer that is not answered within the company setting `fleetops.offer_timeout_minutes` (default 5 minutes, `0` for never) is marked `expired` and dispatch is alerted (`driver_not_accepted`). + + ## Realtime events + + Order changes are broadcast on the socket channels below. Every event has the envelope `{ id, api_version, event, created_at, data }`. These events require Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). + + - `order.changed`: the order's route, items, contact, schedule, notes or amounts were edited after it was placed (through `Update an Order`, `Update a Payload`, the entity endpoints, the console or Storefront). Channels: `order.{public_id}` and `order.{uuid}`, `driver.{public_id}` and `driver.{uuid}` of the assigned driver, `customer.{public_id}` and `company.{uuid}`. Data: `{ id, changes, summary, dropoff: { id, address, location }, cod_amount, total, currency, changed_by, changed_at }`; `changes` lists the parts that changed (`pickup`, `dropoff`, `return`, `waypoints`, `items`, `schedule`, `contact`, `notes`, `cod_amount`, ...) and `summary` is one readable line. When the change concerns the driver, the assigned driver also gets an `order_changed` push. + - `order.driver_unassigned`: a driver was taken off the order (unassigned or replaced). Channels: `driver.{public_id}` and `driver.{uuid}` of the previous driver, and `company.{uuid}`. Data: `{ id, driver }`. The previous driver also gets an `order_unassigned` push, unless they declined the order themselves. + - `order.offer_accepted`, `order.offer_declined`, `order.offer_expired`: the driver offer was answered or ran out. Channels: `order.{public_id}`, `order.{uuid}` and `company.{uuid}`. Data: `{ id, driver, offer }`. order: 8000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/object.yaml b/postman/collections/Fleetbase API/Orders/.resources/object.yaml index 5106994..81bb7e0 100644 --- a/postman/collections/Fleetbase API/Orders/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Orders/.resources/object.yaml @@ -43,6 +43,7 @@ example: | "distance": 0, "time": 0, "meta": {}, + "offer": null, "dispatched_at": null, "started_at": null, "scheduled_at": null, @@ -128,6 +129,9 @@ fields: - name: meta type: object description: Additional order metadata. + - name: offer + type: object + description: "The offer to the assigned driver, mirrored from `meta.offer`, or null: `{ driver, offered_at, expires_at, accepted_at, declined_at, status, reason }`. `status` is `offered`, `accepted`, `declined` or `expired`. An offer is recorded when a driver is assigned and expires after the company setting `fleetops.offer_timeout_minutes` (default 5; `0` never expires, `expires_at` null). An ad hoc driver who takes the order, or a driver who starts it, is recorded as accepted. `reason` is present when the driver gave one on decline. Also included in order webhook payloads. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72)." - name: dispatched_at type: timestamp description: Timestamp when the order was dispatched. diff --git a/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml new file mode 100644 index 0000000..e27f592 --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml @@ -0,0 +1,5 @@ +$kind: params +fields: + - name: driver + type: string + description: "Public ID of the driver accepting the order. Defaults to the driver profile of the authenticated user." diff --git a/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml new file mode 100644 index 0000000..ab0066c --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml @@ -0,0 +1,40 @@ +$kind: http-request +description: |- + The assigned driver accepts the order offered to them, and the order is returned with `offer.status` `accepted`. An ad hoc order that has no driver yet is assigned to the accepting driver. An offer that has expired can still be accepted while the driver is assigned. + + The driver is the one named by `driver`, or else the driver profile of the authenticated user (a driver signed in to the Navigator app). Dispatch and the order's channels receive the realtime event `order.offer_accepted`. Starting the order also accepts an open offer. + + When a driver is assigned, Fleetbase records an offer on the order (`offer`, see the Order object) that expires after the company setting `fleetops.offer_timeout_minutes` (default 5; 0 never expires). An expired offer stays with the driver, is marked `expired`, raises a `driver_not_accepted` alert for dispatch and broadcasts `order.offer_expired`. + + Errors: `404` for an unknown order (`Order resource not found.`) or when no driver is found (`No driver found to accept the order.`), `422` when the order is completed, canceled or expired (`This order can no longer be accepted.`), `403` when the order is assigned to another driver (`This order is not offered to you.`). Error bodies are `{ "error": "..." }`. + + Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). In a contract run this answers for `{{offer_order_id}}` and `{{offer_driver_id}}` when the environment sets them (an order assigned to that driver and not started). Otherwise it addresses an unknown order and asserts the `404`. +url: "{{base_url}}/{{namespace}}/orders/:id/accept" +method: POST +headers: + Content-Type: application/json + Accept: application/json +pathVariables: + - key: id + value: "{{offer_order_id}}" + description: (Required) The id of the order offered to the driver. +body: + type: json + content: |- + { + "driver": "{{offer_driver_id}}" + } +scripts: + - type: beforeRequest + code: |- + // Answering an offer needs an order assigned to a driver that has not started, and + // that driver. A contract run seeds neither: without {{offer_order_id}} and + // {{offer_driver_id}} in the environment the request addresses an order that + // doesn't exist and asserts the 404 refusal instead. + if (!pm.environment.get("offer_order_id") || !pm.environment.get("offer_driver_id")) { + pm.variables.set("offer_order_id", "order_missing"); + pm.variables.set("expected_status", 404); + } + language: text/javascript +examples: ./.resources/Accept an Order.resources/examples +order: 12500 diff --git a/postman/collections/Fleetbase API/Orders/Cancel an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Cancel an Order.params.yaml new file mode 100644 index 0000000..9221127 --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/Cancel an Order.params.yaml @@ -0,0 +1,5 @@ +$kind: params +fields: + - name: reason + type: string + description: "Why the order is canceled. Recorded as the canceled status details and sent with the canceled notifications. Trimmed to 1000 characters." diff --git a/postman/collections/Fleetbase API/Orders/Cancel an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Cancel an Order.request.yaml index 34c71d9..1327c90 100644 --- a/postman/collections/Fleetbase API/Orders/Cancel an Order.request.yaml +++ b/postman/collections/Fleetbase API/Orders/Cancel an Order.request.yaml @@ -1,10 +1,21 @@ $kind: http-request -description: Cancels an order without deleting the order resource. +description: |- + Cancels an order without deleting the order resource. + + An optional `reason` (body or query string) becomes the details of the canceled tracking status and is sent with the canceled notifications: the assigned driver's push carries it as `reason`, so the Navigator app can show why the order was canceled. A reason is trimmed to 1000 characters. The `reason` requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72); earlier versions ignore it. url: "{{base_url}}/{{namespace}}/orders/:id/cancel" method: DELETE +headers: + Content-Type: application/json pathVariables: - key: id value: "{{order_id}}" description: (Required) The id of the `Order`. +body: + type: json + content: |- + { + "reason": "The customer asked to cancel." + } examples: .resources/Cancel an Order.resources/examples order: 13000 diff --git a/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml new file mode 100644 index 0000000..25a1ee1 --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml @@ -0,0 +1,8 @@ +$kind: params +fields: + - name: driver + type: string + description: "Public ID of the driver declining the order. Defaults to the driver profile of the authenticated user." + - name: reason + type: string + description: "Why the driver declines, recorded on the offer and sent to dispatch. Trimmed to 1000 characters." diff --git a/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml new file mode 100644 index 0000000..f71f0e6 --- /dev/null +++ b/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml @@ -0,0 +1,39 @@ +$kind: http-request +description: |- + The assigned driver declines the order offered to them. The driver is taken off the order, the offer is recorded as `declined` (with the `reason`, when given), dispatch is notified, and the realtime events `order.offer_declined` (order channels) and `order.driver_unassigned` (the driver's channels) are broadcast. The order is returned without a driver, ready to be assigned again. + + The driver is the one named by `driver`, or else the driver profile of the authenticated user. + + Errors: `404` for an unknown order (`Order resource not found.`) or when no driver is found (`No driver found to decline the order.`), `403` when the order isn't assigned to this driver (`This order is not offered to you.`), `422` once the order has started (`This order has already been started and cannot be declined.`). Error bodies are `{ "error": "..." }`. + + Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). In a contract run this answers for `{{offer_order_id}}` and `{{offer_driver_id}}` when the environment sets them (an order assigned to that driver and not started). Otherwise it addresses an unknown order and asserts the `404`. +url: "{{base_url}}/{{namespace}}/orders/:id/decline" +method: POST +headers: + Content-Type: application/json + Accept: application/json +pathVariables: + - key: id + value: "{{offer_order_id}}" + description: (Required) The id of the order offered to the driver. +body: + type: json + content: |- + { + "driver": "{{offer_driver_id}}", + "reason": "Vehicle has a flat tyre." + } +scripts: + - type: beforeRequest + code: |- + // Answering an offer needs an order assigned to a driver that has not started, and + // that driver. A contract run seeds neither: without {{offer_order_id}} and + // {{offer_driver_id}} in the environment the request addresses an order that + // doesn't exist and asserts the 404 refusal instead. + if (!pm.environment.get("offer_order_id") || !pm.environment.get("offer_driver_id")) { + pm.variables.set("offer_order_id", "order_missing"); + pm.variables.set("expected_status", 404); + } + language: text/javascript +examples: ./.resources/Decline an Order.resources/examples +order: 12600 diff --git a/postman/collections/Fleetbase API/Orders/Update an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Update an Order.params.yaml index 2ab4096..0934573 100644 --- a/postman/collections/Fleetbase API/Orders/Update an Order.params.yaml +++ b/postman/collections/Fleetbase API/Orders/Update an Order.params.yaml @@ -17,7 +17,15 @@ fields: description: Replacement waypoint list. When sent empty with route endpoint fields, existing waypoints are removed. - name: entities type: array of objects - description: Replacement entities for the order payload. + description: Entities for the order payload. Appended to the payload's entities, or with `entities_mode` `replace`, the payload's full list. + - name: entities_mode + type: enum + values: ["append", "replace"] + default: append + description: "`replace` (or `sync`) makes `entities` the payload's full list: entries with the `id` of one of the payload's entities update it, entries without one are created, and entities left out are deleted. Also read from `payload.entities_mode`." + - name: cod_amount + type: number + description: "Cash to collect on delivery, also accepted as `payload.cod_amount`. `0` clears it; a value that isn't numeric is ignored." - name: service_quote type: string description: Service quote ID to purchase and attach to the order. diff --git a/postman/collections/Fleetbase API/Orders/Update an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Update an Order.request.yaml index adb3e6e..01e6a81 100644 --- a/postman/collections/Fleetbase API/Orders/Update an Order.request.yaml +++ b/postman/collections/Fleetbase API/Orders/Update an Order.request.yaml @@ -1,6 +1,19 @@ $kind: http-request description: |- Updates an order and returns the updated order resource. You can update order metadata, notes, status, assignment fields, scheduling fields, proof-of-delivery settings, and payload details. + + What can still change depends on the order's status. A change the status locks is refused with `422` and nothing is written: `{ "error": "...", "locked": { "": "" } }`. The fields are `pickup`, `dropoff`, `return`, `waypoints`, `items` (the payload's entities), `schedule` (`scheduled_at`), `contact` (`customer`), `notes` and `cod_amount`: + + - Open (`created`, `pending`, `accepted`, `confirmed`, `preparing`, or a custom status before dispatch): everything can change. + - Ready (`ready`, `pickup_ready`, `ready_for_pickup`): items can only be removed and the schedule can only move earlier; everything else can change. + - With a driver (dispatched, started, `enroute`, `driver_*` and similar, or a custom status after dispatch): the route (the driver is rerouted), contact, notes and `cod_amount` can change; items and schedule are locked. + - Finished (`completed`, `canceled`, `expired`, or the order config's complete or terminal activity): nothing can change. + + Entities sent with the payload are appended by default. With `entities_mode` `replace` (at the top level or in `payload`), the list sent becomes the payload's full list: entries with an `id` of one of the payload's entities update it, entries without one are created, and entities left out are deleted. `cod_amount` is accepted at the top level or in `payload`, and `0` is kept (it clears the cash to collect). The order's current waypoint only moves when the route changed, so an order under way keeps its stop. + + An edit that changes anything broadcasts the realtime event `order.changed` (see the Orders folder) and, when the change concerns the driver, sends the assigned driver an `order_changed` push. + + The status guard, `entities_mode` and top-level `cod_amount` require Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). url: "{{base_url}}/{{namespace}}/orders/:id" method: PUT pathVariables: diff --git a/postman/collections/Fleetbase API/Payloads/Update a Payload.params.yaml b/postman/collections/Fleetbase API/Payloads/Update a Payload.params.yaml index b65df7f..2abe996 100644 --- a/postman/collections/Fleetbase API/Payloads/Update a Payload.params.yaml +++ b/postman/collections/Fleetbase API/Payloads/Update a Payload.params.yaml @@ -14,7 +14,12 @@ fields: description: Replacement waypoint list. Sending route endpoint fields without waypoints removes existing waypoints. - name: entities type: array of objects - description: Replacement entities, packages, or items attached to the payload. + description: Entities, packages, or items for the payload. Appended to its entities, or with `entities_mode` `replace`, the payload's full list. + - name: entities_mode + type: enum + values: ["append", "replace"] + default: append + description: "`replace` (or `sync`) makes `entities` the payload's full list: entries with the `id` of one of the payload's entities update it, entries without one are created, and entities left out are deleted." - name: type type: string description: Payload type. @@ -23,7 +28,7 @@ fields: description: Provider identifier associated with the payload. - name: cod_amount type: currency - description: Cash-on-delivery amount. + description: Cash-on-delivery amount. `0` is kept and clears the cash to collect. - name: cod_currency type: currency description: Three-letter currency code for `cod_amount`. diff --git a/postman/collections/Fleetbase API/Payloads/Update a Payload.request.yaml b/postman/collections/Fleetbase API/Payloads/Update a Payload.request.yaml index 59e024b..99c5f9e 100644 --- a/postman/collections/Fleetbase API/Payloads/Update a Payload.request.yaml +++ b/postman/collections/Fleetbase API/Payloads/Update a Payload.request.yaml @@ -1,6 +1,10 @@ $kind: http-request description: |- Updates a payload's route endpoints, waypoints, entities, cash-on-delivery settings, or metadata. The response returns the updated payload with route and entity data. + + Entities are appended by default; with `entities_mode` `replace` the list sent becomes the payload's full list (entries with the `id` of one of its entities update it, entries without one are created, and entities left out are deleted). A `cod_amount` of `0` is kept, clearing the cash to collect; other empty values are ignored as before. + + When the payload belongs to an order, the order's status decides what can still change, as for `Update an Order`: a locked change returns `422` `{ "error": "...", "locked": { "": "" } }` and nothing is written, and an accepted change broadcasts `order.changed`. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). url: "{{base_url}}/{{namespace}}/payloads/:id" method: PUT pathVariables: From 0ae1371e119f3998138048ac3a5c1c4ec869f0f1 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 19:23:03 +0800 Subject: [PATCH 3/6] Document comment visibility, comment.created and chat roles From fleetbase/core-api#306 (release v1.6.69): - Comments: visibility on create and on the resource; the public list and retrieve leave internal comments and replies out; comment.created event. - Chat: participant role and meta, message system and meta, null sender, and the Storefront order chat channel meta. --- .../Chat Channels/.resources/object.yaml | 30 +++++++++++++++---- .../Chat Channels/Send Message.request.yaml | 2 ++ .../Comments/.resources/definition.yaml | 4 +++ .../Comments/.resources/object.yaml | 9 ++++-- .../Comments/Create Comment.params.yaml | 4 +++ .../Comments/Create Comment.request.yaml | 2 ++ .../Comments/Query Comments.request.yaml | 2 +- .../Comments/Retrieve Comment.request.yaml | 2 +- 8 files changed, 46 insertions(+), 9 deletions(-) diff --git a/postman/collections/Fleetbase Core API/Chat Channels/.resources/object.yaml b/postman/collections/Fleetbase Core API/Chat Channels/.resources/object.yaml index d2afa96..cdb60e3 100644 --- a/postman/collections/Fleetbase Core API/Chat Channels/.resources/object.yaml +++ b/postman/collections/Fleetbase Core API/Chat Channels/.resources/object.yaml @@ -8,10 +8,30 @@ example: | "created_by": "user_8xP2L1d", "name": "Dispatch updates", "title": "Dispatch updates", - "last_message": null, + "last_message": { + "id": "chat_message_3Hk9mL2", + "chat_channel": "chat_channel_5VUl2v4", + "content": "Order updated: Drop-off changed to 8 Marina View", + "sender": null, + "system": true, + "meta": { "system": true, "kind": "order_updated" }, + "attachments": [], + "receipts": [], + "updated_at": "2026-10-13T09:30:00Z", + "created_at": "2026-10-13T09:30:00Z" + }, "slug": "dispatch-updates", "feed": [], - "participants": [], + "participants": [ + { + "id": "chat_participant_2Lm4kQ7", + "name": "Ada Buyer", + "role": "customer", + "meta": { "role": "customer" }, + "is_online": true, + "last_seen_at": "2026-10-13T09:29:00Z" + } + ], "meta": {}, "updated_at": "2026-05-07T09:30:00Z", "created_at": "2026-05-07T09:30:00Z", @@ -32,7 +52,7 @@ fields: description: "Display title for the channel." - name: last_message type: object - description: "Most recent chat message for the channel, when available." + description: "Most recent chat message for the channel, when available. Messages carry `system` (true for messages the system posts, such as `order_updated` or `address_change_requested`, read from `meta.system`) and `meta` (with `kind` for system messages). `sender` is the sending participant, or null for a message without one. Requires Core API with fleetbase/core-api#306 (release v1.6.69)." - name: unread_count type: integer description: "Unread message count for the current session user when session context is available." @@ -44,10 +64,10 @@ fields: description: "Messages and feed entries returned for the channel." - name: participants type: array of objects - description: "Chat participants in the channel." + description: "Chat participants in the channel. Each participant has `role` and `meta`: `role` is read from `meta.role` and is `customer`, `driver`, `store` or `system` when one of those was recorded (order chats record it), else null; `meta` is the participant's metadata (an empty object when unset). Requires Core API with fleetbase/core-api#306 (release v1.6.69)." - name: meta type: object - description: "Arbitrary metadata stored with the channel." + description: "Arbitrary metadata stored with the channel. Storefront order chats carry `storefront_order_uuid`, `storefront_order_id`, `order_public_id` and `kind` (`group` for the customer and driver thread, `customer` for the customer and store thread)." - name: updated_at type: timestamp description: "Time the channel was last updated." diff --git a/postman/collections/Fleetbase Core API/Chat Channels/Send Message.request.yaml b/postman/collections/Fleetbase Core API/Chat Channels/Send Message.request.yaml index 9b35fbb..fbdbbff 100644 --- a/postman/collections/Fleetbase Core API/Chat Channels/Send Message.request.yaml +++ b/postman/collections/Fleetbase Core API/Chat Channels/Send Message.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- Sends a message to a chat channel as an existing chat participant. File IDs can be included to attach previously uploaded files to the message. + + The response is the chat message, with `sender` (the participant, including its `role`), `system` and `meta` (see the Chat Channel object). url: "{{base_url}}/{{namespace}}/chat-channels/:id/send-message" method: POST pathVariables: diff --git a/postman/collections/Fleetbase Core API/Comments/.resources/definition.yaml b/postman/collections/Fleetbase Core API/Comments/.resources/definition.yaml index acc98b9..11c6d92 100644 --- a/postman/collections/Fleetbase Core API/Comments/.resources/definition.yaml +++ b/postman/collections/Fleetbase Core API/Comments/.resources/definition.yaml @@ -1,4 +1,8 @@ $kind: collection description: |- Comments attach discussion to Fleetbase resources or reply to existing comments. Use comment endpoints to create threaded context around orders, files, operational records, and other subject models. + + Comments are `internal` (notes for the organization's own users) or `customer` (may be shown to customers and drivers). The public API only returns customer-visible comments. + + New comments are broadcast as the realtime event `comment.created` on `company.{uuid}` and, for customer-visible comments, on the subject's channels (`{subject type}.{public_id}` and `{subject type}.{uuid}`, e.g. `order.order_7YqM3KpL2n`). Payload: `{ id, event, created_at, subject_id, data }`, where `data` is the comment and `subject_id` the subject's public ID. Requires Core API with fleetbase/core-api#306 (release v1.6.69). order: 4000 diff --git a/postman/collections/Fleetbase Core API/Comments/.resources/object.yaml b/postman/collections/Fleetbase Core API/Comments/.resources/object.yaml index dd2cf79..4fe9373 100644 --- a/postman/collections/Fleetbase Core API/Comments/.resources/object.yaml +++ b/postman/collections/Fleetbase Core API/Comments/.resources/object.yaml @@ -7,7 +7,8 @@ example: | "id": "comment_8JVZr4q", "content": "Please confirm the delivery window.", "tags": [], - "meta": {}, + "meta": { "visibility": "customer" }, + "visibility": "customer", "author": { "id": "user_8xP2L1d", "name": "Alex Rivera" @@ -31,12 +32,16 @@ fields: - name: meta type: object description: "Arbitrary metadata stored with the comment." + - name: visibility + type: enum + values: ["internal", "customer"] + description: "Who may see the comment, from `meta.visibility`: `internal` comments are notes for the organization's own users and never appear on the public API, `customer` comments may be shown to customers and drivers. A comment without a visibility is `customer`. Requires Core API with fleetbase/core-api#306 (release v1.6.69)." - name: author type: object description: "User that authored the comment." - name: replies type: array of objects - description: "Nested replies to the comment." + description: "Nested replies to the comment. On the public API, internal replies are left out." - name: editable type: boolean description: "Whether the current session user can edit the comment." diff --git a/postman/collections/Fleetbase Core API/Comments/Create Comment.params.yaml b/postman/collections/Fleetbase Core API/Comments/Create Comment.params.yaml index 21d751d..2b502af 100644 --- a/postman/collections/Fleetbase Core API/Comments/Create Comment.params.yaml +++ b/postman/collections/Fleetbase Core API/Comments/Create Comment.params.yaml @@ -25,3 +25,7 @@ fields: - name: parent type: string description: "Public ID of the parent comment when creating a reply." + - name: visibility + type: enum + values: ["internal", "customer"] + description: "Who may see the comment; any other value returns `422`. Stored as `meta.visibility`. When omitted, a reply takes its parent's visibility, a comment on an order created through this API is `customer` (and `internal` when written from the console), and a comment on any other subject has none, which counts as `customer`. Requires Core API with fleetbase/core-api#306 (release v1.6.69)." diff --git a/postman/collections/Fleetbase Core API/Comments/Create Comment.request.yaml b/postman/collections/Fleetbase Core API/Comments/Create Comment.request.yaml index a243c3e..3f65c1a 100644 --- a/postman/collections/Fleetbase Core API/Comments/Create Comment.request.yaml +++ b/postman/collections/Fleetbase Core API/Comments/Create Comment.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- Creates a comment on a subject resource or as a reply to an existing comment. Provide either a subject reference or a parent comment ID with the comment content. + + Set `visibility` to `internal` for a note only the organization's users should see, or `customer` for one customers and drivers may see (see the Comment object for the default). Internal comments are not returned by `Query Comments` or `Retrieve Comment`. Every new comment broadcasts the realtime event `comment.created` (see the Comments folder). Requires Core API with fleetbase/core-api#306 (release v1.6.69). url: "{{base_url}}/{{namespace}}/comments" method: POST body: diff --git a/postman/collections/Fleetbase Core API/Comments/Query Comments.request.yaml b/postman/collections/Fleetbase Core API/Comments/Query Comments.request.yaml index f7a19d5..4958fb0 100644 --- a/postman/collections/Fleetbase Core API/Comments/Query Comments.request.yaml +++ b/postman/collections/Fleetbase Core API/Comments/Query Comments.request.yaml @@ -1,6 +1,6 @@ $kind: http-request description: |- - Returns comments for the current organization. Use query parameters to filter, sort, and paginate the result set. + Returns comments for the current organization. Use query parameters to filter, sort, and paginate the result set. Internal comments (`visibility` `internal`) and internal replies are left out. Requires Core API with fleetbase/core-api#306 (release v1.6.69). url: "{{base_url}}/{{namespace}}/comments" method: GET queryParams: diff --git a/postman/collections/Fleetbase Core API/Comments/Retrieve Comment.request.yaml b/postman/collections/Fleetbase Core API/Comments/Retrieve Comment.request.yaml index 5b18054..1924191 100644 --- a/postman/collections/Fleetbase Core API/Comments/Retrieve Comment.request.yaml +++ b/postman/collections/Fleetbase Core API/Comments/Retrieve Comment.request.yaml @@ -1,6 +1,6 @@ $kind: http-request description: |- - Retrieves a comment by ID, including its author and any nested replies returned by the API resource. + Retrieves a comment by ID, including its author and any nested replies returned by the API resource. An internal comment returns `404` (`Comment resource not found.`), and internal replies are left out. Requires Core API with fleetbase/core-api#306 (release v1.6.69). url: "{{base_url}}/{{namespace}}/comments/:id" method: GET pathVariables: From 763e2b1824a060ad1c92368f7ae771fd72203dda Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 21:48:59 +0800 Subject: [PATCH 4/6] docs(orders): remove driver offers; only adhoc orders are offered Fleet-Ops drops driver offers in fleetbase/fleetops#398. Remove the Accept an Order and Decline an Order requests and their examples, the offer field on the Order object, the order.offer_* realtime events and the offer_timeout_minutes setting. The Orders folder now explains that an assigned driver simply has the order and only ad hoc orders are offered to nearby drivers, taken with Start an Order and assign. The expected_status override added for the offer requests is reverted, as nothing else in the Fleetbase API collection uses it. --- .../Fleetbase API/.resources/definition.yaml | 11 +--- .../examples/Forbidden.example.yaml | 27 --------- .../examples/OK.example.yaml | 58 ------------------ .../examples/OK.example.yaml | 59 ------------------- .../Unprocessable Content.example.yaml | 27 --------- .../Orders/.resources/definition.yaml | 7 +-- .../Orders/.resources/object.yaml | 4 -- .../Orders/Accept an Order.params.yaml | 5 -- .../Orders/Accept an Order.request.yaml | 40 ------------- .../Orders/Decline an Order.params.yaml | 8 --- .../Orders/Decline an Order.request.yaml | 39 ------------ 11 files changed, 4 insertions(+), 281 deletions(-) delete mode 100644 postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml delete mode 100644 postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index 3ff6a92..09fcd52 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -84,16 +84,7 @@ scripts: // started returning a document would still be caught. const contentType = pm.response.headers.get('Content-Type') || ''; const isDocument = /^(application\/pdf|image\/|application\/octet-stream|text\/csv)/i.test(contentType); - // A request whose CONTRACT is a refusal opts out of the 2xx expectation by setting - // `expected_status` in its beforeRequest script (as the Storefront collection does), - // e.g. answering a driver offer when the run has no order offered to a driver. The - // override is consumed here: "local" variables are scoped to the RUN, so left set it - // would hold every later request against the same status. - const expectedOverride = Number(pm.variables.get("expected_status")) || null; - pm.variables.unset("expected_status"); - const expected = expectedOverride - ? [expectedOverride] - : (pm.request.method === "POST" ? [200, 201, 202] : [200, 201, 202, 204]); + const expected = pm.request.method === "POST" ? [200, 201, 202] : [200, 201, 202, 204]; if (isDocument) { // Still asserted, not excused: a document response must carry actual bytes. diff --git a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml deleted file mode 100644 index 2e6fa66..0000000 --- a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/Forbidden.example.yaml +++ /dev/null @@ -1,27 +0,0 @@ -$kind: http-example -request: - url: "{{base_url}}/{{namespace}}/orders/:id/accept" - method: POST - headers: - Content-Type: application/json - Accept: application/json - pathVariables: - id: "order_7YqM3KpL2n" - body: - type: json - content: |- - { - "driver": "driver_8Tn4Wd1Zx6" - } -response: - statusCode: 403 - statusText: Forbidden - headers: - Content-Type: application/json - body: - type: json - content: |- - { - "error": "This order is not offered to you." - } -order: 2000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml deleted file mode 100644 index 506c9fa..0000000 --- a/postman/collections/Fleetbase API/Orders/.resources/Accept an Order.resources/examples/OK.example.yaml +++ /dev/null @@ -1,58 +0,0 @@ -$kind: http-example -request: - url: "{{base_url}}/{{namespace}}/orders/:id/accept" - method: POST - headers: - Content-Type: application/json - Accept: application/json - pathVariables: - id: "order_7YqM3KpL2n" - body: - type: json - content: |- - { - "driver": "driver_5Hk2Qp9Lm3" - } -response: - statusCode: 200 - statusText: OK - headers: - Content-Type: application/json - body: - type: json - content: |- - { - "id": "order_7YqM3KpL2n", - "internal_id": "ORD-1001", - "order_config": "order_config_transport", - "customer": null, - "payload": { "id": "payload_x9Z2Lm8QpA", "waypoints": [], "entities": [], "meta": {} }, - "facilitator": null, - "driver_assigned": { "id": "driver_5Hk2Qp9Lm3", "name": "Ravi Kumar" }, - "vehicle_assigned": null, - "tracking_number": { "id": "tracking_3Nf8Qw2YxM", "tracking_number": "FB-1001", "status": "dispatched", "status_code": "dispatched", "type": "order" }, - "tracking_statuses": [], - "files": [], - "purchase_rate": null, - "notes": "Deliver through receiving bay.", - "type": "transport", - "status": "dispatched", - "pod_method": null, - "pod_required": false, - "dispatched": true, - "started": false, - "adhoc": false, - "adhoc_distance": 0, - "distance": 0, - "time": 0, - "meta": { - "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": "2026-10-13T08:32:00Z", "declined_at": null, "status": "accepted" } - }, - "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": "2026-10-13T08:32:00Z", "declined_at": null, "status": "accepted" }, - "dispatched_at": "2026-10-13T08:30:00.000000Z", - "started_at": null, - "scheduled_at": null, - "updated_at": "2026-10-13T08:32:00.000000Z", - "created_at": "2026-10-13T08:20:00.000000Z" - } -order: 1000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml deleted file mode 100644 index 384770c..0000000 --- a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/OK.example.yaml +++ /dev/null @@ -1,59 +0,0 @@ -$kind: http-example -request: - url: "{{base_url}}/{{namespace}}/orders/:id/decline" - method: POST - headers: - Content-Type: application/json - Accept: application/json - pathVariables: - id: "order_7YqM3KpL2n" - body: - type: json - content: |- - { - "driver": "driver_5Hk2Qp9Lm3", - "reason": "Vehicle has a flat tyre." - } -response: - statusCode: 200 - statusText: OK - headers: - Content-Type: application/json - body: - type: json - content: |- - { - "id": "order_7YqM3KpL2n", - "internal_id": "ORD-1001", - "order_config": "order_config_transport", - "customer": null, - "payload": { "id": "payload_x9Z2Lm8QpA", "waypoints": [], "entities": [], "meta": {} }, - "facilitator": null, - "driver_assigned": null, - "vehicle_assigned": null, - "tracking_number": { "id": "tracking_3Nf8Qw2YxM", "tracking_number": "FB-1001", "status": "dispatched", "status_code": "dispatched", "type": "order" }, - "tracking_statuses": [], - "files": [], - "purchase_rate": null, - "notes": "Deliver through receiving bay.", - "type": "transport", - "status": "dispatched", - "pod_method": null, - "pod_required": false, - "dispatched": true, - "started": false, - "adhoc": false, - "adhoc_distance": 0, - "distance": 0, - "time": 0, - "meta": { - "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": null, "declined_at": "2026-10-13T08:32:00Z", "status": "declined", "reason": "Vehicle has a flat tyre." } - }, - "offer": { "driver": "driver_5Hk2Qp9Lm3", "offered_at": "2026-10-13T08:30:00Z", "expires_at": "2026-10-13T08:35:00Z", "accepted_at": null, "declined_at": "2026-10-13T08:32:00Z", "status": "declined", "reason": "Vehicle has a flat tyre." }, - "dispatched_at": "2026-10-13T08:30:00.000000Z", - "started_at": null, - "scheduled_at": null, - "updated_at": "2026-10-13T08:32:00.000000Z", - "created_at": "2026-10-13T08:20:00.000000Z" - } -order: 1000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml b/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml deleted file mode 100644 index a3dd3ba..0000000 --- a/postman/collections/Fleetbase API/Orders/.resources/Decline an Order.resources/examples/Unprocessable Content.example.yaml +++ /dev/null @@ -1,27 +0,0 @@ -$kind: http-example -request: - url: "{{base_url}}/{{namespace}}/orders/:id/decline" - method: POST - headers: - Content-Type: application/json - Accept: application/json - pathVariables: - id: "order_7YqM3KpL2n" - body: - type: json - content: |- - { - "driver": "driver_5Hk2Qp9Lm3" - } -response: - statusCode: 422 - statusText: Unprocessable Content - headers: - Content-Type: application/json - body: - type: json - content: |- - { - "error": "This order has already been started and cannot be declined." - } -order: 2000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/definition.yaml b/postman/collections/Fleetbase API/Orders/.resources/definition.yaml index 8108d2c..7dbd717 100644 --- a/postman/collections/Fleetbase API/Orders/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/Orders/.resources/definition.yaml @@ -2,15 +2,14 @@ $kind: collection description: |- Orders represent work to be planned, dispatched, tracked, and completed. Use order endpoints to create delivery or service jobs, assign resources, manage status, capture proof of delivery, and record activity. - ## Driver offers + ## Ad hoc orders - Assigning a driver records an offer on the order (`offer`). The driver answers it with `Accept an Order` or `Decline an Order`; an offer that is not answered within the company setting `fleetops.offer_timeout_minutes` (default 5 minutes, `0` for never) is marked `expired` and dispatch is alerted (`driver_not_accepted`). + A driver assigned to an order, by dispatch or auto-assign, has the order; there is nothing for the driver to accept. Only ad hoc orders (`adhoc: true`) are offered to drivers: dispatching one notifies the drivers within `adhoc_distance`, and Fleet-Ops notifies them again every minute until a driver takes it. The first driver to take it does so with `Start an Order`, sending their driver id as `assign`. ## Realtime events Order changes are broadcast on the socket channels below. Every event has the envelope `{ id, api_version, event, created_at, data }`. These events require Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). - `order.changed`: the order's route, items, contact, schedule, notes or amounts were edited after it was placed (through `Update an Order`, `Update a Payload`, the entity endpoints, the console or Storefront). Channels: `order.{public_id}` and `order.{uuid}`, `driver.{public_id}` and `driver.{uuid}` of the assigned driver, `customer.{public_id}` and `company.{uuid}`. Data: `{ id, changes, summary, dropoff: { id, address, location }, cod_amount, total, currency, changed_by, changed_at }`; `changes` lists the parts that changed (`pickup`, `dropoff`, `return`, `waypoints`, `items`, `schedule`, `contact`, `notes`, `cod_amount`, ...) and `summary` is one readable line. When the change concerns the driver, the assigned driver also gets an `order_changed` push. - - `order.driver_unassigned`: a driver was taken off the order (unassigned or replaced). Channels: `driver.{public_id}` and `driver.{uuid}` of the previous driver, and `company.{uuid}`. Data: `{ id, driver }`. The previous driver also gets an `order_unassigned` push, unless they declined the order themselves. - - `order.offer_accepted`, `order.offer_declined`, `order.offer_expired`: the driver offer was answered or ran out. Channels: `order.{public_id}`, `order.{uuid}` and `company.{uuid}`. Data: `{ id, driver, offer }`. + - `order.driver_unassigned`: a driver was taken off the order (unassigned or replaced). Channels: `driver.{public_id}` and `driver.{uuid}` of the previous driver, and `company.{uuid}`. Data: `{ id, driver }`. The previous driver also gets an `order_unassigned` push. order: 8000 diff --git a/postman/collections/Fleetbase API/Orders/.resources/object.yaml b/postman/collections/Fleetbase API/Orders/.resources/object.yaml index 81bb7e0..5106994 100644 --- a/postman/collections/Fleetbase API/Orders/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Orders/.resources/object.yaml @@ -43,7 +43,6 @@ example: | "distance": 0, "time": 0, "meta": {}, - "offer": null, "dispatched_at": null, "started_at": null, "scheduled_at": null, @@ -129,9 +128,6 @@ fields: - name: meta type: object description: Additional order metadata. - - name: offer - type: object - description: "The offer to the assigned driver, mirrored from `meta.offer`, or null: `{ driver, offered_at, expires_at, accepted_at, declined_at, status, reason }`. `status` is `offered`, `accepted`, `declined` or `expired`. An offer is recorded when a driver is assigned and expires after the company setting `fleetops.offer_timeout_minutes` (default 5; `0` never expires, `expires_at` null). An ad hoc driver who takes the order, or a driver who starts it, is recorded as accepted. `reason` is present when the driver gave one on decline. Also included in order webhook payloads. Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72)." - name: dispatched_at type: timestamp description: Timestamp when the order was dispatched. diff --git a/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml deleted file mode 100644 index e27f592..0000000 --- a/postman/collections/Fleetbase API/Orders/Accept an Order.params.yaml +++ /dev/null @@ -1,5 +0,0 @@ -$kind: params -fields: - - name: driver - type: string - description: "Public ID of the driver accepting the order. Defaults to the driver profile of the authenticated user." diff --git a/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml deleted file mode 100644 index ab0066c..0000000 --- a/postman/collections/Fleetbase API/Orders/Accept an Order.request.yaml +++ /dev/null @@ -1,40 +0,0 @@ -$kind: http-request -description: |- - The assigned driver accepts the order offered to them, and the order is returned with `offer.status` `accepted`. An ad hoc order that has no driver yet is assigned to the accepting driver. An offer that has expired can still be accepted while the driver is assigned. - - The driver is the one named by `driver`, or else the driver profile of the authenticated user (a driver signed in to the Navigator app). Dispatch and the order's channels receive the realtime event `order.offer_accepted`. Starting the order also accepts an open offer. - - When a driver is assigned, Fleetbase records an offer on the order (`offer`, see the Order object) that expires after the company setting `fleetops.offer_timeout_minutes` (default 5; 0 never expires). An expired offer stays with the driver, is marked `expired`, raises a `driver_not_accepted` alert for dispatch and broadcasts `order.offer_expired`. - - Errors: `404` for an unknown order (`Order resource not found.`) or when no driver is found (`No driver found to accept the order.`), `422` when the order is completed, canceled or expired (`This order can no longer be accepted.`), `403` when the order is assigned to another driver (`This order is not offered to you.`). Error bodies are `{ "error": "..." }`. - - Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). In a contract run this answers for `{{offer_order_id}}` and `{{offer_driver_id}}` when the environment sets them (an order assigned to that driver and not started). Otherwise it addresses an unknown order and asserts the `404`. -url: "{{base_url}}/{{namespace}}/orders/:id/accept" -method: POST -headers: - Content-Type: application/json - Accept: application/json -pathVariables: - - key: id - value: "{{offer_order_id}}" - description: (Required) The id of the order offered to the driver. -body: - type: json - content: |- - { - "driver": "{{offer_driver_id}}" - } -scripts: - - type: beforeRequest - code: |- - // Answering an offer needs an order assigned to a driver that has not started, and - // that driver. A contract run seeds neither: without {{offer_order_id}} and - // {{offer_driver_id}} in the environment the request addresses an order that - // doesn't exist and asserts the 404 refusal instead. - if (!pm.environment.get("offer_order_id") || !pm.environment.get("offer_driver_id")) { - pm.variables.set("offer_order_id", "order_missing"); - pm.variables.set("expected_status", 404); - } - language: text/javascript -examples: ./.resources/Accept an Order.resources/examples -order: 12500 diff --git a/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml b/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml deleted file mode 100644 index 25a1ee1..0000000 --- a/postman/collections/Fleetbase API/Orders/Decline an Order.params.yaml +++ /dev/null @@ -1,8 +0,0 @@ -$kind: params -fields: - - name: driver - type: string - description: "Public ID of the driver declining the order. Defaults to the driver profile of the authenticated user." - - name: reason - type: string - description: "Why the driver declines, recorded on the offer and sent to dispatch. Trimmed to 1000 characters." diff --git a/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml deleted file mode 100644 index f71f0e6..0000000 --- a/postman/collections/Fleetbase API/Orders/Decline an Order.request.yaml +++ /dev/null @@ -1,39 +0,0 @@ -$kind: http-request -description: |- - The assigned driver declines the order offered to them. The driver is taken off the order, the offer is recorded as `declined` (with the `reason`, when given), dispatch is notified, and the realtime events `order.offer_declined` (order channels) and `order.driver_unassigned` (the driver's channels) are broadcast. The order is returned without a driver, ready to be assigned again. - - The driver is the one named by `driver`, or else the driver profile of the authenticated user. - - Errors: `404` for an unknown order (`Order resource not found.`) or when no driver is found (`No driver found to decline the order.`), `403` when the order isn't assigned to this driver (`This order is not offered to you.`), `422` once the order has started (`This order has already been started and cannot be declined.`). Error bodies are `{ "error": "..." }`. - - Requires Fleet-Ops with fleetbase/fleetops#394 (release v0.6.72). In a contract run this answers for `{{offer_order_id}}` and `{{offer_driver_id}}` when the environment sets them (an order assigned to that driver and not started). Otherwise it addresses an unknown order and asserts the `404`. -url: "{{base_url}}/{{namespace}}/orders/:id/decline" -method: POST -headers: - Content-Type: application/json - Accept: application/json -pathVariables: - - key: id - value: "{{offer_order_id}}" - description: (Required) The id of the order offered to the driver. -body: - type: json - content: |- - { - "driver": "{{offer_driver_id}}", - "reason": "Vehicle has a flat tyre." - } -scripts: - - type: beforeRequest - code: |- - // Answering an offer needs an order assigned to a driver that has not started, and - // that driver. A contract run seeds neither: without {{offer_order_id}} and - // {{offer_driver_id}} in the environment the request addresses an order that - // doesn't exist and asserts the 404 refusal instead. - if (!pm.environment.get("offer_order_id") || !pm.environment.get("offer_driver_id")) { - pm.variables.set("offer_order_id", "order_missing"); - pm.variables.set("expected_status", 404); - } - language: text/javascript -examples: ./.resources/Decline an Order.resources/examples -order: 12600 From 4e42795beba2baa7cb0794f2bb5ced2cfb32e8e1 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 22:56:30 +0800 Subject: [PATCH 5/6] docs(storefront): items have no packed flag; every item is taken as packed unless marked unavailable --- .../Fleetbase Storefront API/Orders/.resources/object.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml b/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml index 95c0fac..949667d 100644 --- a/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml +++ b/postman/collections/Fleetbase Storefront API/Orders/.resources/object.yaml @@ -14,7 +14,7 @@ example: | { "id": "entity_6Lp2wQ9", "name": "Cold Brew", - "meta": { "product_id": "product_9Lb2mQ1", "quantity": 2, "note": "No ice, please", "packed_at": "2026-10-13T09:41:00+00:00", "unavailable": false } + "meta": { "product_id": "product_9Lb2mQ1", "quantity": 2, "note": "No ice, please", "unavailable": false } }, { "id": "entity_3Hd8vK5", @@ -133,4 +133,4 @@ fields: description: "Set by `Request Address Change`: `{ status, message, place, address, at }`. `status` is `open` until the store updates the address (`resolved`) or dismisses the request (`dismissed`), which also adds `closed_at`; `place` and `address` are the saved place the customer suggested, or null." - name: payload.entities[].meta type: object - description: "Per item, besides the cart line data (`product_id`, `quantity`, ...): `note` (the store's note on the item), `packed_at` (when the store packed it), `unavailable` (the store marked it unavailable), `substitute_for` (the entity id this item replaces) and `added_by_edit` (added when the store edited the order)." + description: "Per item, besides the cart line data (`product_id`, `quantity`, ...): `note` (the store's note on the item), `unavailable` (the store marked it unavailable; every other item is taken as packed and delivered), `substitute_for` (the entity id this item replaces) and `added_by_edit` (added when the store edited the order)." From 441175674c596beca24000caf43d28beeed7797b Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 14 Oct 2026 03:06:30 +0800 Subject: [PATCH 6/6] docs(storefront): the balance-paid callback, and the order_item_unavailable and order_balance_paid notifications; a moved booking's order_updated --- .../Notifications/.resources/object.yaml | 6 ++--- .../Balance Paid Callback.queryParams.yaml | 5 ++++ .../Orders/Balance Paid Callback.request.yaml | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.queryParams.yaml create mode 100644 postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.request.yaml diff --git a/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml b/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml index 9d4d577..e25c89b 100644 --- a/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml +++ b/postman/collections/Fleetbase Storefront API/Notifications/.resources/object.yaml @@ -1,7 +1,7 @@ $kind: object name: Notification description: |- - An inbox item. `type` identifies what the notification is about (for example `order_accepted`, `order_updated`, `order_completed`, `promotional` or `campaign`), and `data` carries the identifiers the app needs to deep link. Recipient details are never included. + An inbox item. `type` identifies what the notification is about (for example `order_accepted`, `order_updated`, `order_item_unavailable`, `order_completed`, `promotional` or `campaign`), and `data` carries the identifiers the app needs to deep link. Recipient details are never included. example: | { "id": "0f4c9a52-8d3f-4a6e-9f0e-2b1f7f2f8c11", @@ -24,13 +24,13 @@ fields: description: "Notification id (uuid)." - name: type type: string - description: "What the notification is about, e.g. `order_accepted`, `order_enroute`, `order_nearby`, `order_completed`, `order_canceled`, `order_ready`, `order_driver_assigned`, `promotional` or `campaign`." + description: "What the notification is about, e.g. `order_accepted`, `order_enroute`, `order_nearby`, `order_completed`, `order_canceled`, `order_ready`, `order_driver_assigned`, `order_updated` (the store edited the order or moved a booking), `order_item_unavailable` (the store marked an item unavailable), `order_balance_paid` (a balance due was paid by its payment link), `promotional` or `campaign`." - name: title type: string description: "Notification title." - name: body type: string - description: "Notification message. For `order_updated` (the store edited the order) it says what changed and what that means for the customer's money: refunded, returned as store credit with the code to use, or a balance to pay with its payment link. For `order_canceled` it gives the store's reason, when there is one, and whether the payment was refunded or returned as store credit." + description: "Notification message. For `order_updated` (the store edited the order) it says what changed and what that means for the customer's money: refunded, returned as store credit with the code to use, or a balance to pay with its payment link. A booking that was only moved says when it is now, in the store's time (`Deep Clean is now Mon 19 Oct, 11:00 to 12:00.`). For `order_item_unavailable` it names the item and says the store will replace or remove it and adjust the total. For `order_balance_paid` it gives the amount received and anything still left to pay. For `order_canceled` it gives the store's reason, when there is one, and whether the payment was refunded or returned as store credit." - name: image type: string description: "Optional image URL." diff --git a/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.queryParams.yaml b/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.queryParams.yaml new file mode 100644 index 0000000..96ecca3 --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.queryParams.yaml @@ -0,0 +1,5 @@ +$kind: params +fields: + - name: order + type: string + description: "Public ID of the order whose balance invoice was paid. QPay may also POST it in the body; either works." diff --git a/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.request.yaml b/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.request.yaml new file mode 100644 index 0000000..5329337 --- /dev/null +++ b/postman/collections/Fleetbase Storefront API/Orders/Balance Paid Callback.request.yaml @@ -0,0 +1,24 @@ +$kind: http-request +description: |- + QPay's callback for a balance invoice: when the store edits a paid order and the total goes up, the customer is sent a payment link for the balance (a QPay invoice, or a Stripe Payment Link), and QPay calls this URL when the invoice is paid. It needs no storefront key; Storefront builds the URL with the order's public id when it creates the invoice. + + Storefront never trusts the callback itself: it asks the gateway what was paid against the order's open balance link. When the balance is paid it records the charge on the order's payment (`meta.payment`), closes the link and sends the customer the `order_balance_paid` notification. Stripe Payment Links have no callback; Storefront checks them every five minutes (`storefront:check-balance-payments`). + + Response: `{ "status": "paid" }` when the balance was settled now, `{ "status": "pending" }` when nothing (more) is paid or the gateway can't be asked, and `404` with `{ "status": "unknown" }` for an unknown order. Calling it again after the balance is settled answers `pending`. + + In a contract run this addresses `{{balance_order_id}}` when the environment sets one. Otherwise it addresses an unknown order and asserts the `404`. +url: "{{base_url}}/{{api_prefix}}/{{namespace}}/orders/balance-paid" +method: GET +queryParams: + order: "{{balance_order_id}}" +scripts: + - type: beforeRequest + code: |- + // A contract run has no order with an open balance link: without one this asks about + // an order that doesn't exist and asserts the 404. + if (!pm.environment.get('balance_order_id')) { + pm.variables.set('balance_order_id', 'order_missing'); + pm.variables.set('expected_status', 404); + } + language: text/javascript +order: 3000