From 38184c372e183dda5f321dc658dd13c8700bbf5d Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 9 Sep 2026 22:19:30 +0800 Subject: [PATCH 1/6] Add the driver inspection requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fleetbase/fleetops adds a driver-facing inspections API under v1 — the published forms, filing a DVIR against one, and reading back what was filed. An Inspections folder documents the six requests, and List Vehicle Inspections sits with them rather than under Vehicles because it needs an inspection to have been filed first. Ordered at 7750, after Vehicles and Drivers, so {{vehicle_id}} and {{driver_id}} exist when it runs. Forms are published from the console, not this API, so List Inspection Forms chains {{inspection_form_id}} from the first published form and the three requests that address a form or file against one need the contract run to seed a published form for the CI organisation. Without that seed those three answer 404 and 422; the listings answer 200 regardless. --- .../Fleetbase API/.resources/definition.yaml | 4 + .../Inspections/.resources/definition.yaml | 8 ++ .../Inspections/.resources/object.yaml | 124 ++++++++++++++++++ .../List Inspection Forms.queryParams.yaml | 14 ++ .../List Inspection Forms.request.yaml | 25 ++++ .../List Inspections.queryParams.yaml | 26 ++++ .../Inspections/List Inspections.request.yaml | 12 ++ .../List Vehicle Inspections.queryParams.yaml | 18 +++ .../List Vehicle Inspections.request.yaml | 15 +++ .../Retrieve an Inspection Form.request.yaml | 15 +++ .../Retrieve an Inspection.request.yaml | 13 ++ .../Submit an Inspection.params.yaml | 69 ++++++++++ .../Submit an Inspection.request.yaml | 60 +++++++++ 13 files changed, 403 insertions(+) create mode 100644 postman/collections/Fleetbase API/Inspections/.resources/definition.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/.resources/object.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Inspection Forms.queryParams.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Inspections.queryParams.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.queryParams.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Submit an Inspection.params.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index de291f3..09fcd52 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -57,6 +57,10 @@ variables: driver_password_reset_code: "" manifest_id: "" manifest_stop_id: "" + # Forms are published from the console, so List Inspection Forms chains the + # first published one; a contract run has to seed it. + inspection_form_id: "" + inspection_id: "" # Fleet hierarchy. The parent is the fleet created first; the subfleet is # created beneath it and deleted before it. parent_fleet_id: "" diff --git a/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml new file mode 100644 index 0000000..a1bab11 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml @@ -0,0 +1,8 @@ +$kind: collection +description: |- + Inspections are the driver's vehicle inspection reports — a DVIR filed before or after a trip against a form the organisation has published from the console. + + These endpoints are for the driver filing the inspection: read the published forms, submit against one, and read back what was filed and what it produced. A failed item can raise an issue and open a work order, depending on how the form is configured; both come back on the submission. + + Authoring and publishing forms, and reviewing or resolving submissions, is fleet management and is not part of the consumable API. The requests here need at least one published form in the organisation, which a contract run has to seed. +order: 7750 diff --git a/postman/collections/Fleetbase API/Inspections/.resources/object.yaml b/postman/collections/Fleetbase API/Inspections/.resources/object.yaml new file mode 100644 index 0000000..c50a36b --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/.resources/object.yaml @@ -0,0 +1,124 @@ +$kind: object +name: Inspection +description: |- + An inspection is one filed report against a published inspection form: the vehicle and driver it was filed for, one result per checklist item with an optional photo set, the pass/fail tally, and the issue or work order the form's settings raised from any failed items. +example: | + { + "id": "inspection_submission_4Mnp8Qx2Vz", + "form": { "id": "inspection_form_9Kd2Lm7Aq1", "name": "Pre-trip DVIR", "type": "dvir" }, + "vehicle": { "id": "vehicle_7Hx3Pq9Rt2", "plate_number": "TRK-7" }, + "driver": { "id": "driver_2Fb8Nc4Wm6", "name": "Dana Driver" }, + "issue": null, + "work_order": null, + "item_results": [ + { + "id": "b3f0c4a2-1d6e-4c1a-9c2b-8e7f6a5d4c3b", + "item_key": "brakes", + "label": "Brakes", + "category": "Safety", + "status": "passed", + "severity": null, + "passed": true, + "comments": null, + "photos": [], + "meta": {}, + "submission_id": "inspection_submission_4Mnp8Qx2Vz", + "updated_at": "2026-09-09T07:30:00.000000Z", + "created_at": "2026-09-09T07:30:00.000000Z" + } + ], + "type": "dvir", + "status": "submitted", + "result": "passed", + "source": "navigator", + "odometer": 120400, + "engine_hours": null, + "total_items": 1, + "failed_items": 0, + "location": { "latitude": 1.3521, "longitude": 103.8198 }, + "signature": {}, + "attachments": [], + "meta": {}, + "form_name": "Pre-trip DVIR", + "vehicle_name": "Truck 7", + "driver_name": "Dana Driver", + "has_failures": false, + "started_at": "2026-09-09T07:10:00.000000Z", + "submitted_at": "2026-09-09T07:30:00.000000Z", + "resolved_at": null, + "updated_at": "2026-09-09T07:30:00.000000Z", + "created_at": "2026-09-09T07:30:00.000000Z" + } +fields: + - name: id + type: string + description: Public inspection identifier. + - name: form + type: object + description: The inspection form the report was filed against, when loaded. + - name: vehicle + type: object + description: The vehicle inspected, when loaded. + - name: driver + type: object + description: The driver who filed the inspection, when loaded. + - name: issue + type: object + description: The issue raised from the failed items, when the form asks for one and any item failed. + - name: work_order + type: object + description: The work order opened for the failed items, when the form asks for one and any item failed. + - name: item_results + type: array of objects + description: One result per checklist item — `item_key`, `label`, `category`, `status`, `severity`, `passed`, `comments` and `photos`. + - name: type + type: string + description: Inspection type, taken from the form — `dvir`, `pre_trip`, `post_trip`, or a custom type. + - name: status + type: string + description: Lifecycle status — `submitted`, `needs_review` or `resolved`. + - name: result + type: string + description: "`passed` when every item passed, otherwise `failed`." + - name: source + type: string + description: How it was filed — `navigator` for the driver app, `public_link` for a tokenised link. + - name: odometer + type: number + description: Odometer reading at the time of the inspection. + - name: engine_hours + type: number + description: Engine hours at the time of the inspection. + - name: total_items + type: integer + description: Number of item results. + - name: failed_items + type: integer + description: Number of item results that did not pass. + - name: location + type: object + description: Where the inspection was filed from. + - name: signature + type: object + description: The driver's signature, as the app captured it. + - name: attachments + type: array + description: Additional files attached to the inspection. + - name: has_failures + type: boolean + description: Whether any item failed. + - name: started_at + type: timestamp + description: When the driver began the inspection. + - name: submitted_at + type: timestamp + description: When the inspection was filed. + - name: resolved_at + type: timestamp + description: When the failed items were resolved from the console, if they have been. + - name: updated_at + type: timestamp + description: Timestamp when the inspection was last updated. + - name: created_at + type: timestamp + description: Timestamp when the inspection was created. diff --git a/postman/collections/Fleetbase API/Inspections/List Inspection Forms.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.queryParams.yaml new file mode 100644 index 0000000..9e5e7a2 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.queryParams.yaml @@ -0,0 +1,14 @@ +$kind: queryParams +fields: + - name: vehicle + type: string + required: false + description: Only forms that apply to this vehicle — bound to it, or bound to no vehicle at all. + - name: type + type: string + required: false + description: Only forms of these types, such as `dvir` or `pre_trip,post_trip`. + - name: limit + type: integer + required: false + description: Maximum forms to return. Defaults to 30. diff --git a/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml new file mode 100644 index 0000000..d52c651 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml @@ -0,0 +1,25 @@ +$kind: http-request +name: "List Inspection Forms" +description: |- + Lists the inspection forms the organisation has published, newest first. Draft and archived forms are never listed: a driver can only file against a published one. + + Send `vehicle` to narrow the list to what applies to one vehicle — forms bound to that vehicle, and forms bound to nothing, which are the organisation-wide ones. Send `type` to narrow by form type, as one value or a comma separated list. +url: "{{base_url}}/{{namespace}}/inspection-forms" +method: GET +queryParams: + vehicle: "{{vehicle_id}}" + +scripts: + - type: afterResponse + code: |- + // Forms are published from the console, not this API, so the first + // published form is what Retrieve an Inspection Form and Submit an + // Inspection address. A contract run has to seed one. + const forms = pm.response.json(); + + if (Array.isArray(forms) && forms.length) { + pm.environment.set("inspection_form_id", forms[0].id); + } + language: text/javascript + +order: 1000 diff --git a/postman/collections/Fleetbase API/Inspections/List Inspections.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/List Inspections.queryParams.yaml new file mode 100644 index 0000000..9930016 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Inspections.queryParams.yaml @@ -0,0 +1,26 @@ +$kind: queryParams +fields: + - name: driver + type: string + required: false + description: Only inspections filed by this driver. + - name: vehicle + type: string + required: false + description: Only inspections of this vehicle. + - name: type + type: string + required: false + description: Only inspections of these types, such as `dvir` or `pre_trip,post_trip`. + - name: result + type: string + required: false + description: "`passed` or `failed`." + - name: status + type: string + required: false + description: "`submitted`, `needs_review` or `resolved`." + - name: limit + type: integer + required: false + description: Maximum inspections to return. Defaults to 30. diff --git a/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml b/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml new file mode 100644 index 0000000..5944086 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml @@ -0,0 +1,12 @@ +$kind: http-request +name: "List Inspections" +description: |- + Lists filed inspections, newest first. + + Narrow by `driver` or `vehicle`, and by `type`, `result` and `status`, each as one value or a comma separated list. Defaults to a recent window rather than the organisation's whole history; use `limit` to widen it. +url: "{{base_url}}/{{namespace}}/inspections" +method: GET +queryParams: + driver: "{{driver_id}}" + +order: 4000 diff --git a/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.queryParams.yaml new file mode 100644 index 0000000..b6ffeae --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.queryParams.yaml @@ -0,0 +1,18 @@ +$kind: queryParams +fields: + - name: type + type: string + required: false + description: Only inspections of these types, such as `dvir` or `pre_trip,post_trip`. + - name: result + type: string + required: false + description: "`passed` or `failed`." + - name: status + type: string + required: false + description: "`submitted`, `needs_review` or `resolved`." + - name: limit + type: integer + required: false + description: Maximum inspections to return. Defaults to 30. diff --git a/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml new file mode 100644 index 0000000..f10153d --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml @@ -0,0 +1,15 @@ +$kind: http-request +name: "List Vehicle Inspections" +description: |- + Lists a vehicle's inspection history, newest first — the same inspections List Inspections answers with `vehicle`, addressed the way the driver app holds them, from the vehicle's screen. + + Narrow by `type`, `result` and `status`, each as one value or a comma separated list. +url: "{{base_url}}/{{namespace}}/vehicles/:id/inspections" +method: GET +pathVariables: + - key: id + value: "{{vehicle_id}}" + description: (Required) The vehicle whose inspections to list. + + +order: 6000 diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml new file mode 100644 index 0000000..e444be7 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml @@ -0,0 +1,15 @@ +$kind: http-request +name: "Retrieve an Inspection Form" +description: |- + Retrieves one published inspection form with its checklist `items` and `settings`. + + A form that is not published — a draft, or one that has been archived — answers 404, the same as one that does not exist: to a driver, a form that cannot be filled in is not there. +url: "{{base_url}}/{{namespace}}/inspection-forms/:id" +method: GET +pathVariables: + - key: id + value: "{{inspection_form_id}}" + description: (Required) The inspection form to retrieve. + + +order: 2000 diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml new file mode 100644 index 0000000..bc66224 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml @@ -0,0 +1,13 @@ +$kind: http-request +name: "Retrieve an Inspection" +description: |- + Retrieves one inspection with its `item_results`, and the `issue` and `work_order` raised from any failed items. +url: "{{base_url}}/{{namespace}}/inspections/:id" +method: GET +pathVariables: + - key: id + value: "{{inspection_id}}" + description: (Required) The inspection to retrieve. + + +order: 5000 diff --git a/postman/collections/Fleetbase API/Inspections/Submit an Inspection.params.yaml b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.params.yaml new file mode 100644 index 0000000..b64b7e5 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.params.yaml @@ -0,0 +1,69 @@ +$kind: params +fields: + - name: inspection_form + type: string + required: true + description: ID of the published inspection form being filed against. + - name: driver + type: string + required: true + description: ID of the driver filing the inspection. + - name: vehicle + type: string + required: false + description: ID of the vehicle inspected. Defaults to the vehicle the driver is assigned to. + - name: started_at + type: timestamp + required: false + description: When the driver began the inspection. Defaults to the time it is filed. + - name: odometer + type: integer + required: false + description: Odometer reading at the time of the inspection. + - name: engine_hours + type: integer + required: false + description: Engine hours at the time of the inspection. + - name: item_results + type: array of objects + required: true + description: One entry per checklist item answered. At least one is required. + fields: + - name: item_key + type: string + description: The form item's key. + - name: label + type: string + required: true + description: The item as shown to the driver. + - name: category + type: string + description: The item's category on the form. + - name: passed + type: boolean + required: true + description: Whether the item passed. + - name: status + type: string + description: "`passed` or `failed`. Derived from `passed` when omitted." + - name: severity + type: string + description: "Severity of a failure — `low`, `medium`, `high` or `critical`. Sets the priority of the issue and work order raised." + - name: comments + type: string + description: The driver's note on the item. + - name: photos + type: array + description: Photos of the item, each a base64 encoded image or a URL. + - name: location + type: object + required: false + description: Where the inspection was filed from, as `latitude` and `longitude`. + - name: signature + type: object + required: false + description: The driver's signature, as the app captured it. + - name: attachments + type: array + required: false + description: Additional files attached to the inspection. diff --git a/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml new file mode 100644 index 0000000..d9badc5 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml @@ -0,0 +1,60 @@ +$kind: http-request +name: "Submit an Inspection" +description: |- + Files an inspection against a published form, as the driver app does at the end of a DVIR. + + The body carries one entry in `item_results` per checklist item the driver answered, each with `passed` and optionally a status, severity, comments and photos. Photos are sent as base64 encoded images or as URLs to files already uploaded. + + Without `vehicle` the inspection is recorded against the vehicle the driver is currently assigned to. Without `started_at` the inspection is taken to have started when it was filed. + + The app queues a submit while offline and replays it later. Send an `Idempotency-Key` header with each attempt: a replay with a key already used by the same driver answers with the inspection the first attempt filed rather than filing a second one. + + The response is the inspection with its `item_results`, and the `issue` and `work_order` the form's settings raised from any failed items. +url: "{{base_url}}/{{namespace}}/inspections" +method: POST +headers: + Idempotency-Key: "{{$guid}}" +body: + type: json + content: |- + { + "inspection_form": "{{inspection_form_id}}", + "driver": "{{driver_id}}", + "vehicle": "{{vehicle_id}}", + "started_at": "2026-09-09T07:10:00Z", + "odometer": 120400, + "engine_hours": 3100, + "location": { + "latitude": 1.3521, + "longitude": 103.8198 + }, + "item_results": [ + { + "item_key": "brakes", + "label": "Brakes", + "category": "Safety", + "passed": false, + "severity": "critical", + "comments": "Pedal is soft.", + "photos": ["{{proof_photo_base64}}"] + }, + { + "item_key": "lights", + "label": "Lights", + "category": "Safety", + "passed": true + } + ] + } + +scripts: + - type: afterResponse + code: |- + const json_response = pm.response.json(); + + if (json_response && json_response.id) { + pm.environment.set("inspection_id", json_response.id); + } + language: text/javascript + +order: 3000 From cc399793a3d9b7414dd34e05bad57a601aa3a246 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Thu, 10 Sep 2026 12:55:50 +0800 Subject: [PATCH 2/6] Answer the second cut of the inspections contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fleetops#319 changed what an inspection form is. A form is no longer a flat list of pass/fail items, it is groups of typed fields, and it answers `grouped_fields`. An inspection is that form filled in: the driver's answers arrive as `custom_field_values` and come back the same way, with every photo and signature stored as a file and resolved to something fetchable, beside the `item_results` the server derives from the pass/fail answers. Retrieve an Inspection Form documents the field types and what a `pass-fail` field's `meta` says happens on failure, and chains a pass-fail, a meter and a signature field from the form it read. Submit an Inspection builds its `custom_field_values` from those, and asserts the files came back resolved. A field is named by an id that only exists once the form has been read, and a form that has not been rebuilt from the first cut has no fields to name. So the submit sends both bodies, as the driver app does: `custom_field_values` when the ids resolved, and the flat `item_results` either way. The server prefers the field values and ignores the duplicated results, which means one request is 2xx against either cut — no request is skipped. --- .../Inspections/.resources/object.yaml | 60 ++++++++++++- .../List Inspection Forms.request.yaml | 2 + .../Inspections/List Inspections.request.yaml | 2 + .../List Vehicle Inspections.request.yaml | 2 + .../Retrieve an Inspection Form.request.yaml | 56 +++++++++++- .../Retrieve an Inspection.request.yaml | 2 +- .../Submit an Inspection.request.yaml | 90 ++++++++++++++++++- 7 files changed, 208 insertions(+), 6 deletions(-) diff --git a/postman/collections/Fleetbase API/Inspections/.resources/object.yaml b/postman/collections/Fleetbase API/Inspections/.resources/object.yaml index c50a36b..fa26eea 100644 --- a/postman/collections/Fleetbase API/Inspections/.resources/object.yaml +++ b/postman/collections/Fleetbase API/Inspections/.resources/object.yaml @@ -1,7 +1,7 @@ $kind: object name: Inspection description: |- - An inspection is one filed report against a published inspection form: the vehicle and driver it was filed for, one result per checklist item with an optional photo set, the pass/fail tally, and the issue or work order the form's settings raised from any failed items. + An inspection is one filed report against a published inspection form: the vehicle and driver it was filed for, the driver's answer to every field of the form, the pass/fail results derived from the `pass-fail` answers, the photos and signature filed with it, and the issue or work order the form's settings raised from any failed items. example: | { "id": "inspection_submission_4Mnp8Qx2Vz", @@ -10,6 +10,56 @@ example: | "driver": { "id": "driver_2Fb8Nc4Wm6", "name": "Dana Driver" }, "issue": null, "work_order": null, + "custom_field_values": [ + { + "custom_field": "0d2b7f1e-4a3c-4d5e-9f61-2c8b7a6d5e4f", + "name": "brakes", + "label": "Brakes", + "type": "pass-fail", + "value_type": "object", + "value": { + "passed": true, + "not_applicable": false, + "severity": null, + "comments": null, + "photos": [], + "unsafe": false + } + }, + { + "custom_field": "7c1a9e42-6b8d-4f30-8a52-1d9f3b6c4e70", + "name": "odometer", + "label": "Odometer", + "type": "number", + "value_type": "number", + "value": 120400 + }, + { + "custom_field": "b8e5d3c1-2f74-4a96-bc08-5e1a7d2f9c63", + "name": "signature", + "label": "Sign here", + "type": "signature", + "value_type": "file", + "value": { + "id": "file_5Qw8Zt2Nb7", + "url": "https://files.fleetbase.io/inspections/4Mnp8Qx2Vz/lq8w1c0v9xk3.png", + "filename": "lq8w1c0v9xk3.png", + "content_type": "image/png" + } + } + ], + "files": [ + { + "id": "file_5Qw8Zt2Nb7", + "uuid": "3a6c9e21-7b4d-4f18-9c05-2d8e1f7a6b34", + "url": "https://files.fleetbase.io/inspections/4Mnp8Qx2Vz/lq8w1c0v9xk3.png", + "original_filename": "lq8w1c0v9xk3.png", + "content_type": "image/png", + "type": "inspection_signature", + "caption": null, + "created_at": "2026-09-09T07:30:00.000000Z" + } + ], "item_results": [ { "id": "b3f0c4a2-1d6e-4c1a-9c2b-8e7f6a5d4c3b", @@ -68,9 +118,15 @@ fields: - name: work_order type: object description: The work order opened for the failed items, when the form asks for one and any item failed. + - name: custom_field_values + type: array of objects + description: The driver's answer to each field of the form — the field's `custom_field` id, `name`, `label`, `type` and `value_type` beside the `value`. A `pass-fail` value is an object of `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`; a photo or signature value is resolved to `{ id, url, filename, content_type }` rather than the base64 that was sent. + - name: files + type: array of objects + description: The photos and signatures filed with the inspection, however they arrived — inside the submit body, or dropped on the console's Photos panel. - name: item_results type: array of objects - description: One result per checklist item — `item_key`, `label`, `category`, `status`, `severity`, `passed`, `comments` and `photos`. + description: One result per `pass-fail` answer, derived from `custom_field_values` — `item_key`, `label`, `category`, `status` (`passed`, `failed` or `not_applicable`), `severity`, `passed`, `comments` and `photos`. This is what issues, work orders and the vehicle's history are built from. - name: type type: string description: Inspection type, taken from the form — `dvir`, `pre_trip`, `post_trip`, or a custom type. diff --git a/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml index d52c651..6e338a2 100644 --- a/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/List Inspection Forms.request.yaml @@ -3,6 +3,8 @@ name: "List Inspection Forms" description: |- Lists the inspection forms the organisation has published, newest first. Draft and archived forms are never listed: a driver can only file against a published one. + Each form carries its structure as `grouped_fields` — the groups of typed fields a driver fills in — so a list is enough to render a form picker without reading each one. + Send `vehicle` to narrow the list to what applies to one vehicle — forms bound to that vehicle, and forms bound to nothing, which are the organisation-wide ones. Send `type` to narrow by form type, as one value or a comma separated list. url: "{{base_url}}/{{namespace}}/inspection-forms" method: GET diff --git a/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml b/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml index 5944086..b2c609b 100644 --- a/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/List Inspections.request.yaml @@ -4,6 +4,8 @@ description: |- Lists filed inspections, newest first. Narrow by `driver` or `vehicle`, and by `type`, `result` and `status`, each as one value or a comma separated list. Defaults to a recent window rather than the organisation's whole history; use `limit` to widen it. + + Each row carries the same shape Retrieve an Inspection answers with: the answers as `custom_field_values`, the `item_results` derived from them, and the `files` filed with the inspection. url: "{{base_url}}/{{namespace}}/inspections" method: GET queryParams: diff --git a/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml index f10153d..ed78ec6 100644 --- a/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/List Vehicle Inspections.request.yaml @@ -4,6 +4,8 @@ description: |- Lists a vehicle's inspection history, newest first — the same inspections List Inspections answers with `vehicle`, addressed the way the driver app holds them, from the vehicle's screen. Narrow by `type`, `result` and `status`, each as one value or a comma separated list. + + Each row carries the answers as `custom_field_values`, the `item_results` derived from them, and the `files` filed with the inspection. url: "{{base_url}}/{{namespace}}/vehicles/:id/inspections" method: GET pathVariables: diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml index e444be7..081b44b 100644 --- a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection Form.request.yaml @@ -1,7 +1,13 @@ $kind: http-request name: "Retrieve an Inspection Form" description: |- - Retrieves one published inspection form with its checklist `items` and `settings`. + Retrieves one published inspection form with its structure and `settings`. + + A form is groups of typed fields. `grouped_fields` is what a driver renders: one entry per group, in the order the builder laid them out, each with a `name`, a `meta.grid_size` and its `fields`. A field carries an `id`, a `name`, a `label`, a `type` — `pass-fail`, `input`, `textarea`, `number`, `select`, `radio-button`, `boolean`, `date-picker`, `date-time-input`, `file-upload` or `signature` — plus `required`, `options` and `meta`. + + A `pass-fail` field's `meta` says what happens when it fails: the default `severity`, whether a photo or a comment is required, whether the defect takes the vehicle out of service (`unsafe_on_fail`), and any `instructions`. A `number` field's `meta` may name its `unit` and the `role` it plays, such as `odometer`. + + The first cut's flat `items` checklist is still answered, read-only, for forms that have not been rebuilt; a form built from fields answers both, and `grouped_fields` is the one to render. A form that is not published — a draft, or one that has been archived — answers 404, the same as one that does not exist: to a driver, a form that cannot be filled in is not there. url: "{{base_url}}/{{namespace}}/inspection-forms/:id" @@ -11,5 +17,53 @@ pathVariables: value: "{{inspection_form_id}}" description: (Required) The inspection form to retrieve. +scripts: + - type: afterResponse + code: |- + // Submit an Inspection answers this form, so the fields it will answer + // are chained from here by type. A form that has not been rebuilt from + // the first cut carries no fields; the ids are cleared rather than left + // pointing at a previous run's form, and the submit falls back to the + // flat `item_results` body on its own. + pm.test("Retrieve an Inspection Form answers a published form", function () { + pm.expect(pm.response.code, "expected a 2xx").to.be.oneOf([200, 201]); + }); + + const form = pm.response.json(); + const groups = (form && Array.isArray(form.grouped_fields)) ? form.grouped_fields : []; + const fields = groups.reduce((all, group) => all.concat(Array.isArray(group.fields) ? group.fields : []), []); + + const firstOfType = (type) => { + const field = fields.find((candidate) => candidate && candidate.type === type); + return field && field.id ? field.id : null; + }; + + const chain = { + inspection_pass_fail_field_id: firstOfType("pass-fail"), + inspection_number_field_id: firstOfType("number"), + inspection_signature_field_id: firstOfType("signature"), + }; + + Object.keys(chain).forEach((key) => { + if (chain[key]) { + pm.environment.set(key, chain[key]); + } else { + pm.environment.unset(key); + } + }); + + pm.test("A form built from fields answers grouped_fields", function () { + if (!groups.length) { + pm.expect(form, "a first-cut form still answers its checklist").to.have.property("items"); + return; + } + + pm.expect(fields.length, "a group with no fields in it").to.be.above(0); + fields.forEach((field) => { + pm.expect(field.id, "a field with no id cannot be answered").to.be.a("string").and.not.empty; + pm.expect(field.type, "a field with no type cannot be rendered").to.be.a("string").and.not.empty; + }); + }); + language: text/javascript order: 2000 diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml index bc66224..971e9d1 100644 --- a/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Retrieve an Inspection.request.yaml @@ -1,7 +1,7 @@ $kind: http-request name: "Retrieve an Inspection" description: |- - Retrieves one inspection with its `item_results`, and the `issue` and `work_order` raised from any failed items. + Retrieves one inspection with everything it produced: `custom_field_values` — the driver's answers, each beside the field's `name`, `label` and `type`, with every photo and signature resolved to `{ id, url, filename, content_type }` — the `item_results` derived from the `pass-fail` answers, the `files` filed with it, and the `issue` and `work_order` raised from any failed items. url: "{{base_url}}/{{namespace}}/inspections/:id" method: GET pathVariables: diff --git a/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml index d9badc5..ed1fa08 100644 --- a/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Submit an Inspection.request.yaml @@ -3,13 +3,17 @@ name: "Submit an Inspection" description: |- Files an inspection against a published form, as the driver app does at the end of a DVIR. - The body carries one entry in `item_results` per checklist item the driver answered, each with `passed` and optionally a status, severity, comments and photos. Photos are sent as base64 encoded images or as URLs to files already uploaded. + A form built from typed fields is answered with `custom_field_values`: one entry per field the driver answered, naming the field by the `id` the form read gave it, with a `value_type` and a `value`. A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`; a meter is a number, a signature or a photo is base64. The server stores every photo and signature as a file and answers with `file:` references resolved, and mirrors each `pass-fail` answer into an `item_results` row, which is what issues, work orders and the vehicle's history are built from. + + The first cut's flat `item_results` body is still accepted, for the tokenised public link and for older app builds. When both arrive the field values win and the duplicated results are ignored — which is exactly what the app sends, so one body works against either cut. + + A failed `pass-fail` answer must carry whatever its field insists on: a form that sets `require_comment_on_fail` or `require_photo_on_fail` refuses the whole submission with a 422 rather than filing half an inspection. Without `vehicle` the inspection is recorded against the vehicle the driver is currently assigned to. Without `started_at` the inspection is taken to have started when it was filed. The app queues a submit while offline and replays it later. Send an `Idempotency-Key` header with each attempt: a replay with a key already used by the same driver answers with the inspection the first attempt filed rather than filing a second one. - The response is the inspection with its `item_results`, and the `issue` and `work_order` the form's settings raised from any failed items. + The response is the inspection with its `custom_field_values`, `item_results` and `files`, and the `issue` and `work_order` the form's settings raised from any failed items. url: "{{base_url}}/{{namespace}}/inspections" method: POST headers: @@ -28,6 +32,7 @@ body: "latitude": 1.3521, "longitude": 103.8198 }, + "custom_field_values": [], "item_results": [ { "item_key": "brakes", @@ -48,6 +53,55 @@ body: } scripts: + - type: beforeRequest + code: |- + // The app sends both bodies and lets the server choose; so does this. + // `custom_field_values` is filled in from the fields Retrieve an + // Inspection Form chained, because a field is named by an id that only + // exists once the form has been read. A form that has not been rebuilt + // from the first cut has no fields to name, so the entry is left empty + // and the server files the flat `item_results` instead — which is what + // an older app build sends anyway. + const resolved = (key) => { + const value = pm.environment.get(key); + return value && !/^\{\{.*\}\}$/.test(String(value)) ? String(value) : null; + }; + + const passFail = resolved("inspection_pass_fail_field_id"); + const meter = resolved("inspection_number_field_id"); + const signature = resolved("inspection_signature_field_id"); + const photo = resolved("proof_photo_base64"); + const values = []; + + if (passFail) { + values.push({ + custom_field: passFail, + value_type: "object", + value: { + passed: false, + not_applicable: false, + severity: "critical", + comments: "Pedal is soft.", + photos: photo ? [photo] : [], + unsafe: true, + }, + }); + } + + if (meter) { + values.push({ custom_field: meter, value_type: "number", value: 120400 }); + } + + if (signature && photo) { + values.push({ custom_field: signature, value_type: "file", value: photo }); + } + + const body = JSON.parse(pm.request.body.raw); + body.custom_field_values = values; + pm.request.body.update({ mode: "raw", raw: JSON.stringify(body, null, 2) }); + + pm.environment.set("inspection_submitted_field_values", String(values.length)); + language: text/javascript - type: afterResponse code: |- const json_response = pm.response.json(); @@ -55,6 +109,38 @@ scripts: if (json_response && json_response.id) { pm.environment.set("inspection_id", json_response.id); } + + pm.test("Submit an Inspection files the report", function () { + pm.expect(pm.response.code, "expected a 2xx").to.be.oneOf([200, 201]); + pm.expect(json_response, "no inspection returned").to.be.an("object"); + pm.expect(json_response.id, "the filed inspection has no id").to.be.a("string").and.not.empty; + pm.expect(json_response.item_results, "no item results were derived").to.be.an("array"); + }); + + pm.test("Answers filed as field values come back with their files resolved", function () { + const sent = Number(pm.environment.get("inspection_submitted_field_values") || 0); + + if (!sent) { + pm.expect(json_response.item_results.length, "a flat submit files its results").to.be.above(0); + return; + } + + pm.expect(json_response.custom_field_values, "no field values were answered").to.be.an("array"); + pm.expect(json_response.custom_field_values.length, "fewer values than were sent").to.eql(sent); + + json_response.custom_field_values.forEach((value) => { + pm.expect(value.custom_field, "a value that names no field").to.be.a("string").and.not.empty; + + // A photo or signature is stored as a file and answered as one, + // never handed back as the base64 that was sent. + const photos = (value.value && Array.isArray(value.value.photos)) ? value.value.photos : []; + photos.forEach((file) => pm.expect(file, "a photo left unresolved").to.be.an("object").and.to.have.property("url")); + + if (value.value_type === "file" && value.value) { + pm.expect(value.value, "a signature left unresolved").to.be.an("object").and.to.have.property("url"); + } + }); + }); language: text/javascript order: 3000 From 39352dcc412cfb54342b07152d8b9a074b2217b5 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 11 Sep 2026 13:24:59 +0800 Subject: [PATCH 3/6] Add the public inspection link requests Three requests for the tokenised public inspection link, in the Inspections folder after the driver requests: - Retrieve a Public Inspection Form (GET) asserts the form and the identity come back, that the driver no longer carries a phone number, and chains the form's grouped_fields for the next two requests. - Upload a Public Inspection Photo (POST, multipart) sends the sample PNG Files/Upload File already uses, with the token as a form field, and chains the returned file_ id. - Submit a Public Inspection (POST) builds custom_field_values in its pre-request script from the chained form, answering every field with a passing value, and file-upload and signature fields with the uploaded file as file:. All three take {{inspection_link_form_id}} and {{inspection_link_token}}, declared empty on the collection; a contract run seeds a link and passes both in. The routes are public and ignore Authorization, so the collection's bearer is left inherited: nothing in this repository shows a verified request-level way to clear auth in the v3 format. --- .../Fleetbase API/.resources/definition.yaml | 5 + .../Inspections/.resources/definition.yaml | 2 + ... a Public Inspection Form.queryParams.yaml | 6 + ...ieve a Public Inspection Form.request.yaml | 56 ++++++++ .../Submit a Public Inspection.params.yaml | 30 +++++ ...ubmit a Public Inspection.queryParams.yaml | 6 + .../Submit a Public Inspection.request.yaml | 127 ++++++++++++++++++ ...load a Public Inspection Photo.params.yaml | 15 +++ ...oad a Public Inspection Photo.request.yaml | 56 ++++++++ 9 files changed, 303 insertions(+) create mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml create mode 100644 postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index 09fcd52..967f438 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -61,6 +61,11 @@ variables: # first published one; a contract run has to seed it. inspection_form_id: "" inspection_id: "" + # A public inspection link is minted from the console and only its hash can be + # looked up, so the public inspection requests take the link's form and token + # from a contract run that seeds one. + inspection_link_form_id: "" + inspection_link_token: "" # Fleet hierarchy. The parent is the fleet created first; the subfleet is # created beneath it and deleted before it. parent_fleet_id: "" diff --git a/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml index a1bab11..9f3c16b 100644 --- a/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml @@ -5,4 +5,6 @@ description: |- These endpoints are for the driver filing the inspection: read the published forms, submit against one, and read back what was filed and what it produced. A failed item can raise an issue and open a work order, depending on how the form is configured; both come back on the submission. Authoring and publishing forms, and reviewing or resolving submissions, is fleet management and is not part of the consumable API. The requests here need at least one published form in the organisation, which a contract run has to seed. + + A form can also be filled in through a public inspection link, by someone with no Fleetbase account. A link is minted from the console for one form, and its token is the only authorisation the three public endpoints take — no API key. Retrieve a Public Inspection Form reads the form and who the link is for, Upload a Public Inspection Photo stores a photo or a signature through the link, and Submit a Public Inspection files the answers. They are mounted at the application root, under `/public/inspections/forms`, and are rate limited per IP address. A contract run has to seed a link and pass its form and token in. order: 7750 diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml new file mode 100644 index 0000000..10335b4 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml @@ -0,0 +1,6 @@ +$kind: queryParams +fields: + - name: token + type: string + required: true + description: The inspection link's token, as the link carries it. It is the only authorisation this route takes. diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml new file mode 100644 index 0000000..148e2cf --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml @@ -0,0 +1,56 @@ +$kind: http-request +name: "Retrieve a Public Inspection Form" +description: |- + Retrieves a published inspection form through a public inspection link, for whoever holds the link — a driver or a contractor with no Fleetbase account, filling the form in on a phone. + + A link is minted from the console for one form, and optionally for one driver and one vehicle. Its token is the whole of the authorisation: send it as `token` in the query string. No API key is needed, and one sent is ignored. These routes are mounted at the application root, with no namespace, and are rate limited to 60 requests a minute per IP address. + + The response carries the `form` — the same shape Retrieve an Inspection Form answers with, its `grouped_fields` being what to render and answer — and an `identity`: the `driver` the link was minted for as `{ id, name }`, the `vehicle` as `{ id, name, plate_number }`, each `null` when the link names none, and the link's `expires_at`. The driver's phone number is deliberately not included: whoever holds the link needs to know who it is for, not how to reach them. + + A token that is missing, unknown, expired, revoked or — for a single-use link — already used answers 403 with an `error`. Reading the form does not spend a single-use link; submitting does. +url: "{{base_url}}/public/inspections/forms/:id" +method: GET +# No auth block. The route is public and ignores Authorization, so the collection's +# inherited bearer is harmless here. A request-level `auth:` mapping is only proven +# in this repository for overriding one bearer with another (Organizations/List +# Organizations); there is no verified shape here for clearing auth, and a wrong +# shape drops the request from the collection. +pathVariables: + - key: id + value: "{{inspection_link_form_id}}" + description: (Required) The published form the link was minted for. +queryParams: + token: "{{inspection_link_token}}" + +scripts: + - type: afterResponse + code: |- + // Upload a Public Inspection Photo and Submit a Public Inspection answer this + // form, so its groups are chained from here as JSON. Cleared to an empty list + // when the read fails, rather than left holding a previous run's fields. + const json_response = pm.response.json(); + const form = (json_response && json_response.form) ? json_response.form : null; + + pm.test("Retrieve a Public Inspection Form answers the form and who it is for", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); + pm.expect(form, "no form returned").to.be.an("object"); + pm.expect(form.id, "the form has no id").to.be.a("string").and.not.empty; + pm.expect(json_response, "no identity returned").to.have.property("identity"); + }); + + pm.test("The identity names the driver without their phone number", function () { + const identity = json_response ? json_response.identity : null; + const driver = identity ? identity.driver : null; + + if (driver === null || driver === undefined) { + return; + } + + pm.expect(driver, "a public link must not disclose the driver's phone").to.not.have.property("phone"); + }); + + const groups = (form && Array.isArray(form.grouped_fields)) ? form.grouped_fields : []; + pm.environment.set("public_inspection_form_fields", JSON.stringify(groups)); + language: text/javascript + +order: 7000 diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml new file mode 100644 index 0000000..1f91840 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml @@ -0,0 +1,30 @@ +$kind: params +fields: + - name: custom_field_values + type: array of objects + required: true + description: One entry per field answered, naming the field by the `uuid` Retrieve a Public Inspection Form gave it. + fields: + - name: custom_field + type: string + required: true + description: The field's `uuid`. + - name: value_type + type: string + description: "`object`, `file`, `text`, `number`, `boolean`, `date` or `datetime`." + - name: value + type: string + required: true + description: "A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id of a file uploaded through the same link. Anything else is plain text, a number, a boolean or a date." + - name: odometer + type: integer + required: false + description: Odometer reading at the time of the inspection. + - name: engine_hours + type: integer + required: false + description: Engine hours at the time of the inspection. + - name: signature + type: object + required: false + description: The signer's signature, as the page captured it. diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml new file mode 100644 index 0000000..10335b4 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml @@ -0,0 +1,6 @@ +$kind: queryParams +fields: + - name: token + type: string + required: true + description: The inspection link's token, as the link carries it. It is the only authorisation this route takes. diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml new file mode 100644 index 0000000..753400b --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml @@ -0,0 +1,127 @@ +$kind: http-request +name: "Submit a Public Inspection" +description: |- + Files an inspection through a public inspection link — what the public inspection page sends when whoever holds the link finishes the form. + + Send the link's `token` in the query string. The body is answered the way Submit an Inspection answers a form built from typed fields: `custom_field_values`, one entry per field, naming the field by the `uuid` Retrieve a Public Inspection Form gave it, with a `value_type` and a `value`. A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id Upload a Public Inspection Photo returned. Anything else is plain text, a number, a boolean or a date. `odometer`, `engine_hours` and `signature` are optional. + + The driver and vehicle are the ones the link was minted for; the body cannot name others. + + The response is `{ "message": "Inspection submitted.", "submission": { … } }`, the submission carrying what Retrieve an Inspection answers with. + + A token that is missing, unknown, expired or revoked answers 403. A single-use link that has already been used answers 409. A `file:` reference to a file that was not uploaded through the same link answers 422, as does a body carrying only the first cut's flat `item_results`: a public link answers the form's fields. +url: "{{base_url}}/public/inspections/forms/:id/submit" +method: POST +# No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. +pathVariables: + - key: id + value: "{{inspection_link_form_id}}" + description: (Required) The published form the link was minted for. +queryParams: + token: "{{inspection_link_token}}" +body: + type: json + content: |- + { + "odometer": null, + "engine_hours": null, + "signature": null, + "custom_field_values": [] + } + +scripts: + - type: beforeRequest + code: |- + // `custom_field_values` is filled in from the form Retrieve a Public + // Inspection Form chained, because a field is named by a uuid that only + // exists once the form has been read. Every field is answered, with a + // value that passes: a contract run proves the link files a report, not + // what a failed item raises — Submit an Inspection covers that. + // + // A file-upload or signature field is answered with the file Upload a + // Public Inspection Photo stored through the same link. With no file, or a + // select with no options, the field is left unanswered and a required one + // makes the server say so, rather than this script inventing a value. + let groups = []; + try { + groups = JSON.parse(pm.environment.get("public_inspection_form_fields") || "[]"); + } catch (error) { + groups = []; + } + + const fields = (Array.isArray(groups) ? groups : []) + .reduce((all, group) => all.concat(group && Array.isArray(group.fields) ? group.fields : []), []); + + const fileId = pm.environment.get("public_inspection_file_id"); + const now = new Date(); + const today = now.toISOString().slice(0, 10); + + const firstOption = (field) => { + const option = Array.isArray(field.options) ? field.options[0] : undefined; + + if (option && typeof option === "object") { + return option.value !== undefined ? option.value : option.label; + } + + return option; + }; + + const answer = (field) => { + switch (field.type) { + case "pass-fail": + return { + value_type: "object", + value: { + passed: true, + not_applicable: false, + severity: null, + comments: "", + photos: [], + unsafe: false, + }, + }; + case "number": + return { value_type: "number", value: 1 }; + case "boolean": + return { value_type: "boolean", value: true }; + case "select": + case "radio-button": { + const option = firstOption(field); + return option === undefined || option === null ? null : { value_type: "text", value: option }; + } + case "date-picker": + return { value_type: "date", value: today }; + case "date-time-input": + return { value_type: "datetime", value: now.toISOString() }; + case "file-upload": + case "signature": + return fileId ? { value_type: "file", value: "file:" + fileId } : null; + default: + // input, textarea and any text-like type added later. + return { value_type: "text", value: "ok" }; + } + }; + + const values = []; + + fields.forEach((field) => { + const key = field && (field.uuid || field.id); + const given = key ? answer(field) : null; + + if (given) { + values.push(Object.assign({ custom_field: key }, given)); + } + }); + + const body = JSON.parse(pm.request.body.raw); + body.custom_field_values = values; + pm.request.body.update({ mode: "raw", raw: JSON.stringify(body, null, 2) }); + language: text/javascript + - type: afterResponse + code: |- + pm.test("Submit a Public Inspection files the report", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); + }); + language: text/javascript + +order: 9000 diff --git a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml new file mode 100644 index 0000000..23baf42 --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml @@ -0,0 +1,15 @@ +$kind: params +fields: + - name: file + type: string + required: true + description: "Multipart file upload. Must be an image — JPEG, PNG, WebP, GIF or HEIC — of at most 10 MB." + - name: type + type: enum + values: ["inspection_photo", "inspection_signature"] + required: false + description: "What the file is for. Defaults to `inspection_photo`." + - name: token + type: string + required: false + description: "The inspection link's token. Required here unless it is sent as `token` in the query string instead." diff --git a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml new file mode 100644 index 0000000..8527e7d --- /dev/null +++ b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml @@ -0,0 +1,56 @@ +$kind: http-request +name: "Upload a Public Inspection Photo" +description: |- + Uploads a photo or a signature through a public inspection link, so a `pass-fail` photo slot, a `file-upload` field or a `signature` field can be answered by someone who has no Fleetbase session. + + Send the link's `token` as a form field, as here, or in the query string. The body is multipart form data: the `file`, which must be an image of at most 10 MB, and optionally a `type` of `inspection_photo` (the default) or `inspection_signature`. Uploads are rate limited to 20 a minute, and one link can take a limited number of files in all. + + The response is the stored `file` as `{ id, url, filename, content_type }`. Answer a field with it by sending `"file:" + file.id` as the value in Submit a Public Inspection. A file is only accepted by a submission through the same link it was uploaded through; any other file id answers 422. + + A token that is missing, unknown, expired, revoked or already used answers 403 with an `error`. +url: "{{base_url}}/public/inspections/forms/:id/files" +method: POST +# No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. +pathVariables: + - key: id + value: "{{inspection_link_form_id}}" + description: (Required) The published form the link was minted for. +body: + type: formdata + content: + - type: file + key: file + # Path is resolved against the CLI's process working directory, and --working-dir + # does not change that — so it is written relative to the repo root, which is where + # you run the collection from. The same image Files/Upload File sends. + src: "postman/collections/Fleetbase Core API/Files/sample-upload.png" + - type: text + key: token + value: "{{inspection_link_token}}" + - type: text + key: type + value: "inspection_photo" + +scripts: + - type: afterResponse + code: |- + // Submit a Public Inspection answers the form's file-upload and signature + // fields with this file. Cleared when the upload fails, so the submit does + // not reference a previous run's file — which the server would refuse + // with a 422 for belonging to another link, hiding the real failure here. + const json_response = pm.response.json(); + const file = (json_response && json_response.file) ? json_response.file : {}; + + if (typeof file.id === "string" && file.id.indexOf("file_") === 0) { + pm.environment.set("public_inspection_file_id", file.id); + } else { + pm.environment.unset("public_inspection_file_id"); + } + + pm.test("Upload a Public Inspection Photo stores the image as a file", function () { + pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); + pm.expect(file.id, "the stored file has no public id").to.be.a("string").and.match(/^file_/); + }); + language: text/javascript + +order: 8000 From 83b39892bf8b9cd73bb5c9230162ea8da383f0ea Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 11 Sep 2026 14:41:35 +0800 Subject: [PATCH 4/6] Send and document the public inspection link PIN Links now carry a six-digit PIN, shared separately from the link. The three public requests send it as X-Inspection-Pin from inspection_link_pin, and describe the 403s a missing, wrong or locked PIN answers. The retrieve test also checks the identity names who the link is for. --- .../collections/Fleetbase API/.resources/definition.yaml | 3 ++- .../Retrieve a Public Inspection Form.request.yaml | 9 +++++++-- .../Inspections/Submit a Public Inspection.request.yaml | 8 +++++--- .../Upload a Public Inspection Photo.request.yaml | 6 ++++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index 967f438..1a60c0d 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -63,9 +63,10 @@ variables: inspection_id: "" # A public inspection link is minted from the console and only its hash can be # looked up, so the public inspection requests take the link's form and token - # from a contract run that seeds one. + # from a contract run that seeds one, with the PIN the link asks for. inspection_link_form_id: "" inspection_link_token: "" + inspection_link_pin: "" # Fleet hierarchy. The parent is the fleet created first; the subfleet is # created beneath it and deleted before it. parent_fleet_id: "" diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml index 148e2cf..60b9196 100644 --- a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml @@ -3,11 +3,13 @@ name: "Retrieve a Public Inspection Form" description: |- Retrieves a published inspection form through a public inspection link, for whoever holds the link — a driver or a contractor with no Fleetbase account, filling the form in on a phone. - A link is minted from the console for one form, and optionally for one driver and one vehicle. Its token is the whole of the authorisation: send it as `token` in the query string. No API key is needed, and one sent is ignored. These routes are mounted at the application root, with no namespace, and are rate limited to 60 requests a minute per IP address. + A link is minted from the console for one form, and optionally assigned to one user and made for one driver and one vehicle. Send its token as `token` in the query string, and its PIN in the `X-Inspection-Pin` header. No API key is needed, and one sent is ignored. These routes are mounted at the application root, with no namespace, and are rate limited to 60 requests a minute per IP address. - The response carries the `form` — the same shape Retrieve an Inspection Form answers with, its `grouped_fields` being what to render and answer — and an `identity`: the `driver` the link was minted for as `{ id, name }`, the `vehicle` as `{ id, name, plate_number }`, each `null` when the link names none, and the link's `expires_at`. The driver's phone number is deliberately not included: whoever holds the link needs to know who it is for, not how to reach them. + The response carries the `form` — the same shape Retrieve an Inspection Form answers with, its `grouped_fields` being what to render and answer — and an `identity`: the `assignee` the link is for as `{ id, name }`, the `driver` it was minted for as `{ id, name }`, the `vehicle` as `{ id, name, plate_number }`, each `null` when the link names none, and the link's `expires_at`. The driver's phone number is deliberately not included: whoever holds the link needs to know who it is for, not how to reach them. A token that is missing, unknown, expired, revoked or — for a single-use link — already used answers 403 with an `error`. Reading the form does not spend a single-use link; submitting does. + + Every link carries a six-digit PIN, generated with it and shared separately: shown in the console, or sent by email or SMS to the user the link is assigned to, else to the driver. The same PIN is asked for by all three public routes. Without it, every one answers 403 with `pin_required: true`; a wrong one answers the same with `attempts_left`, and the fifth wrong PIN locks the link, which then answers 403 with `locked: true` until a new link is generated. Links minted before PINs existed carry none and ask for none. A `pin` field is accepted too, but the header keeps the PIN out of URLs and the access logs that record them. url: "{{base_url}}/public/inspections/forms/:id" method: GET # No auth block. The route is public and ignores Authorization, so the collection's @@ -19,6 +21,8 @@ pathVariables: - key: id value: "{{inspection_link_form_id}}" description: (Required) The published form the link was minted for. +headers: + X-Inspection-Pin: "{{inspection_link_pin}}" queryParams: token: "{{inspection_link_token}}" @@ -36,6 +40,7 @@ scripts: pm.expect(form, "no form returned").to.be.an("object"); pm.expect(form.id, "the form has no id").to.be.a("string").and.not.empty; pm.expect(json_response, "no identity returned").to.have.property("identity"); + pm.expect(json_response.identity, "the identity does not say who the link is for").to.have.property("assignee"); }); pm.test("The identity names the driver without their phone number", function () { diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml index 753400b..95b8af7 100644 --- a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml @@ -3,13 +3,13 @@ name: "Submit a Public Inspection" description: |- Files an inspection through a public inspection link — what the public inspection page sends when whoever holds the link finishes the form. - Send the link's `token` in the query string. The body is answered the way Submit an Inspection answers a form built from typed fields: `custom_field_values`, one entry per field, naming the field by the `uuid` Retrieve a Public Inspection Form gave it, with a `value_type` and a `value`. A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id Upload a Public Inspection Photo returned. Anything else is plain text, a number, a boolean or a date. `odometer`, `engine_hours` and `signature` are optional. + Send the link's `token` in the query string and its PIN in the `X-Inspection-Pin` header, as Retrieve a Public Inspection Form describes. The body is answered the way Submit an Inspection answers a form built from typed fields: `custom_field_values`, one entry per field, naming the field by the `uuid` Retrieve a Public Inspection Form gave it, with a `value_type` and a `value`. A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id Upload a Public Inspection Photo returned. Anything else is plain text, a number, a boolean or a date. `odometer`, `engine_hours` and `signature` are optional. - The driver and vehicle are the ones the link was minted for; the body cannot name others. + The driver and vehicle are the ones the link was minted for; the body cannot name others. The submission is credited to the user the link is assigned to, or else to the driver's account, and the name given as `signature.name` is kept in its `meta` as `completed_by_name`, beside `pin_verified`. The response is `{ "message": "Inspection submitted.", "submission": { … } }`, the submission carrying what Retrieve an Inspection answers with. - A token that is missing, unknown, expired or revoked answers 403. A single-use link that has already been used answers 409. A `file:` reference to a file that was not uploaded through the same link answers 422, as does a body carrying only the first cut's flat `item_results`: a public link answers the form's fields. + A token that is missing, unknown, expired or revoked answers 403, as does a missing or wrong PIN or a link locked by too many wrong ones. A single-use link that has already been used answers 409. A `file:` reference to a file that was not uploaded through the same link answers 422, as does a body carrying only the first cut's flat `item_results`: a public link answers the form's fields. url: "{{base_url}}/public/inspections/forms/:id/submit" method: POST # No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. @@ -17,6 +17,8 @@ pathVariables: - key: id value: "{{inspection_link_form_id}}" description: (Required) The published form the link was minted for. +headers: + X-Inspection-Pin: "{{inspection_link_pin}}" queryParams: token: "{{inspection_link_token}}" body: diff --git a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml index 8527e7d..3ee8fce 100644 --- a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml @@ -3,11 +3,11 @@ name: "Upload a Public Inspection Photo" description: |- Uploads a photo or a signature through a public inspection link, so a `pass-fail` photo slot, a `file-upload` field or a `signature` field can be answered by someone who has no Fleetbase session. - Send the link's `token` as a form field, as here, or in the query string. The body is multipart form data: the `file`, which must be an image of at most 10 MB, and optionally a `type` of `inspection_photo` (the default) or `inspection_signature`. Uploads are rate limited to 20 a minute, and one link can take a limited number of files in all. + Send the link's `token` as a form field, as here, or in the query string, and its PIN in the `X-Inspection-Pin` header. The body is multipart form data: the `file`, which must be an image of at most 10 MB, and optionally a `type` of `inspection_photo` (the default) or `inspection_signature`. Uploads are rate limited to 20 a minute, and one link can take a limited number of files in all. The response is the stored `file` as `{ id, url, filename, content_type }`. Answer a field with it by sending `"file:" + file.id` as the value in Submit a Public Inspection. A file is only accepted by a submission through the same link it was uploaded through; any other file id answers 422. - A token that is missing, unknown, expired, revoked or already used answers 403 with an `error`. + A token that is missing, unknown, expired, revoked or already used answers 403 with an `error`, as does a missing or wrong PIN or a locked link. url: "{{base_url}}/public/inspections/forms/:id/files" method: POST # No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. @@ -15,6 +15,8 @@ pathVariables: - key: id value: "{{inspection_link_form_id}}" description: (Required) The published form the link was minted for. +headers: + X-Inspection-Pin: "{{inspection_link_pin}}" body: type: formdata content: From bae2c73216a1ce5ffc210578013cd24868b3d858 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 11 Sep 2026 15:56:08 +0800 Subject: [PATCH 5/6] Say the PIN can be sent with the link fleetops#319 now emails or texts the link and its PIN together, so the retrieve description no longer says the PIN is shared separately. --- .../Inspections/Retrieve a Public Inspection Form.request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml index 60b9196..5e3db8c 100644 --- a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml +++ b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml @@ -9,7 +9,7 @@ description: |- A token that is missing, unknown, expired, revoked or — for a single-use link — already used answers 403 with an `error`. Reading the form does not spend a single-use link; submitting does. - Every link carries a six-digit PIN, generated with it and shared separately: shown in the console, or sent by email or SMS to the user the link is assigned to, else to the driver. The same PIN is asked for by all three public routes. Without it, every one answers 403 with `pin_required: true`; a wrong one answers the same with `attempts_left`, and the fifth wrong PIN locks the link, which then answers 403 with `locked: true` until a new link is generated. Links minted before PINs existed carry none and ask for none. A `pin` field is accepted too, but the header keeps the PIN out of URLs and the access logs that record them. + Every link carries a six-digit PIN, generated with it: shown in the console, or sent by email or SMS with the link to the user the link is assigned to, else to the driver. The PIN stops anyone who has the link alone. The same PIN is asked for by all three public routes. Without it, every one answers 403 with `pin_required: true`; a wrong one answers the same with `attempts_left`, and the fifth wrong PIN locks the link, which then answers 403 with `locked: true` until a new link is generated. Links minted before PINs existed carry none and ask for none. A `pin` field is accepted too, but the header keeps the PIN out of URLs and the access logs that record them. url: "{{base_url}}/public/inspections/forms/:id" method: GET # No auth block. The route is public and ignores Authorization, so the collection's From 3d5e14ceceac1fdb6e10022a9fadc87208481536 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 11 Sep 2026 16:02:13 +0800 Subject: [PATCH 6/6] Remove the public inspection link requests The public inspection-link routes in fleetops#319 have no session, but their only consumer is the console's public inspection page, so they are internal by use and do not belong in a collection documenting the consumable API. Integrations file inspections through Submit an Inspection (POST /v1/inspections). This removes the three requests, their collection variables, and the folder description's paragraph about them. --- .../Fleetbase API/.resources/definition.yaml | 6 - .../Inspections/.resources/definition.yaml | 2 - ... a Public Inspection Form.queryParams.yaml | 6 - ...ieve a Public Inspection Form.request.yaml | 61 --------- .../Submit a Public Inspection.params.yaml | 30 ---- ...ubmit a Public Inspection.queryParams.yaml | 6 - .../Submit a Public Inspection.request.yaml | 129 ------------------ ...load a Public Inspection Photo.params.yaml | 15 -- ...oad a Public Inspection Photo.request.yaml | 58 -------- 9 files changed, 313 deletions(-) delete mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml delete mode 100644 postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml diff --git a/postman/collections/Fleetbase API/.resources/definition.yaml b/postman/collections/Fleetbase API/.resources/definition.yaml index 1a60c0d..09fcd52 100644 --- a/postman/collections/Fleetbase API/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/.resources/definition.yaml @@ -61,12 +61,6 @@ variables: # first published one; a contract run has to seed it. inspection_form_id: "" inspection_id: "" - # A public inspection link is minted from the console and only its hash can be - # looked up, so the public inspection requests take the link's form and token - # from a contract run that seeds one, with the PIN the link asks for. - inspection_link_form_id: "" - inspection_link_token: "" - inspection_link_pin: "" # Fleet hierarchy. The parent is the fleet created first; the subfleet is # created beneath it and deleted before it. parent_fleet_id: "" diff --git a/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml index 9f3c16b..a1bab11 100644 --- a/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml +++ b/postman/collections/Fleetbase API/Inspections/.resources/definition.yaml @@ -5,6 +5,4 @@ description: |- These endpoints are for the driver filing the inspection: read the published forms, submit against one, and read back what was filed and what it produced. A failed item can raise an issue and open a work order, depending on how the form is configured; both come back on the submission. Authoring and publishing forms, and reviewing or resolving submissions, is fleet management and is not part of the consumable API. The requests here need at least one published form in the organisation, which a contract run has to seed. - - A form can also be filled in through a public inspection link, by someone with no Fleetbase account. A link is minted from the console for one form, and its token is the only authorisation the three public endpoints take — no API key. Retrieve a Public Inspection Form reads the form and who the link is for, Upload a Public Inspection Photo stores a photo or a signature through the link, and Submit a Public Inspection files the answers. They are mounted at the application root, under `/public/inspections/forms`, and are rate limited per IP address. A contract run has to seed a link and pass its form and token in. order: 7750 diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml deleted file mode 100644 index 10335b4..0000000 --- a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.queryParams.yaml +++ /dev/null @@ -1,6 +0,0 @@ -$kind: queryParams -fields: - - name: token - type: string - required: true - description: The inspection link's token, as the link carries it. It is the only authorisation this route takes. diff --git a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml b/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml deleted file mode 100644 index 5e3db8c..0000000 --- a/postman/collections/Fleetbase API/Inspections/Retrieve a Public Inspection Form.request.yaml +++ /dev/null @@ -1,61 +0,0 @@ -$kind: http-request -name: "Retrieve a Public Inspection Form" -description: |- - Retrieves a published inspection form through a public inspection link, for whoever holds the link — a driver or a contractor with no Fleetbase account, filling the form in on a phone. - - A link is minted from the console for one form, and optionally assigned to one user and made for one driver and one vehicle. Send its token as `token` in the query string, and its PIN in the `X-Inspection-Pin` header. No API key is needed, and one sent is ignored. These routes are mounted at the application root, with no namespace, and are rate limited to 60 requests a minute per IP address. - - The response carries the `form` — the same shape Retrieve an Inspection Form answers with, its `grouped_fields` being what to render and answer — and an `identity`: the `assignee` the link is for as `{ id, name }`, the `driver` it was minted for as `{ id, name }`, the `vehicle` as `{ id, name, plate_number }`, each `null` when the link names none, and the link's `expires_at`. The driver's phone number is deliberately not included: whoever holds the link needs to know who it is for, not how to reach them. - - A token that is missing, unknown, expired, revoked or — for a single-use link — already used answers 403 with an `error`. Reading the form does not spend a single-use link; submitting does. - - Every link carries a six-digit PIN, generated with it: shown in the console, or sent by email or SMS with the link to the user the link is assigned to, else to the driver. The PIN stops anyone who has the link alone. The same PIN is asked for by all three public routes. Without it, every one answers 403 with `pin_required: true`; a wrong one answers the same with `attempts_left`, and the fifth wrong PIN locks the link, which then answers 403 with `locked: true` until a new link is generated. Links minted before PINs existed carry none and ask for none. A `pin` field is accepted too, but the header keeps the PIN out of URLs and the access logs that record them. -url: "{{base_url}}/public/inspections/forms/:id" -method: GET -# No auth block. The route is public and ignores Authorization, so the collection's -# inherited bearer is harmless here. A request-level `auth:` mapping is only proven -# in this repository for overriding one bearer with another (Organizations/List -# Organizations); there is no verified shape here for clearing auth, and a wrong -# shape drops the request from the collection. -pathVariables: - - key: id - value: "{{inspection_link_form_id}}" - description: (Required) The published form the link was minted for. -headers: - X-Inspection-Pin: "{{inspection_link_pin}}" -queryParams: - token: "{{inspection_link_token}}" - -scripts: - - type: afterResponse - code: |- - // Upload a Public Inspection Photo and Submit a Public Inspection answer this - // form, so its groups are chained from here as JSON. Cleared to an empty list - // when the read fails, rather than left holding a previous run's fields. - const json_response = pm.response.json(); - const form = (json_response && json_response.form) ? json_response.form : null; - - pm.test("Retrieve a Public Inspection Form answers the form and who it is for", function () { - pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); - pm.expect(form, "no form returned").to.be.an("object"); - pm.expect(form.id, "the form has no id").to.be.a("string").and.not.empty; - pm.expect(json_response, "no identity returned").to.have.property("identity"); - pm.expect(json_response.identity, "the identity does not say who the link is for").to.have.property("assignee"); - }); - - pm.test("The identity names the driver without their phone number", function () { - const identity = json_response ? json_response.identity : null; - const driver = identity ? identity.driver : null; - - if (driver === null || driver === undefined) { - return; - } - - pm.expect(driver, "a public link must not disclose the driver's phone").to.not.have.property("phone"); - }); - - const groups = (form && Array.isArray(form.grouped_fields)) ? form.grouped_fields : []; - pm.environment.set("public_inspection_form_fields", JSON.stringify(groups)); - language: text/javascript - -order: 7000 diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml deleted file mode 100644 index 1f91840..0000000 --- a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.params.yaml +++ /dev/null @@ -1,30 +0,0 @@ -$kind: params -fields: - - name: custom_field_values - type: array of objects - required: true - description: One entry per field answered, naming the field by the `uuid` Retrieve a Public Inspection Form gave it. - fields: - - name: custom_field - type: string - required: true - description: The field's `uuid`. - - name: value_type - type: string - description: "`object`, `file`, `text`, `number`, `boolean`, `date` or `datetime`." - - name: value - type: string - required: true - description: "A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id of a file uploaded through the same link. Anything else is plain text, a number, a boolean or a date." - - name: odometer - type: integer - required: false - description: Odometer reading at the time of the inspection. - - name: engine_hours - type: integer - required: false - description: Engine hours at the time of the inspection. - - name: signature - type: object - required: false - description: The signer's signature, as the page captured it. diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml deleted file mode 100644 index 10335b4..0000000 --- a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.queryParams.yaml +++ /dev/null @@ -1,6 +0,0 @@ -$kind: queryParams -fields: - - name: token - type: string - required: true - description: The inspection link's token, as the link carries it. It is the only authorisation this route takes. diff --git a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml b/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml deleted file mode 100644 index 95b8af7..0000000 --- a/postman/collections/Fleetbase API/Inspections/Submit a Public Inspection.request.yaml +++ /dev/null @@ -1,129 +0,0 @@ -$kind: http-request -name: "Submit a Public Inspection" -description: |- - Files an inspection through a public inspection link — what the public inspection page sends when whoever holds the link finishes the form. - - Send the link's `token` in the query string and its PIN in the `X-Inspection-Pin` header, as Retrieve a Public Inspection Form describes. The body is answered the way Submit an Inspection answers a form built from typed fields: `custom_field_values`, one entry per field, naming the field by the `uuid` Retrieve a Public Inspection Form gave it, with a `value_type` and a `value`. A `pass-fail` answer is an object — `passed`, `not_applicable`, `severity`, `comments`, `photos` and `unsafe`. A `file-upload` or `signature` answer is `file:` followed by the id Upload a Public Inspection Photo returned. Anything else is plain text, a number, a boolean or a date. `odometer`, `engine_hours` and `signature` are optional. - - The driver and vehicle are the ones the link was minted for; the body cannot name others. The submission is credited to the user the link is assigned to, or else to the driver's account, and the name given as `signature.name` is kept in its `meta` as `completed_by_name`, beside `pin_verified`. - - The response is `{ "message": "Inspection submitted.", "submission": { … } }`, the submission carrying what Retrieve an Inspection answers with. - - A token that is missing, unknown, expired or revoked answers 403, as does a missing or wrong PIN or a link locked by too many wrong ones. A single-use link that has already been used answers 409. A `file:` reference to a file that was not uploaded through the same link answers 422, as does a body carrying only the first cut's flat `item_results`: a public link answers the form's fields. -url: "{{base_url}}/public/inspections/forms/:id/submit" -method: POST -# No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. -pathVariables: - - key: id - value: "{{inspection_link_form_id}}" - description: (Required) The published form the link was minted for. -headers: - X-Inspection-Pin: "{{inspection_link_pin}}" -queryParams: - token: "{{inspection_link_token}}" -body: - type: json - content: |- - { - "odometer": null, - "engine_hours": null, - "signature": null, - "custom_field_values": [] - } - -scripts: - - type: beforeRequest - code: |- - // `custom_field_values` is filled in from the form Retrieve a Public - // Inspection Form chained, because a field is named by a uuid that only - // exists once the form has been read. Every field is answered, with a - // value that passes: a contract run proves the link files a report, not - // what a failed item raises — Submit an Inspection covers that. - // - // A file-upload or signature field is answered with the file Upload a - // Public Inspection Photo stored through the same link. With no file, or a - // select with no options, the field is left unanswered and a required one - // makes the server say so, rather than this script inventing a value. - let groups = []; - try { - groups = JSON.parse(pm.environment.get("public_inspection_form_fields") || "[]"); - } catch (error) { - groups = []; - } - - const fields = (Array.isArray(groups) ? groups : []) - .reduce((all, group) => all.concat(group && Array.isArray(group.fields) ? group.fields : []), []); - - const fileId = pm.environment.get("public_inspection_file_id"); - const now = new Date(); - const today = now.toISOString().slice(0, 10); - - const firstOption = (field) => { - const option = Array.isArray(field.options) ? field.options[0] : undefined; - - if (option && typeof option === "object") { - return option.value !== undefined ? option.value : option.label; - } - - return option; - }; - - const answer = (field) => { - switch (field.type) { - case "pass-fail": - return { - value_type: "object", - value: { - passed: true, - not_applicable: false, - severity: null, - comments: "", - photos: [], - unsafe: false, - }, - }; - case "number": - return { value_type: "number", value: 1 }; - case "boolean": - return { value_type: "boolean", value: true }; - case "select": - case "radio-button": { - const option = firstOption(field); - return option === undefined || option === null ? null : { value_type: "text", value: option }; - } - case "date-picker": - return { value_type: "date", value: today }; - case "date-time-input": - return { value_type: "datetime", value: now.toISOString() }; - case "file-upload": - case "signature": - return fileId ? { value_type: "file", value: "file:" + fileId } : null; - default: - // input, textarea and any text-like type added later. - return { value_type: "text", value: "ok" }; - } - }; - - const values = []; - - fields.forEach((field) => { - const key = field && (field.uuid || field.id); - const given = key ? answer(field) : null; - - if (given) { - values.push(Object.assign({ custom_field: key }, given)); - } - }); - - const body = JSON.parse(pm.request.body.raw); - body.custom_field_values = values; - pm.request.body.update({ mode: "raw", raw: JSON.stringify(body, null, 2) }); - language: text/javascript - - type: afterResponse - code: |- - pm.test("Submit a Public Inspection files the report", function () { - pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); - }); - language: text/javascript - -order: 9000 diff --git a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml deleted file mode 100644 index 23baf42..0000000 --- a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.params.yaml +++ /dev/null @@ -1,15 +0,0 @@ -$kind: params -fields: - - name: file - type: string - required: true - description: "Multipart file upload. Must be an image — JPEG, PNG, WebP, GIF or HEIC — of at most 10 MB." - - name: type - type: enum - values: ["inspection_photo", "inspection_signature"] - required: false - description: "What the file is for. Defaults to `inspection_photo`." - - name: token - type: string - required: false - description: "The inspection link's token. Required here unless it is sent as `token` in the query string instead." diff --git a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml b/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml deleted file mode 100644 index 3ee8fce..0000000 --- a/postman/collections/Fleetbase API/Inspections/Upload a Public Inspection Photo.request.yaml +++ /dev/null @@ -1,58 +0,0 @@ -$kind: http-request -name: "Upload a Public Inspection Photo" -description: |- - Uploads a photo or a signature through a public inspection link, so a `pass-fail` photo slot, a `file-upload` field or a `signature` field can be answered by someone who has no Fleetbase session. - - Send the link's `token` as a form field, as here, or in the query string, and its PIN in the `X-Inspection-Pin` header. The body is multipart form data: the `file`, which must be an image of at most 10 MB, and optionally a `type` of `inspection_photo` (the default) or `inspection_signature`. Uploads are rate limited to 20 a minute, and one link can take a limited number of files in all. - - The response is the stored `file` as `{ id, url, filename, content_type }`. Answer a field with it by sending `"file:" + file.id` as the value in Submit a Public Inspection. A file is only accepted by a submission through the same link it was uploaded through; any other file id answers 422. - - A token that is missing, unknown, expired, revoked or already used answers 403 with an `error`, as does a missing or wrong PIN or a locked link. -url: "{{base_url}}/public/inspections/forms/:id/files" -method: POST -# No auth block — see Retrieve a Public Inspection Form. The inherited bearer is ignored. -pathVariables: - - key: id - value: "{{inspection_link_form_id}}" - description: (Required) The published form the link was minted for. -headers: - X-Inspection-Pin: "{{inspection_link_pin}}" -body: - type: formdata - content: - - type: file - key: file - # Path is resolved against the CLI's process working directory, and --working-dir - # does not change that — so it is written relative to the repo root, which is where - # you run the collection from. The same image Files/Upload File sends. - src: "postman/collections/Fleetbase Core API/Files/sample-upload.png" - - type: text - key: token - value: "{{inspection_link_token}}" - - type: text - key: type - value: "inspection_photo" - -scripts: - - type: afterResponse - code: |- - // Submit a Public Inspection answers the form's file-upload and signature - // fields with this file. Cleared when the upload fails, so the submit does - // not reference a previous run's file — which the server would refuse - // with a 422 for belonging to another link, hiding the real failure here. - const json_response = pm.response.json(); - const file = (json_response && json_response.file) ? json_response.file : {}; - - if (typeof file.id === "string" && file.id.indexOf("file_") === 0) { - pm.environment.set("public_inspection_file_id", file.id); - } else { - pm.environment.unset("public_inspection_file_id"); - } - - pm.test("Upload a Public Inspection Photo stores the image as a file", function () { - pm.expect(pm.response.code, `got HTTP ${pm.response.code}`).to.eql(200); - pm.expect(file.id, "the stored file has no public id").to.be.a("string").and.match(/^file_/); - }); - language: text/javascript - -order: 8000