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..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 @@ -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,45 @@ 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, + "vehicle": null, + "vendor": null, + "current_job": 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..d9f4670 --- /dev/null +++ b/postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml @@ -0,0 +1,45 @@ +$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, + "vehicle": null, + "vendor": null, + "current_job": 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..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 @@ -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,29 @@ 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", + "company": "company_6zV1KqN9pR", + "company_name": "Fleetbase", + "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..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 @@ -5,7 +5,7 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "driver_A1b2C3d4E5" response: statusCode: 200 statusText: OK @@ -15,25 +15,49 @@ 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, + "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, + "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..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 @@ -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,49 @@ 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, + "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, + "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..4a16fe9 100644 --- a/postman/collections/Fleetbase API/Drivers/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Drivers/.resources/object.yaml @@ -1,7 +1,11 @@ $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. + + `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", @@ -12,7 +16,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 +36,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" @@ -39,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. @@ -69,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. @@ -118,3 +137,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..620c686 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,20 @@ body: "name": "John Doe", "email": "{{$randomEmail}}", "phone": "{{$randomPhoneNumber}}", - "password": "{{driver_seed_password}}" + "password": "{{driver_seed_password}}", + "timezone": "Asia/Singapore", + "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 +55,65 @@ 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 echoes the contact details it was given", function () { + // 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"); + }); + + 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"); + 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 () { + 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); + }); + }); 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..551cd88 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. **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_...`). Exact match. + - 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,7 @@ 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. + - 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 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.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..b0c81ba 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,102 @@ 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", + "timezone": "Europe/Amsterdam", + "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"); + + // 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 () { + // 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); + }); + }); + 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..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 @@ -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,18 @@ 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_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" } 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..b914b83 --- /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_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" + } +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..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 @@ -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,21 @@ 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_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" + } + ] 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..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 @@ -5,7 +5,7 @@ request: headers: Accept: application/json pathVariables: - id: "" + id: "fleet_4Mnp8Qx2Vz" response: statusCode: 200 statusText: OK @@ -15,12 +15,18 @@ 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_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" } 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..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 @@ -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,18 @@ 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_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" } 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..d30e279 100644 --- a/postman/collections/Fleetbase API/Fleets/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Fleets/.resources/object.yaml @@ -1,20 +1,22 @@ $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. + + 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", "name": "Haulers", - "task": null, + "color": "#2563EB", + "task": "Long haul distribution", "status": "active", - "service_area": null, - "zone": null, - "vendor": null, - "parent_fleet": null, - "subfleets": [], - "drivers": [], - "vehicles": [], + "photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png", + "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" } @@ -25,33 +27,58 @@ 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_id + type: string + 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`. Always present. + - name: vendor_id + type: string + 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: Service area assigned to the fleet, when loaded. + description: |- + 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: Zone assigned to the fleet, when loaded. + description: The assigned zone, returned only when requested through `with=zone`. - name: vendor type: object - description: Vendor assigned to the fleet, when loaded. + description: The assigned vendor, returned only when requested through `with=vendor`. - name: parent_fleet type: object - description: Parent fleet, when loaded. + 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 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..7e83f15 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,54 @@ 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 reports the service area as an additive public id", function () { + const serviceAreaId = pm.variables.get("service_area_id"); + 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_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_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 () { + // 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); + }); + }); 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..889414c --- /dev/null +++ b/postman/collections/Fleetbase API/Fleets/Create a Subfleet.request.yaml @@ -0,0 +1,56 @@ +$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_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_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.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..2b42eff 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_...`). Exact match. + - 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 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/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..507825f 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,65 @@ $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 additive public ids", function () { + const serviceAreaId = pm.variables.get("service_area_id"); + pm.expect(json_response).to.have.property("service_area_id"); + if (serviceAreaId) { + pm.expect(json_response.service_area_id).to.eql(serviceAreaId); + } + 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 () { + ["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..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 @@ -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,33 @@ 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_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" } 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/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 23baa90..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 @@ -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_id": "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..371c062 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. + + 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", @@ -21,6 +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, "location": { "latitude": 40.7484, "longitude": -73.9857 @@ -54,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. @@ -91,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. @@ -104,8 +121,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 +212,176 @@ fields: - name: meta type: object description: Additional vehicle metadata. + - 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 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: object + description: The assigned category, returned only when requested through `with=category`. + - name: warranty + 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. + - 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..c7b6d1b 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,57 @@ 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", + "class": "N1", "plate_number": "ABC123", + "serial_number": "SER-1001", + "call_sign": "DEPOT-1", + "fuel_card_number": "FC-1001", "status": "maintenance", - "online": false + "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, + "vendor": "{{vendor_id}}" } scripts: - type: afterResponse @@ -25,6 +72,91 @@ 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_/); + }); + + // 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 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 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 reports relationships as additive public ids", function () { + const vendorId = pm.variables.get("vendor_id"); + ["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_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 () { + 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 examples: ./.resources/Create a Vehicle.resources/examples order: 1000 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 9d85844..9723e20 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. **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. + - name: vin + type: string + description: Filter by vehicle identification number. + - name: public_id + type: string + description: Filter by vehicle public ID (`vehicle_...`). Exact match. + - 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,7 @@ 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. + - 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/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