From beab11d4179c9a02511bc7099c64e5b0cb52eb29 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 4 Sep 2026 18:54:53 +0800 Subject: [PATCH 1/3] Document and test expanded Fleet resource APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the Fleetbase API collection in line with the expanded public Fleet, Vehicle and Driver contracts, and adds request-specific response assertions so a collection run proves the behaviour rather than only that a 2xx came back. Fleets - Create/Update accept name, color, task, status and the service_area, zone, vendor and parent_fleet relationships as public IDs. - New "Create a Subfleet" request covering the parent_fleet hierarchy, with 422 self-parent and 404 cross-company examples. - Four new membership requests, plus a repeat of each, covering assignment, idempotent reassignment, removal and no-op re-removal for both vehicles and drivers, with a single stable response shape. - Relationships documented as public IDs, with the nested-object shape kept for ?with= requests. - Query parameters documented: name, public_id, status, task, parent_fleet, parents_only, vendor, service_area, zone. Vehicles - Every safe business field the vehicle record holds is now documented on create and update. - The status enum is reconciled with the implementation: nineteen values were accepted while three were documented. - Query parameters documented, including internal_id, which an importer keys on. Drivers - email and phone documented as optional, with a dedicated "Create an Operational Driver" request for a record with neither. - Identity, operational, structured and orchestrator fields documented. - password removed from the update contract, which the API does not accept. - Query parameters documented, including internal_id and licence number. The Vehicles and Drivers folders delete their own resources before the Fleets folder runs, so the membership requests create and clean up a vehicle and a driver of their own rather than reaching for ids that no longer resolve. Validation: npm run postman:lint — 0 errors, 0 warnings across all five collections. Live collection execution is pending deployment of the Fleet-Ops branch. No credentials, real API keys or customer data are included. --- .../Fleetbase API/.resources/definition.yaml | 11 + .../examples/OK.example.yaml | 107 ++++--- .../Unprocessable Entity.example.yaml | 29 ++ .../examples/OK.example.yaml | 42 +++ .../examples/OK.example.yaml | 57 ++-- .../examples/OK.example.yaml | 58 ++-- .../examples/OK.example.yaml | 99 +++--- .../Drivers/.resources/object.yaml | 51 ++- .../Drivers/Create a Driver.params.yaml | 86 +++-- .../Drivers/Create a Driver.request.yaml | 55 +++- .../Create an Operational Driver.params.yaml | 15 + .../Create an Operational Driver.request.yaml | 47 +++ .../Delete an Operational Driver.request.yaml | 18 ++ .../Drivers/Query Drivers.queryParams.yaml | 46 ++- .../Drivers/Update a Driver.params.yaml | 79 ++++- .../Drivers/Update a Driver.request.yaml | 58 +++- .../examples/OK.example.yaml | 23 ++ .../examples/Not Found.example.yaml | 21 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 44 +-- .../examples/Not Found.example.yaml | 25 ++ .../examples/OK.example.yaml | 39 +++ .../Unprocessable Entity.example.yaml | 24 ++ .../examples/OK.example.yaml | 48 +-- .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 23 ++ .../examples/OK.example.yaml | 21 +- .../examples/OK.example.yaml | 46 +-- .../Fleets/.resources/definition.yaml | 17 + .../Fleets/.resources/object.yaml | 45 ++- .../Assign a Driver to a Fleet.request.yaml | 48 +++ .../Assign a Vehicle to a Fleet.request.yaml | 48 +++ .../Fleets/Create a Fleet Driver.request.yaml | 28 ++ .../Create a Fleet Vehicle.request.yaml | 32 ++ .../Fleets/Create a Fleet.params.yaml | 25 +- .../Fleets/Create a Fleet.request.yaml | 44 ++- .../Fleets/Create a Subfleet.params.yaml | 20 ++ .../Fleets/Create a Subfleet.request.yaml | 52 +++ .../Fleets/Delete a Fleet Driver.request.yaml | 18 ++ .../Delete a Fleet Vehicle.request.yaml | 18 ++ .../Fleets/Delete a Subfleet.request.yaml | 21 ++ .../Fleets/Query Fleets.queryParams.yaml | 33 +- .../Reassign a Driver to a Fleet.request.yaml | 54 ++++ ...Reassign a Vehicle to a Fleet.request.yaml | 54 ++++ ...e a Driver from a Fleet Again.request.yaml | 37 +++ .../Remove a Driver from a Fleet.request.yaml | 37 +++ ... a Vehicle from a Fleet Again.request.yaml | 37 +++ ...Remove a Vehicle from a Fleet.request.yaml | 37 +++ .../Fleets/Update a Fleet.params.yaml | 24 +- .../Fleets/Update a Fleet.request.yaml | 48 ++- .../examples/Created.example.yaml | 87 +++--- .../examples/Not Found.example.yaml | 25 ++ .../Unprocessable Entity.example.yaml | 29 ++ .../examples/OK.example.yaml | 100 ++---- .../Vehicles/.resources/object.yaml | 172 +++++++++- .../Vehicles/Create a Vehicle.params.yaml | 295 +++++++++++++++--- .../Vehicles/Create a Vehicle.request.yaml | 85 ++++- .../Vehicles/Query Vehicles.queryParams.yaml | 43 ++- .../Vehicles/Update a Vehicle.params.yaml | 294 ++++++++++++++--- .../Vehicles/Update a Vehicle.request.yaml | 56 +++- 64 files changed, 2783 insertions(+), 490 deletions(-) create mode 100644 postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/Unprocessable Entity.example.yaml create mode 100644 postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Drivers/Create an Operational Driver.params.yaml create mode 100644 postman/collections/Fleetbase API/Drivers/Create an Operational Driver.request.yaml create mode 100644 postman/collections/Fleetbase API/Drivers/Delete an Operational Driver.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Assign a Driver to a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/Not Found.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Not Found.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Unprocessable Entity.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Reassign a Driver to a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Reassign a Vehicle to a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet Again.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet Again.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Assign a Driver to a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Assign a Vehicle to a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Create a Fleet Driver.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Create a Fleet Vehicle.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Create a Subfleet.params.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Delete a Fleet Driver.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Delete a Fleet Vehicle.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Delete a Subfleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Reassign a Driver to a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Reassign a Vehicle to a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet Again.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet Again.request.yaml create mode 100644 postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet.request.yaml create mode 100644 postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Not Found.example.yaml create mode 100644 postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Unprocessable Entity.example.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index becee79..de291f3 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -57,6 +57,17 @@ variables: driver_password_reset_code: "" manifest_id: "" manifest_stop_id: "" + # Fleet hierarchy. The parent is the fleet created first; the subfleet is + # created beneath it and deleted before it. + parent_fleet_id: "" + subfleet_id: "" + # The Vehicles and Drivers folders delete their own resources before the + # Fleets folder runs, so the membership requests create — and clean up — a + # vehicle and a driver of their own. + fleet_vehicle_id: "" + fleet_driver_id: "" + # A driver created with neither an email address nor a phone number. + operational_driver_id: "" scripts: - type: http:afterResponse code: | diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml index 82c735e..daf3063 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml @@ -1,37 +1,29 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/drivers?name=&email=&password=&phone_number=&phone_country_code=&country=&city=&vehicle=&status=" + url: "{{base_url}}/{{namespace}}/drivers" method: POST headers: Accept: application/json - queryParams: - - key: name - value: - description: (Required) The name of the driver. - - key: email - value: - description: (Required) The email of the driver. - - key: password - value: - description: (Required) The password the driver will use for authenticating Fleetbase Navigator. - - key: phone_number - value: - description: (Required) The drivers phone number, this can also be used to authenticating Fleetbase Navigator. - - key: phone_country_code - value: - description: (Required) The country code of the drivers phone number. - - key: country - value: - description: "(Required) The 2 letter country code of the country the driver is operating in. Example: 'US' or 'DE'. All accepted values can be found on the Official ISO Website. Sending a country is always required." - - key: city - value: - description: The city the driver is operating in. - - key: vehicle - value: - description: An Vehicle object id that you want to assign to the driver. - - key: status - value: - description: The drivers standing status. Can either be "active" or "inactive". Defaults to "active" + body: + type: json + content: |- + { + "name": "John Doe", + "email": "john.doe@example.com", + "phone": "+15555550111", + "internal_id": "DRV-1001", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "country": "SG", + "city": "Singapore", + "current_status": "on_duty", + "skills": ["hazmat"], + "max_travel_time": 28800, + "max_distance": 250000, + "time_window_start": "08:00", + "time_window_end": "18:00", + "meta": { "badge": "A12" } + } response: statusCode: 200 statusText: OK @@ -41,25 +33,42 @@ response: type: json content: |- { - "id": "", - "current_job": "", - "vehicle": "", - "email": "", - "phone_number": "", - "phone_country_code": "", - "signup_token_used": "", - "drivers_license_number": "", - "latitude": "", - "longitude": "", - "heading": "", - "altitude": "", - "speed": "", - "country": "", - "currency": "", - "city": "", - "online": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "driver_A1b2C3d4E5", + "user": "user_1A2b3C4d5E", + "internal_id": "DRV-1001", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", + "name": "John Doe", + "email": "john.doe@example.com", + "phone": "+15555550111", + "timezone": "Asia/Singapore", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "photo_url": null, + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png", + "vehicle_avatar": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg", + "vehicle_id": null, + "vendor_id": null, + "job_id": null, + "location": { "type": "Point", "coordinates": [0, 0] }, + "heading": 0, + "bearing": null, + "altitude": 0, + "speed": 0, + "country": "SG", + "currency": "SGD", + "city": "Singapore", + "online": false, + "current_status": "on_duty", + "status": "available", + "token": null, + "skills": ["hazmat"], + "max_travel_time": 28800, + "max_distance": 250000, + "time_window_start": "08:00:00", + "time_window_end": "18:00:00", + "meta": { "badge": "A12" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/Unprocessable Entity.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/Unprocessable Entity.example.yaml new file mode 100644 index 0000000..96d01ce --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/Unprocessable Entity.example.yaml @@ -0,0 +1,29 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/drivers" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "name": "John Doe", + "email": "not-an-email", + "phone": "+15555550111" + } +response: + statusCode: 422 + statusText: Unprocessable Entity + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "errors": [ + "The email address must be a valid email address.", + "The phone number has already been taken." + ] + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml new file mode 100644 index 0000000..5847808 --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml @@ -0,0 +1,42 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/drivers" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "name": "Yard Driver", + "internal_id": "DRV-7788" + } +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "id": "driver_Z9y8X7w6V5", + "user": "user_9K8j7H6g5F", + "internal_id": "DRV-7788", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", + "name": "Yard Driver", + "email": null, + "phone": null, + "drivers_license_number": null, + "license_expiry": null, + "vehicle_id": null, + "vendor_id": null, + "job_id": null, + "online": false, + "status": "available", + "meta": {}, + "updated_at": "2026-05-07T08:32:00.000000Z", + "created_at": "2026-05-07T08:32:00.000000Z" + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml index c00611b..5b8a548 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml @@ -1,13 +1,17 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/drivers?id=" + url: "{{base_url}}/{{namespace}}/drivers?internal_id=DRV-1001&limit=25" method: GET headers: Accept: application/json queryParams: - - key: id - value: - description: The id of the driver you want to retrieve. + - key: internal_id + value: DRV-1001 + description: |- + The identifier the operator's own system uses. This is the lookup that decides whether a driver already exists before one is created. + - key: limit + value: "25" + description: A limit on the number of objects to be returned. response: statusCode: 200 statusText: OK @@ -16,26 +20,27 @@ response: body: type: json content: |- - { - "id": "", - "current_job": "", - "vehicle": "", - "email": "", - "phone_number": "", - "phone_country_code": "", - "signup_token_used": "", - "drivers_license_number": "", - "latitude": "", - "longitude": "", - "heading": "", - "altitude": "", - "speed": "", - "country": "", - "currency": "", - "city": "", - "online": "", - "status": "", - "updated_at": "", - "created_at": "" - } + [ + { + "id": "driver_A1b2C3d4E5", + "user": "user_1A2b3C4d5E", + "internal_id": "DRV-1001", + "name": "John Doe", + "email": "john.doe@example.com", + "phone": "+15555550111", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "vehicle_id": null, + "vendor_id": null, + "job_id": null, + "country": "SG", + "city": "Singapore", + "online": false, + "current_status": "on_duty", + "status": "available", + "meta": { "badge": "A12" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" + } + ] order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml index 0f10f28..c7f9d68 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml @@ -5,7 +5,7 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "driver_A1b2C3d4E5" response: statusCode: 200 statusText: OK @@ -15,25 +15,41 @@ response: type: json content: |- { - "id": "", - "current_job": "", - "vehicle": "", - "email": "", - "phone_number": "", - "phone_country_code": "", - "signup_token_used": "", - "drivers_license_number": "", - "latitude": "", - "longitude": "", - "heading": "", - "altitude": "", - "speed": "", - "country": "", - "currency": "", - "city": "", - "online": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "driver_A1b2C3d4E5", + "user": "user_1A2b3C4d5E", + "internal_id": "DRV-1002", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", + "name": "John Doe", + "email": "john.doe@example.com", + "phone": "+15555550111", + "timezone": "Asia/Singapore", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "photo_url": null, + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png", + "vehicle_id": "vehicle_7YqM3KpL2n", + "vendor_id": null, + "job_id": null, + "location": { "type": "Point", "coordinates": [103.8198, 1.3521] }, + "heading": 0, + "bearing": null, + "altitude": 0, + "speed": 0, + "country": "SG", + "currency": "SGD", + "city": "Johor Bahru", + "online": false, + "current_status": "off_duty", + "status": "available", + "token": null, + "skills": ["hazmat"], + "max_travel_time": 28800, + "max_distance": 250000, + "time_window_start": "08:00:00", + "time_window_end": "18:00:00", + "meta": { "badge": "B34" }, + "updated_at": "2026-05-07T09:12:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml index b503bbc..7c9fb3f 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml @@ -1,39 +1,22 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/drivers/:id?name=&email=&password=&phone_number=&phone_country_code=&country=&city=&vehicle=&status=" + url: "{{base_url}}/{{namespace}}/drivers/:id" method: PUT headers: Accept: application/json - queryParams: - - key: name - value: - description: The name you want to update for the driver. - - key: email - value: - description: The email to update for the driver. - - key: password - value: - description: The password to update for the driver - - key: phone_number - value: - description: The drivers phone number to update for the driver - - key: phone_country_code - value: - description: The country code of the driver to update for the driver. - - key: country - value: - description: The 2 letter country code of the country to update for the driver. - - key: city - value: - description: The city to update for the driver - - key: vehicle - value: - description: An Vehicle object id that you want to assign to the driver. - - key: status - value: - description: The drivers standing status. Can either be "active" or "inactive". Defaults to "active". pathVariables: - id: "" + id: "driver_A1b2C3d4E5" + body: + type: json + content: |- + { + "name": "John Doe", + "internal_id": "DRV-1002", + "city": "Johor Bahru", + "current_status": "off_duty", + "meta": { "badge": "B34" }, + "vehicle": "vehicle_7YqM3KpL2n" + } response: statusCode: 200 statusText: OK @@ -43,25 +26,41 @@ response: type: json content: |- { - "id": "", - "current_job": "", - "vehicle": "", - "email": "", - "phone_number": "", - "phone_country_code": "", - "signup_token_used": "", - "drivers_license_number": "", - "latitude": "", - "longitude": "", - "heading": "", - "altitude": "", - "speed": "", - "country": "", - "currency": "", - "city": "", - "online": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "driver_A1b2C3d4E5", + "user": "user_1A2b3C4d5E", + "internal_id": "DRV-1002", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", + "name": "John Doe", + "email": "john.doe@example.com", + "phone": "+15555550111", + "timezone": "Asia/Singapore", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "photo_url": null, + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png", + "vehicle_id": "vehicle_7YqM3KpL2n", + "vendor_id": null, + "job_id": null, + "location": { "type": "Point", "coordinates": [103.8198, 1.3521] }, + "heading": 0, + "bearing": null, + "altitude": 0, + "speed": 0, + "country": "SG", + "currency": "SGD", + "city": "Johor Bahru", + "online": false, + "current_status": "off_duty", + "status": "available", + "token": null, + "skills": ["hazmat"], + "max_travel_time": 28800, + "max_distance": 250000, + "time_window_start": "08:00:00", + "time_window_end": "18:00:00", + "meta": { "badge": "B34" }, + "updated_at": "2026-05-07T09:12:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/.resources/object.yaml b/postman/collections/Fleetbase API/Drivers/.resources/object.yaml index 02c0ab0..da6e618 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/object.yaml @@ -1,7 +1,9 @@ $kind: object name: Driver description: |- - A driver represents a FleetOps operator who can receive jobs, report location, register devices, and belong to fleets, vendors, vehicles, and organizations. + A driver represents a Fleet-Ops operator who can receive jobs, report location, register devices, and belong to fleets, vendors, vehicles, and organizations. + + `email` and `phone` are optional: an operational driver record may have neither, in which case the driver cannot sign in to Navigator until credentials are supplied. Assignments are reported as public IDs in `vehicle_id`, `vendor_id` and `job_id`; internal uuid columns and the authentication token are never part of the public contract. example: | { "id": "driver_A1b2C3d4E5", @@ -12,7 +14,12 @@ example: | "name": "Alex Driver", "email": "alex.driver@example.com", "phone": "+15555550111", + "timezone": "Asia/Singapore", "drivers_license_number": null, + "license_expiry": null, + "vehicle_id": null, + "vendor_id": null, + "job_id": null, "photo_url": null, "avatar_url": null, "vehicle_avatar": null, @@ -27,8 +34,14 @@ example: | "currency": "SGD", "city": "Singapore", "online": false, + "current_status": null, "status": "active", "token": null, + "skills": null, + "max_travel_time": null, + "max_distance": null, + "time_window_start": null, + "time_window_end": null, "meta": {}, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" @@ -118,3 +131,39 @@ fields: - name: created_at type: timestamp description: Timestamp when the driver was created. + - name: timezone + type: string + description: Timezone on the driver's linked user account. + - name: license_expiry + type: date + description: Date the driver's licence expires. + - name: vehicle_id + type: string + description: Public ID of the assigned vehicle, or `null`. + - name: vendor_id + type: string + description: Public ID of the assigned vendor, or `null`. + - name: job_id + type: string + description: Public ID of the driver's current order, or `null`. + - name: bearing + type: number + description: Current bearing. + - name: current_status + type: string + description: Free-form operational status, distinct from the lifecycle `status`. + - name: skills + type: array of strings + description: Qualifications or certifications the driver holds, used for orchestrator assignment. + - name: max_travel_time + type: integer + description: Maximum driving time per route, in seconds. + - name: max_distance + type: integer + description: Maximum driving distance per route, in metres. + - name: time_window_start + type: string + description: Default shift start time. + - name: time_window_end + type: string + description: Default shift end time. diff --git a/postman/collections/Fleetbase API/Drivers/Create a Driver.params.yaml b/postman/collections/Fleetbase API/Drivers/Create a Driver.params.yaml index 221e5f3..57a3ec6 100644 --- a/postman/collections/Fleetbase API/Drivers/Create a Driver.params.yaml +++ b/postman/collections/Fleetbase API/Drivers/Create a Driver.params.yaml @@ -6,40 +6,52 @@ fields: description: Driver display name. - name: email type: string - required: true - description: Driver email address. Must be unique when creating a driver. + description: |- + Driver email address. Optional. Validated as an email address and required to be unique across users when it is supplied. - name: phone type: string - required: true - description: Driver phone number. Must be unique when creating a driver. + description: |- + Driver phone number. Optional. Required to be unique across users when it is supplied. - name: password type: string - description: Optional password for the driver user account. + description: |- + Optional password for the driver user account. Accepted on create only; changing a password afterwards uses the dedicated password endpoints. - name: timezone type: string - description: Timezone for the driver user account. + description: Timezone for the driver's user account. + - name: internal_id + type: string + description: |- + The identifier the operator's own system uses for this driver. Queryable through `?internal_id=`, which is how an importer decides whether a driver already exists. + - name: drivers_license_number + type: string + description: Driver licence number. Queryable through `?drivers_license_number=`. + - name: license_expiry + type: date + description: Date the driver's licence expires. + - name: photo + type: string + description: File ID, upload reference, or resolvable file input for the driver photo. - name: country type: string description: Two-letter country code. + - name: currency + type: string + description: Three-letter currency code. - name: city type: string description: Driver city. - - name: vehicle - type: string - description: Vehicle ID to assign to the driver. - - name: vendor + - name: online + type: boolean + default: false + description: Whether the driver is online. + - name: current_status type: string - description: Vendor ID to assign to the driver. - - name: job - type: string - description: Current order ID to assign to the driver. + description: Free-form operational status, distinct from the lifecycle `status`. - name: status type: enum - values: ["active", "inactive"] - description: Driver lifecycle status. - - name: photo - type: string - description: File ID, upload reference, or resolvable file input for the driver photo. + values: ["active", "available", "inactive"] + description: Driver lifecycle status. `active` is accepted and stored as `available`. - name: location type: object description: Resolvable point used as the driver's current location. @@ -49,6 +61,42 @@ fields: - name: longitude type: number description: Decimal longitude. Required with `latitude`. + - name: heading + type: number + description: Current heading in degrees. + - name: bearing + type: number + description: Current bearing. + - name: altitude + type: number + description: Current altitude. + - name: speed + type: number + description: Current speed. - name: meta type: object description: Additional driver metadata. + - name: skills + type: array of strings + description: Qualifications or certifications the driver holds, used for orchestrator assignment. + - name: max_travel_time + type: integer + description: Maximum driving time per route, in seconds. + - name: max_distance + type: integer + description: Maximum driving distance per route, in metres. + - name: time_window_start + type: string + description: Default shift start time, as `HH:MM`. + - name: time_window_end + type: string + description: Default shift end time, as `HH:MM`. + - name: vehicle + type: string + description: Vehicle public ID (`vehicle_...`) to assign to the driver. Send `null` to clear. Must belong to the authenticated organization. + - name: vendor + type: string + description: Vendor public ID (`vendor_...`) to assign to the driver. Send `null` to clear. + - name: job + type: string + description: Current order public ID (`order_...`) to assign to the driver. Send `null` to clear. diff --git a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml index 2bf7619..1c29044 100644 --- a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml @@ -1,6 +1,10 @@ $kind: http-request description: |- - Creates a driver profile and linked user account. Provide a unique email and phone number, then optionally assign a vehicle, vendor, current job, location, or photo. + Creates a driver profile and its linked user account. + + Only `name` is required. `email` and `phone` are optional — an operational fleet record may legitimately have neither — but each is validated and required to be unique across users when it is supplied. No placeholder address or number is ever generated, and no invitation is sent when there is no deliverable contact method. A driver created without credentials cannot sign in to Navigator until credentials are added; see **Create an Operational Driver**. + + Beyond the account fields the endpoint accepts the driver's identity (`internal_id`, `drivers_license_number`, `license_expiry`, `photo`), operational fields (`country`, `currency`, `city`, `online`, `current_status`, `status`, location and telemetry), structured `meta`, orchestrator constraints, and the `vehicle`, `vendor` and `job` relationships given as public IDs and resolved inside the authenticated organization. url: "{{base_url}}/{{namespace}}/drivers" method: POST headers: @@ -12,7 +16,19 @@ body: "name": "John Doe", "email": "{{$randomEmail}}", "phone": "{{$randomPhoneNumber}}", - "password": "{{driver_seed_password}}" + "password": "{{driver_seed_password}}", + "internal_id": "DRV-1001", + "drivers_license_number": "S1234567A", + "license_expiry": "2030-06-30", + "country": "SG", + "city": "Singapore", + "current_status": "on_duty", + "skills": ["hazmat"], + "max_travel_time": 28800, + "max_distance": 250000, + "time_window_start": "08:00", + "time_window_end": "18:00", + "meta": { "badge": "A12" } } scripts: - type: afterResponse @@ -38,6 +54,41 @@ scripts: // pm.variables resolves across scopes; the seed is a collection // variable, which pm.environment cannot see. cv.set("created_driver_password", pm.variables.get("driver_seed_password")); + + pm.test("Create a Driver returns the persisted driver", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a driver public id").to.match(/^driver_/); + pm.expect(json_response.name).to.eql("John Doe"); + }); + + pm.test("Create a Driver persists the identity fields that were sent", function () { + pm.expect(json_response.internal_id, "internal_id was not persisted").to.eql("DRV-1001"); + pm.expect(json_response.drivers_license_number).to.eql("S1234567A"); + pm.expect(String(json_response.license_expiry)).to.match(/^2030-06-30/); + pm.expect(json_response.country).to.eql("SG"); + pm.expect(json_response.city).to.eql("Singapore"); + }); + + pm.test("Create a Driver persists meta and orchestrator constraints", function () { + // `meta` reached the model through a blocklist that dropped it, and + // the model itself spelled the fillable entry `meta,` — so it never + // persisted through the public API at all. + pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); + pm.expect(json_response.meta.badge).to.eql("A12"); + pm.expect(json_response.current_status).to.eql("on_duty"); + pm.expect(json_response.skills).to.be.an('array').that.includes("hazmat"); + pm.expect(Number(json_response.max_travel_time)).to.eql(28800); + pm.expect(Number(json_response.max_distance)).to.eql(250000); + pm.expect(String(json_response.time_window_start)).to.match(/^08:00/); + pm.expect(String(json_response.time_window_end)).to.match(/^18:00/); + }); + + pm.test("Create a Driver exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token", "signup_token_used", "_key"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); language: text/javascript examples: ./.resources/Create a Driver.resources/examples order: 1000 diff --git a/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.params.yaml b/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.params.yaml new file mode 100644 index 0000000..d646e1e --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.params.yaml @@ -0,0 +1,15 @@ +$kind: params +fields: + - name: name + type: string + required: true + description: Driver display name. The only required field. + - name: internal_id + type: string + description: The identifier the operator's own system uses for this driver. + - name: email + type: string + description: Optional. Omit it when the driver has no mailbox. + - name: phone + type: string + description: Optional. Omit it when the driver has no handset. diff --git a/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.request.yaml new file mode 100644 index 0000000..effe246 --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/Create an Operational Driver.request.yaml @@ -0,0 +1,47 @@ +$kind: http-request +description: |- + Creates a driver record for someone with no deliverable email address and no phone number — a subcontracted or yard-only driver, or a record imported from an operator's own system. + + Only `name` is required. Nothing is invented to fill the gap: no placeholder address, no placeholder number, and no invitation or credential notification is sent, because there is nowhere to send one. The Driver-to-User relationship, the organization membership, the `driver` user type and the `Driver` role are all created exactly as they are for a credentialed driver. + + **A driver created this way cannot sign in to Navigator until credentials are supplied.** Add an email address or phone number with `PUT /v1/drivers/{id}` when one becomes available. +url: "{{base_url}}/{{namespace}}/drivers" +method: POST +headers: + Accept: application/json +body: + type: json + content: | + { + "name": "Yard Driver", + "internal_id": "DRV-7788" + } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + let cv = pm.environment; + + cv.set("operational_driver_id", json_response.id); + + pm.test("Create an Operational Driver succeeds without credentials", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a driver public id").to.match(/^driver_/); + pm.expect(json_response.name).to.eql("Yard Driver"); + pm.expect(json_response.internal_id).to.eql("DRV-7788"); + }); + + pm.test("Create an Operational Driver invents no contact details", function () { + // A generated address would sit in the tenant's user table looking + // real, could be mailed to, and would block the genuine value later. + pm.expect(json_response.email, "an email address was generated").to.be.oneOf([null, ""]); + pm.expect(json_response.phone, "a phone number was generated").to.be.oneOf([null, ""]); + }); + + pm.test("Create an Operational Driver still links a user account", function () { + pm.expect(json_response.user, "the Driver-to-User relationship must be preserved").to.be.a('string').and.not.empty; + }); + language: text/javascript +examples: ./.resources/Create an Operational Driver.resources/examples +order: 1100 diff --git a/postman/collections/Fleetbase API/Drivers/Delete an Operational Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Delete an Operational Driver.request.yaml new file mode 100644 index 0000000..33d1b4f --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/Delete an Operational Driver.request.yaml @@ -0,0 +1,18 @@ +$kind: http-request +description: Deletes the credential-less driver created by **Create an Operational Driver**. +url: "{{base_url}}/{{namespace}}/drivers/:id" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{operational_driver_id}}" + description: (Required) The public id of the `Driver`. +scripts: + - type: afterResponse + code: |- + pm.test("Delete an Operational Driver succeeds", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 204]); + }); + language: text/javascript +order: 4900 diff --git a/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml b/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml index 6f5bb4e..ec123a0 100644 --- a/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml +++ b/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml @@ -1,11 +1,46 @@ $kind: params fields: - - name: id - type: string - description: Driver ID filter used by this request example. - name: query type: string - description: Search term matched against driver fields. + description: Free-text search across the driver's name, email, phone and licence number. + - name: name + type: string + description: Filter by the driver's name. + - name: internal_id + type: string + description: |- + Filter by the identifier the operator's own system uses. This is the lookup an importer keys on to decide whether a driver already exists. + - name: public_id + type: string + description: Filter by driver public ID (`driver_...`). + - name: drivers_license_number + type: string + description: Filter by driver licence number. + - name: phone + type: string + description: Filter by the phone number on the driver's linked user account. + - name: country + type: string + description: Filter by country code. Accepts a comma-separated list. + - name: status + type: string + description: Filter by driver status. Accepts a comma-separated list. + - name: vendor + type: string + description: Filter by vendor, given as its public ID (`vendor_...`). + - name: vehicle + type: string + description: |- + Filter by assigned vehicle, given as its public ID (`vehicle_...`) or internal ID. Pass `unassigned` to return drivers with no vehicle. + - name: fleet + type: string + description: Filter to the drivers in a fleet, given as its public ID (`fleet_...`). + - name: nearby + type: string + description: Coordinates or an address string to search near. Pair with `radius`. + - name: radius + type: integer + description: Search radius in metres, used with `nearby`. - name: limit type: integer description: Maximum number of drivers to return. @@ -15,6 +50,3 @@ fields: - name: sort type: string description: Sort expression for the driver query. - - name: vendor - type: string - description: Vendor ID used to filter drivers assigned to a vendor. diff --git a/postman/collections/Fleetbase API/Drivers/Update a Driver.params.yaml b/postman/collections/Fleetbase API/Drivers/Update a Driver.params.yaml index 35d8ebf..872fe50 100644 --- a/postman/collections/Fleetbase API/Drivers/Update a Driver.params.yaml +++ b/postman/collections/Fleetbase API/Drivers/Update a Driver.params.yaml @@ -9,25 +9,42 @@ fields: - name: phone type: string description: Driver phone number. - - name: password + - name: timezone type: string - description: Optional password for the driver user account. - - name: vehicle - type: string - description: Vehicle ID to assign to the driver. - - name: vendor + description: Timezone for the driver's user account. + - name: internal_id type: string - description: Vendor ID to assign to the driver. - - name: job + description: |- + The identifier the operator's own system uses for this driver. Queryable through `?internal_id=`, which is how an importer decides whether a driver already exists. + - name: drivers_license_number type: string - description: Current order ID to assign to the driver. - - name: status - type: enum - values: ["active", "inactive"] - description: Driver lifecycle status. + description: Driver licence number. Queryable through `?drivers_license_number=`. + - name: license_expiry + type: date + description: Date the driver's licence expires. - name: photo type: string description: File ID, upload reference, or resolvable file input for the driver photo. + - name: country + type: string + description: Two-letter country code. + - name: currency + type: string + description: Three-letter currency code. + - name: city + type: string + description: Driver city. + - name: online + type: boolean + default: false + description: Whether the driver is online. + - name: current_status + type: string + description: Free-form operational status, distinct from the lifecycle `status`. + - name: status + type: enum + values: ["active", "available", "inactive"] + description: Driver lifecycle status. `active` is accepted and stored as `available`. - name: location type: object description: Resolvable point used as the driver's current location. @@ -37,6 +54,42 @@ fields: - name: longitude type: number description: Decimal longitude. Required with `latitude`. + - name: heading + type: number + description: Current heading in degrees. + - name: bearing + type: number + description: Current bearing. + - name: altitude + type: number + description: Current altitude. + - name: speed + type: number + description: Current speed. - name: meta type: object description: Additional driver metadata. + - name: skills + type: array of strings + description: Qualifications or certifications the driver holds, used for orchestrator assignment. + - name: max_travel_time + type: integer + description: Maximum driving time per route, in seconds. + - name: max_distance + type: integer + description: Maximum driving distance per route, in metres. + - name: time_window_start + type: string + description: Default shift start time, as `HH:MM`. + - name: time_window_end + type: string + description: Default shift end time, as `HH:MM`. + - name: vehicle + type: string + description: Vehicle public ID (`vehicle_...`) to assign to the driver. Send `null` to clear. Must belong to the authenticated organization. + - name: vendor + type: string + description: Vendor public ID (`vendor_...`) to assign to the driver. Send `null` to clear. + - name: job + type: string + description: Current order public ID (`order_...`) to assign to the driver. Send `null` to clear. diff --git a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml index 8fe6a01..ef3b9e7 100644 --- a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml @@ -1,6 +1,10 @@ $kind: http-request description: |- - Updates a driver's account fields, assignment, status, location, photo, or metadata. + Updates a driver's account fields, identity, assignment, operational state, location, photo, orchestrator constraints, or metadata. + + `password` is deliberately not accepted here. Changing a password requires proving the old one and resetting it requires a code, neither of which a general `PUT` can express — use **Change Driver Password**, **Request Driver Password Reset** and **Reset Driver Password** instead. + + Relationship inputs take public IDs and are resolved inside the authenticated organization. Sending `null` clears an assignment. url: "{{base_url}}/{{namespace}}/drivers/:id" method: PUT headers: @@ -8,14 +12,62 @@ headers: pathVariables: - key: id value: "{{driver_id}}" - description: (Required) The id of the `Driver`. + description: (Required) The public id of the `Driver`. body: type: json content: | { "name": "John Doe", "email": "{{$randomEmail}}", - "phone": "{{$randomPhoneNumber}}" + "phone": "{{$randomPhoneNumber}}", + "internal_id": "DRV-1002", + "city": "Johor Bahru", + "current_status": "off_duty", + "meta": { "badge": "B34" } } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + let cv = pm.environment; + + // Keep the password-contract variables tracking this driver's identity. + cv.set("created_driver_email", json_response.email); + cv.set("created_driver_phone", json_response.phone); + + pm.test("Update a Driver returns the persisted driver", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a driver public id").to.match(/^driver_/); + }); + + pm.test("Update a Driver persists every field that was sent", function () { + pm.expect(json_response.name).to.eql("John Doe"); + pm.expect(json_response.internal_id).to.eql("DRV-1002"); + pm.expect(json_response.city).to.eql("Johor Bahru"); + pm.expect(json_response.current_status).to.eql("off_duty"); + pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); + pm.expect(json_response.meta.badge).to.eql("B34"); + }); + + pm.test("Update a Driver reports assignments as public ids", function () { + // `vehicle`, `vendor` and `job` are written as public ids and read + // back as public ids. The vehicle created earlier in this run has + // already been deleted by the Vehicles folder, so the assignment + // itself is exercised by the Fleet-Ops contract suite rather than + // against a resource that no longer exists. + pm.expect(json_response).to.have.property("vehicle_id"); + pm.expect(json_response).to.have.property("vendor_id"); + if (json_response.vehicle_id) { + pm.expect(json_response.vehicle_id).to.match(/^vehicle_/); + } + }); + + pm.test("Update a Driver exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript examples: ./.resources/Update a Driver.resources/examples order: 4000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Assign a Driver to a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Driver to a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..bc11f72 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Driver to a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" + method: POST + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + driver: "driver_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "driver": "driver_7YqM3KpL2n", + "assigned": true + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/Not Found.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/Not Found.example.yaml new file mode 100644 index 0000000..c842e00 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/Not Found.example.yaml @@ -0,0 +1,21 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" + method: POST + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + vehicle: "vehicle_NotInThisOrg" +response: + statusCode: 404 + statusText: Not Found + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "Fleet or vehicle resource not found." + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..7ba6200 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Assign a Vehicle to a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" + method: POST + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + vehicle: "vehicle_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "vehicle": "vehicle_7YqM3KpL2n", + "assigned": true + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml index 79e29de..e62196a 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml @@ -1,22 +1,19 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/fleets?name=&service_area=&task=&status=" + url: "{{base_url}}/{{namespace}}/fleets" method: POST headers: Accept: application/json - queryParams: - - key: name - value: - description: (Required) The name of the fleet. - - key: service_area - value: - description: The id of the ServiceArea you want to assign to the fleet. - - key: task - value: - description: Specification of the fleet's misssion. - - key: status - value: - description: 'The status of the fleet. Valid opetions: "active", "inactive", "disabled"' + body: + type: json + content: |- + { + "name": "Haulers", + "color": "#2563EB", + "task": "Long haul distribution", + "status": "active", + "service_area": "service_area_9Kp2Vx4Qm1" + } response: statusCode: 200 statusText: OK @@ -26,12 +23,17 @@ response: type: json content: |- { - "id": "", - "service_area": "", - "name": "", - "task": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "fleet_4Mnp8Qx2Vz", + "name": "Haulers", + "color": "#2563EB", + "task": "Long haul distribution", + "status": "active", + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": "service_area_9Kp2Vx4Qm1", + "zone": null, + "vendor": null, + "parent_fleet": null, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Not Found.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Not Found.example.yaml new file mode 100644 index 0000000..e80a9db --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Not Found.example.yaml @@ -0,0 +1,25 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "name": "Carpool", + "parent_fleet": "fleet_NotInThisOrg" + } +response: + statusCode: 404 + statusText: Not Found + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "No parent fleet resource found for the identifier provided." + } +order: 3000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..5725e8e --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml @@ -0,0 +1,39 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "name": "Carpool", + "color": "#059669", + "task": "Employee transport", + "status": "active", + "parent_fleet": "fleet_4Mnp8Qx2Vz" + } +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "id": "fleet_1Bd7Rk9Tz3", + "name": "Carpool", + "color": "#059669", + "task": "Employee transport", + "status": "active", + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": null, + "zone": null, + "vendor": null, + "parent_fleet": "fleet_4Mnp8Qx2Vz", + "updated_at": "2026-05-07T08:31:00.000000Z", + "created_at": "2026-05-07T08:31:00.000000Z" + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Unprocessable Entity.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Unprocessable Entity.example.yaml new file mode 100644 index 0000000..1ade86d --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/Unprocessable Entity.example.yaml @@ -0,0 +1,24 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/fleet_4Mnp8Qx2Vz" + method: PUT + headers: + Accept: application/json + body: + type: json + content: |- + { + "parent_fleet": "fleet_4Mnp8Qx2Vz" + } +response: + statusCode: 422 + statusText: Unprocessable Entity + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "A fleet cannot be its own parent fleet." + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml index 7c84e95..21a38df 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml @@ -1,25 +1,22 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/fleets?query=&limit=&offset=&sort=&status=active" + url: "{{base_url}}/{{namespace}}/fleets?limit=25&offset=0&sort=created_at&parents_only=true" method: GET headers: Accept: application/json queryParams: - - key: query - value: - description: This parameter allows you to filter the results by their name. - key: limit - value: - description: A cursor for use in pagination. A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + value: "25" + description: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. - key: offset - value: - description: A cursor for use in pagination. A offset for the results, when the offset is supplied the results will start from the offset. + value: "0" + description: A cursor for use in pagination. When an offset is supplied the results start from that offset. - key: sort - value: - description: A cursor used to sort results. Sort can be any property of the object, to sort descending sort must append the minus symbol. To sort by created date descending sort would be `-created_at` to sort in ascending order the sort would be `created_at`. - - key: status - value: active - description: Filter for filtering results by status. + value: created_at + description: Any property of the object. Prefix with a minus symbol to sort descending, for example `-created_at`. + - key: parents_only + value: "true" + description: Return only root fleets — those with no parent fleet. response: statusCode: 200 statusText: OK @@ -28,13 +25,20 @@ response: body: type: json content: |- - { - "id": "", - "service_area": "", - "name": "", - "task": "", - "status": "", - "updated_at": "", - "created_at": "" - } + [ + { + "id": "fleet_4Mnp8Qx2Vz", + "name": "Haulers", + "color": "#2563EB", + "task": "Long haul distribution", + "status": "active", + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": "service_area_9Kp2Vx4Qm1", + "zone": null, + "vendor": null, + "parent_fleet": null, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" + } + ] order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Driver to a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Driver to a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..bc11f72 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Driver to a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" + method: POST + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + driver: "driver_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "driver": "driver_7YqM3KpL2n", + "assigned": true + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Vehicle to a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Vehicle to a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..7ba6200 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Reassign a Vehicle to a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" + method: POST + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + vehicle: "vehicle_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "vehicle": "vehicle_7YqM3KpL2n", + "assigned": true + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet Again.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet Again.resources/examples/OK.example.yaml new file mode 100644 index 0000000..659b595 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet Again.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" + method: DELETE + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + driver: "driver_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "driver": "driver_7YqM3KpL2n", + "assigned": false + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..659b595 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Driver from a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" + method: DELETE + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + driver: "driver_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "driver": "driver_7YqM3KpL2n", + "assigned": false + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet Again.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet Again.resources/examples/OK.example.yaml new file mode 100644 index 0000000..1043b2e --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet Again.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" + method: DELETE + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + vehicle: "vehicle_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "vehicle": "vehicle_7YqM3KpL2n", + "assigned": false + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet.resources/examples/OK.example.yaml new file mode 100644 index 0000000..1043b2e --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/.resources/Remove a Vehicle from a Fleet.resources/examples/OK.example.yaml @@ -0,0 +1,23 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" + method: DELETE + headers: + Accept: application/json + pathVariables: + id: "fleet_4Mnp8Qx2Vz" + vehicle: "vehicle_7YqM3KpL2n" +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "fleet": "fleet_4Mnp8Qx2Vz", + "vehicle": "vehicle_7YqM3KpL2n", + "assigned": false + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml index 2203374..7b0df73 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml @@ -5,7 +5,7 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "fleet_4Mnp8Qx2Vz" response: statusCode: 200 statusText: OK @@ -15,12 +15,17 @@ response: type: json content: |- { - "id": "", - "service_area": "", - "name": "", - "task": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "fleet_4Mnp8Qx2Vz", + "name": "Haulers", + "color": "#2563EB", + "task": "Long haul distribution", + "status": "active", + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": "service_area_9Kp2Vx4Qm1", + "zone": null, + "vendor": null, + "parent_fleet": null, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml index a5ae5e7..31719e4 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml @@ -1,24 +1,21 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/fleets/:id?name=&service_area=&task=&status=" + url: "{{base_url}}/{{namespace}}/fleets/:id" method: PUT headers: Accept: application/json - queryParams: - - key: name - value: - description: The name of the fleet. - - key: service_area - value: - description: The id of the ServiceArea you want to assign to the fleet. - - key: task - value: - description: Specification of the fleet's misssion. - - key: status - value: - description: 'The status of the fleet. Valid opetions: "active", "inactive", "disabled"' pathVariables: - id: "" + id: "fleet_4Mnp8Qx2Vz" + body: + type: json + content: |- + { + "name": "Haulers North", + "color": "#7C3AED", + "task": "Regional distribution", + "status": "active", + "service_area": "service_area_9Kp2Vx4Qm1" + } response: statusCode: 200 statusText: OK @@ -28,12 +25,17 @@ response: type: json content: |- { - "id": "", - "service_area": "", - "name": "", - "task": "", - "status": "", - "created_at": "", - "updated_at": "" + "id": "fleet_4Mnp8Qx2Vz", + "name": "Haulers North", + "color": "#7C3AED", + "task": "Regional distribution", + "status": "active", + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": "service_area_9Kp2Vx4Qm1", + "zone": null, + "vendor": null, + "parent_fleet": null, + "updated_at": "2026-05-07T09:12:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/definition.yaml b/postman/collections/Fleetbase API/Fleets/.resources/definition.yaml index d88e496..2dd3b6c 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/definition.yaml @@ -1,4 +1,21 @@ $kind: collection description: |- Fleets group vehicles and drivers under an operating unit. Use fleets to organize capacity by team, region, or service area before assigning resources to orders. + + Fleets can be nested: sending `parent_fleet` on create or update makes a fleet a subfleet of another. A fleet may not be its own parent, and may not be moved beneath one of its own descendants. + + Membership is managed with four endpoints that all share one response shape: + + ``` + POST /v1/fleets/{fleet}/vehicles/{vehicle} + DELETE /v1/fleets/{fleet}/vehicles/{vehicle} + POST /v1/fleets/{fleet}/drivers/{driver} + DELETE /v1/fleets/{fleet}/drivers/{driver} + ``` + + ```json + { "fleet": "fleet_123", "vehicle": "vehicle_123", "assigned": true } + ``` + + Every parameter is a public ID. Assignment is idempotent, removal is a safe no-op, removing a membership never deletes the driver or vehicle, and neither operation changes a driver's current vehicle or its membership of any other fleet. order: 9000 diff --git a/postman/collections/Fleetbase API/Fleets/.resources/object.yaml b/postman/collections/Fleetbase API/Fleets/.resources/object.yaml index 94e425d..e0a04c5 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/object.yaml @@ -1,20 +1,21 @@ $kind: object name: Fleet description: |- - A fleet groups drivers and vehicles for operational assignment. Fleets can be linked to service areas, zones, vendors, parent fleets, subfleets, drivers, and vehicles. + A fleet groups drivers and vehicles for operational assignment. Fleets can be linked to a service area, a zone, a vendor and a parent fleet, and can themselves hold subfleets, drivers and vehicles. + + Relationships are returned as the related resource's public ID. Asking for a relationship through `?with=` returns it as a nested object instead. Internal uuid columns are never part of the public contract. example: | { "id": "fleet_4Mnp8Qx2Vz", "name": "Haulers", - "task": null, + "color": "#2563EB", + "task": "Long haul distribution", "status": "active", - "service_area": null, + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "service_area": "service_area_9Kp2Vx4Qm1", "zone": null, "vendor": null, "parent_fleet": null, - "subfleets": [], - "drivers": [], - "vehicles": [], "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } @@ -25,33 +26,43 @@ fields: - name: name type: string description: Fleet display name. + - name: color + type: string + description: Display colour for the fleet. - name: task type: string description: Task associated with the fleet. - name: status type: string description: Fleet status. + - name: photo_url + type: string + description: URL of the fleet image, or the default fleet image when none is set. - name: service_area - type: object - description: Service area assigned to the fleet, when loaded. + type: string + description: |- + Public ID of the assigned service area, or `null`. Returned as a nested object when requested through `with=service_area`. - name: zone - type: object - description: Zone assigned to the fleet, when loaded. + type: string + description: |- + Public ID of the assigned zone, or `null`. Returned as a nested object when requested through `with=zone`. - name: vendor - type: object - description: Vendor assigned to the fleet, when loaded. + type: string + description: |- + Public ID of the assigned vendor, or `null`. Returned as a nested object when requested through `with=vendor`. - name: parent_fleet - type: object - description: Parent fleet, when loaded. + type: string + description: |- + Public ID of the parent fleet, or `null` for a root fleet. Returned as a nested object when requested through `with=parent_fleet`. - name: subfleets type: array of objects - description: Child fleets, when loaded. + description: Child fleets, when requested through `with=subfleets`. - name: drivers type: array of objects - description: Drivers assigned to the fleet, when loaded. + description: Drivers assigned to the fleet, when requested through `with=drivers`. - name: vehicles type: array of objects - description: Vehicles assigned to the fleet, when loaded. + description: Vehicles assigned to the fleet, when requested through `with=vehicles`. - name: updated_at type: timestamp description: Timestamp when the fleet was last updated. diff --git a/postman/collections/Fleetbase API/Fleets/Assign a Driver to a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Assign a Driver to a Fleet.request.yaml new file mode 100644 index 0000000..5781e47 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Assign a Driver to a Fleet.request.yaml @@ -0,0 +1,48 @@ +$kind: http-request +description: |- + Adds a driver to a fleet. Both path parameters are public IDs; no internal uuid is accepted or returned. + + Assignment is idempotent. Repeating it answers the same way and never creates a second membership; a membership that was previously removed is restored rather than duplicated. Assigning to one fleet does not remove the resource from any other fleet, and does not change a driver's current vehicle. +url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" +method: POST +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: driver + value: "{{fleet_driver_id}}" + description: (Required) The public id of the `Driver` to add to the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Assign a Driver to a Fleet confirms the membership", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.driver, "driver must be a public id").to.match(/^driver_/); + pm.expect(json_response.assigned, "assigned must be true after an assignment").to.be.true; + }); + + pm.test("Assign a Driver to a Fleet echoes the ids that were sent", function () { + const fleetId = pm.variables.get("fleet_id"); + const subjectId = pm.variables.get("fleet_driver_id"); + if (fleetId) { + pm.expect(json_response.fleet).to.eql(fleetId); + } + if (subjectId) { + pm.expect(json_response.driver).to.eql(subjectId); + } + }); + + pm.test("Assign a Driver to a Fleet returns no internal uuids", function () { + ["fleet_uuid", "driver_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Assign a Driver to a Fleet.resources/examples +order: 3200 diff --git a/postman/collections/Fleetbase API/Fleets/Assign a Vehicle to a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Assign a Vehicle to a Fleet.request.yaml new file mode 100644 index 0000000..20ce93c --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Assign a Vehicle to a Fleet.request.yaml @@ -0,0 +1,48 @@ +$kind: http-request +description: |- + Adds a vehicle to a fleet. Both path parameters are public IDs; no internal uuid is accepted or returned. + + Assignment is idempotent. Repeating it answers the same way and never creates a second membership; a membership that was previously removed is restored rather than duplicated. Assigning to one fleet does not remove the resource from any other fleet, and does not change a driver's current vehicle. +url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" +method: POST +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: vehicle + value: "{{fleet_vehicle_id}}" + description: (Required) The public id of the `Vehicle` to add to the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Assign a Vehicle to a Fleet confirms the membership", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.vehicle, "vehicle must be a public id").to.match(/^vehicle_/); + pm.expect(json_response.assigned, "assigned must be true after an assignment").to.be.true; + }); + + pm.test("Assign a Vehicle to a Fleet echoes the ids that were sent", function () { + const fleetId = pm.variables.get("fleet_id"); + const subjectId = pm.variables.get("fleet_vehicle_id"); + if (fleetId) { + pm.expect(json_response.fleet).to.eql(fleetId); + } + if (subjectId) { + pm.expect(json_response.vehicle).to.eql(subjectId); + } + }); + + pm.test("Assign a Vehicle to a Fleet returns no internal uuids", function () { + ["fleet_uuid", "vehicle_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Assign a Vehicle to a Fleet.resources/examples +order: 3100 diff --git a/postman/collections/Fleetbase API/Fleets/Create a Fleet Driver.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Fleet Driver.request.yaml new file mode 100644 index 0000000..ee688c2 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Create a Fleet Driver.request.yaml @@ -0,0 +1,28 @@ +$kind: http-request +description: |- + Creates a driver for the fleet membership requests to operate on. + + The driver created in the **Drivers** folder is deleted before this folder runs, so the membership assertions need a resource of their own. It is removed again by **Delete a Fleet Driver** at the end of this folder. +url: "{{base_url}}/{{namespace}}/drivers" +method: POST +headers: + Accept: application/json +body: + type: json + content: |- + { + "name": "Fleet Membership Driver", + "internal_id": "DRV-FLT-0001" + } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + pm.environment.set("fleet_driver_id", json_response.id); + + pm.test("Create a Fleet Driver returns a driver to assign", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response.id, "id is not a driver public id").to.match(/^driver_/); + }); + language: text/javascript +order: 1300 diff --git a/postman/collections/Fleetbase API/Fleets/Create a Fleet Vehicle.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Fleet Vehicle.request.yaml new file mode 100644 index 0000000..5742e2c --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Create a Fleet Vehicle.request.yaml @@ -0,0 +1,32 @@ +$kind: http-request +description: |- + Creates a vehicle for the fleet membership requests to operate on. + + The vehicle created in the **Vehicles** folder is deleted before this folder runs, so the membership assertions need a resource of their own. It is removed again by **Delete a Fleet Vehicle** at the end of this folder. +url: "{{base_url}}/{{namespace}}/vehicles" +method: POST +headers: + Accept: application/json +body: + type: json + content: |- + { + "name": "Fleet Membership Van", + "make": "Toyota", + "model": "HiAce", + "year": 2024, + "plate_number": "FLT-0001", + "status": "available" + } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + pm.environment.set("fleet_vehicle_id", json_response.id); + + pm.test("Create a Fleet Vehicle returns a vehicle to assign", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response.id, "id is not a vehicle public id").to.match(/^vehicle_/); + }); + language: text/javascript +order: 1200 diff --git a/postman/collections/Fleetbase API/Fleets/Create a Fleet.params.yaml b/postman/collections/Fleetbase API/Fleets/Create a Fleet.params.yaml index c6ec555..02d2bbe 100644 --- a/postman/collections/Fleetbase API/Fleets/Create a Fleet.params.yaml +++ b/postman/collections/Fleetbase API/Fleets/Create a Fleet.params.yaml @@ -4,6 +4,29 @@ fields: type: string required: true description: Fleet display name. + - name: color + type: string + description: Display colour for the fleet, for example a hex value such as `#2563EB`. + - name: task + type: string + description: Free-text description of what the fleet is used for. + - name: status + type: string + description: |- + Fleet status. The console offers `active`, `disabled` and `decommissioned`; the field is not a closed enum, so any short status string an integration already uses is accepted. - name: service_area type: string - description: Service area ID to assign to the fleet. + description: Service area public ID (`service_area_...`) to assign to the fleet. Must belong to the authenticated organization. + - name: zone + type: string + description: Zone public ID (`zone_...`) to assign to the fleet. Must belong to the authenticated organization. + - name: vendor + type: string + description: Vendor public ID (`vendor_...`) to assign to the fleet. Must belong to the authenticated organization. + - name: parent_fleet + type: string + description: |- + Parent fleet public ID (`fleet_...`), which makes the new fleet a subfleet. Omit it to create a root fleet. Must belong to the authenticated organization. + - name: photo + type: string + description: File public ID (`file_...`) to use as the fleet image. diff --git a/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml index a681a29..85e76a9 100644 --- a/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml +++ b/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml @@ -1,6 +1,10 @@ $kind: http-request description: |- - Creates a fleet for grouping drivers and vehicles. Assign a service area when the fleet should be constrained to a specific operating area. + Creates a fleet for grouping drivers and vehicles. + + Every safe fleet field is accepted: name, colour, task, status, and the service area, zone, vendor and parent fleet relationships. Relationships are given as public IDs (`service_area_...`, `zone_...`, `vendor_...`, `fleet_...`) and are resolved inside the authenticated organization — a public ID belonging to another organization is rejected exactly as a non-existent one is. + + Omitting `parent_fleet` creates a root fleet. This request creates the parent used by **Create a Subfleet**. url: "{{base_url}}/{{namespace}}/fleets" method: POST headers: @@ -10,6 +14,9 @@ body: content: |- { "name": "Haulers", + "color": "#2563EB", + "task": "Long haul distribution", + "status": "active", "service_area": "{{service_area_id}}" } scripts: @@ -20,6 +27,41 @@ scripts: cv.set("fleet_id", json_response.id); cv.set("fleet_name", json_response.name); + // The subfleet request below parents itself to this fleet. + cv.set("parent_fleet_id", json_response.id); + + pm.test("Create a Fleet returns the persisted fleet", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "no public id returned").to.be.a('string').and.not.empty; + pm.expect(json_response.id, "id is not a fleet public id").to.match(/^fleet_/); + }); + + pm.test("Create a Fleet persists every field that was sent", function () { + pm.expect(json_response.name).to.eql("Haulers"); + pm.expect(json_response.color).to.eql("#2563EB"); + pm.expect(json_response.task).to.eql("Long haul distribution"); + pm.expect(json_response.status).to.eql("active"); + }); + + pm.test("Create a Fleet resolves the service area to a public id", function () { + const serviceAreaId = pm.variables.get("service_area_id"); + pm.expect(json_response).to.have.property("service_area"); + if (serviceAreaId) { + pm.expect(json_response.service_area).to.eql(serviceAreaId); + } + }); + + pm.test("Create a Fleet is a root fleet when no parent was sent", function () { + pm.expect(json_response).to.have.property("parent_fleet"); + pm.expect(json_response.parent_fleet, "a fleet created without a parent must have none").to.be.null; + }); + + pm.test("Create a Fleet exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "service_area_uuid", "zone_uuid", "vendor_uuid", "parent_fleet_uuid", "image_uuid", "_key"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); language: text/javascript examples: ./.resources/Create a Fleet.resources/examples order: 1000 diff --git a/postman/collections/Fleetbase API/Fleets/Create a Subfleet.params.yaml b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.params.yaml new file mode 100644 index 0000000..1c72ab1 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.params.yaml @@ -0,0 +1,20 @@ +$kind: params +fields: + - name: name + type: string + required: true + description: Subfleet display name. + - name: parent_fleet + type: string + required: true + description: |- + Public ID (`fleet_...`) of the fleet this subfleet belongs to. A fleet may not name itself, and may not be placed beneath one of its own descendants. + - name: color + type: string + description: Display colour for the subfleet. + - name: task + type: string + description: Free-text description of what the subfleet is used for. + - name: status + type: string + description: Subfleet status. diff --git a/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml new file mode 100644 index 0000000..127fe9a --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml @@ -0,0 +1,52 @@ +$kind: http-request +description: |- + Creates a fleet nested beneath another fleet by sending the parent's public ID in `parent_fleet`. + + Hierarchies are validated on write: a fleet cannot be its own parent, and cannot be moved beneath one of its own subfleets. Both are answered with `422`. A parent belonging to another organization is answered with `404`, the same as a parent that does not exist. +url: "{{base_url}}/{{namespace}}/fleets" +method: POST +headers: + Accept: application/json +body: + type: json + content: |- + { + "name": "Carpool", + "color": "#059669", + "task": "Employee transport", + "status": "active", + "parent_fleet": "{{parent_fleet_id}}" + } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + let cv = pm.environment; + + cv.set("subfleet_id", json_response.id); + + pm.test("Create a Subfleet returns the persisted subfleet", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a fleet public id").to.match(/^fleet_/); + pm.expect(json_response.name).to.eql("Carpool"); + }); + + pm.test("Create a Subfleet resolves the parent to its public id", function () { + const parentFleetId = pm.variables.get("parent_fleet_id"); + pm.expect(json_response.parent_fleet, "parent_fleet missing from the response").to.be.a('string'); + pm.expect(json_response.parent_fleet, "parent_fleet must be a public id, never a uuid").to.match(/^fleet_/); + if (parentFleetId) { + pm.expect(json_response.parent_fleet).to.eql(parentFleetId); + } + pm.expect(json_response.id, "a fleet must not be its own parent").to.not.eql(json_response.parent_fleet); + }); + + pm.test("Create a Subfleet exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "parent_fleet_uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Create a Subfleet.resources/examples +order: 1100 diff --git a/postman/collections/Fleetbase API/Fleets/Delete a Fleet Driver.request.yaml b/postman/collections/Fleetbase API/Fleets/Delete a Fleet Driver.request.yaml new file mode 100644 index 0000000..a3e80fa --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Delete a Fleet Driver.request.yaml @@ -0,0 +1,18 @@ +$kind: http-request +description: Deletes the driver created for the fleet membership requests. Runs after every membership has been removed. +url: "{{base_url}}/{{namespace}}/drivers/:id" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_driver_id}}" + description: (Required) The public id of the `Driver`. +scripts: + - type: afterResponse + code: |- + pm.test("Delete a Fleet Driver succeeds", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 204]); + }); + language: text/javascript +order: 4800 diff --git a/postman/collections/Fleetbase API/Fleets/Delete a Fleet Vehicle.request.yaml b/postman/collections/Fleetbase API/Fleets/Delete a Fleet Vehicle.request.yaml new file mode 100644 index 0000000..0dc878e --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Delete a Fleet Vehicle.request.yaml @@ -0,0 +1,18 @@ +$kind: http-request +description: Deletes the vehicle created for the fleet membership requests. Runs after every membership has been removed. +url: "{{base_url}}/{{namespace}}/vehicles/:id" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_vehicle_id}}" + description: (Required) The public id of the `Vehicle`. +scripts: + - type: afterResponse + code: |- + pm.test("Delete a Fleet Vehicle succeeds", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 204]); + }); + language: text/javascript +order: 4700 diff --git a/postman/collections/Fleetbase API/Fleets/Delete a Subfleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Delete a Subfleet.request.yaml new file mode 100644 index 0000000..ab7c9bc --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Delete a Subfleet.request.yaml @@ -0,0 +1,21 @@ +$kind: http-request +description: |- + Deletes the subfleet created by **Create a Subfleet**. + + It runs before **Delete a Fleet** so the child is removed before its parent. +url: "{{base_url}}/{{namespace}}/fleets/:id" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{subfleet_id}}" + description: (Required) The public id of the subfleet. +scripts: + - type: afterResponse + code: |- + pm.test("Delete a Subfleet succeeds", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 204]); + }); + language: text/javascript +order: 4900 diff --git a/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml b/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml index 4228a3c..1691506 100644 --- a/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml +++ b/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml @@ -1,5 +1,35 @@ $kind: params fields: + - name: query + type: string + description: Free-text search across the fleet's name, task and public ID. + - name: name + type: string + description: Filter by fleet name. + - name: public_id + type: string + description: Filter by fleet public ID (`fleet_...`). + - name: status + type: string + description: Filter by fleet status. Accepts a comma-separated list. + - name: task + type: string + description: Filter by the fleet's task. + - name: parent_fleet + type: string + description: Filter to the subfleets of a parent fleet, given as its public ID (`fleet_...`). + - name: parents_only + type: boolean + description: Return only root fleets — those with no parent fleet. + - name: vendor + type: string + description: Filter by vendor, given as its public ID (`vendor_...`). + - name: service_area + type: string + description: Filter by service area, given as its public ID (`service_area_...`). + - name: zone + type: string + description: Filter by zone, given as its public ID (`zone_...`). - name: limit type: integer description: Maximum number of fleets to return. @@ -11,4 +41,5 @@ fields: description: Sort expression for the fleet query. - name: with type: array of strings - description: Relationships to include in fleet responses. + description: |- + Relationships to include in fleet responses. A relationship named here is returned as a nested object; otherwise it is returned as the related resource's public ID. diff --git a/postman/collections/Fleetbase API/Fleets/Reassign a Driver to a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Reassign a Driver to a Fleet.request.yaml new file mode 100644 index 0000000..0505d64 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Reassign a Driver to a Fleet.request.yaml @@ -0,0 +1,54 @@ +$kind: http-request +description: |- + Adds a driver to a fleet. Both path parameters are public IDs; no internal uuid is accepted or returned. + + This request repeats the assignment above deliberately: assignment is idempotent, so a second call answers exactly as the first did and produces one active membership, not two. +url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" +method: POST +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: driver + value: "{{fleet_driver_id}}" + description: (Required) The public id of the `Driver` to add to the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Reassign a Driver to a Fleet confirms the membership", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.driver, "driver must be a public id").to.match(/^driver_/); + pm.expect(json_response.assigned, "assigned must be true after an assignment").to.be.true; + }); + + pm.test("Reassign a Driver to a Fleet echoes the ids that were sent", function () { + const fleetId = pm.variables.get("fleet_id"); + const subjectId = pm.variables.get("fleet_driver_id"); + if (fleetId) { + pm.expect(json_response.fleet).to.eql(fleetId); + } + if (subjectId) { + pm.expect(json_response.driver).to.eql(subjectId); + } + }); + + pm.test("Reassign a Driver to a Fleet stays idempotent", function () { + // The response is identical to the first assignment: no duplicate + // membership is created and no error is raised. + pm.expect(json_response.assigned, "a repeated assignment must remain assigned").to.be.true; + }); + + pm.test("Reassign a Driver to a Fleet returns no internal uuids", function () { + ["fleet_uuid", "driver_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Reassign a Driver to a Fleet.resources/examples +order: 3250 diff --git a/postman/collections/Fleetbase API/Fleets/Reassign a Vehicle to a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Reassign a Vehicle to a Fleet.request.yaml new file mode 100644 index 0000000..f7c5b71 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Reassign a Vehicle to a Fleet.request.yaml @@ -0,0 +1,54 @@ +$kind: http-request +description: |- + Adds a vehicle to a fleet. Both path parameters are public IDs; no internal uuid is accepted or returned. + + This request repeats the assignment above deliberately: assignment is idempotent, so a second call answers exactly as the first did and produces one active membership, not two. +url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" +method: POST +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: vehicle + value: "{{fleet_vehicle_id}}" + description: (Required) The public id of the `Vehicle` to add to the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Reassign a Vehicle to a Fleet confirms the membership", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.vehicle, "vehicle must be a public id").to.match(/^vehicle_/); + pm.expect(json_response.assigned, "assigned must be true after an assignment").to.be.true; + }); + + pm.test("Reassign a Vehicle to a Fleet echoes the ids that were sent", function () { + const fleetId = pm.variables.get("fleet_id"); + const subjectId = pm.variables.get("fleet_vehicle_id"); + if (fleetId) { + pm.expect(json_response.fleet).to.eql(fleetId); + } + if (subjectId) { + pm.expect(json_response.vehicle).to.eql(subjectId); + } + }); + + pm.test("Reassign a Vehicle to a Fleet stays idempotent", function () { + // The response is identical to the first assignment: no duplicate + // membership is created and no error is raised. + pm.expect(json_response.assigned, "a repeated assignment must remain assigned").to.be.true; + }); + + pm.test("Reassign a Vehicle to a Fleet returns no internal uuids", function () { + ["fleet_uuid", "vehicle_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Reassign a Vehicle to a Fleet.resources/examples +order: 3150 diff --git a/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet Again.request.yaml b/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet Again.request.yaml new file mode 100644 index 0000000..8e0a2f9 --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet Again.request.yaml @@ -0,0 +1,37 @@ +$kind: http-request +description: |- + Removes a driver from a fleet. Both path parameters are public IDs. + + This request repeats the removal above deliberately: removing a membership that is not there is a documented, successful no-op. +url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: driver + value: "{{fleet_driver_id}}" + description: (Required) The public id of the `Driver` to remove from the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Remove a Driver from a Fleet Again confirms the membership is gone", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.driver, "driver must be a public id").to.match(/^driver_/); + pm.expect(json_response.assigned, "assigned must be false after a removal").to.be.false; + }); + + pm.test("Remove a Driver from a Fleet Again returns no internal uuids", function () { + ["fleet_uuid", "driver_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Remove a Driver from a Fleet Again.resources/examples +order: 4250 diff --git a/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet.request.yaml new file mode 100644 index 0000000..db0496b --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Remove a Driver from a Fleet.request.yaml @@ -0,0 +1,37 @@ +$kind: http-request +description: |- + Removes a driver from a fleet. Both path parameters are public IDs. + + Removing a membership never deletes the driver, never changes a driver's current vehicle, and never affects the resource's membership of any other fleet. Repeating the removal is a successful no-op. +url: "{{base_url}}/{{namespace}}/fleets/:id/drivers/:driver" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: driver + value: "{{fleet_driver_id}}" + description: (Required) The public id of the `Driver` to remove from the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Remove a Driver from a Fleet confirms the membership is gone", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.driver, "driver must be a public id").to.match(/^driver_/); + pm.expect(json_response.assigned, "assigned must be false after a removal").to.be.false; + }); + + pm.test("Remove a Driver from a Fleet returns no internal uuids", function () { + ["fleet_uuid", "driver_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Remove a Driver from a Fleet.resources/examples +order: 4200 diff --git a/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet Again.request.yaml b/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet Again.request.yaml new file mode 100644 index 0000000..e631ffc --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet Again.request.yaml @@ -0,0 +1,37 @@ +$kind: http-request +description: |- + Removes a vehicle from a fleet. Both path parameters are public IDs. + + This request repeats the removal above deliberately: removing a membership that is not there is a documented, successful no-op. +url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: vehicle + value: "{{fleet_vehicle_id}}" + description: (Required) The public id of the `Vehicle` to remove from the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Remove a Vehicle from a Fleet Again confirms the membership is gone", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.vehicle, "vehicle must be a public id").to.match(/^vehicle_/); + pm.expect(json_response.assigned, "assigned must be false after a removal").to.be.false; + }); + + pm.test("Remove a Vehicle from a Fleet Again returns no internal uuids", function () { + ["fleet_uuid", "vehicle_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Remove a Vehicle from a Fleet Again.resources/examples +order: 4150 diff --git a/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet.request.yaml new file mode 100644 index 0000000..109a9be --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Remove a Vehicle from a Fleet.request.yaml @@ -0,0 +1,37 @@ +$kind: http-request +description: |- + Removes a vehicle from a fleet. Both path parameters are public IDs. + + Removing a membership never deletes the vehicle, never changes a driver's current vehicle, and never affects the resource's membership of any other fleet. Repeating the removal is a successful no-op. +url: "{{base_url}}/{{namespace}}/fleets/:id/vehicles/:vehicle" +method: DELETE +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. + - key: vehicle + value: "{{fleet_vehicle_id}}" + description: (Required) The public id of the `Vehicle` to remove from the fleet. +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Remove a Vehicle from a Fleet confirms the membership is gone", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.fleet, "fleet must be a public id").to.match(/^fleet_/); + pm.expect(json_response.vehicle, "vehicle must be a public id").to.match(/^vehicle_/); + pm.expect(json_response.assigned, "assigned must be false after a removal").to.be.false; + }); + + pm.test("Remove a Vehicle from a Fleet returns no internal uuids", function () { + ["fleet_uuid", "vehicle_uuid", "uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript +examples: ./.resources/Remove a Vehicle from a Fleet.resources/examples +order: 4100 diff --git a/postman/collections/Fleetbase API/Fleets/Update a Fleet.params.yaml b/postman/collections/Fleetbase API/Fleets/Update a Fleet.params.yaml index 048a104..5489248 100644 --- a/postman/collections/Fleetbase API/Fleets/Update a Fleet.params.yaml +++ b/postman/collections/Fleetbase API/Fleets/Update a Fleet.params.yaml @@ -3,6 +3,28 @@ fields: - name: name type: string description: Fleet display name. + - name: color + type: string + description: Display colour for the fleet. + - name: task + type: string + description: Free-text description of what the fleet is used for. + - name: status + type: string + description: Fleet status. - name: service_area type: string - description: Service area ID to assign to the fleet. + description: Service area public ID (`service_area_...`). Send `null` to clear the assignment. + - name: zone + type: string + description: Zone public ID (`zone_...`). Send `null` to clear the assignment. + - name: vendor + type: string + description: Vendor public ID (`vendor_...`). Send `null` to clear the assignment. + - name: parent_fleet + type: string + description: |- + Parent fleet public ID (`fleet_...`). Send `null` to promote the fleet back to a root fleet. A fleet may not be its own parent, and may not be moved beneath one of its own subfleets. + - name: photo + type: string + description: File public ID (`file_...`) to use as the fleet image. diff --git a/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml index d0240b3..b89f95e 100644 --- a/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml +++ b/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml @@ -1,18 +1,60 @@ $kind: http-request description: |- - Updates a fleet's name or assigned service area. + Updates any safe fleet field: name, colour, task, status, and the service area, zone, vendor and parent fleet relationships. + + Relationship inputs take public IDs and are resolved inside the authenticated organization. Sending `null` for a relationship clears it — `"parent_fleet": null` promotes a subfleet back to a root fleet. url: "{{base_url}}/{{namespace}}/fleets/:id" method: PUT headers: Accept: application/json pathVariables: - id: "{{fleet_id}}" + - key: id + value: "{{fleet_id}}" + description: (Required) The public id of the `Fleet`. body: type: json content: |- { - "name": "Haulers", + "name": "Haulers North", + "color": "#7C3AED", + "task": "Regional distribution", + "status": "active", "service_area": "{{service_area_id}}" } +scripts: + - type: afterResponse + code: |- + var json_response = pm.response.json(); + + pm.test("Update a Fleet returns the persisted fleet", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a fleet public id").to.match(/^fleet_/); + }); + + pm.test("Update a Fleet persists every field that was sent", function () { + pm.expect(json_response.name).to.eql("Haulers North"); + pm.expect(json_response.color).to.eql("#7C3AED"); + pm.expect(json_response.task).to.eql("Regional distribution"); + pm.expect(json_response.status).to.eql("active"); + }); + + pm.test("Update a Fleet resolves relationships to public ids", function () { + const serviceAreaId = pm.variables.get("service_area_id"); + pm.expect(json_response).to.have.property("service_area"); + if (serviceAreaId) { + pm.expect(json_response.service_area).to.eql(serviceAreaId); + } + if (json_response.parent_fleet !== null && json_response.parent_fleet !== undefined) { + pm.expect(json_response.parent_fleet, "parent_fleet must be a public id").to.match(/^fleet_/); + } + }); + + pm.test("Update a Fleet exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "service_area_uuid", "zone_uuid", "vendor_uuid", "parent_fleet_uuid", "image_uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript examples: ./.resources/Update a Fleet.resources/examples order: 4000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml index f11c3c9..5eb2175 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml @@ -1,34 +1,28 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/vehicles?vin=&driver=&year=&make=&model=&trim=&plate_number=&status=maintenance" + url: "{{base_url}}/{{namespace}}/vehicles" method: POST headers: Accept: application/json - queryParams: - - key: vin - value: - description: (Required) The VIN (Vehicle Identification Number) of the vehicle. - - key: driver - value: - description: The id of the Driver to be assigned to the vehicle. - - key: year - value: - description: The year the vehicle was produced. - - key: make - value: - description: The vehicle make. - - key: model - value: - description: The vehicle model. - - key: trim - value: - description: The trim of the vehicle. - - key: plate_number - value: - description: The vehicle's plate number. - - key: status - value: maintenance - description: (Required) The status of the vehicle. + body: + type: json + content: |- + { + "internal_id": "VEH-1001", + "name": "Depot Van", + "vin": "1GCGSBEA0G1111111", + "year": 2023, + "make": "Toyota", + "model": "Camry", + "trim": "SE", + "plate_number": "ABC123", + "status": "maintenance", + "odometer": 41000, + "odometer_unit": "km", + "skills": ["tail_lift"], + "meta": { "depot": "north" }, + "vendor": "vendor_5Nq8Tz1Wm4" + } response: statusCode: 201 statusText: Created @@ -38,19 +32,32 @@ response: type: json content: |- { - "id": "", - "name": "", - "vin": "", - "driver": "", - "photo_url": "", - "make": "", - "model": "", - "year": "", - "trim": "", - "type": "", - "plate_number": "", - "status": "", - "updated_at": "", - "created_at": "" + "id": "vehicle_7YqM3KpL2n", + "internal_id": "VEH-1001", + "name": "Depot Van", + "vin": "1GCGSBEA0G1111111", + "photo_url": "https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png", + "make": "Toyota", + "model": "Camry", + "year": "2023", + "trim": "SE", + "type": "sedan", + "plate_number": "ABC123", + "status": "maintenance", + "online": false, + "odometer": 41000, + "odometer_unit": "km", + "skills": ["tail_lift"], + "max_tasks": null, + "time_window_start": null, + "time_window_end": null, + "return_to_depot": true, + "driver": null, + "vendor": "vendor_5Nq8Tz1Wm4", + "category": null, + "warranty": null, + "meta": { "depot": "north" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Not Found.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Not Found.example.yaml new file mode 100644 index 0000000..c5e15c1 --- /dev/null +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Not Found.example.yaml @@ -0,0 +1,25 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/vehicles" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "make": "Toyota", + "vendor": "vendor_NotInThisOrg" + } +response: + statusCode: 404 + statusText: Not Found + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "error": "No vendor resource found for the identifier provided." + } +order: 3000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Unprocessable Entity.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Unprocessable Entity.example.yaml new file mode 100644 index 0000000..8c8a786 --- /dev/null +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Unprocessable Entity.example.yaml @@ -0,0 +1,29 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/vehicles" + method: POST + headers: + Accept: application/json + body: + type: json + content: |- + { + "make": "Toyota", + "odometer": -5, + "status": "not-a-status" + } +response: + statusCode: 422 + statusText: Unprocessable Entity + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "errors": [ + "The odometer must be at least 0.", + "The selected status is invalid." + ] + } +order: 2000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml index 23baa90..64aab4d 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml @@ -5,7 +5,18 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "vehicle_7YqM3KpL2n" + body: + type: json + content: |- + { + "plate_number": "ABC123", + "status": "operational", + "odometer": 41250, + "color": "Silver", + "specs": { "doors": 4 }, + "meta": { "depot": "south" } + } response: statusCode: 200 statusText: OK @@ -15,75 +26,22 @@ response: type: json content: |- { - "id": "", - "name": "", - "vin": "", - "driver": "", - "photo_url": "", - "make": "", - "model": "", - "year": "", - "trim": "", - "type": "", - "plate_number": "", - "model_data": { - "0_to_100_kph": "", - "body": "", - "co2": "", - "doors": "", - "drive": "", - "engine_bore_mm": "", - "engine_cc": "", - "engine_compression": "", - "engine_cyl": "", - "engine_fuel": "", - "engine_position": "", - "engine_power_ps": "", - "engine_power_rpm": "", - "engine_stroke_mm": "", - "engine_torque_nm": "", - "engine_torque_rpm": "", - "engine_valves_per_cyl": "", - "fuel_cap_l": "", - "length_mm": "", - "lkm_city": "", - "lkm_hwy": "", - "lkm_mixed": "", - "make_display": "", - "seats": "", - "sold_in_us": "", - "top_speed_kph": "", - "transmission_type": "", - "weight_kg": "", - "wheelbase_mm": "", - "width_mm": "" - }, - "vin_data": { - "make": "", - "manufacturer": "", - "plant_country": "", - "product_type": "", - "manufacturer_address": "", - "check_digit": "", - "model_year": "", - "sequential_number": "", - "model": "", - "plant_city": "", - "trim": "", - "plant_state": "", - "body": "", - "number_of_doors": "", - "transmission": "", - "number_of_gears": "", - "engine_displacement_ccm": "", - "engine_model": "", - "engine_power_kw": "", - "series": "", - "fuel_type_primary": "", - "production_stopped": "" - }, - "status": "", - "updated_at": "", - "created_at": "" + "id": "vehicle_7YqM3KpL2n", + "internal_id": "VEH-1001", + "name": "Depot Van", + "make": "Toyota", + "model": "Camry", + "year": "2023", + "plate_number": "ABC123", + "color": "Silver", + "status": "operational", + "online": false, + "odometer": 41250, + "odometer_unit": "km", + "specs": { "doors": 4 }, + "meta": { "depot": "south" }, + "vendor": "vendor_5Nq8Tz1Wm4", + "updated_at": "2026-05-07T09:12:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml index f449526..fcc3613 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml @@ -1,7 +1,9 @@ $kind: object name: Vehicle description: |- - A vehicle represents a trackable asset used for FleetOps assignment, routing, and capacity planning. It stores vehicle identity, status, assignment, current position, capacity, orchestrator constraints, and metadata. + A vehicle represents a trackable asset used for Fleet-Ops assignment, routing, and capacity planning. It stores vehicle identity, status, assignment, current position, capacity, dimensions, engine and regulatory specifications, financing, orchestrator constraints, and metadata. + + Relationships are returned as the related resource's public ID; asking for one through `?with=` returns it as a nested object instead. Internal uuid columns are never part of the public contract. example: | { "id": "vehicle_7YqM3KpL2n", @@ -21,6 +23,14 @@ example: | "status": "operational", "online": false, "payload_capacity": 500, + "skills": ["tail_lift"], + "max_tasks": 40, + "time_window_start": "08:00:00", + "time_window_end": "18:00:00", + "return_to_depot": true, + "vendor": "vendor_5Nq8Tz1Wm4", + "category": null, + "warranty": null, "location": { "latitude": 40.7484, "longitude": -73.9857 @@ -104,8 +114,10 @@ fields: type: object description: Additional vehicle detail data. - name: status - type: string - description: Vehicle operational status. + type: enum + values: ["active", "available", "in_use", "maintenance", "out_of_service", "reserved", "retired", "staging", "on_route", "idle", "cleaning", "awaiting_parts", "inspection_due", "inspection_failed", "accident", "compliance_hold", "stolen", "operational", "decommissioned"] + description: |- + Vehicle operational status. `active` is accepted on input and stored as `available`. - name: online type: boolean description: Whether the vehicle is online. @@ -193,3 +205,157 @@ fields: - name: meta type: object description: Additional vehicle metadata. + - name: vendor + type: string + description: |- + Public ID of the assigned vendor, or `null`. Returned as a nested object when requested through `with=vendor`. + - name: category + type: string + description: Public ID of the assigned category, or `null`. + - name: warranty + type: string + description: Public ID of the assigned warranty, or `null`. + - name: skills + type: array of strings + description: Capabilities the vehicle offers for orchestrator assignment. + - name: payload_capacity_volume + type: number + description: Payload volume capacity used by orchestrator assignment. + - name: payload_capacity_pallets + type: integer + description: Pallet capacity used by orchestrator assignment. + - name: payload_capacity_parcels + type: integer + description: Parcel capacity used by orchestrator assignment. + - name: max_tasks + type: integer + description: Maximum concurrent tasks for orchestrator assignment. + - name: time_window_start + type: string + description: Earliest time of day the vehicle is available. + - name: time_window_end + type: string + description: Latest time of day the vehicle is available. + - name: return_to_depot + type: boolean + description: Whether the vehicle should return to depot after assigned work. + - name: fuel_volume_unit + type: string + description: Unit for fuel volumes. + - name: odometer_at_purchase + type: number + description: Odometer reading at the time of purchase. + - name: transmission + type: string + description: Transmission type. + - name: body_type + type: string + description: Vehicle body type. + - name: body_sub_type + type: string + description: Vehicle body sub type. + - name: usage_type + type: string + description: How the vehicle is used. + - name: ownership_type + type: string + description: How the vehicle is held. + - name: cargo_volume + type: number + description: Cargo volume. + - name: passenger_volume + type: number + description: Passenger volume. + - name: interior_volume + type: number + description: Total interior volume. + - name: ground_clearance + type: number + description: Ground clearance. + - name: bed_length + type: number + description: Bed length. + - name: fuel_capacity + type: number + description: Fuel tank capacity. + - name: emission_standard + type: string + description: Emission standard the vehicle meets. + - name: dpf_equipped + type: boolean + description: Whether a diesel particulate filter is fitted. + - name: scr_equipped + type: boolean + description: Whether selective catalytic reduction is fitted. + - name: gvwr + type: number + description: Gross vehicle weight rating. + - name: gcwr + type: number + description: Gross combined weight rating. + - name: engine_number + type: string + description: Engine number. + - name: engine_make + type: string + description: Engine manufacturer. + - name: engine_model + type: string + description: Engine model. + - name: engine_family + type: string + description: Engine family. + - name: engine_configuration + type: string + description: Engine configuration. + - name: engine_size + type: number + description: Engine size. + - name: engine_displacement + type: number + description: Engine displacement. + - name: cylinder_arrangement + type: string + description: Cylinder arrangement. + - name: number_of_cylinders + type: integer + description: Number of cylinders. + - name: horsepower + type: number + description: Engine horsepower. + - name: horsepower_rpm + type: integer + description: RPM at which peak horsepower is produced. + - name: torque + type: number + description: Engine torque. + - name: torque_rpm + type: integer + description: RPM at which peak torque is produced. + - name: estimated_service_life_distance + type: integer + description: Estimated service life expressed as a distance. + - name: estimated_service_life_distance_unit + type: string + description: Unit for the estimated service life distance. + - name: estimated_service_life_months + type: integer + description: Estimated service life expressed in months. + - name: financing_status + type: string + description: Vehicle financing status. + - name: insurance_value + type: currency + description: Insured value. + - name: depreciation_rate + type: number + description: Depreciation rate. + - name: loan_amount + type: number + description: Loan principal. + - name: loan_number_of_payments + type: integer + description: Number of loan payments. + - name: loan_first_payment + type: date + description: Date of the first loan payment. diff --git a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.params.yaml b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.params.yaml index 7e34866..9f1e4dd 100644 --- a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.params.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.params.yaml @@ -1,85 +1,298 @@ $kind: params fields: - - name: vin + - name: internal_id type: string - description: Vehicle identification number. - - name: year - type: integer - description: Model year of the vehicle. + description: The identifier the operator's own system uses for this vehicle. Queryable through `?internal_id=`. + - name: name + type: string + description: Vehicle display name. + - name: description + type: string + description: Free-text description of the vehicle. - name: make type: string description: Vehicle manufacturer. - name: model type: string description: Vehicle model. + - name: model_type + type: string + description: Vehicle model type or variant family. + - name: year + type: integer + description: Model year of the vehicle. - name: trim type: string description: Vehicle trim or variant. + - name: color + type: string + description: Vehicle colour. - name: type type: string description: Vehicle type. + - name: class + type: string + description: Vehicle class. - name: plate_number type: string - description: Vehicle license plate number. - - name: status - type: enum - values: ["operational", "maintenance", "decommissioned"] - description: Vehicle operational status. - - name: vendor + description: Vehicle licence plate number. Queryable through `?plate_number=`. + - name: vin type: string - description: Vendor ID to assign to the vehicle. - - name: driver + description: Vehicle identification number. Updating it re-runs VIN decoding. Queryable through `?vin=`. + - name: serial_number + type: string + description: Vehicle serial number. + - name: call_sign + type: string + description: Vehicle call sign. + - name: fuel_card_number + type: string + description: Fuel card number assigned to the vehicle. + - name: odometer + type: number + description: Current odometer reading. Must be zero or greater. + - name: odometer_unit + type: string + description: Unit for odometer readings, for example `km` or `mi`. + - name: odometer_at_purchase + type: number + description: Odometer reading at the time of purchase. + - name: measurement_system + type: string + description: Measurement system used for the vehicle's metrics, for example `metric` or `imperial`. + - name: fuel_type type: string - description: Driver ID to assign this vehicle to after creation. + description: Vehicle fuel type. + - name: fuel_volume_unit + type: string + description: Unit for fuel volumes, for example `l` or `gal`. - name: online type: boolean default: false - description: Whether the vehicle is online. - - name: location - type: object - description: Resolvable point used as the vehicle's current location. - - name: latitude + description: |- + Whether the vehicle is online. On create it defaults to `false`; on update it is left alone unless it is sent. + - name: status + type: enum + values: ["active", "available", "in_use", "maintenance", "out_of_service", "reserved", "retired", "staging", "on_route", "idle", "cleaning", "awaiting_parts", "inspection_due", "inspection_failed", "accident", "compliance_hold", "stolen", "operational", "decommissioned"] + description: |- + Vehicle operational status. `active` is accepted and stored as `available`. + - name: transmission + type: string + description: Transmission type. + - name: body_type + type: string + description: Vehicle body type. + - name: body_sub_type + type: string + description: Vehicle body sub type. + - name: usage_type + type: string + description: How the vehicle is used, for example `commercial`. + - name: ownership_type + type: string + description: How the vehicle is held, for example `owned` or `leased`. + - name: cargo_volume type: number - description: Decimal latitude. Required with `longitude`. - - name: longitude + description: Cargo volume. + - name: passenger_volume type: number - description: Decimal longitude. Required with `latitude`. - - name: altitude + description: Passenger volume. + - name: interior_volume type: number - description: Current altitude. - - name: heading + description: Total interior volume. + - name: weight type: number - description: Current heading in degrees. - - name: speed + description: Vehicle weight. + - name: width type: number - description: Current speed. + description: Vehicle width. + - name: length + type: number + description: Vehicle length. + - name: height + type: number + description: Vehicle height. + - name: towing_capacity + type: number + description: Towing capacity. - name: payload_capacity type: number - description: Payload capacity value used by orchestrator assignment. + description: Payload capacity. + - name: seating_capacity + type: integer + description: Seating capacity. + - name: ground_clearance + type: number + description: Ground clearance. + - name: bed_length + type: number + description: Bed length. + - name: fuel_capacity + type: number + description: Fuel tank capacity. + - name: financing_status + type: string + description: Vehicle financing status. + - name: loan_number_of_payments + type: integer + description: Number of loan payments. + - name: loan_first_payment + type: date + description: Date of the first loan payment. + - name: loan_amount + type: number + description: Loan principal. + - name: estimated_service_life_distance_unit + type: string + description: Unit for the estimated service life distance. + - name: estimated_service_life_distance + type: integer + description: Estimated service life expressed as a distance. + - name: estimated_service_life_months + type: integer + description: Estimated service life expressed in months. + - name: insurance_value + type: number + description: Insured value. + - name: depreciation_rate + type: number + description: Depreciation rate. + - name: current_value + type: number + description: Current vehicle value. + - name: acquisition_cost + type: number + description: Acquisition cost. + - name: currency + type: string + description: Three-letter currency code for the vehicle's monetary fields. + - name: purchased_at + type: timestamp + description: When the vehicle was purchased. + - name: lease_expires_at + type: timestamp + description: When the vehicle's lease expires. + - name: emission_standard + type: string + description: Emission standard the vehicle meets, for example `euro6`. + - name: dpf_equipped + type: boolean + description: Whether a diesel particulate filter is fitted. + - name: scr_equipped + type: boolean + description: Whether selective catalytic reduction is fitted. + - name: gvwr + type: number + description: Gross vehicle weight rating. + - name: gcwr + type: number + description: Gross combined weight rating. + - name: engine_number + type: string + description: Engine number. + - name: engine_model + type: string + description: Engine model. + - name: engine_make + type: string + description: Engine manufacturer. + - name: engine_family + type: string + description: Engine family. + - name: engine_configuration + type: string + description: Engine configuration. + - name: engine_displacement + type: number + description: Engine displacement. + - name: engine_size + type: number + description: Engine size. + - name: horsepower + type: number + description: Engine horsepower. + - name: horsepower_rpm + type: integer + description: RPM at which peak horsepower is produced. + - name: torque + type: number + description: Engine torque. + - name: torque_rpm + type: integer + description: RPM at which peak torque is produced. + - name: number_of_cylinders + type: integer + description: Number of cylinders. + - name: cylinder_arrangement + type: string + description: Cylinder arrangement, for example `I4` or `V6`. + - name: specs + type: object + description: Structured vehicle specification data. + - name: details + type: object + description: Structured additional vehicle detail data. + - name: notes + type: string + description: Free-text notes about the vehicle. + - name: meta + type: object + description: Additional vehicle metadata. + - name: skills + type: array of strings + description: Capabilities the vehicle offers for orchestrator assignment, for example `tail_lift`. - name: payload_capacity_volume type: number - description: Payload volume capacity. + description: Payload volume capacity used by orchestrator assignment. - name: payload_capacity_pallets type: integer - description: Pallet capacity. + description: Pallet capacity used by orchestrator assignment. - name: payload_capacity_parcels type: integer - description: Parcel capacity. - - name: skills - type: array of strings - description: Skills supported by the vehicle for orchestrator assignment. + description: Parcel capacity used by orchestrator assignment. - name: max_tasks type: integer description: Maximum concurrent tasks for orchestrator assignment. - name: time_window_start - type: timestamp - description: Earliest time the vehicle is available. + type: string + description: Earliest time of day the vehicle is available, as `HH:MM`. - name: time_window_end - type: timestamp - description: Latest time the vehicle is available. + type: string + description: Latest time of day the vehicle is available, as `HH:MM`. - name: return_to_depot type: boolean description: Whether the vehicle should return to depot after assigned work. - - name: meta + - name: vendor + type: string + description: Vendor public ID (`vendor_...`). Send `null` to clear. Must belong to the authenticated organization. + - name: driver + type: string + description: |- + Driver public ID (`driver_...`) to assign this vehicle to. Send an empty value to unassign the current driver. Must belong to the authenticated organization. + - name: category + type: string + description: Category public ID (`category_...`). Send `null` to clear. + - name: warranty + type: string + description: Warranty public ID (`warranty_...`). Send `null` to clear. + - name: photo + type: string + description: File public ID (`file_...`) to use as the vehicle photo. + - name: location type: object - description: Additional vehicle metadata. + description: Resolvable point used as the vehicle's current location. + - name: latitude + type: number + description: Decimal latitude. Required with `longitude`. + - name: longitude + type: number + description: Decimal longitude. Required with `latitude`. + - name: altitude + type: number + description: Current altitude. + - name: heading + type: number + description: Current heading in degrees. + - name: speed + type: number + description: Current speed. diff --git a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml index b469db4..cfa040c 100644 --- a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml @@ -1,6 +1,10 @@ $kind: http-request description: |- - Creates a vehicle for the current company. Send VIN, make/model fields, assignment fields, status, location, capacity, or orchestrator constraints as needed. + Creates a vehicle for the current organization. + + Every safe business field the vehicle record holds is accepted: identity and description, odometer and measurement, body, capacity and dimensions, lifecycle and financing, regulatory and engine specifications, structured `specs` / `details` / `meta`, orchestrator constraints, and the vendor, driver, category and warranty relationships. + + Relationships take public IDs (`vendor_...`, `driver_...`, `category_...`, `warranty_...`) and are resolved inside the authenticated organization; another organization's public ID is rejected exactly as a non-existent one is. Server-managed values — decoded `vin_data`, `telematics`, the generated `slug`, and every internal uuid column — are not accepted as input. url: "{{base_url}}/{{namespace}}/vehicles" method: POST headers: @@ -9,14 +13,33 @@ body: type: json content: | { + "internal_id": "VEH-1001", + "name": "Depot Van", + "description": "City route van", "vin": "1GCGSBEA0G1111111", "year": 2023, "make": "Toyota", "model": "Camry", "trim": "SE", + "color": "White", + "type": "sedan", "plate_number": "ABC123", "status": "maintenance", - "online": false + "online": false, + "odometer": 41000, + "odometer_unit": "km", + "measurement_system": "metric", + "fuel_type": "diesel", + "seating_capacity": 5, + "payload_capacity": 1400, + "skills": ["tail_lift"], + "max_tasks": 40, + "time_window_start": "08:00", + "time_window_end": "18:00", + "return_to_depot": true, + "notes": "City route pool", + "meta": { "depot": "north" }, + "vendor": "{{vendor_id}}" } scripts: - type: afterResponse @@ -25,6 +48,64 @@ scripts: let cv = pm.environment; cv.set("vehicle_id", json_response.id); cv.set("vehicle_name", json_response.make); + + pm.test("Create a Vehicle returns the persisted vehicle", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200, 201]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a vehicle public id").to.match(/^vehicle_/); + }); + + pm.test("Create a Vehicle persists the identity fields that were sent", function () { + pm.expect(json_response.internal_id).to.eql("VEH-1001"); + pm.expect(json_response.name).to.eql("Depot Van"); + pm.expect(json_response.description).to.eql("City route van"); + pm.expect(json_response.vin).to.eql("1GCGSBEA0G1111111"); + pm.expect(Number(json_response.year)).to.eql(2023); + pm.expect(json_response.make).to.eql("Toyota"); + pm.expect(json_response.model).to.eql("Camry"); + pm.expect(json_response.trim).to.eql("SE"); + pm.expect(json_response.color).to.eql("White"); + pm.expect(json_response.type).to.eql("sedan"); + pm.expect(json_response.plate_number).to.eql("ABC123"); + }); + + pm.test("Create a Vehicle persists the operational fields that were sent", function () { + // The odometer was accepted but discarded before this contract; a + // silent no-op is the worst possible answer to a mileage write. + pm.expect(Number(json_response.odometer), "odometer was not persisted").to.eql(41000); + pm.expect(json_response.odometer_unit).to.eql("km"); + pm.expect(json_response.measurement_system).to.eql("metric"); + pm.expect(json_response.fuel_type).to.eql("diesel"); + pm.expect(Number(json_response.seating_capacity)).to.eql(5); + pm.expect(Number(json_response.payload_capacity)).to.eql(1400); + pm.expect(json_response.status).to.eql("maintenance"); + pm.expect(json_response.online).to.eql(false); + pm.expect(json_response.notes).to.eql("City route pool"); + }); + + pm.test("Create a Vehicle persists structured and orchestrator fields", function () { + pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); + pm.expect(json_response.meta.depot).to.eql("north"); + pm.expect(json_response.skills).to.be.an('array').that.includes("tail_lift"); + pm.expect(Number(json_response.max_tasks)).to.eql(40); + pm.expect(String(json_response.time_window_start)).to.match(/^08:00/); + pm.expect(String(json_response.time_window_end)).to.match(/^18:00/); + pm.expect(json_response.return_to_depot).to.eql(true); + }); + + pm.test("Create a Vehicle resolves the vendor to a public id", function () { + const vendorId = pm.variables.get("vendor_id"); + pm.expect(json_response).to.have.property("vendor"); + if (vendorId) { + pm.expect(json_response.vendor).to.eql(vendorId); + } + }); + + pm.test("Create a Vehicle exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "vendor_uuid", "category_uuid", "warranty_uuid", "photo_uuid", "telematic_uuid", "_key"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); language: text/javascript examples: ./.resources/Create a Vehicle.resources/examples order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml b/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml index 9d85844..58cd893 100644 --- a/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml @@ -2,7 +2,45 @@ $kind: params fields: - name: query type: string - description: Search term matched against vehicle fields. + description: Free-text search across the vehicle's searchable fields. + - name: internal_id + type: string + description: |- + Filter by the identifier the operator's own system uses. This is the lookup an importer keys on to decide whether a vehicle already exists. + - name: plate_number + type: string + description: Filter by licence plate number. + - name: vin + type: string + description: Filter by vehicle identification number. + - name: public_id + type: string + description: Filter by vehicle public ID (`vehicle_...`). + - name: display_name + type: string + description: Filter across year, make, model and plate number together. + - name: vehicle_make + type: string + description: Filter by manufacturer. + - name: vehicle_model + type: string + description: Filter by model. + - name: vehicle_year + type: string + description: Filter by model year. + - name: vendor + type: string + description: Filter by vendor, given as its public ID (`vendor_...`) or internal ID. + - name: driver + type: string + description: |- + Filter by assigned driver, given as its public ID (`driver_...`) or internal ID. Pass `unassigned` to return vehicles with no driver. + - name: fleet + type: string + description: Filter to the vehicles in a fleet, given as its public ID (`fleet_...`). + - name: assigned_fleet + type: string + description: Pass `false` to return vehicles that belong to no fleet. - name: limit type: integer description: Maximum number of vehicles to return. @@ -12,6 +50,3 @@ fields: - name: sort type: string description: Sort expression for the vehicle query. - - name: vendor - type: string - description: Vendor ID used to filter vehicles assigned to a vendor. diff --git a/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.params.yaml b/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.params.yaml index e202788..9f1e4dd 100644 --- a/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.params.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.params.yaml @@ -1,82 +1,298 @@ $kind: params fields: - - name: vin + - name: internal_id type: string - description: Vehicle identification number. Updating the VIN refreshes decoded VIN data. - - name: year - type: integer - description: Model year of the vehicle. + description: The identifier the operator's own system uses for this vehicle. Queryable through `?internal_id=`. + - name: name + type: string + description: Vehicle display name. + - name: description + type: string + description: Free-text description of the vehicle. - name: make type: string description: Vehicle manufacturer. - name: model type: string description: Vehicle model. + - name: model_type + type: string + description: Vehicle model type or variant family. + - name: year + type: integer + description: Model year of the vehicle. - name: trim type: string description: Vehicle trim or variant. + - name: color + type: string + description: Vehicle colour. - name: type type: string description: Vehicle type. + - name: class + type: string + description: Vehicle class. - name: plate_number type: string - description: Vehicle license plate number. - - name: status - type: enum - values: ["operational", "maintenance", "decommissioned"] - description: Vehicle operational status. - - name: vendor + description: Vehicle licence plate number. Queryable through `?plate_number=`. + - name: vin type: string - description: Vendor ID to assign to the vehicle. + description: Vehicle identification number. Updating it re-runs VIN decoding. Queryable through `?vin=`. + - name: serial_number + type: string + description: Vehicle serial number. + - name: call_sign + type: string + description: Vehicle call sign. + - name: fuel_card_number + type: string + description: Fuel card number assigned to the vehicle. + - name: odometer + type: number + description: Current odometer reading. Must be zero or greater. + - name: odometer_unit + type: string + description: Unit for odometer readings, for example `km` or `mi`. + - name: odometer_at_purchase + type: number + description: Odometer reading at the time of purchase. + - name: measurement_system + type: string + description: Measurement system used for the vehicle's metrics, for example `metric` or `imperial`. + - name: fuel_type + type: string + description: Vehicle fuel type. + - name: fuel_volume_unit + type: string + description: Unit for fuel volumes, for example `l` or `gal`. - name: online type: boolean default: false - description: Whether the vehicle is online. - - name: location - type: object - description: Resolvable point used as the vehicle's current location. - - name: latitude + description: |- + Whether the vehicle is online. On create it defaults to `false`; on update it is left alone unless it is sent. + - name: status + type: enum + values: ["active", "available", "in_use", "maintenance", "out_of_service", "reserved", "retired", "staging", "on_route", "idle", "cleaning", "awaiting_parts", "inspection_due", "inspection_failed", "accident", "compliance_hold", "stolen", "operational", "decommissioned"] + description: |- + Vehicle operational status. `active` is accepted and stored as `available`. + - name: transmission + type: string + description: Transmission type. + - name: body_type + type: string + description: Vehicle body type. + - name: body_sub_type + type: string + description: Vehicle body sub type. + - name: usage_type + type: string + description: How the vehicle is used, for example `commercial`. + - name: ownership_type + type: string + description: How the vehicle is held, for example `owned` or `leased`. + - name: cargo_volume type: number - description: Decimal latitude. Required with `longitude`. - - name: longitude + description: Cargo volume. + - name: passenger_volume type: number - description: Decimal longitude. Required with `latitude`. - - name: altitude + description: Passenger volume. + - name: interior_volume type: number - description: Current altitude. - - name: heading + description: Total interior volume. + - name: weight type: number - description: Current heading in degrees. - - name: speed + description: Vehicle weight. + - name: width type: number - description: Current speed. + description: Vehicle width. + - name: length + type: number + description: Vehicle length. + - name: height + type: number + description: Vehicle height. + - name: towing_capacity + type: number + description: Towing capacity. - name: payload_capacity type: number - description: Payload capacity value used by orchestrator assignment. + description: Payload capacity. + - name: seating_capacity + type: integer + description: Seating capacity. + - name: ground_clearance + type: number + description: Ground clearance. + - name: bed_length + type: number + description: Bed length. + - name: fuel_capacity + type: number + description: Fuel tank capacity. + - name: financing_status + type: string + description: Vehicle financing status. + - name: loan_number_of_payments + type: integer + description: Number of loan payments. + - name: loan_first_payment + type: date + description: Date of the first loan payment. + - name: loan_amount + type: number + description: Loan principal. + - name: estimated_service_life_distance_unit + type: string + description: Unit for the estimated service life distance. + - name: estimated_service_life_distance + type: integer + description: Estimated service life expressed as a distance. + - name: estimated_service_life_months + type: integer + description: Estimated service life expressed in months. + - name: insurance_value + type: number + description: Insured value. + - name: depreciation_rate + type: number + description: Depreciation rate. + - name: current_value + type: number + description: Current vehicle value. + - name: acquisition_cost + type: number + description: Acquisition cost. + - name: currency + type: string + description: Three-letter currency code for the vehicle's monetary fields. + - name: purchased_at + type: timestamp + description: When the vehicle was purchased. + - name: lease_expires_at + type: timestamp + description: When the vehicle's lease expires. + - name: emission_standard + type: string + description: Emission standard the vehicle meets, for example `euro6`. + - name: dpf_equipped + type: boolean + description: Whether a diesel particulate filter is fitted. + - name: scr_equipped + type: boolean + description: Whether selective catalytic reduction is fitted. + - name: gvwr + type: number + description: Gross vehicle weight rating. + - name: gcwr + type: number + description: Gross combined weight rating. + - name: engine_number + type: string + description: Engine number. + - name: engine_model + type: string + description: Engine model. + - name: engine_make + type: string + description: Engine manufacturer. + - name: engine_family + type: string + description: Engine family. + - name: engine_configuration + type: string + description: Engine configuration. + - name: engine_displacement + type: number + description: Engine displacement. + - name: engine_size + type: number + description: Engine size. + - name: horsepower + type: number + description: Engine horsepower. + - name: horsepower_rpm + type: integer + description: RPM at which peak horsepower is produced. + - name: torque + type: number + description: Engine torque. + - name: torque_rpm + type: integer + description: RPM at which peak torque is produced. + - name: number_of_cylinders + type: integer + description: Number of cylinders. + - name: cylinder_arrangement + type: string + description: Cylinder arrangement, for example `I4` or `V6`. + - name: specs + type: object + description: Structured vehicle specification data. + - name: details + type: object + description: Structured additional vehicle detail data. + - name: notes + type: string + description: Free-text notes about the vehicle. + - name: meta + type: object + description: Additional vehicle metadata. + - name: skills + type: array of strings + description: Capabilities the vehicle offers for orchestrator assignment, for example `tail_lift`. - name: payload_capacity_volume type: number - description: Payload volume capacity. + description: Payload volume capacity used by orchestrator assignment. - name: payload_capacity_pallets type: integer - description: Pallet capacity. + description: Pallet capacity used by orchestrator assignment. - name: payload_capacity_parcels type: integer - description: Parcel capacity. - - name: skills - type: array of strings - description: Skills supported by the vehicle for orchestrator assignment. + description: Parcel capacity used by orchestrator assignment. - name: max_tasks type: integer description: Maximum concurrent tasks for orchestrator assignment. - name: time_window_start - type: timestamp - description: Earliest time the vehicle is available. + type: string + description: Earliest time of day the vehicle is available, as `HH:MM`. - name: time_window_end - type: timestamp - description: Latest time the vehicle is available. + type: string + description: Latest time of day the vehicle is available, as `HH:MM`. - name: return_to_depot type: boolean description: Whether the vehicle should return to depot after assigned work. - - name: meta + - name: vendor + type: string + description: Vendor public ID (`vendor_...`). Send `null` to clear. Must belong to the authenticated organization. + - name: driver + type: string + description: |- + Driver public ID (`driver_...`) to assign this vehicle to. Send an empty value to unassign the current driver. Must belong to the authenticated organization. + - name: category + type: string + description: Category public ID (`category_...`). Send `null` to clear. + - name: warranty + type: string + description: Warranty public ID (`warranty_...`). Send `null` to clear. + - name: photo + type: string + description: File public ID (`file_...`) to use as the vehicle photo. + - name: location type: object - description: Additional vehicle metadata. + description: Resolvable point used as the vehicle's current location. + - name: latitude + type: number + description: Decimal latitude. Required with `longitude`. + - name: longitude + type: number + description: Decimal longitude. Required with `latitude`. + - name: altitude + type: number + description: Current altitude. + - name: heading + type: number + description: Current heading in degrees. + - name: speed + type: number + description: Current speed. diff --git a/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.request.yaml b/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.request.yaml index 750106a..a441460 100644 --- a/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.request.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Update a Vehicle.request.yaml @@ -1,6 +1,8 @@ $kind: http-request description: |- - Updates a vehicle's identity, operational status, vendor assignment, location, capacity, or orchestrator constraints. Updating the VIN refreshes decoded VIN data. + Updates any safe vehicle field. Updating the VIN re-runs VIN decoding. + + A partial update touches only the fields that are sent: omitting `online` leaves the vehicle's online state alone rather than taking it offline. Relationship inputs take public IDs and are resolved inside the authenticated organization; sending `null` clears the assignment, and sending an empty `driver` unassigns the current driver. url: "{{base_url}}/{{namespace}}/vehicles/:id" method: PUT headers: @@ -8,16 +10,66 @@ headers: pathVariables: - key: id value: "{{vehicle_id}}" - description: (Required) The id of the `Vehicle`. + description: (Required) The public id of the `Vehicle`. body: type: json content: | { "plate_number": "ABC123", "status": "operational", + "odometer": 41250, + "color": "Silver", + "body_type": "sedan", + "transmission": "automatic", + "seating_capacity": 5, + "currency": "SGD", + "purchased_at": "2026-01-02", + "specs": { "doors": 4 }, + "details": { "liftgate": true }, + "meta": { "depot": "south" }, "latitude": 40.7484, "longitude": -73.9857, "speed": 90 } +scripts: + - type: afterResponse + code: | + var json_response = pm.response.json(); + + pm.test("Update a Vehicle returns the persisted vehicle", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response, "response is not an object").to.be.an('object'); + pm.expect(json_response.id, "id is not a vehicle public id").to.match(/^vehicle_/); + }); + + pm.test("Update a Vehicle persists every field that was sent", function () { + pm.expect(json_response.plate_number).to.eql("ABC123"); + pm.expect(json_response.status).to.eql("operational"); + pm.expect(Number(json_response.odometer)).to.eql(41250); + pm.expect(json_response.color).to.eql("Silver"); + pm.expect(json_response.body_type).to.eql("sedan"); + pm.expect(json_response.transmission).to.eql("automatic"); + pm.expect(Number(json_response.seating_capacity)).to.eql(5); + pm.expect(json_response.currency).to.eql("SGD"); + pm.expect(json_response.specs).to.be.an('object'); + pm.expect(json_response.specs.doors).to.eql(4); + pm.expect(json_response.details.liftgate).to.eql(true); + pm.expect(json_response.meta.depot).to.eql("south"); + pm.expect(Number(json_response.speed)).to.eql(90); + }); + + pm.test("Update a Vehicle leaves fields that were not sent alone", function () { + // `online` was not part of this request, so it must keep whatever + // value the vehicle already had rather than defaulting to false. + pm.expect(json_response).to.have.property("online"); + pm.expect(json_response.make, "an unrelated field was cleared by a partial update").to.eql("Toyota"); + }); + + pm.test("Update a Vehicle exposes no internal uuid columns", function () { + ["uuid", "public_id", "company_uuid", "vendor_uuid", "category_uuid", "warranty_uuid", "photo_uuid"].forEach(function (key) { + pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + }); + }); + language: text/javascript examples: ./.resources/Update a Vehicle.resources/examples order: 4000 From 0fcdff3835bf208e3468bfe9fe72570e9d7cf5f2 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Sat, 5 Sep 2026 15:17:08 +0800 Subject: [PATCH 2/3] Document relationship ids as additive, and exact public identifier lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows the Fleet-Ops contract correction: a relationship is two separate keys, never one key with two types. Relationship shape - `_id` is the public identifier and is always present. `` is the nested object. The previous revision of this branch documented and asserted a public id *under* the object key, which would have made a property an object on one call and a string on another — the SDK stores what the API returns verbatim, so every consumer that dereferences it breaks. - Fleet, Vehicle and Driver object definitions, examples and assertions all reflect that. Driver examples keep `user`, `company`, `company_name`, `vehicle`, `vendor` and `current_job`; `user` is documented as a string that is never expanded, because Navigator interpolates it into a socket channel name. - Vehicle gains `driver_id`, `vendor_id`, `category_id`, `warranty_id` and `photo_id`, and `fuel_card_number`, which the object definition never had. Identifier lookup - `internal_id` and `public_id` are documented as exact on the public API. The console's partial search is called out as internal-only, so nobody reads the public contract as fuzzy — an importer that keys on `VEH-10` must not match `VEH-100`. Expansion - `?with=vendor`, `?with[]=vendor`, `?with=vendor,driver` and the `expand` alias are documented and proven equivalent by two new requests, one per spelling. Both assert that expansion adds the object, leaves the identifier untouched, and that an unsupported name is ignored rather than raising. Assertions - Vehicle create and Driver update now drive their assertions off the request body, so a field cannot be added to a request without an assertion following it. `purchased_at`, the coordinates-to-`location` canonicalisation, driver email/phone and the new driver `timezone` are all asserted. - The uuid-leak checks became a sweep over every response key rather than a fixed list, so a column added later cannot slip out unnoticed. Validation: npm run postman:lint — 0 errors, 0 warnings across all five collections. Live execution against a deployment carrying the Fleet-Ops branch is reported separately. --- .../examples/OK.example.yaml | 3 + .../examples/OK.example.yaml | 3 + .../examples/OK.example.yaml | 2 + .../examples/OK.example.yaml | 8 ++ .../examples/OK.example.yaml | 8 ++ .../Drivers/.resources/object.yaml | 16 ++- .../Drivers/Create a Driver.request.yaml | 25 +++- .../Drivers/Query Drivers.queryParams.yaml | 8 +- .../Drivers/Update a Driver.request.yaml | 49 +++++-- .../examples/OK.example.yaml | 9 +- .../examples/OK.example.yaml | 8 +- .../examples/OK.example.yaml | 9 +- .../examples/OK.example.yaml | 9 +- .../examples/OK.example.yaml | 9 +- .../Fleets/.resources/object.yaml | 48 ++++--- .../Fleets/Create a Fleet.request.yaml | 25 +++- .../Fleets/Create a Subfleet.request.yaml | 12 +- .../Fleets/Query Fleets.queryParams.yaml | 4 +- .../Fleets/Update a Fleet.request.yaml | 15 +- .../examples/Created.example.yaml | 9 +- .../examples/OK.example.yaml | 58 ++++++++ .../examples/OK.example.yaml | 125 ++++++----------- .../examples/OK.example.yaml | 103 +++++--------- .../examples/OK.example.yaml | 2 +- .../Vehicles/.resources/object.yaml | 48 +++++-- .../Vehicles/Create a Vehicle.request.yaml | 129 ++++++++++++------ .../Expand a Vehicle Scalar.request.yaml | 28 ++++ .../Vehicles/Expand a Vehicle.request.yaml | 54 ++++++++ .../Vehicles/Query Vehicles.queryParams.yaml | 8 +- 29 files changed, 553 insertions(+), 281 deletions(-) create mode 100644 postman/collections/Fleetbase API/Vehicles/.resources/Expand a Vehicle.resources/examples/OK.example.yaml create mode 100644 postman/collections/Fleetbase API/Vehicles/Expand a Vehicle Scalar.request.yaml create mode 100644 postman/collections/Fleetbase API/Vehicles/Expand a Vehicle.request.yaml diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml index daf3063..1036802 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml @@ -50,6 +50,9 @@ response: "vehicle_id": null, "vendor_id": null, "job_id": null, + "vehicle": null, + "vendor": null, + "current_job": null, "location": { "type": "Point", "coordinates": [0, 0] }, "heading": 0, "bearing": null, diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml index 5847808..d9f4670 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml @@ -33,6 +33,9 @@ response: "vehicle_id": null, "vendor_id": null, "job_id": null, + "vehicle": null, + "vendor": null, + "current_job": null, "online": false, "status": "available", "meta": {}, diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml index 5b8a548..3c208d1 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml @@ -25,6 +25,8 @@ response: "id": "driver_A1b2C3d4E5", "user": "user_1A2b3C4d5E", "internal_id": "DRV-1001", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", "name": "John Doe", "email": "john.doe@example.com", "phone": "+15555550111", diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml index c7f9d68..219f06c 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml @@ -31,6 +31,14 @@ response: "vehicle_id": "vehicle_7YqM3KpL2n", "vendor_id": null, "job_id": null, + "vehicle": { + "id": "vehicle_7YqM3KpL2n", + "plate_number": "ABC123", + "make": "Toyota", + "model": "Camry" + }, + "vendor": null, + "current_job": null, "location": { "type": "Point", "coordinates": [103.8198, 1.3521] }, "heading": 0, "bearing": null, diff --git a/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml index 7c9fb3f..5de7b6b 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml @@ -42,6 +42,14 @@ response: "vehicle_id": "vehicle_7YqM3KpL2n", "vendor_id": null, "job_id": null, + "vehicle": { + "id": "vehicle_7YqM3KpL2n", + "plate_number": "ABC123", + "make": "Toyota", + "model": "Camry" + }, + "vendor": null, + "current_job": null, "location": { "type": "Point", "coordinates": [103.8198, 1.3521] }, "heading": 0, "bearing": null, diff --git a/postman/collections/Fleetbase API/Drivers/.resources/object.yaml b/postman/collections/Fleetbase API/Drivers/.resources/object.yaml index da6e618..4a16fe9 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/object.yaml @@ -3,7 +3,9 @@ name: Driver description: |- A driver represents a Fleet-Ops operator who can receive jobs, report location, register devices, and belong to fleets, vendors, vehicles, and organizations. - `email` and `phone` are optional: an operational driver record may have neither, in which case the driver cannot sign in to Navigator until credentials are supplied. Assignments are reported as public IDs in `vehicle_id`, `vendor_id` and `job_id`; internal uuid columns and the authentication token are never part of the public contract. + `email` and `phone` are optional: an operational driver record may have neither, in which case the driver cannot sign in to Navigator until credentials are supplied. + + `user` and `company` are public-ID strings and have always been — Navigator interpolates `driver.user` straight into a socket channel name, so neither is ever expanded into an object. `vehicle`, `vendor` and `current_job` are objects on create, update and retrieve, which load them. The `vehicle_id`, `vendor_id` and `job_id` keys are additive identifiers beside those objects, not replacements for them. Internal uuid columns and the authentication token are never part of the public contract. example: | { "id": "driver_A1b2C3d4E5", @@ -52,7 +54,8 @@ fields: description: Public driver identifier. - name: user type: string - description: Public user ID linked to the driver. + description: |- + Public user ID linked to the driver. Always a string, never an object — Navigator uses it directly as a chat participant identifier and as a socket channel name. - name: internal_id type: string description: Optional internal identifier. @@ -82,13 +85,16 @@ fields: description: Driver avatar URL. - name: vehicle type: object - description: Assigned vehicle, when loaded. + description: |- + Assigned vehicle. Returned as an object by create, update and retrieve, which load it. Read `vehicle_id` for the identifier. - name: current_job type: object - description: Current order, when loaded. + description: |- + Current order. Returned as an object by create, update and retrieve, which load it. Read `job_id` for the identifier. - name: vendor type: object - description: Assigned vendor, when loaded. + description: |- + Assigned vendor. Returned as an object by create, update and retrieve, which load it. Read `vendor_id` for the identifier. - name: fleets type: array of objects description: Fleets the driver belongs to, when loaded. diff --git a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml index 1c29044..0cb383d 100644 --- a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml @@ -17,6 +17,7 @@ body: "email": "{{$randomEmail}}", "phone": "{{$randomPhoneNumber}}", "password": "{{driver_seed_password}}", + "timezone": "Asia/Singapore", "internal_id": "DRV-1001", "drivers_license_number": "S1234567A", "license_expiry": "2030-06-30", @@ -62,6 +63,25 @@ scripts: pm.expect(json_response.name).to.eql("John Doe"); }); + pm.test("Create a Driver echoes the contact details it was given", function () { + const sent = JSON.parse(pm.request.body.raw); + + pm.expect(json_response.email, "email did not round trip").to.eql(json_response.email && sent.email ? sent.email : json_response.email); + pm.expect(json_response.email, "email missing").to.be.a('string'); + pm.expect(json_response.phone, "phone missing").to.be.a('string'); + pm.expect(json_response.timezone, "timezone was not persisted").to.eql("Asia/Singapore"); + }); + + pm.test("Create a Driver keeps every relationship the released contract returned", function () { + pm.expect(json_response.user, "user must stay a public-id string").to.be.a('string'); + pm.expect(json_response.user).to.match(/^user_/); + pm.expect(json_response).to.have.property("company"); + pm.expect(json_response).to.have.property("company_name"); + ["vehicle_id", "vendor_id", "job_id"].forEach(function (key) { + pm.expect(json_response, key + " is missing").to.have.property(key); + }); + }); + pm.test("Create a Driver persists the identity fields that were sent", function () { pm.expect(json_response.internal_id, "internal_id was not persisted").to.eql("DRV-1001"); pm.expect(json_response.drivers_license_number).to.eql("S1234567A"); @@ -85,7 +105,10 @@ scripts: }); pm.test("Create a Driver exposes no internal uuid columns", function () { - ["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token", "signup_token_used", "_key"].forEach(function (key) { + Object.keys(json_response).forEach(function (key) { + pm.expect(key, `${key} leaks an internal column`).to.not.match(/(^uuid$|_uuid$)/); + }); + ["public_id", "auth_token", "signup_token_used", "_key"].forEach(function (key) { pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); }); }); diff --git a/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml b/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml index ec123a0..551cd88 100644 --- a/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml +++ b/postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml @@ -9,10 +9,10 @@ fields: - name: internal_id type: string description: |- - Filter by the identifier the operator's own system uses. This is the lookup an importer keys on to decide whether a driver already exists. + Filter by the identifier the operator's own system uses. **Exact match** on the public API: `DRV-10` returns only `DRV-10`, never `DRV-100`. That is what lets an importer use it to decide between update and create. (The Fleet-Ops console does a partial search on the same field; that behaviour is internal to the console and not part of this contract.) - name: public_id type: string - description: Filter by driver public ID (`driver_...`). + description: Filter by driver public ID (`driver_...`). Exact match. - name: drivers_license_number type: string description: Filter by driver licence number. @@ -50,3 +50,7 @@ fields: - name: sort type: string description: Sort expression for the driver query. + - name: with + type: array of strings + description: |- + Relationships to expand. Accepted as `?with=vendor`, `?with[]=vendor`, `?with[]=vendor&with[]=driver` and `?with=vendor,driver`; `expand` is an alias for all four. A relationship named here is additionally returned as a nested object — it never replaces or retypes the `_id` identifier, which is always present. A name outside the supported set is ignored rather than rejected. diff --git a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml index ef3b9e7..b7683be 100644 --- a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml @@ -21,6 +21,7 @@ body: "email": "{{$randomEmail}}", "phone": "{{$randomPhoneNumber}}", "internal_id": "DRV-1002", + "timezone": "Europe/Amsterdam", "city": "Johor Bahru", "current_status": "off_duty", "meta": { "badge": "B34" } @@ -42,27 +43,59 @@ scripts: }); pm.test("Update a Driver persists every field that was sent", function () { + const sent = JSON.parse(pm.request.body.raw); + pm.expect(json_response.name).to.eql("John Doe"); pm.expect(json_response.internal_id).to.eql("DRV-1002"); pm.expect(json_response.city).to.eql("Johor Bahru"); pm.expect(json_response.current_status).to.eql("off_duty"); pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); pm.expect(json_response.meta.badge).to.eql("B34"); + + // Email and phone are echoed back exactly as sent, and resending an + // unchanged address must not trip the uniqueness check against the + // driver's own user account. + pm.expect(json_response.email, "email did not round trip").to.eql(sent.email); + pm.expect(json_response.phone, "phone did not round trip").to.eql(sent.phone); }); - pm.test("Update a Driver reports assignments as public ids", function () { - // `vehicle`, `vendor` and `job` are written as public ids and read - // back as public ids. The vehicle created earlier in this run has - // already been deleted by the Vehicles folder, so the assignment - // itself is exercised by the Fleet-Ops contract suite rather than - // against a resource that no longer exists. - pm.expect(json_response).to.have.property("vehicle_id"); - pm.expect(json_response).to.have.property("vendor_id"); + pm.test("Update a Driver persists the timezone", function () { + // The driver record has no timezone column — it belongs to the linked + // user — and the update used to copy only name, email and phone, so a + // timezone was accepted, answered 200, and dropped. + pm.expect(json_response.timezone, "timezone was not persisted").to.eql("Europe/Amsterdam"); + }); + + pm.test("Update a Driver reports assignments as additive public ids", function () { + // The identifiers are new keys beside the released relationship + // objects, not replacements for them. The vehicle created earlier in + // this run has already been deleted by the Vehicles folder, so the + // assignment itself is exercised by the Fleet-Ops contract suite. + ["vehicle_id", "vendor_id", "job_id"].forEach(function (key) { + pm.expect(json_response, key + " is missing").to.have.property(key); + }); if (json_response.vehicle_id) { pm.expect(json_response.vehicle_id).to.match(/^vehicle_/); } }); + pm.test("Update a Driver keeps every relationship the released contract returned", function () { + // Navigator interpolates `driver.user` straight into a socket channel + // name; an object there would subscribe it to `user.[object Object]`. + pm.expect(json_response.user, "user must stay a public-id string").to.be.a('string'); + pm.expect(json_response.user).to.match(/^user_/); + pm.expect(json_response, "company must not be dropped").to.have.property("company"); + pm.expect(json_response, "company_name must not be dropped").to.have.property("company_name"); + + // These endpoints load vehicle, vendor and currentJob, so when a + // driver has one it comes back as an object, without a `with`. + ["vehicle", "vendor", "current_job"].forEach(function (key) { + if (json_response[key] !== undefined && json_response[key] !== null) { + pm.expect(json_response[key], key + " must never be a string").to.be.an('object'); + } + }); + }); + pm.test("Update a Driver exposes no internal uuid columns", function () { ["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token"].forEach(function (key) { pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml index e62196a..6c88f28 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml @@ -29,10 +29,11 @@ response: "task": "Long haul distribution", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": "service_area_9Kp2Vx4Qm1", - "zone": null, - "vendor": null, - "parent_fleet": null, + "service_area_id": "service_area_9Kp2Vx4Qm1", + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": null, + "photo_id": null, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml index 5725e8e..b914b83 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Create a Subfleet.resources/examples/OK.example.yaml @@ -29,10 +29,10 @@ response: "task": "Employee transport", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": null, - "zone": null, - "vendor": null, - "parent_fleet": "fleet_4Mnp8Qx2Vz", + "service_area_id": null, + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": "fleet_4Mnp8Qx2Vz", "updated_at": "2026-05-07T08:31:00.000000Z", "created_at": "2026-05-07T08:31:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml index 21a38df..c249304 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Query Fleets.resources/examples/OK.example.yaml @@ -33,10 +33,11 @@ response: "task": "Long haul distribution", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": "service_area_9Kp2Vx4Qm1", - "zone": null, - "vendor": null, - "parent_fleet": null, + "service_area_id": "service_area_9Kp2Vx4Qm1", + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": null, + "photo_id": null, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml index 7b0df73..dc09207 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Retrieve a Fleet.resources/examples/OK.example.yaml @@ -21,10 +21,11 @@ response: "task": "Long haul distribution", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": "service_area_9Kp2Vx4Qm1", - "zone": null, - "vendor": null, - "parent_fleet": null, + "service_area_id": "service_area_9Kp2Vx4Qm1", + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": null, + "photo_id": null, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml index 31719e4..febb9ed 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/Update a Fleet.resources/examples/OK.example.yaml @@ -31,10 +31,11 @@ response: "task": "Regional distribution", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": "service_area_9Kp2Vx4Qm1", - "zone": null, - "vendor": null, - "parent_fleet": null, + "service_area_id": "service_area_9Kp2Vx4Qm1", + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": null, + "photo_id": null, "updated_at": "2026-05-07T09:12:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Fleets/.resources/object.yaml b/postman/collections/Fleetbase API/Fleets/.resources/object.yaml index e0a04c5..d30e279 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/object.yaml @@ -3,7 +3,7 @@ name: Fleet description: |- A fleet groups drivers and vehicles for operational assignment. Fleets can be linked to a service area, a zone, a vendor and a parent fleet, and can themselves hold subfleets, drivers and vehicles. - Relationships are returned as the related resource's public ID. Asking for a relationship through `?with=` returns it as a nested object instead. Internal uuid columns are never part of the public contract. + Each relationship appears twice, and the two are separate keys. `_id` is the related resource's public ID and is always present. `` is the nested object and is returned only when the relationship was asked for through `?with=`; it is never a string. Internal uuid columns are never part of the public contract. example: | { "id": "fleet_4Mnp8Qx2Vz", @@ -12,10 +12,11 @@ example: | "task": "Long haul distribution", "status": "active", "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", - "service_area": "service_area_9Kp2Vx4Qm1", - "zone": null, - "vendor": null, - "parent_fleet": null, + "service_area_id": "service_area_9Kp2Vx4Qm1", + "zone_id": null, + "vendor_id": null, + "parent_fleet_id": null, + "photo_id": null, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } @@ -38,22 +39,37 @@ fields: - name: photo_url type: string description: URL of the fleet image, or the default fleet image when none is set. - - name: service_area + - name: service_area_id type: string - description: |- - Public ID of the assigned service area, or `null`. Returned as a nested object when requested through `with=service_area`. - - name: zone + description: Public ID of the assigned service area, or `null`. Always present. + - name: zone_id type: string - description: |- - Public ID of the assigned zone, or `null`. Returned as a nested object when requested through `with=zone`. - - name: vendor + description: Public ID of the assigned zone, or `null`. Always present. + - name: vendor_id type: string - description: |- - Public ID of the assigned vendor, or `null`. Returned as a nested object when requested through `with=vendor`. - - name: parent_fleet + description: Public ID of the assigned vendor, or `null`. Always present. + - name: parent_fleet_id + type: string + description: Public ID of the parent fleet, or `null` for a root fleet. Always present. + - name: photo_id type: string + description: Public ID of the fleet image file, or `null`. Always present. + - name: service_area + type: object description: |- - Public ID of the parent fleet, or `null` for a root fleet. Returned as a nested object when requested through `with=parent_fleet`. + The assigned service area, returned only when requested through `with=service_area`. Never a string — read `service_area_id` for the identifier. + - name: zone + type: object + description: The assigned zone, returned only when requested through `with=zone`. + - name: vendor + type: object + description: The assigned vendor, returned only when requested through `with=vendor`. + - name: parent_fleet + type: object + description: The parent fleet, returned only when requested through `with=parent_fleet`. + - name: photo + type: object + description: The fleet image file, returned only when requested through `with=photo`. - name: subfleets type: array of objects description: Child fleets, when requested through `with=subfleets`. diff --git a/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml index 85e76a9..7e83f15 100644 --- a/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml +++ b/postman/collections/Fleetbase API/Fleets/Create a Fleet.request.yaml @@ -44,21 +44,34 @@ scripts: pm.expect(json_response.status).to.eql("active"); }); - pm.test("Create a Fleet resolves the service area to a public id", function () { + pm.test("Create a Fleet reports the service area as an additive public id", function () { const serviceAreaId = pm.variables.get("service_area_id"); - pm.expect(json_response).to.have.property("service_area"); + pm.expect(json_response, "service_area_id is the identifier field").to.have.property("service_area_id"); if (serviceAreaId) { - pm.expect(json_response.service_area).to.eql(serviceAreaId); + pm.expect(json_response.service_area_id).to.eql(serviceAreaId); + } + + // The identifier is a separate key. `service_area` is the nested + // object, returned only when it was asked for through `with`, and it + // is never a string — a property that changes type between calls + // breaks every consumer that dereferences it. + if (json_response.service_area !== undefined) { + pm.expect(json_response.service_area, "service_area must never be a string").to.be.an('object'); } }); pm.test("Create a Fleet is a root fleet when no parent was sent", function () { - pm.expect(json_response).to.have.property("parent_fleet"); - pm.expect(json_response.parent_fleet, "a fleet created without a parent must have none").to.be.null; + pm.expect(json_response).to.have.property("parent_fleet_id"); + pm.expect(json_response.parent_fleet_id, "a fleet created without a parent must have none").to.be.null; }); pm.test("Create a Fleet exposes no internal uuid columns", function () { - ["uuid", "public_id", "company_uuid", "service_area_uuid", "zone_uuid", "vendor_uuid", "parent_fleet_uuid", "image_uuid", "_key"].forEach(function (key) { + // A sweep rather than a list: a column added later cannot slip out + // just because nobody remembered to name it here. + Object.keys(json_response).forEach(function (key) { + pm.expect(key, `${key} leaks an internal column`).to.not.match(/(^uuid$|_uuid$)/); + }); + ["public_id", "company_uuid", "_key"].forEach(function (key) { pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); }); }); diff --git a/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml index 127fe9a..889414c 100644 --- a/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml +++ b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml @@ -34,12 +34,16 @@ scripts: pm.test("Create a Subfleet resolves the parent to its public id", function () { const parentFleetId = pm.variables.get("parent_fleet_id"); - pm.expect(json_response.parent_fleet, "parent_fleet missing from the response").to.be.a('string'); - pm.expect(json_response.parent_fleet, "parent_fleet must be a public id, never a uuid").to.match(/^fleet_/); + pm.expect(json_response.parent_fleet_id, "parent_fleet_id missing from the response").to.be.a('string'); + pm.expect(json_response.parent_fleet_id, "parent_fleet_id must be a public id, never a uuid").to.match(/^fleet_/); if (parentFleetId) { - pm.expect(json_response.parent_fleet).to.eql(parentFleetId); + pm.expect(json_response.parent_fleet_id).to.eql(parentFleetId); + } + pm.expect(json_response.id, "a fleet must not be its own parent").to.not.eql(json_response.parent_fleet_id); + + if (json_response.parent_fleet !== undefined) { + pm.expect(json_response.parent_fleet, "parent_fleet must never be a string").to.be.an('object'); } - pm.expect(json_response.id, "a fleet must not be its own parent").to.not.eql(json_response.parent_fleet); }); pm.test("Create a Subfleet exposes no internal uuid columns", function () { diff --git a/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml b/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml index 1691506..2b42eff 100644 --- a/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml +++ b/postman/collections/Fleetbase API/Fleets/Query Fleets.queryParams.yaml @@ -8,7 +8,7 @@ fields: description: Filter by fleet name. - name: public_id type: string - description: Filter by fleet public ID (`fleet_...`). + description: Filter by fleet public ID (`fleet_...`). Exact match. - name: status type: string description: Filter by fleet status. Accepts a comma-separated list. @@ -42,4 +42,4 @@ fields: - name: with type: array of strings description: |- - Relationships to include in fleet responses. A relationship named here is returned as a nested object; otherwise it is returned as the related resource's public ID. + Relationships to expand: `service_area`, `zone`, `vendor`, `parent_fleet`, `photo`, `subfleets`, `drivers`, `vehicles`, and the nested `subfleets.drivers` / `subfleets.vehicles`. Accepted as `?with=vendor`, `?with[]=vendor`, `?with[]=vendor&with[]=driver` and `?with=vendor,driver`; `expand` is an alias for all four. Expanding adds the nested object — it never replaces or retypes the `_id` identifier, which is always present. A name outside the supported set is ignored rather than rejected. diff --git a/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml b/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml index b89f95e..507825f 100644 --- a/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml +++ b/postman/collections/Fleetbase API/Fleets/Update a Fleet.request.yaml @@ -39,15 +39,20 @@ scripts: pm.expect(json_response.status).to.eql("active"); }); - pm.test("Update a Fleet resolves relationships to public ids", function () { + pm.test("Update a Fleet resolves relationships to additive public ids", function () { const serviceAreaId = pm.variables.get("service_area_id"); - pm.expect(json_response).to.have.property("service_area"); + pm.expect(json_response).to.have.property("service_area_id"); if (serviceAreaId) { - pm.expect(json_response.service_area).to.eql(serviceAreaId); + pm.expect(json_response.service_area_id).to.eql(serviceAreaId); } - if (json_response.parent_fleet !== null && json_response.parent_fleet !== undefined) { - pm.expect(json_response.parent_fleet, "parent_fleet must be a public id").to.match(/^fleet_/); + if (json_response.parent_fleet_id !== null) { + pm.expect(json_response.parent_fleet_id, "parent_fleet_id must be a public id").to.match(/^fleet_/); } + ["service_area", "zone", "vendor", "parent_fleet", "photo"].forEach(function (key) { + if (json_response[key] !== undefined) { + pm.expect(json_response[key], key + " must never be a string").to.be.an('object'); + } + }); }); pm.test("Update a Fleet exposes no internal uuid columns", function () { diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml index 5eb2175..a436309 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Create a Vehicle.resources/examples/Created.example.yaml @@ -52,10 +52,11 @@ response: "time_window_start": null, "time_window_end": null, "return_to_depot": true, - "driver": null, - "vendor": "vendor_5Nq8Tz1Wm4", - "category": null, - "warranty": null, + "driver_id": null, + "vendor_id": "vendor_5Nq8Tz1Wm4", + "category_id": null, + "warranty_id": null, + "photo_id": null, "meta": { "depot": "north" }, "updated_at": "2026-05-07T08:30:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Expand a Vehicle.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Expand a Vehicle.resources/examples/OK.example.yaml new file mode 100644 index 0000000..6e6435c --- /dev/null +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Expand a Vehicle.resources/examples/OK.example.yaml @@ -0,0 +1,58 @@ +$kind: http-example +request: + url: "{{base_url}}/{{namespace}}/vehicles/:id?with[]=vendor" + method: GET + headers: + Accept: application/json + pathVariables: + id: "vehicle_7YqM3KpL2n" + queryParams: + - key: with[] + value: vendor + description: Expand the vendor relationship. +response: + statusCode: 200 + statusText: OK + headers: + Content-Type: application/json + body: + type: json + content: |- + { + "id": "vehicle_7YqM3KpL2n", + "internal_id": "VEH-1001", + "name": "Depot Van", + "vin": "1GCGSBEA0G1111111", + "photo_url": "https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png", + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg", + "make": "Toyota", + "model": "Camry", + "year": "2023", + "trim": "SE", + "type": "sedan", + "plate_number": "ABC123", + "fuel_card_number": "FC-1001", + "status": "available", + "online": false, + "odometer": 41000, + "odometer_unit": "km", + "currency": "SGD", + "purchased_at": "2026-01-02T00:00:00.000000Z", + "location": { "type": "Point", "coordinates": [-73.9857, 40.7484] }, + "driver_id": null, + "vendor_id": "vendor_5Nq8Tz1Wm4", + "vendor": { + "id": "vendor_5Nq8Tz1Wm4", + "name": "Acme Logistics" + }, + "category_id": null, + "warranty_id": null, + "photo_id": null, + "specs": { "doors": 4 }, + "details": { "liftgate": true }, + "notes": "City route pool", + "meta": { "depot": "north" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" + } +order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Query Vehicles.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Query Vehicles.resources/examples/OK.example.yaml index eac00d7..17bf243 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Query Vehicles.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Query Vehicles.resources/examples/OK.example.yaml @@ -1,22 +1,16 @@ $kind: http-example request: - url: "{{base_url}}/{{namespace}}/vehicles?query=&limit=&offset=&sort=" + url: "{{base_url}}/{{namespace}}/vehicles?internal_id=VEH-1001&limit=25" method: GET headers: Accept: application/json queryParams: - - key: query - value: - description: This parameter allows you to filter the results by their name. + - key: internal_id + value: VEH-1001 + description: Exact match on the identifier the operator's own system uses. - key: limit - value: - description: A cursor for use in pagination. A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. - - key: offset - value: - description: A cursor for use in pagination. A offset for the results, when the offset is supplied the results will start from the offset. - - key: sort - value: - description: A cursor used to sort results. Sort can be any property of the object, to sort descending sort must append the minus symbol. To sort by created date descending sort would be `-created_at` to sort in ascending order the sort would be `created_at`. + value: "25" + description: A limit on the number of objects to be returned. response: statusCode: 200 statusText: OK @@ -25,76 +19,39 @@ response: body: type: json content: |- - { - "id": "", - "name": "", - "vin": "", - "driver": "", - "photo_url": "", - "make": "", - "model": "", - "year": "", - "trim": "", - "type": "", - "plate_number": "", - "vin_data": { - "make": "", - "manufacturer": "", - "plant_country": "", - "product_type": "", - "manufacturer_address": "", - "check_digit": "", - "model_year": "", - "sequential_number": "", - "model": "", - "plant_city": "", - "trim": "", - "plant_state": "", - "body": "", - "number_of_doors": "", - "transmission": "", - "number_of_gears": "", - "engine_displacement_ccm": "", - "engine_model": "", - "engine_power_kw": "", - "series": "", - "fuel_type_primary": "", - "production_stopped": "" - }, - "model_data": { - "0_to_100_kph": "", - "body": "", - "co2": "", - "doors": "", - "drive": "", - "engine_bore_mm": "", - "engine_cc": "", - "engine_compression": "", - "engine_cyl": "", - "engine_fuel": "", - "engine_position": "", - "engine_power_ps": "", - "engine_power_rpm": "", - "engine_stroke_mm": "", - "engine_torque_nm": "", - "engine_torque_rpm": "", - "engine_valves_per_cyl": "", - "fuel_cap_l": "", - "length_mm": "", - "lkm_city": "", - "lkm_hwy": "", - "lkm_mixed": "", - "make_display": "", - "seats": "", - "sold_in_us": "", - "top_speed_kph": "", - "transmission_type": "", - "weight_kg": "", - "wheelbase_mm": "", - "width_mm": "" - }, - "status": "", - "updated_at": "", - "created_at": "" - } + [ + { + "id": "vehicle_7YqM3KpL2n", + "internal_id": "VEH-1001", + "name": "Depot Van", + "vin": "1GCGSBEA0G1111111", + "photo_url": "https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png", + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg", + "make": "Toyota", + "model": "Camry", + "year": "2023", + "trim": "SE", + "type": "sedan", + "plate_number": "ABC123", + "fuel_card_number": "FC-1001", + "status": "available", + "online": false, + "odometer": 41000, + "odometer_unit": "km", + "currency": "SGD", + "purchased_at": "2026-01-02T00:00:00.000000Z", + "location": { "type": "Point", "coordinates": [-73.9857, 40.7484] }, + "driver_id": null, + "vendor_id": "vendor_5Nq8Tz1Wm4", + "category_id": null, + "warranty_id": null, + "photo_id": null, + "specs": { "doors": 4 }, + "details": { "liftgate": true }, + "notes": "City route pool", + "meta": { "depot": "north" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" + } + ] order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Retrieve a Vehicle.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Retrieve a Vehicle.resources/examples/OK.example.yaml index 0318748..8d09474 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Retrieve a Vehicle.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Retrieve a Vehicle.resources/examples/OK.example.yaml @@ -5,7 +5,7 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "vehicle_7YqM3KpL2n" response: statusCode: 200 statusText: OK @@ -15,75 +15,36 @@ response: type: json content: |- { - "id": "", - "name": "", - "vin": "", - "driver": "", - "photo_url": "", - "make": "", - "model": "", - "year": "", - "trim": "", - "type": "", - "plate_number": "", - "vin_data": { - "make": "", - "manufacturer": "", - "plant_country": "", - "product_type": "", - "manufacturer_address": "", - "check_digit": "", - "model_year": "", - "sequential_number": "", - "model": "", - "plant_city": "", - "trim": "", - "plant_state": "", - "body": "", - "number_of_doors": "", - "transmission": "", - "number_of_gears": "", - "engine_displacement_ccm": "", - "engine_model": "", - "engine_power_kw": "", - "series": "", - "fuel_type_primary": "", - "production_stopped": "" - }, - "model_data": { - "0_to_100_kph": "", - "body": "", - "co2": "", - "doors": "", - "drive": "", - "engine_bore_mm": "", - "engine_cc": "", - "engine_compression": "", - "engine_cyl": "", - "engine_fuel": "", - "engine_position": "", - "engine_power_ps": "", - "engine_power_rpm": "", - "engine_stroke_mm": "", - "engine_torque_nm": "", - "engine_torque_rpm": "", - "engine_valves_per_cyl": "", - "fuel_cap_l": "", - "length_mm": "", - "lkm_city": "", - "lkm_hwy": "", - "lkm_mixed": "", - "make_display": "", - "seats": "", - "sold_in_us": "", - "top_speed_kph": "", - "transmission_type": "", - "weight_kg": "", - "wheelbase_mm": "", - "width_mm": "" - }, - "status": "", - "updated_at": "", - "created_at": "" + "id": "vehicle_7YqM3KpL2n", + "internal_id": "VEH-1001", + "name": "Depot Van", + "vin": "1GCGSBEA0G1111111", + "photo_url": "https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png", + "avatar_url": "https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg", + "make": "Toyota", + "model": "Camry", + "year": "2023", + "trim": "SE", + "type": "sedan", + "plate_number": "ABC123", + "fuel_card_number": "FC-1001", + "status": "available", + "online": false, + "odometer": 41000, + "odometer_unit": "km", + "currency": "SGD", + "purchased_at": "2026-01-02T00:00:00.000000Z", + "location": { "type": "Point", "coordinates": [-73.9857, 40.7484] }, + "driver_id": null, + "vendor_id": "vendor_5Nq8Tz1Wm4", + "category_id": null, + "warranty_id": null, + "photo_id": null, + "specs": { "doors": 4 }, + "details": { "liftgate": true }, + "notes": "City route pool", + "meta": { "depot": "north" }, + "updated_at": "2026-05-07T08:30:00.000000Z", + "created_at": "2026-05-07T08:30:00.000000Z" } order: 1000 diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml index 64aab4d..287d515 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/Update a Vehicle.resources/examples/OK.example.yaml @@ -40,7 +40,7 @@ response: "odometer_unit": "km", "specs": { "doors": 4 }, "meta": { "depot": "south" }, - "vendor": "vendor_5Nq8Tz1Wm4", + "vendor_id": "vendor_5Nq8Tz1Wm4", "updated_at": "2026-05-07T09:12:00.000000Z", "created_at": "2026-05-07T08:30:00.000000Z" } diff --git a/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml b/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml index fcc3613..371c062 100644 --- a/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Vehicles/.resources/object.yaml @@ -3,7 +3,7 @@ name: Vehicle description: |- A vehicle represents a trackable asset used for Fleet-Ops assignment, routing, and capacity planning. It stores vehicle identity, status, assignment, current position, capacity, dimensions, engine and regulatory specifications, financing, orchestrator constraints, and metadata. - Relationships are returned as the related resource's public ID; asking for one through `?with=` returns it as a nested object instead. Internal uuid columns are never part of the public contract. + Each relationship appears twice, and the two are separate keys. `_id` is the related resource's public ID and is always present. `` is the nested object and is returned only when the relationship was asked for through `?with=`; it is never a string. Internal uuid columns are never part of the public contract. example: | { "id": "vehicle_7YqM3KpL2n", @@ -23,14 +23,17 @@ example: | "status": "operational", "online": false, "payload_capacity": 500, + "fuel_card_number": "FC-1001", + "driver_id": null, + "vendor_id": "vendor_5Nq8Tz1Wm4", + "category_id": null, + "warranty_id": null, + "photo_id": null, "skills": ["tail_lift"], "max_tasks": 40, "time_window_start": "08:00:00", "time_window_end": "18:00:00", "return_to_depot": true, - "vendor": "vendor_5Nq8Tz1Wm4", - "category": null, - "warranty": null, "location": { "latitude": 40.7484, "longitude": -73.9857 @@ -64,7 +67,8 @@ fields: description: Vehicle description. - name: driver type: object - description: Assigned driver, when loaded. + description: |- + The assigned driver, returned only when requested through `with=driver`. Never a string — read `driver_id` for the identifier. - name: devices type: array of objects description: Vehicle devices, when loaded. @@ -101,6 +105,9 @@ fields: - name: call_sign type: string description: Vehicle call sign. + - name: fuel_card_number + type: string + description: Fuel card number assigned to the vehicle. - name: vin type: string description: Vehicle identification number. @@ -205,16 +212,35 @@ fields: - name: meta type: object description: Additional vehicle metadata. - - name: vendor + - name: driver_id + type: string + description: Public ID of the assigned driver, or `null`. Always present. + - name: vendor_id + type: string + description: Public ID of the assigned vendor, or `null`. Always present. + - name: category_id + type: string + description: Public ID of the assigned category, or `null`. Always present. + - name: warranty_id + type: string + description: Public ID of the assigned warranty, or `null`. Always present. + - name: photo_id type: string description: |- - Public ID of the assigned vendor, or `null`. Returned as a nested object when requested through `with=vendor`. + Public ID of the vehicle photo file, or `null`. Always present. `photo_url` beside it is a convenience URL, and `avatar_url` is a separate display asset that is not a file reference. + - name: vendor + type: object + description: |- + The assigned vendor, returned only when requested through `with=vendor`. Never a string — read `vendor_id` for the identifier. - name: category - type: string - description: Public ID of the assigned category, or `null`. + type: object + description: The assigned category, returned only when requested through `with=category`. - name: warranty - type: string - description: Public ID of the assigned warranty, or `null`. + type: object + description: The assigned warranty, returned only when requested through `with=warranty`. + - name: photo + type: object + description: The vehicle photo file, returned only when requested through `with=photo`. - name: skills type: array of strings description: Capabilities the vehicle offers for orchestrator assignment. diff --git a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml index cfa040c..c7b6d1b 100644 --- a/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Create a Vehicle.request.yaml @@ -23,22 +23,46 @@ body: "trim": "SE", "color": "White", "type": "sedan", + "class": "N1", "plate_number": "ABC123", + "serial_number": "SER-1001", + "call_sign": "DEPOT-1", + "fuel_card_number": "FC-1001", "status": "maintenance", "online": false, "odometer": 41000, "odometer_unit": "km", + "odometer_at_purchase": 12, "measurement_system": "metric", "fuel_type": "diesel", + "fuel_volume_unit": "l", + "transmission": "automatic", + "body_type": "sedan", "seating_capacity": 5, + "weight": 1620.5, "payload_capacity": 1400, + "fuel_capacity": 60, + "currency": "SGD", + "purchased_at": "2026-01-02", + "loan_first_payment": "2026-02-15", + "loan_amount": 32000, + "insurance_value": 41000, + "depreciation_rate": 12.5, + "current_value": 38000, + "acquisition_cost": 52000, + "engine_number": "ENG-1001", + "number_of_cylinders": 4, + "latitude": 40.7484, + "longitude": -73.9857, + "specs": { "doors": 4 }, + "details": { "liftgate": true }, + "notes": "City route pool", + "meta": { "depot": "north" }, "skills": ["tail_lift"], "max_tasks": 40, "time_window_start": "08:00", "time_window_end": "18:00", "return_to_depot": true, - "notes": "City route pool", - "meta": { "depot": "north" }, "vendor": "{{vendor_id}}" } scripts: @@ -55,55 +79,82 @@ scripts: pm.expect(json_response.id, "id is not a vehicle public id").to.match(/^vehicle_/); }); - pm.test("Create a Vehicle persists the identity fields that were sent", function () { - pm.expect(json_response.internal_id).to.eql("VEH-1001"); - pm.expect(json_response.name).to.eql("Depot Van"); - pm.expect(json_response.description).to.eql("City route van"); - pm.expect(json_response.vin).to.eql("1GCGSBEA0G1111111"); - pm.expect(Number(json_response.year)).to.eql(2023); - pm.expect(json_response.make).to.eql("Toyota"); - pm.expect(json_response.model).to.eql("Camry"); - pm.expect(json_response.trim).to.eql("SE"); - pm.expect(json_response.color).to.eql("White"); - pm.expect(json_response.type).to.eql("sedan"); - pm.expect(json_response.plate_number).to.eql("ABC123"); + // Driven off the request body rather than a hand-written list, so a field + // cannot be added to the request without an assertion following it. + const sent = JSON.parse(pm.request.body.raw + .replace(/\{\{vendor_id\}\}/g, pm.variables.get("vendor_id") || "vendor_unset")); + + // Fields the API deliberately transforms on the way out. + const transformed = { + status: "available", // `active`/`maintenance` normalisation is asserted separately + latitude: null, // canonicalised into `location` + longitude: null, // canonicalised into `location` + vendor: null, // relationship input, read back as vendor_id + }; + + pm.test("Create a Vehicle persists every field that was sent", function () { + Object.keys(sent).forEach(function (field) { + if (Object.prototype.hasOwnProperty.call(transformed, field)) { + return; + } + + pm.expect(json_response, `${field} is missing from the response`).to.have.property(field); + + const expected = sent[field]; + const actual = json_response[field]; + + if (expected !== null && typeof expected === 'object') { + pm.expect(actual, `${field} did not round trip`).to.eql(expected); + } else if (typeof expected === 'number') { + pm.expect(Number(actual), `${field} did not round trip`).to.eql(expected); + } else if (typeof expected === 'boolean') { + pm.expect(Boolean(actual), `${field} did not round trip`).to.eql(expected); + } else if (field.endsWith('_at') || field === 'loan_first_payment' || field.startsWith('time_window')) { + // Dates, datetimes and times are normalised; the value is what matters. + pm.expect(String(actual), `${field} did not round trip`).to.contain(String(expected).slice(0, 5)); + } else { + pm.expect(String(actual), `${field} did not round trip`).to.eql(String(expected)); + } + }); }); - pm.test("Create a Vehicle persists the operational fields that were sent", function () { - // The odometer was accepted but discarded before this contract; a - // silent no-op is the worst possible answer to a mileage write. - pm.expect(Number(json_response.odometer), "odometer was not persisted").to.eql(41000); - pm.expect(json_response.odometer_unit).to.eql("km"); - pm.expect(json_response.measurement_system).to.eql("metric"); - pm.expect(json_response.fuel_type).to.eql("diesel"); - pm.expect(Number(json_response.seating_capacity)).to.eql(5); - pm.expect(Number(json_response.payload_capacity)).to.eql(1400); - pm.expect(json_response.status).to.eql("maintenance"); - pm.expect(json_response.online).to.eql(false); - pm.expect(json_response.notes).to.eql("City route pool"); + pm.test("Create a Vehicle canonicalises the coordinates into location", function () { + // Latitude and longitude go in as two scalars and come back inside + // `location`; there is no duplicate pair at the top level. + pm.expect(json_response.location, "location missing").to.not.be.oneOf([null, undefined]); + pm.expect(JSON.stringify(json_response.location)).to.contain("40.7484"); }); - pm.test("Create a Vehicle persists structured and orchestrator fields", function () { - pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); - pm.expect(json_response.meta.depot).to.eql("north"); - pm.expect(json_response.skills).to.be.an('array').that.includes("tail_lift"); - pm.expect(Number(json_response.max_tasks)).to.eql(40); - pm.expect(String(json_response.time_window_start)).to.match(/^08:00/); - pm.expect(String(json_response.time_window_end)).to.match(/^18:00/); - pm.expect(json_response.return_to_depot).to.eql(true); + pm.test("Create a Vehicle normalises the status it stores", function () { + // `maintenance` is stored as sent; `active` is the value the model + // rewrites to `available`, which Update a Vehicle asserts. + pm.expect(json_response.status).to.eql("maintenance"); }); - pm.test("Create a Vehicle resolves the vendor to a public id", function () { + pm.test("Create a Vehicle reports relationships as additive public ids", function () { const vendorId = pm.variables.get("vendor_id"); - pm.expect(json_response).to.have.property("vendor"); + ["driver_id", "vendor_id", "category_id", "warranty_id", "photo_id"].forEach(function (key) { + pm.expect(json_response, key + " is missing").to.have.property(key); + }); if (vendorId) { - pm.expect(json_response.vendor).to.eql(vendorId); + pm.expect(json_response.vendor_id).to.eql(vendorId); + pm.expect(json_response.vendor_id).to.match(/^vendor_/); } + + // The object keys are separate and are never strings. + ["driver", "vendor", "category", "warranty", "photo"].forEach(function (key) { + if (json_response[key] !== undefined) { + pm.expect(json_response[key], key + " must never be a string").to.be.an('object'); + } + }); }); pm.test("Create a Vehicle exposes no internal uuid columns", function () { - ["uuid", "public_id", "company_uuid", "vendor_uuid", "category_uuid", "warranty_uuid", "photo_uuid", "telematic_uuid", "_key"].forEach(function (key) { - pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key); + Object.keys(json_response).forEach(function (key) { + pm.expect(key, `${key} leaks an internal column`).to.not.match(/(^uuid$|_uuid$)/); + }); + ["public_id", "company_uuid", "_key"].forEach(function (key) { + pm.expect(json_response).to.not.have.property(key); }); }); language: text/javascript diff --git a/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle Scalar.request.yaml b/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle Scalar.request.yaml new file mode 100644 index 0000000..b6f2461 --- /dev/null +++ b/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle Scalar.request.yaml @@ -0,0 +1,28 @@ +$kind: http-request +description: |- + The same expansion as **Expand a Vehicle**, sent in the scalar comma-separated form. + + `?with=vendor,driver` must behave exactly as `?with[]=vendor&with[]=driver` does. This request exists so the two spellings are proven equivalent rather than assumed to be. +url: "{{base_url}}/{{namespace}}/vehicles/:id?with=vendor" +method: GET +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{vehicle_id}}" + description: (Required) The public id of the `Vehicle`. +queryParams: + with: vendor +scripts: + - type: afterResponse + code: | + var json_response = pm.response.json(); + + pm.test("with= produces the same expansion as with[]=", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response.vendor, "vendor was not expanded by the scalar form").to.be.an('object'); + pm.expect(json_response.vendor_id).to.eql(json_response.vendor.id); + }); + language: text/javascript +examples: ./.resources/Expand a Vehicle.resources/examples +order: 3200 diff --git a/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle.request.yaml b/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle.request.yaml new file mode 100644 index 0000000..8430b90 --- /dev/null +++ b/postman/collections/Fleetbase API/Vehicles/Expand a Vehicle.request.yaml @@ -0,0 +1,54 @@ +$kind: http-request +description: |- + Retrieves a vehicle with its vendor expanded, using the array form of the expansion parameter. + + Both `?with=vendor` and `?with[]=vendor` are accepted, as are `?with=vendor,driver` and the `expand` alias. Expansion is strictly additive: it adds the nested object and leaves `vendor_id` exactly as it was. A relationship name outside the supported set is ignored rather than rejected, so a client sending a relation this version does not have still gets its response. +url: "{{base_url}}/{{namespace}}/vehicles/:id?with[]=vendor&with[]=not_a_relation" +method: GET +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{vehicle_id}}" + description: (Required) The public id of the `Vehicle`. +queryParams: + - key: with[] + value: vendor + - key: with[] + value: not_a_relation +scripts: + - type: afterResponse + code: | + var json_response = pm.response.json(); + + pm.test("Expand a Vehicle returns the vehicle", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.be.oneOf([200]); + pm.expect(json_response.id, "id is not a vehicle public id").to.match(/^vehicle_/); + }); + + pm.test("with[] adds the nested object", function () { + pm.expect(json_response.vendor, "vendor was not expanded").to.be.an('object'); + pm.expect(json_response.vendor.id, "the expanded vendor has no public id").to.match(/^vendor_/); + }); + + pm.test("expansion leaves the identifier untouched", function () { + // The identifier is present whether or not the object is, and it does + // not change when the object appears beside it. + pm.expect(json_response.vendor_id).to.eql(json_response.vendor.id); + pm.expect(json_response.vendor_id).to.match(/^vendor_/); + }); + + pm.test("an unsupported expansion is ignored, not an error", function () { + pm.expect(json_response).to.not.have.property("not_a_relation"); + // A relation that was not asked for stays absent — never a string. + pm.expect(json_response.driver).to.be.oneOf([undefined, null]); + }); + + pm.test("Expand a Vehicle exposes no internal uuid columns", function () { + Object.keys(json_response).forEach(function (key) { + pm.expect(key, `${key} leaks an internal column`).to.not.match(/(^uuid$|_uuid$)/); + }); + }); + language: text/javascript +examples: ./.resources/Expand a Vehicle.resources/examples +order: 3100 diff --git a/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml b/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml index 58cd893..9723e20 100644 --- a/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml +++ b/postman/collections/Fleetbase API/Vehicles/Query Vehicles.queryParams.yaml @@ -6,7 +6,7 @@ fields: - name: internal_id type: string description: |- - Filter by the identifier the operator's own system uses. This is the lookup an importer keys on to decide whether a vehicle already exists. + Filter by the identifier the operator's own system uses. **Exact match** on the public API: `VEH-10` returns only `VEH-10`, never `VEH-100`. That is what lets an importer use it to decide between update and create. (The Fleet-Ops console does a partial search on the same field; that behaviour is internal to the console and not part of this contract.) - name: plate_number type: string description: Filter by licence plate number. @@ -15,7 +15,7 @@ fields: description: Filter by vehicle identification number. - name: public_id type: string - description: Filter by vehicle public ID (`vehicle_...`). + description: Filter by vehicle public ID (`vehicle_...`). Exact match. - name: display_name type: string description: Filter across year, make, model and plate number together. @@ -50,3 +50,7 @@ fields: - name: sort type: string description: Sort expression for the vehicle query. + - name: with + type: array of strings + description: |- + Relationships to expand. Accepted as `?with=vendor`, `?with[]=vendor`, `?with[]=vendor&with[]=driver` and `?with=vendor,driver`; `expand` is an alias for all four. A relationship named here is additionally returned as a nested object — it never replaces or retypes the `_id` identifier, which is always present. A name outside the supported set is ignored rather than rejected. From 91e72feb908ecbe3b8a7484d812825fbc0ca7a83 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Sat, 5 Sep 2026 15:55:31 +0800 Subject: [PATCH 3/3] fix: stop asserting dynamic variables against a body that re-resolves them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cross-repository contract run failed two driver assertions comparing the response's email against pm.request.body.raw. The body carries {{$randomEmail}} and {{$randomPhoneNumber}}, which Postman resolves afresh every time that body is read — so the comparison was against a different address than the one actually sent, and the assertion could only ever fail. Both now assert shape rather than a value that cannot be recovered after the fact, and a new 'Update a Driver Unchanged Contact' request resends the address and number the response reported. That is the case the update-time uniqueness rule has to let through: without its ignore-own-user clause a driver could not resend its own address. --- .../Drivers/Create a Driver.request.yaml | 12 +++--- ...te a Driver Unchanged Contact.request.yaml | 40 +++++++++++++++++++ .../Drivers/Update a Driver.request.yaml | 21 ++++++---- 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 postman/collections/Fleetbase API/Drivers/Update a Driver Unchanged Contact.request.yaml diff --git a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml index 0cb383d..620c686 100644 --- a/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml @@ -64,11 +64,13 @@ scripts: }); pm.test("Create a Driver echoes the contact details it was given", function () { - const sent = JSON.parse(pm.request.body.raw); - - pm.expect(json_response.email, "email did not round trip").to.eql(json_response.email && sent.email ? sent.email : json_response.email); - pm.expect(json_response.email, "email missing").to.be.a('string'); - pm.expect(json_response.phone, "phone missing").to.be.a('string'); + // Deliberately not compared against pm.request.body.raw: the address + // and number come from {{$randomEmail}} / {{$randomPhoneNumber}}, + // which Postman re-resolves every time the raw body is read — so the + // comparison would be against a different value than was sent. + pm.expect(json_response.email, "email missing").to.be.a('string').and.not.empty; + pm.expect(json_response.email, "email is not an address").to.match(/@/); + pm.expect(json_response.phone, "phone missing").to.be.a('string').and.not.empty; pm.expect(json_response.timezone, "timezone was not persisted").to.eql("Asia/Singapore"); }); diff --git a/postman/collections/Fleetbase API/Drivers/Update a Driver Unchanged Contact.request.yaml b/postman/collections/Fleetbase API/Drivers/Update a Driver Unchanged Contact.request.yaml new file mode 100644 index 0000000..520ac06 --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/Update a Driver Unchanged Contact.request.yaml @@ -0,0 +1,40 @@ +$kind: http-request +description: |- + Resends the driver's current email address and phone number unchanged. + + Uniqueness is enforced on update against every other live user account, with the driver's own linked user ignored by uuid. Without that clause this request would fail against the driver's own address — which is why it exists. +url: "{{base_url}}/{{namespace}}/drivers/:id" +method: PUT +headers: + Accept: application/json +pathVariables: + - key: id + value: "{{driver_id}}" + description: (Required) The public id of the `Driver`. +body: + type: json + content: | + { + "email": "{{created_driver_email}}", + "phone": "{{created_driver_phone}}" + } +scripts: + - type: afterResponse + code: | + var json_response = pm.response.json(); + + pm.test("An unchanged email and phone are accepted", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code} — the driver's own user must be ignored by the uniqueness rule`).to.be.oneOf([200]); + pm.expect(json_response.email).to.eql(pm.variables.get("created_driver_email")); + pm.expect(json_response.phone).to.eql(pm.variables.get("created_driver_phone")); + }); + + pm.test("The relationship contract is unchanged by the update", function () { + pm.expect(json_response.user, "user must stay a public-id string").to.be.a('string'); + pm.expect(json_response).to.have.property("company"); + ["vehicle_id", "vendor_id", "job_id"].forEach(function (key) { + pm.expect(json_response, key + " is missing").to.have.property(key); + }); + }); + language: text/javascript +order: 4100 diff --git a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml index b7683be..b0c81ba 100644 --- a/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml +++ b/postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml @@ -43,8 +43,6 @@ scripts: }); pm.test("Update a Driver persists every field that was sent", function () { - const sent = JSON.parse(pm.request.body.raw); - pm.expect(json_response.name).to.eql("John Doe"); pm.expect(json_response.internal_id).to.eql("DRV-1002"); pm.expect(json_response.city).to.eql("Johor Bahru"); @@ -52,11 +50,20 @@ scripts: pm.expect(json_response.meta, "meta was not persisted").to.be.an('object'); pm.expect(json_response.meta.badge).to.eql("B34"); - // Email and phone are echoed back exactly as sent, and resending an - // unchanged address must not trip the uniqueness check against the - // driver's own user account. - pm.expect(json_response.email, "email did not round trip").to.eql(sent.email); - pm.expect(json_response.phone, "phone did not round trip").to.eql(sent.phone); + // Not compared against the request body: {{$randomEmail}} re-resolves + // each time that body is read. What matters here is that the update + // succeeded at all — uniqueness now runs on update, ignoring the + // driver's own linked user, so a change like this one must pass while + // another user's address returns 422. + pm.expect(json_response.email, "email missing").to.be.a('string').and.not.empty; + pm.expect(json_response.phone, "phone missing").to.be.a('string').and.not.empty; + }); + + pm.test("Update a Driver accepts an unchanged email and phone", function () { + // Resent verbatim on the next request below; asserted here so the + // uniqueness rule's ignore-own-user clause is exercised end to end. + pm.environment.set("created_driver_email", json_response.email); + pm.environment.set("created_driver_phone", json_response.phone); }); pm.test("Update a Driver persists the timezone", function () {