{
  "openapi": "3.0.4",
  "info": {
    "title": "Orin Partner API",
    "description": "\n> **Version:** v1, **last updated:** September 2026\n\n# Overview\n\nThe Orin Partner API lets external systems, such as warehouse management systems (WMS), ERP platforms and order management tools, create, track and manage transport requests in the Orin platform. It also serves carriers and subcontractors who receive work from a transport company that runs Orin.\n\nThis guide is the narrative half of the documentation. The endpoint reference below it (paths, parameters, request and response schemas, examples) is generated from the API itself and is always the contract the live API enforces. Read this guide for how the pieces fit together, then use the reference for the exact fields.\n\n**Who is this for?**\n\nTwo different kinds of integrator, pointing in opposite directions:\n\n| You are | You want to | Read |\n|---|---|---|\n| A **shipper**: a WMS, ERP or order management system handing work *to* the transport company | Create transports, track them, pull proof of delivery, read invoices, book dock slots | Path A or Path B below |\n| A **carrier or subcontractor**: receiving work *from* the transport company | Collect dispatched trips, acknowledge them, push status and POD back | [Carrier and subcontractor API](#section/Carrier-and-subcontractor-API) |\n\nMost of this guide is written for the first case. If you have been given an API key by a transport company that subcontracts work to you, the section you want is near the end.\n\n**What can you do with it?**\n\n- Create transport requests from your WMS or ERP\n- Track shipment status and delivery progress\n- Retrieve proof of delivery (photos, signatures)\n- Update transport notes, contact details and pickup or delivery time windows while the transport is still `Pending` or `Confirmed`\n- Cancel transports that have not yet been completed\n- Read the invoices issued to you, and the charges behind a transport before they are invoiced\n- Search free dock slots and book, move or cancel dock appointments\n- Receive webhook notifications on status changes\n\n**Base URL:**\n\n```\nhttps://api.orin.software/api/partner/v1\n```\n\nAll timestamps in requests and responses are in **UTC** and use **ISO 8601** format (for example `2026-03-15T09:00:00Z`).\n\n---\n\n# Picking your integration path\n\nOrin exposes **three integration paths** for partners. They share authentication, idempotency and the same internal transport pipeline server-side, but the contract you write against differs.\n\n| Path | Endpoint | Best for |\n|---|---|---|\n| **A: Transport REST API** | `POST /api/partner/v1/transports` (plus list, get, update, cancel, tracking, POD, invoices, appointments) | Partners building a fresh integration; teams comfortable writing against a typed JSON contract; a tight feedback loop (synchronous 201 with the transport id). |\n| **B: EDI Gateway** ([section](#section/EDI-Gateway)) | `POST /api/inbound/{aliasSlug}` (raw EDIFACT, X12, XML, JSON or CSV) | Partners with an existing EDI or CSV format they want to push as-is; the transport company configures a per-partner mapping that translates your payload into the same internal transport. |\n| **C: Dispatch API** ([section](#section/Carrier-and-subcontractor-API)) | `GET /api/partner/v1/dispatches` (plus acknowledge, leg status, POD, complete, exceptions) | Carriers and subcontractors on the receiving end: work arrives from the transport company and you report execution back. The opposite direction to A and B. |\n\nPaths A and B emit the same **outbound webhooks** ([Webhooks](#section/Webhooks)), so you can mix them: integrate inbound via EDI and consume outbound via JSON webhooks, or the other way round.\n\nThe Authentication, Permissions and Error handling sections apply to **all three paths**.\n\n---\n\n# Authentication\n\n## API key\n\nEvery request must include your API key in the `X-API-Key` header:\n\n```\nX-API-Key: orin_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n```\n\nAPI keys:\n\n- Always start with the `orin_pk_` prefix\n- Are issued through an invite link provided by the transport company (the Orin tenant)\n- Are scoped to one partner organisation\n- Can be revoked at any time by the transport company\n- Carry a set of permissions (see [Permissions](#section/Permissions))\n- May have an expiry date\n\nA missing, invalid, expired or revoked key answers `401 Unauthorized` with an empty body.\n\n## Obtaining your API key\n\n1. The transport company generates a partner invite link in their Orin dashboard.\n2. You open the invite link and accept it.\n3. You receive your API key. **It is shown only once.** Store it securely.\n4. You send the key in the `X-API-Key` header on every request.\n\n## Rate limits\n\n| Policy | Limit | Applies to |\n|---|---|---|\n| Partner API | 60 requests per minute per API key | Everything under `/api/partner/v1` |\n| EDI Gateway | 300 requests per minute per API key | `/api/inbound` |\n\nThe transport company can set a different limit for your key. The window is a fixed minute. When the limit is exceeded the API answers `429 Too Many Requests` with a `Retry-After` header (seconds until the window opens again) and a problem-details body whose `code` is `rate_limited`. Wait the given seconds and retry.\n\nBest practices:\n\n- **Batch reads.** Use the list endpoints with filters instead of fetching transports one by one.\n- **Use webhooks** for status changes rather than polling.\n- **Cache** transport details locally and refresh only when needed.\n\n## IP allowlisting\n\nThe transport company can configure an IP allowlist for your key. If one is configured, requests from other addresses are rejected with `403 Forbidden` and the problem code `partner_api.ip_not_allowed`.\n\n## Kill switch\n\nThe transport company, or the platform, can temporarily disable API access. While a kill switch is active every request answers `401 Unauthorized` with a `Retry-After: 300` header. This is used for emergency maintenance and is communicated separately.\n\n## Sandbox mode\n\nYour API key may be configured in sandbox mode for testing. In sandbox mode:\n\n- Requests work normally, but a transport's notes are prefixed with `[SANDBOX]` so the transport company can recognise test data\n- Responses from the transport create, list, get and cancel endpoints carry an `X-Orin-Sandbox: true` header (see [Response headers](#section/Response-headers))\n\n---\n\n# Permissions\n\nEach API key carries a set of permission flags, chosen when the invite is created.\n\n**Shipper-facing** (paths A and B):\n\n| Permission | Value | Description |\n|---|---|---|\n| `CreateTransport` | 1 | Create new transport requests |\n| `ReadTransport` | 2 | List and retrieve transports |\n| `ReadPod` | 4 | Access proof of delivery photos and signatures |\n| `ReadTracking` | 8 | Access tracking events |\n| `UpdateTransport` | 16 | Update transport details (contact info, time windows) |\n| `CancelTransport` | 32 | Cancel transports |\n| `ReadInvoice` | 2048 | Read issued invoices and the charge breakdown behind a transport |\n| `ReadAppointment` | 4096 | See appointments on your transports, and search free dock slots |\n| `BookAppointment` | 8192 | Book, reschedule and cancel dock appointments |\n\n**Supplier-facing** (path C, [Carrier and subcontractor API](#section/Carrier-and-subcontractor-API)):\n\n| Permission | Value | Description |\n|---|---|---|\n| `ReadDispatch` | 64 | View dispatched trips and their leg details |\n| `AcknowledgeDispatch` | 128 | Accept, reject, or accept-with-exception a dispatch |\n| `UpdateDispatchStatus` | 256 | Push execution status on dispatched legs |\n| `UploadDispatchPod` | 512 | Upload proof-of-delivery files for dispatched legs |\n| `CompleteDispatch` | 1024 | Declare a dispatch completed |\n\n**Common presets:**\n\n| Preset | Permissions | Value |\n|---|---|---|\n| ReadOnly | ReadTransport + ReadPod + ReadTracking | 14 |\n| Standard | CreateTransport + ReadTransport + ReadPod + ReadTracking | 15 |\n| Full | Standard + UpdateTransport + CancelTransport + ReadInvoice + ReadAppointment + BookAppointment | 14399 |\n| SupplierReadOnly | ReadDispatch | 64 |\n| SupplierStandard | ReadDispatch + AcknowledgeDispatch + UpdateDispatchStatus + UploadDispatchPod | 960 |\n| SupplierFull | SupplierStandard + CompleteDispatch | 1984 |\n\nA key is issued with one set or the other. The two sides are separate on purpose: a subcontractor executing your work has no reason to create transports, and a shipper handing you orders has no reason to see your dispatch board.\n\nCalling an endpoint without the required permission answers `403 Forbidden` with the problem code `partner_api.forbidden`.\n\n---\n\n# Quick start\n\n## Step 1: verify connectivity\n\nTest that your API key works:\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\" \\\n  \"https://api.orin.software/api/partner/v1/transports?pageSize=1\" \\\n  -H \"X-API-Key: orin_pk_your_api_key_here\"\n```\n\nA `200` confirms your key is valid.\n\n## Step 2: create your first transport\n\n```bash\ncurl -X POST \"https://api.orin.software/api/partner/v1/transports\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: orin_pk_your_api_key_here\" \\\n  -H \"Idempotency-Key: test-001\" \\\n  -d '{\n    \"externalReference\": \"WMS-2026-00001\",\n    \"plannedPickupAt\": \"2026-04-01T08:00:00Z\",\n    \"plannedDeliveryAt\": \"2026-04-01T14:00:00Z\",\n    \"origin\": {\n      \"address\": \"Industrieweg 42, 1234 AB Amsterdam, Netherlands\",\n      \"locationName\": \"Warehouse Amsterdam\",\n      \"contactName\": \"Jan de Vries\",\n      \"contactPhone\": \"+31 6 12345678\",\n      \"instructions\": \"Loading dock B, call 15 min before arrival\",\n      \"windowStart\": \"2026-04-01T08:00:00Z\",\n      \"windowEnd\": \"2026-04-01T11:00:00Z\"\n    },\n    \"destination\": {\n      \"address\": \"Havenstraat 100, 5678 CD Rotterdam, Netherlands\",\n      \"locationName\": \"Distribution Center Rotterdam\",\n      \"contactName\": \"Piet Jansen\",\n      \"contactPhone\": \"+31 6 87654321\",\n      \"instructions\": \"Gate 5, ID required\",\n      \"windowStart\": \"2026-04-01T14:00:00Z\",\n      \"windowEnd\": \"2026-04-01T17:00:00Z\"\n    },\n    \"cargo\": {\n      \"description\": \"Consumer electronics, palletised\",\n      \"palletCount\": 6,\n      \"weightKg\": 1200.0,\n      \"volumeCbm\": 4.8,\n      \"loadingMeters\": 3.0\n    },\n    \"notes\": \"Fragile goods, handle with care\"\n  }'\n```\n\nThe response is `201 Created` with the transport as Orin holds it. The full request and response schemas are in the generated reference below.\n\n## Step 3: check transport status\n\nUse the `id` from the response, or your own `externalReference`:\n\n```bash\n# By Orin id\ncurl \"https://api.orin.software/api/partner/v1/transports/{id}\" \\\n  -H \"X-API-Key: orin_pk_your_api_key_here\"\n\n# By your external reference\ncurl \"https://api.orin.software/api/partner/v1/transports/by-reference/WMS-2026-00001\" \\\n  -H \"X-API-Key: orin_pk_your_api_key_here\"\n```\n\n---\n\n# Transports\n\nThe transport is the unit a shipper works with: one collection, one delivery, and the cargo between them. Orin turns it into a shipment for the transport company's planners. The exact request and response fields are in the generated reference; this section covers the behaviour around them.\n\n## Endpoints\n\n| Endpoint | Purpose | Permission |\n|---|---|---|\n| `POST /transports` | Create a transport. Answers `201` with the transport. | `CreateTransport` |\n| `GET /transports` | Your transports, paged and filterable (status, planned date, external reference, last update). | `ReadTransport` |\n| `GET /transports/{id}` | One transport by Orin id. | `ReadTransport` |\n| `GET /transports/by-reference/{reference}` | One transport by your own reference, so you never have to store ours. | `ReadTransport` |\n| `PATCH /transports/{id}` | Change notes, stop contacts, stop instructions and time windows. Only the fields you send change. | `UpdateTransport` |\n| `POST /transports/{id}/cancel` | Cancel, with a reason the planner sees. | `CancelTransport` |\n| `GET /transports/{id}/tracking` | The event timeline. | `ReadTracking` |\n| `GET /transports/{id}/pod` | Proof of delivery once the transport is completed. | `ReadPod` |\n| `GET /transports/{id}/charges` | What the transport is costing you (see [Invoices and charges](#section/Invoices-and-charges)). | `ReadInvoice` |\n\nA transport belonging to another partner answers `404`, never `403`, so the endpoints cannot be used to probe which ids exist.\n\n## What each status allows\n\n| Status | Update | Cancel | Tracking | POD |\n|---|---|---|---|---|\n| `Pending` | Yes | Yes | Timeline shows creation | No |\n| `Confirmed` | Yes | Yes | Yes | No |\n| `InProgress` | No (`403`) | Yes | Yes | No |\n| `Completed` | No (`403`) | No (`403`) | Yes | Yes |\n| `Cancelled` | No (`403`) | No (`409`) | Yes | No |\n\nUpdates apply to the transport's first leg (origin) and last leg (destination) and their bounding stops. Cancelling a transport that is already in execution is accepted, but the truck may already be on its way; anything time-critical is worth a phone call as well.\n\n## Tracking and proof of delivery\n\nThe tracking timeline is built from the shipment's own history: `Created`, `Confirmed`, every milestone recorded by the driver or a subcontractor (for example `ArrivedAtPickup`, `LoadingCompleted`, `InTransit`), `Completed` and `Cancelled`, ordered by time. A transport with no milestones yet still has its creation event.\n\nProof of delivery is available once the transport is `Completed` **and** evidence exists. Both sources are returned: photos the transport company's own driver captured in the app, and any POD document a subcontractor uploaded against the shipment. `signedBy` and `signatureUrl` are populated only where a CMR delivery signature was captured; domestic work is often proven by photo alone, so treat both as optional.\n\n**The links expire after 20 minutes.** Fetch the bytes and store them on your side; do not persist a URL and expect it to resolve later.\n\n## Polling for changes\n\nIf you cannot receive webhooks, poll the list endpoint with `updatedFrom`:\n\n```bash\n# Every transport changed in the last hour\ncurl \"https://api.orin.software/api/partner/v1/transports?updatedFrom=2026-03-27T09:30:00Z&pageSize=100\" \\\n  -H \"X-API-Key: orin_pk_your_api_key_here\"\n```\n\n`page` is 1-based and `pageSize` is clamped to 100.\n\n## Duplicate references\n\n`externalReference` must be unique within your partner account. Creating a second transport with a reference that already exists answers `409 Conflict`, and the problem details carry the existing transport:\n\n```json\n{\n  \"type\": \"https://httpstatuses.com/409\",\n  \"title\": \"conflict\",\n  \"status\": 409,\n  \"detail\": \"A transport with external reference 'WMS-2026-00001' already exists\",\n  \"code\": \"partner_api.conflict\",\n  \"correlationId\": \"00-abc123def456-789012345678-01\",\n  \"existingTransportId\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"existingTransportNumber\": \"SH-2026-000042\"\n}\n```\n\nThe transport company can make `externalReference` mandatory for your key (`requireExternalReference`); a create without one then answers `400`.\n\n---\n\n# Idempotency\n\nTo prevent duplicate transports after a network retry, send an `Idempotency-Key` header on `POST /transports` and `PATCH /transports/{id}`:\n\n```\nIdempotency-Key: your-unique-key-here\n```\n\n**How it works:**\n\n- The same key with the same request body returns the original response.\n- The same key with a **different** body answers `409 Conflict`.\n- If the original request is still being processed, the API answers `409 Conflict` with a `Retry-After` header.\n- Keys are scoped to your API key and to the endpoint, so the same value on a create and on an update never collide.\n\n`externalReference` provides a second layer of protection on create: a duplicate answers `409` with the existing transport, as described under [Duplicate references](#section/Transports/Duplicate-references).\n\n**Recommendation:** always send an `Idempotency-Key` on create. Use your WMS order id or a UUID.\n\n---\n\n# Address requirements\n\n## Free-text and structured addresses\n\nEach stop needs at least one of:\n\n1. **Free text**: the `address` field with the full address as one string.\n2. **Structured** (recommended): the `structuredAddress` object with the individual components.\n\nEither is enough; you do not need both. When both are sent, the structured address is used for geocoding and the free text is stored for display. A structured address must carry a `city` and a `country` or `countryCode`.\n\n## Country codes\n\nUse ISO 3166-1 alpha-2 codes, two uppercase letters. Any code in that shape is accepted; examples:\n\n| Code | Country |\n|---|---|\n| `NL` | Netherlands |\n| `BE` | Belgium |\n| `DE` | Germany |\n| `FR` | France |\n| `LU` | Luxembourg |\n| `AT` | Austria |\n| `CH` | Switzerland |\n\n## Postal code formats\n\nPostal codes are checked against the country's pattern when the country is one of these; elsewhere any combination of letters, digits, spaces and dashes is accepted.\n\n| Country | Format | Example |\n|---|---|---|\n| Netherlands | `1234 AB` (space optional) | `1234 AB` |\n| Belgium | `1234` | `9000` |\n| Germany | `12345` | `10115` |\n| France | `12345` | `75001` |\n| Luxembourg | `1234` | `1234` |\n| Austria | `1234` | `1010` |\n| Switzerland | `1234` | `8001` |\n\n## Geocoding\n\nOrin geocodes every address. If geocoding fails (an ambiguous address, say), the transport is **still created**: the address is stored as-is and the transport company corrects the location in their dashboard.\n\nA structured address with a postal code and a country code geocodes far more reliably than free text. Sending `latitude` and `longitude` bypasses geocoding entirely.\n\n---\n\n# WMS integration\n\nThe Partner API includes a few fields designed for warehouse management systems.\n\n## Warehouse code\n\n`warehouseCode` says which of your warehouses is sending the order. It matters when several warehouses share one API key.\n\n```json\n{\n  \"warehouseCode\": \"WH-AMS-01\"\n}\n```\n\nThe transport company can configure **warehouse profiles** in Orin that map a warehouse code to a department, a legal entity and default settings. A code that matches no profile is not an error: the transport is created with your partner defaults.\n\n## Department code\n\n`departmentCode` assigns the transport to a department of the transport company explicitly:\n\n```json\n{\n  \"departmentCode\": \"TRANSPORT-NL\"\n}\n```\n\n**Department resolution**, in order:\n\n1. An explicit `departmentCode` in the request\n2. The warehouse profile's default department, when `warehouseCode` matches a profile\n3. Routing rules, if the transport company has configured them\n4. The partner's default department, from your partner settings\n\nA `departmentCode` that matches nothing falls through to the remaining steps. No error is returned.\n\n## Auto-create internal transfer\n\nWhen enabled by the transport company, an incoming partner transport can automatically create an internal transfer record that links your organisation to their operational workflow. This is configured per warehouse profile.\n\n## Auto-confirm\n\nThe transport company can enable `autoConfirmOrders` for your key. New transports then start as `Confirmed` instead of `Pending`, so planners can assign them straight away.\n\n---\n\n# Status flow\n\nTransports follow this lifecycle:\n\n```\n                                  +--------------+\n                                  |              |\n                                  v              |\n+----------+    +-----------+    +------------+  |  +-----------+\n| Pending  |--->| Confirmed |--->| InProgress |--+->| Completed |\n+----------+    +-----------+    +------------+     +-----------+\n     |               |                 |\n     v               v                 v\n+-----------------------------------------------+\n|                   Cancelled                   |\n+-----------------------------------------------+\n```\n\n| Status | Code | Description |\n|---|---|---|\n| `Pending` | 0 | Created, awaiting confirmation by the transport company |\n| `Confirmed` | 1 | Accepted and scheduled for execution |\n| `InProgress` | 2 | The driver has started (pickup or in transit) |\n| `Completed` | 3 | Delivered |\n| `Cancelled` | 4 | Cancelled by you or by the transport company |\n\nEvery transport response carries both `statusCode` (the number) and `status` (the name). Branch on the number; show the name.\n\n---\n\n# Error handling\n\n## Error response format\n\nThe transport endpoints answer errors as RFC 7807 problem details with a machine-readable `code`:\n\n```json\n{\n  \"type\": \"https://httpstatuses.com/400\",\n  \"title\": \"validation failed\",\n  \"status\": 400,\n  \"detail\": \"Cargo description is required\",\n  \"code\": \"partner_api.validation_failed\",\n  \"correlationId\": \"00-abc123def456-789012345678-01\"\n}\n```\n\n| Field | Description |\n|---|---|\n| `type` | URI reference for the error type |\n| `title` | Short human-readable summary |\n| `status` | HTTP status code |\n| `detail` | The specific message for this occurrence |\n| `code` | Machine-readable error code (table below) |\n| `correlationId` | Request trace id, for support questions |\n\nA request body that fails validation answers `400` with the standard validation problem shape instead: the failing fields are listed under `errors`, each with its messages.\n\nThe invoice, appointment, dispatch, execution and EDI Gateway endpoints answer errors as a small JSON object with an `error` and, where it applies, a `code` or `message`. Each section below lists its codes, and the generated reference shows the exact shape per endpoint.\n\n## Error codes\n\n| Code | HTTP status | Description | Action |\n|---|---|---|---|\n| `partner_api.validation_failed` | 400 | The request is invalid, or a required reference is missing | Fix the request and retry |\n| (no body) | 401 | API key missing, invalid, expired or revoked; or the kill switch is active (then with `Retry-After: 300`) | Check your key; wait if the kill switch is on |\n| `partner_api.forbidden` | 403 | Missing permission, or the action is not allowed in the transport's current status | Check the key's permissions and the status rules |\n| `partner_api.ip_not_allowed` | 403 | Your address is not on the allowlist | Contact the transport company |\n| `partner_api.not_found` | 404 | The transport does not exist or does not belong to your account | Verify the id |\n| `partner_api.conflict` | 409 | Duplicate external reference, idempotency key reuse with a different body, a request still in progress, or a cancel of an already cancelled transport | See [Conflict response (409)](#section/Error-handling/Conflict-response-(409)) |\n| `rate_limited` | 429 | Rate limit exceeded | Wait for `Retry-After` seconds, then retry |\n\n## Conflict response (409)\n\nA duplicate `externalReference` answers with the existing transport's `existingTransportId` and `existingTransportNumber` in the problem details (example under [Duplicate references](#section/Transports/Duplicate-references)). An idempotency key whose first request is still running answers with a `Retry-After` header. Wait and retry the same request.\n\n## Retry guidance\n\n| Scenario | Recommended action |\n|---|---|\n| `429 Too Many Requests` | Wait for `Retry-After` seconds, then retry |\n| `409 Conflict` with `Retry-After` (request in progress) | Wait the given seconds, then retry the same request |\n| `5xx Server Error` | Retry with exponential backoff (1 s, 2 s, 4 s, 8 s, max 60 s) |\n| `401 Unauthorized` with `Retry-After` (kill switch) | Wait at least 5 minutes, then retry |\n| `400 Bad Request` | Do not retry; fix the request |\n| `404 Not Found` | Do not retry; verify the resource exists |\n\n---\n\n# Webhooks\n\nOrin can call a URL of yours when something happens to a transport you created or a trip dispatched to you. The transport company configures the URL and the secret for your partner account.\n\n## Event types\n\nThe catalogue is short on purpose. Two families exist: transport events for shippers, and dispatch events for carriers and subcontractors.\n\n**Transport events** (sent to the shipper's webhook):\n\n| Event | When |\n|---|---|\n| `transport.created` | A transport was created, via the REST API or the EDI Gateway |\n| `transport.status_changed` | The transport was confirmed (`status` `Confirmed`, `previousStatus` `Pending`) or completed (`status` `Completed`, `previousStatus` `InProgress`) |\n| `transport.cancelled` | The transport was cancelled |\n| `transport.exception` | A stop failed (refused goods, nobody present, and so on); `status` is `InProgress` |\n\n**Dispatch events** (sent to the webhook of the organisation the trip was dispatched to):\n\n| Event | When | `status` |\n|---|---|---|\n| `dispatch.assigned` | A trip was dispatched to your fleet | `dispatched` |\n| `dispatch.completed` | The trip finished | `completed` |\n| `dispatch.cancelled` | The transport company cancelled the trip | `cancelled` |\n| `dispatch.exception` | A trip-level exception was raised | the exception type |\n\nOn dispatch events `transportId` carries the **trip id** and `externalReference` carries the trip's batch reference, or its trip number when there is none.\n\n> Earlier drafts of this document listed `transport.confirmed`, `transport.completed` and `transport.in_progress` as separate events, and later `dispatch.acknowledged` and three `inbound.message.*` events. None of those are emitted. A status transition arrives as `transport.status_changed` with the destination state in `status`.\n\n## Webhook payload\n\n```json\n{\n  \"event\": \"transport.status_changed\",\n  \"transportId\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"externalReference\": \"WMS-2026-00001\",\n  \"status\": \"Completed\",\n  \"previousStatus\": \"InProgress\",\n  \"occurredAt\": \"2026-04-01T14:30:00Z\",\n  \"signature\": null\n}\n```\n\n| Field | Type | Description |\n|---|---|---|\n| `event` | string | Event type |\n| `transportId` | string | Orin transport id (trip id on dispatch events) |\n| `externalReference` | string or null | Your external reference (batch reference or trip number on dispatch events) |\n| `status` | string | The state after the event |\n| `previousStatus` | string or null | The state before it, on `transport.status_changed` |\n| `occurredAt` | datetime | When the event happened, UTC |\n| `signature` | null | Always null in the body; the signature travels in the header |\n\n## Webhook headers\n\n| Header | Description |\n|---|---|\n| `Content-Type` | `application/json` (or `application/edifact`, see [EDIFACT outbound variants](#section/EDI-Gateway/EDIFACT-outbound-variants)) |\n| `X-Orin-Event` | The event type, for example `transport.status_changed` |\n| `X-Orin-Timestamp` | Unix epoch seconds when the delivery was built; sent on every webhook |\n| `X-Orin-Signature` | HMAC-SHA256 of the body, `sha256=<hex>` |\n| `X-Orin-Signature-Previous` | Present **only during a secret rotation window**: the same body signed with the previous secret (see [Secret rotation](#section/Webhooks/Secret-rotation)) |\n| `X-Orin-Content-Format` | The body format, on EDIFACT deliveries |\n\n## Signature verification\n\nEvery delivery is signed with HMAC-SHA256 using the webhook secret the transport company configured for you. To verify:\n\n1. Read the raw request body.\n2. Compute HMAC-SHA256 of the body with the shared secret.\n3. Compare, in constant time, with the `X-Orin-Signature` header (`sha256=` followed by the lowercase hex digest).\n\n**Python:**\n\n```python\nimport hmac\nimport hashlib\n\ndef verify_webhook(body: bytes, signature: str, secret: str) -> bool:\n    expected = \"sha256=\" + hmac.new(\n        secret.encode(), body, hashlib.sha256\n    ).hexdigest()\n    return hmac.compare_digest(expected, signature)\n```\n\n**C#:**\n\n```csharp\nusing System.Security.Cryptography;\nusing System.Text;\n\nbool VerifyWebhook(string body, string signature, string secret)\n{\n    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));\n    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));\n    var expected = \"sha256=\" + Convert.ToHexString(hash).ToLowerInvariant();\n    return CryptographicOperations.FixedTimeEquals(\n        Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature));\n}\n```\n\n## Secret rotation\n\nThe transport company can rotate your webhook secret with a **dual-key overlap window** of 1 to 1440 minutes, chosen at rotation time. During the window every delivery is signed with **both** secrets:\n\n- `X-Orin-Signature`: signed with the **new** secret\n- `X-Orin-Signature-Previous`: the same body signed with the **old** secret\n\nTo roll over without dropping deliveries, accept a request if **either** header verifies against the secret you hold. Once you have switched to the new secret (before the window ends) you can ignore `X-Orin-Signature-Previous`. After the window only `X-Orin-Signature` is sent.\n\n## Retry policy\n\nIf your endpoint does not answer `2xx`, Orin retries with exponential backoff. The transport company configures the numbers; the defaults are 3 retries with a 30 second base that doubles on each attempt (30 s, 60 s, 120 s). Failed deliveries are logged and visible in the transport company's dashboard, from where a delivery can be retried by hand.\n\n## Event filtering\n\nThe transport company can restrict which events are sent to your webhook. By default all events are delivered.\n\n---\n\n# EDI Gateway\n\nThis is **Path B** from [Picking your integration path](#section/Picking-your-integration-path), for partners who already have an EDI or CSV format and do not want to map to the typed JSON contract. The transport company configures a per-partner mapping that translates your payload into the same internal transport as Path A.\n\n## Endpoint\n\n```\nPOST /api/inbound/{aliasSlug}\n```\n\n`aliasSlug` is the URL slug issued per integration (for example `dhl-freight-nl-orders`). One slug per partner role per message type per tenant keeps everything isolated and auditable. The slug is bound to your partner organisation; posting to another partner's slug with your key answers `403`.\n\n## Required headers\n\n| Header | Notes |\n|---|---|\n| `X-API-Key` | The same key as under [Authentication](#section/Authentication) |\n| `Idempotency-Key` | Unique per logical message, 1 to 100 characters. Re-sending with the same key answers `200 OK` with `duplicate: true` and never creates two transports |\n| `X-Message-Type` | The message contract, up to 50 characters (for example `EDIFACT_IFTMIN`, `EDIFACT_IFTSTA`, `X12_204`, `CSV_BOOKING_V1`) |\n| `Content-Type` | Matches the body: `application/edifact`, `application/xml`, `application/json`, `text/csv`, `text/plain` or `application/octet-stream` |\n\nEDIFACT bodies are decoded according to the character set advised in the `UNB` segment; other formats are read as UTF-8.\n\n## Optional headers\n\n| Header | Default | Behaviour |\n|---|---|---|\n| `X-Sync-Hint: no` | Inline processing when the body is under 256 KB | Opt out of inline processing: always answer `202` and process in the background |\n\n## Size limits and rate limit\n\n- **Body**: 2 MB hard cap, otherwise `413 Payload Too Large`\n- **Rate limit**: 300 requests per minute per API key (a separate policy from the Path A limit, sized for batch uploads)\n\n## Response\n\nEvery answer has the same shape (`InboundReceiveResponse` in the reference): `messageId`, `status`, `processingPhase`, `duplicate`, `statusUrl`, `processedDocumentId`, `errorCode` and `errorMessage`. Which ones carry a value depends on the path the message took.\n\n**Sync path** (body under 256 KB and processed within 10 seconds): `200 OK` with the outcome.\n\n```json\n{\n  \"messageId\": \"8d2f...\",\n  \"status\": \"Processed\",\n  \"processingPhase\": \"Executed\",\n  \"duplicate\": false,\n  \"statusUrl\": \"/api/inbound/messages/8d2f.../status\",\n  \"processedDocumentId\": \"f93a...\",\n  \"errorCode\": null,\n  \"errorMessage\": null\n}\n```\n\n**Async path** (large body, opt-out, or the inline budget was exceeded): `202 Accepted` as soon as the message is durably stored. Poll `statusUrl`.\n\n```json\n{\n  \"messageId\": \"8d2f...\",\n  \"status\": \"Received\",\n  \"processingPhase\": \"Received\",\n  \"duplicate\": false,\n  \"statusUrl\": \"/api/inbound/messages/8d2f.../status\",\n  \"processedDocumentId\": null,\n  \"errorCode\": null,\n  \"errorMessage\": null\n}\n```\n\n**Duplicate** (`Idempotency-Key` already seen): `200 OK` describing the original message. Nothing was ingested twice.\n\n```json\n{\n  \"messageId\": \"<original-id>\",\n  \"status\": \"Processed\",\n  \"processingPhase\": \"Executed\",\n  \"duplicate\": true,\n  \"statusUrl\": \"/api/inbound/messages/<original-id>/status\",\n  \"processedDocumentId\": \"f93a...\",\n  \"errorCode\": null,\n  \"errorMessage\": null\n}\n```\n\n## Worked example: EDIFACT IFTMIN\n\n```http\nPOST /api/inbound/dhl-freight-nl-orders HTTP/1.1\nX-API-Key: orin_pk_live_...\nIdempotency-Key: DHL-2026-05-20-001\nX-Message-Type: EDIFACT_IFTMIN\nContent-Type: application/edifact\n\nUNH+1+IFTMIN:D:96A:UN'\nBGM+334+ORD-9382+9'\nDTM+137:202605201430:203'\nDTM+2:202605211200:203'\nDTM+3:202605221700:203'\nNAD+SH+++ACME SHIPPER LTD+x+ROTTERDAM+x+x+NL'\nNAD+CN+++ALBERT HEIJN BV+x+AMSTERDAM+x+x+NL'\nMEA+AAE+WT+KGM:18500'\nUNT+8+1'\n```\n\nThe mapping that translates this into a transport is configured per partner; see [Mapping workbench](#section/EDI-Gateway/Mapping-workbench).\n\n## Status polling and replay\n\n### Poll\n\n```\nGET /api/inbound/messages/{messageId}/status\n```\n\nReturns the current status and error information for any message you posted. Useful when:\n\n- You received `202 Accepted` and want to know what became of the message\n- You are debugging why a message landed in `Failed` or `DeadLettered`\n\n```json\n{\n  \"id\": \"8d2f...\",\n  \"status\": \"Failed\",\n  \"processingPhase\": \"Failed\",\n  \"messageType\": \"EDIFACT_IFTMIN\",\n  \"receivedAt\": \"2026-05-20T14:31:00Z\",\n  \"processedAt\": null,\n  \"retryCount\": 1,\n  \"errorCode\": \"mapping-failed\",\n  \"lastError\": \"Required field 'externalReference' has no value at source path 'BGM[1][0]'\"\n}\n```\n\n`processingPhase` is one of `Received`, `Parsed`, `Mapped`, `Validated`, `Executed`, `AckSent`, `Failed`. `status` tells you whether to keep polling:\n\n| `status` | Meaning | Keep polling? |\n|---|---|---|\n| `Received` | Stored, waiting for the processor | Yes |\n| `Processing` | Being parsed, mapped and executed now | Yes |\n| `Processed` | Done; `processedDocumentId` on the receive response names the transport it created | No |\n| `Failed` | The last attempt failed; `errorCode` says why. Codes that retry come back through `Processing` | Yes, until `retryCount` stops rising or the status moves on |\n| `DeadLettered` | Failed permanently, or retries exhausted. The planner can fix the mapping and replay it | No |\n| `Duplicate` | A later message carried the same `Idempotency-Key` and was discarded in favour of this one | No |\n\n### Replay (planner action)\n\nIf processing failed because of a mapping problem, your Orin contact can:\n\n1. Fix the mapping (Inbound Mappings, activate a new version)\n2. Replay the message through the new mapping (Workbench, Replay)\n\nYou do not need to re-send. The original payload and `Idempotency-Key` are preserved.\n\n### EDI Gateway error codes\n\n| Code | Meaning | Retry? |\n|---|---|---|\n| `edi-parse-failed` | Malformed EDIFACT (segment terminator missing, and so on) | Re-post with a corrected payload |\n| `edi-empty` | The body contained no segments | Re-post with content |\n| `alias-missing` | The `aliasSlug` does not exist for your tenant | Contact the transport company |\n| `alias-unbound` | The alias exists but has no partner role configured | Contact the transport company |\n| `partner-settings-missing` | Your partner settings are incomplete | Contact the transport company |\n| `auth-key-id-missing` | Internal: the message lost its key reference | Contact the transport company |\n| `mapping-failed` | A required field could not be extracted (see `lastError`) | Mapping problem or a change in your payload shape |\n| `mapping-not-configured` | No active mapping for this partner role and message type | The transport company configures one in the Workbench |\n| `iftsta-ref-missing` | The IFTSTA had no `RFF+CN`, `RFF+UCN` or `RFF+AAQ`, so it cannot be correlated to a trip | Add a consignment reference segment |\n| `iftsta-trip-not-found` | The IFTSTA references a trip that was never dispatched to you | Check that `RFF+CN` matches the dispatch |\n| `iftsta-status-unsupported` | The `STS` qualifier is not supported | Contact the transport company |\n| `iftsta-transition-rejected` | A domain rule rejected the event (for example a start on an already completed trip) | Send events in chronological order; check the trip state |\n| `stale-processing-recovery` | A previous run claimed the row and crashed; the sweep recovered it | Informational; the message is retried automatically |\n| `transport-service-error` | The mapped transport was rejected (see `lastError`) | Often fixable on your side; retried automatically |\n\nEvery code above except `stale-processing-recovery` and `transport-service-error` is permanent: the message goes to `DeadLettered` immediately. Retried codes use exponential backoff of 5 to the power of the attempt number in minutes (5, 25, 125, ...), up to 5 attempts, and then dead-letter.\n\n## EDIFACT outbound variants\n\nPartners on EDIFACT can receive the Path A webhooks as EDIFACT instead of JSON: `IFTMBC` (booking confirmation) for `transport.created` and `IFTSTA` (status) for `transport.status_changed`. They are signed exactly like JSON webhooks (see [Webhooks](#section/Webhooks)) and sent with `Content-Type: application/edifact` and `X-Orin-Content-Format: application/edifact`, so you can branch on either header at your edge. The format is chosen per event in your partner settings by the transport company.\n\n## Mapping workbench\n\nThe mapping that translates your inbound payload into a transport is editable per partner per message type. Your Orin contact at the transport company has access to:\n\n- **Inbound Mappings** (Settings, Integrations): versioned mapping definitions per partner role and message type. Activation is atomic: only one version per pair is live.\n- **Mapping Workbench**: a live inbox of inbound messages with status, payload, mapping evidence and a replay button.\n\nWhen you send a payload variant that has not been mapped yet, your contact can:\n\n1. Upload a sample (`.edi`, `.txt`, `.json`, `.csv` or `.xml`, at most 2 MB)\n2. Run the AI proposer to scaffold a mapping\n3. Edit fields by hand\n4. Save as draft and review the diff against the active version\n5. Have an approver (Admin or KeyUser) activate it\n\nThe Workbench updates in real time: the moment a replay succeeds, the row turns green and the linked shipment is created.\n\n## Partner self-service (Customer Portal)\n\nSince 2026-07-20 partners do not have to go through an Orin contact for every mapping iteration or key rotation. If the transport company grants your portal login the **inbound-integration** scope, you can serve yourself from the **Customer Portal** (Settings, Integrations).\n\n> These endpoints use **Customer Portal authentication** (a portal login bound to a per-organisation grant), **not** the `X-API-Key` partner key. They are driven from the portal's Integrations page (Mappings, Sample test and API key tabs) and rate-limited at 60 requests per minute per portal user. Your organisation role is always taken from the validated grant, never from the request body.\n\n| Method | Route | Purpose |\n|---|---|---|\n| `GET` | `/api/portal/integration/mappings` | List your own mappings (active and proposed) across your message types |\n| `GET` | `/api/portal/integration/mappings/{messageType}` | Version history and definitions for one message type |\n| `POST` | `/api/portal/integration/mappings/{messageType}/propose` | **Propose** a new mapping revision from a definition you write; it is created inactive |\n| `POST` | `/api/portal/integration/mappings/sample-test` | Dry-run a proposed definition against a sample payload; nothing is written |\n| `GET` | `/api/portal/integration/api-key` | List your API keys (public prefix only; the secret is never returned) |\n| `POST` | `/api/portal/integration/api-key/regenerate` | Rotate the key for one of your organisation roles: revokes the active keys and mints a fresh one, returned once |\n\n**Governance is preserved.** You can only **propose** a mapping revision; the new version stays inactive until the transport company's planner activates it. There is no partner self-activation. Proposing a mapping from a sample with AI, and webhook configuration and testing, are not available in the portal; those still go through your Orin contact.\n\n## EDI Gateway quick reference\n\n- **Inbound POST**: `POST /api/inbound/{aliasSlug}` with `Idempotency-Key`, `X-Message-Type` and `X-API-Key`\n- **Status poll**: `GET /api/inbound/messages/{messageId}/status`\n- **Body cap**: 2 MB\n- **Rate limit**: 300 per minute per API key\n- **Webhook signature** (when receiving outbound EDI): the same `sha256=hex(hmac_sha256(secret, body))` in `X-Orin-Signature` as JSON webhooks\n- **Outbound EDIFACT**: `IFTMBC` and `IFTSTA`, signed identically to JSON, `Content-Type: application/edifact`\n\n---\n\n# Invoices and charges\n\nWhat a movement costs you, on the same key that created it. Read-only throughout: a disputed charge is a conversation, not a `PATCH`.\n\n```\nGET /api/partner/v1/invoices                        invoices issued to you\nGET /api/partner/v1/invoices/{id}                   one invoice, with its lines\nGET /api/partner/v1/invoices/{id}/pdf               the document itself\nGET /api/partner/v1/transports/{id}/charges         what one transport is costing\n```\n\nRequires `ReadInvoice` (2048). Deliberately separate from the transport permissions: a key issued to move freight does not automatically get to read prices.\n\n## What you can see\n\nOnly invoices that have actually been **sent** to you: statuses `Sent`, `Paid`, `PartiallyPaid`, `Overdue` and `Disputed`. An invoice the transport company is still preparing is their working state, and it is filtered out rather than exposed in a draft status you would have to learn to ignore. Credit notes appear alongside sales invoices, because a credit note changes what you owe and leaving it out would make your totals wrong.\n\n`GET /invoices` supports `status`, `search` (invoice number, your reference, external reference), `invoiceDateFrom` and `invoiceDateTo`, `dueDateFrom` and `dueDateTo`, `page` and `pageSize` (max 100), newest first.\n\nAn unknown `status` answers `400 INVALID_STATUS` with the allowed values, rather than quietly ignoring the filter and handing back an unfiltered list you would have trusted.\n\nThe PDF endpoint answers `application/pdf` as a file download, not JSON. Everything else on this API answers JSON, so a client that assumes a JSON body everywhere needs a branch here.\n\n## Charges before invoicing\n\n`GET /transports/{id}/charges` is the useful one during execution: it shows what a movement is costing **while it is still running**, rather than on a statement weeks later.\n\n```json\n{\n  \"transportId\": \"a1b2c3d4-...\",\n  \"currencyCode\": \"EUR\",\n  \"totalAmount\": 845.50,\n  \"containsEstimates\": true,\n  \"lines\": [\n    { \"chargeType\": \"FRT\", \"chargeTypeName\": \"Freight\", \"description\": \"Amsterdam to Rotterdam\",\n      \"quantity\": 1, \"unitOfMeasure\": null, \"currencyCode\": \"EUR\",\n      \"amount\": 720.00, \"isEstimate\": false, \"legId\": \"...\", \"invoicedAt\": null },\n    { \"chargeType\": \"TOLL\", \"chargeTypeName\": \"Toll\", \"description\": \"NL Vrachtwagenheffing\",\n      \"quantity\": 1, \"unitOfMeasure\": null, \"currencyCode\": \"EUR\",\n      \"amount\": 125.50, \"isEstimate\": true, \"legId\": \"...\", \"invoicedAt\": null }\n  ]\n}\n```\n\n**Mind `isEstimate`.** Orin follows one rule everywhere: once a charge is actualised the actual amount is authoritative, and until then the estimate stands. A line that is still an estimate can move. `containsEstimates` on the envelope tells you at a glance whether the total is final, and `invoicedAt` tells you which lines have already reached an invoice.\n\n---\n\n# Dock appointments\n\nBooking a slot on the transport company's dock, from your own system.\n\n```\nGET  /api/partner/v1/appointments/availability      free slots\nGET  /api/partner/v1/appointments                   your appointments\nPOST /api/partner/v1/appointments                   book one\nGET  /api/partner/v1/appointments/{id}              one appointment\nPUT  /api/partner/v1/appointments/{id}              move it\nPOST /api/partner/v1/appointments/{id}/cancel       give the slot back\n```\n\nReading needs `ReadAppointment` (4096); booking, rescheduling and cancelling need `BookAppointment` (8192).\n\n## Search, then book\n\n```\nGET /api/partner/v1/appointments/availability?locationId={id}&from=2026-04-02T06:00:00Z&to=2026-04-02T18:00:00Z\n```\n\nGive a site (`locationId`) or a single dock (`dockResourceId`) and a window. One of the two is required (`400 TARGET_REQUIRED` otherwise); `from` defaults to now and `to` to seven days later. Blocked periods and existing bookings are already removed, so what comes back is bookable. Slot length comes from the dock's own configuration unless you pass `slotMinutes`. Searches are capped at 31 days.\n\n```json\n{\n  \"fromUtc\": \"2026-04-02T06:00:00Z\",\n  \"toUtc\": \"2026-04-02T18:00:00Z\",\n  \"slots\": [\n    { \"dockResourceId\": \"...\", \"dockResourceCode\": \"D1\", \"dockResourceName\": \"Dock 1\",\n      \"locationId\": \"...\", \"startUtc\": \"2026-04-02T09:00:00Z\", \"endUtc\": \"2026-04-02T10:00:00Z\",\n      \"remainingCapacity\": 1 }\n  ]\n}\n```\n\n`remainingCapacity` matters on sites that run more than one trailer per door: a dock with capacity 2 keeps appearing until it is genuinely full.\n\n**Availability is a snapshot, not a hold.** Nothing is reserved until you book, and a slot can be taken between the two calls. Treat a `409` on booking as ordinary rather than exceptional, and re-search.\n\n## Booking\n\n```json\nPOST /api/partner/v1/appointments\n{\n  \"transportId\": \"a1b2c3d4-...\",\n  \"legId\": \"2c1a...\",\n  \"appointmentType\": \"Delivery\",\n  \"locationId\": \"...\",\n  \"dockResourceId\": \"...\",\n  \"windowStartUtc\": \"2026-04-02T09:00:00Z\",\n  \"windowEndUtc\": \"2026-04-02T10:00:00Z\",\n  \"reference\": \"WMS-APPT-4471\"\n}\n```\n\n`appointmentType` is one of `Pickup`, `Delivery`, `CrossDock`, `Warehouse`, `TerminalPickup`, `TerminalDropoff`. Pickup and delivery appointments must name the `legId` they belong to. A multi-leg movement has more than one place where goods change hands, and an appointment that does not say which one cannot be acted on.\n\nNew appointments start as `Requested`. This endpoint **asks for** a slot; the transport company confirms it. Rescheduling returns a confirmed appointment to `Requested`, since a slot they agreed to is no longer the slot you now want. Cancelled appointments stay in the list so a reschedule leaves a trail.\n\n| Status | Code | When |\n|---|---|---|\n| 400 | `INVALID_APPOINTMENT_TYPE` | Not one of the types above (the response lists them) |\n| 400 | `LEG_REQUIRED` | Pickup or Delivery without a `legId` |\n| 400 | `LEG_NOT_ON_TRANSPORT` | The leg belongs to a different transport |\n| 400 | `INVALID_WINDOW` | Start is not before end |\n| 409 | `DOCK_RESOURCE_FULL` | The dock is at capacity for that window |\n| 409 | `DOCK_DOOR_OVERLAP` | Something already has that door |\n| 409 | `DOCK_BLOCKED` | The window overlaps a blocked period |\n| 409 | `DOCK_RESOURCE_NOT_FOUND` | The dock named in the window check no longer exists |\n| 409 | `APPOINTMENT_NOT_RESCHEDULABLE` | Already completed or cancelled |\n| 409 | `ALREADY_CANCELLED` | Cancelling an appointment that is already cancelled |\n| 409 | `APPOINTMENT_COMPLETED` | Cancelling a completed appointment |\n| 404 | `partner_api.not_found` | Transport, dock or appointment is not yours |\n\nCancelling frees the capacity immediately, so the slot returns to the availability search for everyone else.\n\n---\n\n# Carrier and subcontractor API\n\nEverything above assumes you are sending work *to* a transport company. This section is the other direction: you are a carrier or subcontractor and the transport company sends work *to you*.\n\nThe shape of the integration is a work queue. Trips the transport company has dispatched to your organisation appear in your list; you accept them, report progress as your driver runs them, attach the proof, and close them out.\n\n```\nGET  /dispatches                              your inbox\nGET  /dispatches/{tripId}                     the detail you need to run it\nPOST /dispatches/{tripId}/acknowledge         accept or reject\nPOST /dispatches/{tripId}/legs/{legId}/status progress, or an exception\nPOST /dispatches/{tripId}/legs/{legId}/pod    the evidence\nPOST /dispatches/{tripId}/complete            done\nGET  /dispatches/{tripId}/exceptions          what went wrong, and whether it was resolved\n```\n\nAuthentication and rate limits are the same as everywhere else in this API. What differs is the permission set: you need the `Supplier*` presets from [Permissions](#section/Permissions), not the shipper ones. Errors on these endpoints are a JSON object with `error` (the code) and `message`; a trip that is not yours, or still a draft, answers `404 TRIP_NOT_FOUND`.\n\n## The unit of work is the leg, not the trip\n\nA trip is a day's driving. It carries one or more **legs**, and a leg is one shipment moving between two points. Status updates and POD uploads are addressed **per leg**, which is what lets a multi-drop trip report its second delivery without waiting for the fifth.\n\nAcknowledgement and completion are addressed per **trip**, because those are decisions about the whole job.\n\n## List your dispatches\n\n```\nGET /api/partner/v1/dispatches\n```\n\n| Query parameter | Type | Description |\n|---|---|---|\n| `status` | string | Filter by trip status: `Planned`, `Dispatched`, `InProgress`, `Completed`, `Cancelled` |\n| `search` | string | Free text over trip number, batch reference and your own trip reference |\n| `from`, `to` | datetime | Bound by trip date |\n| `page` | integer | Defaults to 1 |\n| `pageSize` | integer | Defaults to 20, clamped to 100 |\n\n```json\n{\n  \"items\": [\n    {\n      \"tripId\": \"8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f\",\n      \"tripNumber\": \"TR-2026-000318\",\n      \"batchReference\": \"BATCH-0042\",\n      \"dispatchMethod\": \"Agent\",\n      \"status\": \"Dispatched\",\n      \"partnerTripRef\": null,\n      \"instructions\": \"Two drops, both tail lift.\",\n      \"tripDate\": \"2026-04-02T00:00:00Z\",\n      \"createdAt\": \"2026-04-01T15:02:40Z\",\n      \"dispatchedAt\": \"2026-04-01T16:20:11Z\",\n      \"acknowledgedAt\": null,\n      \"completedAt\": null,\n      \"cancelledAt\": null,\n      \"shipmentCount\": 2,\n      \"legCount\": 2,\n      \"requiredActions\": [\"acknowledge\"]\n    }\n  ],\n  \"totalCount\": 7,\n  \"page\": 1,\n  \"pageSize\": 20\n}\n```\n\n**`requiredActions` is the field to build your UI around.** It tells you what Orin is currently waiting for on that trip, so you do not have to reimplement the state machine:\n\n| Trip state | `requiredActions` |\n|---|---|\n| Dispatched, not yet acknowledged | `[\"acknowledge\"]` |\n| Dispatched, acknowledged | `[\"update_status\", \"upload_pod\"]` |\n| InProgress | `[\"update_status\", \"upload_pod\", \"complete\"]` |\n\nOnly trips dispatched to your organisation are returned, and drafts are excluded: an uncommitted plan is not yet an instruction to you.\n\n## Get the detail\n\n```\nGET /api/partner/v1/dispatches/{tripId}\n```\n\nReturns the trip with its shipments, its stops in sequence with addresses and instructions, its legs with pickup and delivery endpoints, the plate and driver you reported, and any open exceptions. This is what the driver needs.\n\nLegs carry the `legId` you will use for every status and POD call. A leg's `status` is one of `Planned`, `AssignedToTrip`, `HandedOver`, `Accepted`, `Dispatched`, `InTransit`, `Completed`, `Cancelled`.\n\n## Acknowledge\n\n```\nPOST /api/partner/v1/dispatches/{tripId}/acknowledge\n```\n\n```json\n{\n  \"response\": \"accepted\",\n  \"comment\": null,\n  \"partnerTripRef\": \"OUR-JOB-88421\",\n  \"items\": [\n    {\n      \"legId\": \"2c1a...\",\n      \"supplierReference\": \"OUR-JOB-88421-1\",\n      \"vehiclePlateNumber\": \"12-ABC-3\",\n      \"driverName\": \"P. Nowak\",\n      \"driverPhone\": \"+31 6 11223344\"\n    }\n  ]\n}\n```\n\n`response` is `accepted`, `accepted_with_exception` or `rejected`. A rejection requires a `comment`; it is carried back to the planner as the reason.\n\nSend the plate and driver details here if you know them at acknowledgement time. They roll up to the trip and are what the transport company's own customer sees on their tracking page.\n\n**Acknowledgement is single-winner.** The first call wins; a second one answers `409 ALREADY_ACKNOWLEDGED`, including when two of your own systems race. Treat that 409 as *already recorded*, not as an error to retry.\n\n| Status | Code | When |\n|---|---|---|\n| 400 | `INVALID_RESPONSE` | `response` is not one of the three values |\n| 400 | `SUPPLIER_REFERENCE_REQUIRED` | The transport company requires a supplier reference and none was sent, either as `partnerTripRef` or per leg |\n| 409 | `ALREADY_ACKNOWLEDGED` | Already acknowledged, by you or by a concurrent call |\n| 409 | `TRIP_CANCELLED` | The trip was cancelled before you got to it |\n| 409 | `TRIP_COMPLETED` | Already completed |\n| 409 | `TRIP_NOT_DISPATCHED` | The trip is not in a state that can be acknowledged |\n\n## Report progress, or report trouble\n\n```\nPOST /api/partner/v1/dispatches/{tripId}/legs/{legId}/status\n```\n\n```json\n{\n  \"status\": \"picked_up\",\n  \"eventTime\": \"2026-04-02T09:14:00Z\",\n  \"notes\": \"Loaded, 24 pallets confirmed\",\n  \"supplierReference\": \"OUR-JOB-88421-1\",\n  \"vehiclePlateNumber\": \"12-ABC-3\",\n  \"driverName\": \"P. Nowak\",\n  \"driverPhone\": \"+31 6 11223344\",\n  \"estimatedArrival\": \"2026-04-02T13:30:00Z\"\n}\n```\n\nProgress values, in order:\n\n`acknowledged`, `en_route_pickup`, `picked_up`, `en_route_delivery`, `delivered`\n\nMoving backwards answers `409 INVALID_STATUS_SEQUENCE`; an unknown value answers `400 INVALID_STATUS`.\n\n**The same endpoint reports exceptions.** Send one of these in `status` instead of a progress value, and Orin records an exception against the trip and raises it to the planner rather than moving the leg forward:\n\n`delay`, `failed_pickup`, `delivery_issue`, `damage`, `missing_goods`, `no_show`, `reschedule_request`\n\nFor an exception, `estimatedArrival` is stored as the estimated resolution time. This is the highest-value call in the whole path. An exception the planner learns about while the driver is still at the gate can be re-planned; the same fact discovered the next morning is just an invoice dispute.\n\nSend `eventTime` as the moment the thing actually happened, not the moment you are posting it. Orin keeps the event log, so a late upload still lands in the right place on the timeline.\n\n| Status | Code | When |\n|---|---|---|\n| 403 | `STATUS_UPDATES_NOT_ALLOWED` | The transport company has switched status updates off for your account |\n| 404 | `LEG_NOT_ON_TRIP` | The leg is not part of this trip |\n| 409 | `STATUS_NOT_ALLOWED_AFTER_CANCELLATION` | The dispatch was cancelled |\n\n## Upload proof of delivery\n\n```\nPOST /api/partner/v1/dispatches/{tripId}/legs/{legId}/pod\nContent-Type: multipart/form-data\n```\n\nOne file per call, as the `file` part, up to 25 MB, as `.pdf`, `.jpg`, `.jpeg`, `.png`, `.heic` or `.tiff`. Optional form fields: `documentType` (defaults to `ProofOfDelivery`) and `notes`. Call it repeatedly to attach several files: signed CMR, delivery note, damage photo. Uploads accumulate on the leg rather than replacing one another.\n\n| Status | Code | When |\n|---|---|---|\n| 400 | `NO_FILE` | No file, or an empty one |\n| 400 | `INVALID_FILE_TYPE` | Not one of the extensions above |\n| 403 | `POD_UPLOAD_NOT_ALLOWED` | The transport company has switched POD upload off for your account |\n| 404 | `LEG_NOT_ON_TRIP` | The leg is not part of this trip |\n| 409 | `POD_NOT_ALLOWED_AFTER_CANCELLATION` | The dispatch was cancelled |\n\n## Complete\n\n```\nPOST /api/partner/v1/dispatches/{tripId}/complete\n```\n\nRuns the same completion path the transport company's own fleet goes through: remaining legs are closed, the trip is completed, cost allocation is finalised, and the completion event is published downstream. A trip still sitting in `Dispatched` is started first, so you do not need a separate \"started\" call. Completing a trip that is already completed answers `200` again with the same result.\n\n**Upload the PODs before completing.** Completion is what makes the work invoiceable, and an invoice raised without its proof attached is the one that gets queried.\n\n| Status | Code | When |\n|---|---|---|\n| 400 | `LEG_TRANSITION_FAILED` | A leg could not be moved to its final state (see `message`) |\n| 409 | `TRIP_CANCELLED` | The dispatch was cancelled |\n| 409 | `TRIP_COMPLETION_BLOCKED` | Open stops or equipment still to be dispositioned; `unresolved` lists them |\n\n## Exception history\n\n```\nGET /api/partner/v1/dispatches/{tripId}/exceptions\n```\n\nEvery exception on the trip, resolved and unresolved, newest first, with when it was reported, any estimated resolution, and how it was closed. Resolved entries are included deliberately: an exception that was raised and then cleared is exactly what you need when a delivery is disputed weeks later. Filter on `isResolved` for what is currently open.\n\n## Trading-partner execution\n\nThere is a second, narrower supplier surface at `/api/partner/v1/execution`, for the case where **both sides run Orin** and work is passed tenant to tenant rather than to an outside subcontractor.\n\n```\nGET  /api/partner/v1/execution                      shipments assigned to you\nGET  /api/partner/v1/execution/{shipmentId}         one of them\nPOST /api/partner/v1/execution/{shipmentId}/events  report an execution event\n```\n\nEvery call needs one extra header:\n\n```\nX-Partner-Tenant-Id: <the Orin tenant id of the transport company you execute for>\n```\n\nAccess is granted through an active trading-partner link between the two tenants rather than through a dispatch, and then narrowed to the shipments on trips actually linked to you. A missing or malformed header answers `400`; a key without tenant claims answers `401`; no active link answers `403`. A shipment that is not linked to you answers `404`.\n\nEvents are `picked_up`, `in_transit`, `delivered` and `exception`, with an optional `legId`, an `eventTime` (defaults to now), a `podReference` and `notes`. The response echoes the resulting shipment and leg status.\n\nIf you were given a key by a transport company and you are not yourself an Orin tenant, this is not the surface you want; use `/dispatches` above.\n\n---\n\n# Validation rules\n\nThe generated reference shows the type, length and required flag of every field. The rules below are the ones the schema cannot express.\n\n## Top-level fields\n\n| Field | Rule |\n|---|---|\n| `externalReference` | Max 100 characters. Unique per partner account. Angle brackets (`<`, `>`) are rejected. Required when the transport company has enabled `requireExternalReference` for your key. |\n| `plannedPickupAt` | Not more than 1 hour in the past. Before `plannedDeliveryAt` when both are set. |\n| `plannedDeliveryAt` | After `plannedPickupAt` when both are set. |\n| `notes` | Max 2000 characters. Angle brackets are rejected. |\n| `warehouseCode` | Max 50 characters. Letters, digits, dashes and underscores only. |\n| `departmentCode` | Max 50 characters. Letters, digits, dashes and underscores only. |\n\n## Origin and destination\n\n| Field | Rule |\n|---|---|\n| `address` | Max 500 characters. Required when `structuredAddress` is absent. |\n| `structuredAddress` | Required when `address` is absent. |\n| `locationName` | Max 200 characters. |\n| `contactName` | Max 100 characters. |\n| `contactPhone` | Max 50 characters. Digits, `+`, spaces, dashes and parentheses only. |\n| `instructions` | Max 1000 characters. |\n| `windowStart`, `windowEnd` | Start before end when both are set. |\n\n## Structured address\n\n| Field | Rule |\n|---|---|\n| `street` | Max 200 characters. |\n| `houseNumber` | Max 20 characters. |\n| `postalCode` | Max 20 characters. Checked against the country pattern for NL, DE, BE, FR, LU, AT and CH; letters, digits, spaces and dashes elsewhere. |\n| `city` | Required. Max 100 characters. |\n| `country`, `countryCode` | At least one is required. `countryCode` must be two uppercase letters. |\n| `latitude` | Between -90 and 90. |\n| `longitude` | Between -180 and 180. |\n\n## Cargo\n\n| Field | Rule |\n|---|---|\n| `description` | Required. Max 500 characters. |\n| `palletCount` | 0 to 9999. |\n| `packageCount` | 0 to 99999. |\n| `weightKg` | 0 to 999999. |\n| `volumeCbm` | 0 to 9999. |\n| `loadingMeters` | 0 to 99. |\n| `isAdr` | Default `false`. |\n| `adrDetails` | Max 500 characters. Recommended when `isAdr` is true. |\n| `vehicleRequirements` | Max 200 characters. |\n| `temperature` | Max 50 characters. |\n| `incoterms` | Max 10 characters. |\n| `equipmentType` | Max 50 characters. |\n| `serviceLevel` | Max 50 characters. |\n\nAt least one of `weightKg`, `volumeCbm`, `palletCount` or `packageCount` must be greater than 0.\n\n## Update fields\n\nAt least one of `notes`, `origin` or `destination` must be present. `notes` follows the top-level rule; the stop fields (`contactName`, `contactPhone`, `instructions`, `windowStart`, `windowEnd`) follow the origin and destination rules above.\n\n## Cancel fields\n\nThe body is optional. When a body is sent, `reason` is required and at most 500 characters.\n\n---\n\n# Response headers\n\nFour transport endpoints add two headers to their successful responses: create, list, get by id, and cancel.\n\n| Header | Description |\n|---|---|\n| `X-Orin-Api-Version` | The API version, `v1` |\n| `X-Orin-Sandbox` | Present and `true` when the request was processed in sandbox mode |\n\nThe other transport endpoints (get by reference, update, tracking, POD) and the invoice, appointment, dispatch, execution and EDI Gateway endpoints do not send them.\n\n---\n\n# Changelog\n\n## 2026-09 (Current): complete reference on the public docs page\n\n- **This guide now renders on the public documentation page** (orin.software/api-docs) as the narrative above the generated endpoint reference, and the API serves it as part of its OpenAPI document.\n- **Response schemas for invoices, charges, appointments, dispatches, execution and the EDI Gateway** are now generated from typed contracts. Before this, five of the six endpoint groups showed no response body at all in the reference.\n- **Every query, path and header parameter is described**, and the document carries the server URL.\n- **`X-Partner-Tenant-Id` documented** for the trading-partner execution endpoints; it had always been required.\n- **The `inbound.message.*` webhook events and `dispatch.acknowledged` were removed** from the event catalogue: they were documented but never emitted.\n- **The hand-written transport endpoint reference was removed** in favour of the generated one; the behaviour around it (status rules, polling, duplicate references) moved to the [Transports](#section/Transports) section.\n- **`429` now carries `Retry-After` and a problem-details body** (`code: rate_limited`). Until this release it was an empty body with no header, while this guide had promised the header since v1.\n- **Corrections**: `401` carries no body; IP allowlist rejections use `partner_api.ip_not_allowed`; `transport.status_changed` is emitted on Confirmed and Completed only; the IFTSTA rejection code is `iftsta-transition-rejected` and every `iftsta-*` code is permanent; outbound EDIFACT is `IFTMBC` and `IFTSTA` only; the dispatch list `search` also covers your own trip reference; the appointment validation can also answer `DOCK_RESOURCE_NOT_FOUND`.\n\n## 2026-08: invoices, charges and dock appointments\n\n- **[Invoices and charges](#section/Invoices-and-charges)**: the shipper journey previously stopped at \"created and tracked\" and never reached \"priced\" or \"invoiced\". Four read-only endpoints close it, including per-transport charges visible *before* invoicing, flagged estimate against actualised. Gated by the new `ReadInvoice` permission, separate from the transport ones.\n- **[Dock appointments](#section/Dock-appointments)**: slot availability search, book, reschedule and cancel. New `ReadAppointment` and `BookAppointment` permissions. Availability and booking answer from one set of rules, so a slot that is offered will normally be accepted.\n- **Proof of delivery now works for own-fleet deliveries.** `photoUrls` previously read only subcontractor-uploaded documents, so a delivery run by the transport company's own driver returned 404. Both sources are now included, the links are real time-limited URLs rather than storage keys, and `signedBy` and `signatureUrl` are populated from the CMR delivery signature.\n- **`Full` preset is now 14399** (was 63). Keys already issued keep the integer they were stored with, so nothing gains access retroactively; only newly issued `Full` keys carry the new flags.\n\n## 2026-08: supplier surface documented, OpenAPI enriched\n\n- **Added the [Carrier and subcontractor API](#section/Carrier-and-subcontractor-API)**: the seven `/dispatches` endpoints and the three `/execution` endpoints were live but absent from this guide, which described only the shipper direction. The Overview and integration-path table now distinguish the two directions, and Permissions lists the `Supplier*` flags and presets.\n- **OpenAPI document enriched**: every operation carries a description, every published transport property has a description and an example, and operations have stable `operationId`s so generated clients get readable method names.\n- **Fixed**: documentation on contract types declared outside the API project could not reach the document.\n\n## 2026-08: documentation re-baseline\n\n- **Corrected the base URL** to `https://api.orin.software` throughout.\n- **Contact-detail and time-window updates are GA**: `PATCH /transports/{id}` updates `notes`, origin and destination contacts, instructions, and pickup and delivery windows while a transport is `Pending` or `Confirmed`.\n- **Documented partner self-service via the Customer Portal** (shipped 2026-07-20): propose your own inbound mapping and view or rotate your own API key from the portal (propose only; the planner still activates).\n- **Webhook secret rotation and headers**: documented the dual-key overlap window (`X-Orin-Signature-Previous`) and the always-present `X-Orin-Timestamp` header.\n\n## 2026-03\n\n- Added `warehouseCode` and `departmentCode` fields for WMS integration\n- Added structured address support (`structuredAddress` object)\n- Added sandbox mode support\n- Added per-partner IP allowlisting\n- Added per-partner kill switch\n- Added per-partner rate limit overrides\n- Webhook delivery logging and retry support\n\n## 2025-01 (v1, initial release)\n\n- Partner API launched at `/api/partner/v1`\n- Unified \"Transport\" abstraction for FMS shipments\n- Module-scoped API keys with `orin_pk_` prefix\n- Permission-based access control per API key\n- Create, list, get, update and cancel operations\n- Tracking events and proof of delivery endpoints\n- Idempotency support via the `Idempotency-Key` header\n- Webhook support for status change notifications\n- Problem details error format with `partner_api.*` codes\n",
    "contact": {
      "name": "Orin Support",
      "email": "support@orin.software"
    },
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.orin.software",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/inbound/{aliasSlug}": {
      "post": {
        "tags": [
          "Inbound"
        ],
        "summary": "Send an inbound message",
        "description": "Returns `202 Accepted` with a poll URL once the inbox row is durably persisted.\nA duplicate (matched on tenant, source and `Idempotency-Key`) returns\n`200 OK` with the original message id, so a retry is safe.\n            \nPath B: post the file your system already produces instead of mapping to our JSON. EDIFACT,\nX12, XML, CSV and JSON are accepted, and the transport company configures how their partner's\nformat maps onto Orin fields. The `aliasSlug` identifies which of their inbound channels\nyou are posting to.\n            \nThe response is `202 Accepted` with a `statusUrl` as soon as the message is\ndurably stored, not once it has been processed. Processing happens after the response, so\npoll the status URL to find out what became of it.\n            \nAlways send an `Idempotency-Key`. Retries carrying the same key answer `200 OK`\nwith the original message id rather than ingesting the file twice, which makes a network\ntimeout safe to retry blindly — the single most useful property when a batch job posts\novernight and nobody is watching.",
        "operationId": "Inbound_Receive",
        "parameters": [
          {
            "name": "aliasSlug",
            "in": "path",
            "description": "The inbound channel the transport company issued to you, for example `dhl-freight-nl-orders`.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Unique per logical message, 1 to 100 characters. A repeat with the same key answers 200 with duplicate: true and ingests nothing twice.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "DHL-2026-05-20-001"
          },
          {
            "name": "X-Message-Type",
            "in": "header",
            "description": "The message contract, up to 50 characters: EDIFACT_IFTMIN, EDIFACT_IFTSTA, X12_204, CSV_BOOKING_V1, ...",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "EDIFACT_IFTMIN"
          },
          {
            "name": "X-Sync-Hint",
            "in": "header",
            "description": "Send `no` to skip inline processing and always get 202. Without it a body under 256 KB is processed inline and answers 200 with the outcome.",
            "schema": {
              "enum": [
                "no"
              ],
              "type": "string"
            },
            "example": "no"
          }
        ],
        "requestBody": {
          "description": "The message as your system produces it, at most 2 MB. EDIFACT is decoded per its UNB charset advice; everything else is read as UTF-8.",
          "content": {
            "application/edifact": {
              "schema": {
                "type": "string"
              },
              "example": "UNH+1+IFTMIN:D:96A:UN'\nBGM+334+ORD-9382+9'\nDTM+137:202605201430:203'\nNAD+SH+++ACME SHIPPER LTD+x+ROTTERDAM+x+x+NL'\nNAD+CN+++ALBERT HEIJN BV+x+AMSTERDAM+x+x+NL'\nMEA+AAE+WT+KGM:18500'\nUNT+6+1'"
            },
            "application/xml": {
              "schema": {
                "type": "string"
              }
            },
            "application/json": {
              "schema": {
                "type": "string"
              }
            },
            "text/csv": {
              "schema": {
                "type": "string"
              }
            },
            "text/plain": {
              "schema": {
                "type": "string"
              }
            },
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboundReceiveResponse"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboundReceiveResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Content Too Large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported Media Type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/inbound/messages/{messageId}/status": {
      "get": {
        "tags": [
          "Inbound"
        ],
        "summary": "Get processing status",
        "description": "Poll the `statusUrl` that `POST /api/inbound/{aliasSlug}` returned, to find\nout what happened to a file you posted.\n            \n`status` is one of `Received` (stored, not started), `Processing`,\n`Processed` (it worked, and `processedDocumentId` names what was created),\n`Failed` (it will be retried), `DeadLettered` (retries exhausted, waiting for\nsomeone at the transport company) or `Duplicate`. `processingPhase` says how\nfar it got: `Received`, `Parsed`, `Mapped`, `Validated`,\n`Executed`, `Acknowledged`. Because ingestion answers 202 before\nprocessing, this is where success or failure actually shows up: the processing phase, the\nresulting document when it worked, and an error code when it did not.\n            \nA rejected message is not lost. It stays visible to the transport company's planner, who can\ncorrect the mapping and replay it, so a failure here is a conversation rather than a\nre-send.",
        "operationId": "Inbound_GetStatus",
        "parameters": [
          {
            "name": "messageId",
            "in": "path",
            "description": "The message, from the `messageId` the gateway answered with.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboundMessageStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/appointments/availability": {
      "get": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "Search free dock slots.",
        "description": "Give a site or a single dock and a window, and this returns what can actually be booked,\nwith blocked periods and existing bookings already removed.\n            \nSlot length comes from the dock's own configuration unless you override it. A dock with\ncapacity above one keeps appearing until it is genuinely full, which is how a site runs two\ntrailers on the same door; `remainingCapacity` says how much is left.\n            \nAvailability is a snapshot, not a hold. Nothing is reserved until you book, and a slot can\nbe taken between the two calls, so treat a 409 on booking as ordinary rather than\nexceptional and re-search.\n            \nSearches are capped at 31 days. Either `locationId` or `dockResourceId` is\nrequired: without one there is no meaningful question being asked.",
        "operationId": "PartnerAppointments_GetAvailability",
        "parameters": [
          {
            "name": "locationId",
            "in": "query",
            "description": "The site to search. Required unless `dockResourceId` is given.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dockResourceId",
            "in": "query",
            "description": "One dock to search. Required unless `locationId` is given.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Window start, UTC. Defaults to now.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Window end, UTC. Defaults to seven days after `from`; at most 31 days after it.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "slotMinutes",
            "in": "query",
            "description": "Slot length to offer. Defaults to the dock's own configuration.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerDockAvailabilityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/appointments": {
      "get": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "Appointments on your transports.",
        "description": "Filter to one transport with `transportId`, or omit it for everything currently booked\nagainst your freight. Cancelled appointments are included so a reschedule leaves a trail;\nfilter on `status` if you only want live ones.",
        "operationId": "PartnerAppointments_List",
        "parameters": [
          {
            "name": "transportId",
            "in": "query",
            "description": "Only appointments on this transport.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, 1-based.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Appointments per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "Book a dock slot against one of your transports.",
        "description": "The window is validated against the same rules the availability search applies, so a slot\nthat was offered will normally be accepted. Normally, not always: nothing is held between\nsearching and booking, and a 409 means someone reached it first. Re-search rather than\nretrying the same window.\n            \nPickup and delivery appointments must name the leg they belong to. A multi-leg movement has\nmore than one place where goods change hands, and an appointment that does not say which one\ncannot be acted on.\n            \nBooked appointments start as `Requested`. The transport company confirms them; this\nendpoint asks for a slot rather than granting one.",
        "operationId": "PartnerAppointments_Book",
        "requestBody": {
          "description": "The slot to book.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerBookAppointmentRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerBookAppointmentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerBookAppointmentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/appointments/{id}": {
      "get": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "One appointment.",
        "description": "An appointment on another customer's freight answers 404 rather than 403, so the endpoint\ncannot be used to discover which appointment ids exist.",
        "operationId": "PartnerAppointments_GetById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The appointment.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      },
      "put": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "Move an appointment to a different slot.",
        "description": "Revalidates the new window, so the same 409s apply as on booking. The appointment returns to\n`Requested`, because a slot the transport company confirmed is not still confirmed once\nyou have moved it.\n            \nA completed or cancelled appointment cannot be moved; book a new one.",
        "operationId": "PartnerAppointments_Reschedule",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The appointment to move.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "The new slot.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerRescheduleAppointmentRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerRescheduleAppointmentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerRescheduleAppointmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/appointments/{id}/cancel": {
      "post": {
        "tags": [
          "PartnerAppointments"
        ],
        "summary": "Give the slot back.",
        "description": "Cancelling frees the capacity immediately, so the slot returns to the availability search\nfor everyone else. Cancelling one that is already cancelled answers 409 rather than\nsucceeding quietly, so a duplicate call is visible rather than silent.",
        "operationId": "PartnerAppointments_Cancel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The appointment to cancel.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Optional reason, shown to the site.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerCancelAppointmentRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerCancelAppointmentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerCancelAppointmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches": {
      "get": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "List your dispatches",
        "description": "This is the subcontractor's inbox: the work the transport company has given you, newest\nfirst. Poll it, or receive a webhook and call this to reconcile.\n            \nOnly trips actually dispatched to your organisation are visible; a trip the transport\ncompany kept on its own fleet never appears here. Draft trips are excluded, because a plan\nthat has not been committed is not yet an instruction to you.\n            \nThe usual sequence from here is: acknowledge the dispatch, push leg status as the driver\nprogresses, upload the POD, then complete.",
        "operationId": "PartnerDispatch_ListDispatches",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Only trips in this status: Planned, Dispatched, InProgress, Completed or Cancelled. An unknown value is ignored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Free text over the trip number, batch reference and your own trip reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Trip date on or after this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Trip date on or before this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, 1-based.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Trips per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchListResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}": {
      "get": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "Get a dispatch",
        "description": "Everything needed to actually run the job: the shipments on the trip, the stops in sequence\nwith their addresses and instructions, the legs you report progress against, and any open\nexceptions.\n            \nThe leg is the unit of execution. Status updates and POD uploads are addressed per leg, not\nper trip, so a multi-drop trip reports progress as it goes rather than only at the end.\n            \nA trip belonging to another supplier, or still in Draft, answers 404.",
        "operationId": "PartnerDispatch_GetDispatch",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip, from your inbox.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchWorkItemDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}/acknowledge": {
      "post": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "Acknowledge a dispatch",
        "description": "Tell the transport company whether you are taking the job. `response` is\n`accepted`, `accepted_with_exception` or `rejected`; a rejection requires a\ncomment, which is carried back to the planner as the reason.\n            \nAcknowledgement is single-winner and idempotent-by-conflict: the first call wins and any\nsecond one answers 409 `ALREADY_ACKNOWLEDGED`, including when two of your systems race.\nTreat that 409 as success-already-recorded rather than as an error to retry.\n            \nOther 409s describe the trip rather than your request: `TRIP_CANCELLED`,\n`TRIP_COMPLETED`, and `TRIP_NOT_DISPATCHED` when the trip is not in a state that\ncan be acknowledged. If the transport company requires a supplier reference, omitting it\nanswers 400 `SUPPLIER_REFERENCE_REQUIRED`; send it as `partnerTripRef` or per leg.\n            \nSend the plate, driver name and phone in `items` if you know them at this point. They\nroll up to the trip, and they are what the end customer sees on the tracking page.",
        "operationId": "PartnerDispatch_AcknowledgeDispatch",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip to answer.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Your answer.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcknowledgeDispatchRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AcknowledgeDispatchRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AcknowledgeDispatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchAcknowledgeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}/legs/{legId}/status": {
      "post": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "Report progress or an exception on a leg",
        "description": "Report progress on one leg as the driver moves: `acknowledged`, `en_route_pickup`,\n`picked_up`, `en_route_delivery`, `delivered`. Each update is what the end\ncustomer sees on their tracking link, so the value of this endpoint is proportional to how\npromptly you call it.\n            \nThe same endpoint reports trouble. Sending an exception type instead of a progress status\nrecords an exception against the trip and surfaces it to the planner, rather than moving the\nleg forward. Use it for a refused delivery or a wait at the gate: an exception the planner\nlearns about in the moment is worth far more than one reconstructed afterwards.\n            \nA leg that is not on this trip answers 404 `LEG_NOT_ON_TRIP`.",
        "operationId": "PartnerDispatch_UpdateLegStatus",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip the leg is on.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "legId",
            "in": "path",
            "description": "The leg, from the trip detail.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "The progress status or exception.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DispatchLegStatusUpdateRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/DispatchLegStatusUpdateRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/DispatchLegStatusUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchLegStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}/legs/{legId}/pod": {
      "post": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "Upload proof of delivery for a leg",
        "description": "Upload the delivery evidence for one leg as `multipart/form-data`: a photo of the\nsigned CMR, a delivery note, a damage photo. Call it once per file; uploads accumulate on\nthe leg rather than replacing each other.\n            \nThis is what closes the loop commercially. The transport company invoices from the proof,\nand their customer sees it on the tracking page, so a POD uploaded at the kerb is worth more\nthan a batch at the end of the day.\n            \nA cancelled dispatch answers 409 `POD_NOT_ALLOWED_AFTER_CANCELLATION`, a leg that is\nnot on this trip 404 `LEG_NOT_ON_TRIP`, and an empty upload 400 `NO_FILE`.",
        "operationId": "PartnerDispatch_UploadPod",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip the leg is on.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "legId",
            "in": "path",
            "description": "The leg the file belongs to.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "description": "The file: .pdf, .jpg, .jpeg, .png, .heic or .tiff, at most 25 MB.",
                    "format": "binary"
                  },
                  "documentType": {
                    "type": "string",
                    "description": "How to file it: ProofOfDelivery (default), CMR, DeliveryNote, DamageReport, LoadingPhoto or another Orin document type."
                  },
                  "notes": {
                    "type": "string",
                    "description": "A note stored with the document."
                  }
                }
              },
              "encoding": {
                "file": {
                  "style": "form"
                },
                "documentType": {
                  "style": "form"
                },
                "notes": {
                  "style": "form"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchPodUploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}/complete": {
      "post": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "Complete a dispatch",
        "description": "Call this once the last drop is done. It runs the same completion path the transport\ncompany's own fleet goes through: any remaining legs are closed, the trip is completed, cost\nallocation is finalised and the completion event is published to downstream consumers.\n            \nA trip still sitting in `Dispatched` is started first, so you do not have to send a\nseparate \"started\" call to make completion legal.\n            \nUpload the PODs before completing. Completion is what makes the work invoiceable, and an\ninvoice raised without its proof attached is the thing that gets queried.",
        "operationId": "PartnerDispatch_CompleteDispatch",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip to complete.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchCompleteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/dispatches/{tripId}/exceptions": {
      "get": {
        "tags": [
          "PartnerDispatch"
        ],
        "summary": "List the exceptions on a dispatch",
        "description": "The full exception history for the trip, including ones already resolved, each with who\nreported it, when, any estimated resolution, and how it was closed.\n            \nResolved entries are deliberately included rather than filtered out: an exception that was\nraised and then cleared is exactly what you need when reconciling a disputed delivery weeks\nlater. Filter on `isResolved` if you only want what is currently open.\n            \nExceptions are raised through the leg status endpoint by sending an exception type in place\nof a progress status.",
        "operationId": "PartnerDispatch_ListExceptions",
        "parameters": [
          {
            "name": "tripId",
            "in": "path",
            "description": "The trip.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchExceptionListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/execution/{shipmentId}": {
      "get": {
        "tags": [
          "PartnerExecution"
        ],
        "summary": "Get an assigned shipment",
        "description": "The shipment-level view for a trading partner executing work on another Orin tenant's\nbehalf. This surface is for tenant-to-tenant execution, where both sides run Orin; a\nsubcontractor working from a dispatch inbox should use `/dispatches` instead.\n            \nAccess is checked against the trading-partner link rather than ownership, so you can read\nonly the shipments actually assigned to you.",
        "operationId": "PartnerExecution_GetShipment",
        "parameters": [
          {
            "name": "shipmentId",
            "in": "path",
            "description": "The shipment, from your work list.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Partner-Tenant-Id",
            "in": "header",
            "description": "The Orin tenant id of the transport company whose work you are executing. Missing or not a GUID answers 400; no active trading-partner link between the two tenants answers 403.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerShipmentDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/execution/{shipmentId}/events": {
      "post": {
        "tags": [
          "PartnerExecution"
        ],
        "summary": "Report an execution event",
        "description": "Report what happened on a shipment you are executing. The event drives the leg through its\nstatus transitions, which is what the owning tenant's planner and their end customer see.\n            \nSend the time the event actually occurred, not the time you are posting it. Orin keeps an\nevent log rather than only a current status, so a late-arriving event still lands in the\nright place in the timeline; overwriting it with the upload time loses that.",
        "operationId": "PartnerExecution_PushEvent",
        "parameters": [
          {
            "name": "shipmentId",
            "in": "path",
            "description": "The shipment the event is about.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Partner-Tenant-Id",
            "in": "header",
            "description": "The Orin tenant id of the transport company whose work you are executing. Missing or not a GUID answers 400; no active trading-partner link between the two tenants answers 403.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
          }
        ],
        "requestBody": {
          "description": "The event.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerExecutionEventDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerExecutionEventDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerExecutionEventDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerExecutionEventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/execution": {
      "get": {
        "tags": [
          "PartnerExecution"
        ],
        "summary": "List your assigned shipments",
        "description": "Your work list as a trading partner: every shipment linked to you for execution, paged.\n            \nPoll this to discover new assignments, then use the per-shipment endpoint for detail and the\nevents endpoint to report progress. Nothing outside your trading-partner links is returned,\nso the list is safe to fetch without further filtering.",
        "operationId": "PartnerExecution_GetAssignedShipments",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number, 1-based.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Shipments per page.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "X-Partner-Tenant-Id",
            "in": "header",
            "description": "The Orin tenant id of the transport company whose work you are executing. Missing or not a GUID answers 400; no active trading-partner link between the two tenants answers 403.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAssignedShipmentListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/invoices": {
      "get": {
        "tags": [
          "PartnerInvoices"
        ],
        "summary": "List the invoices issued to you.",
        "description": "Newest first, paged. This is the reconciliation endpoint: pull what you have been charged\nand match it against your own purchase ledger without asking anyone for a statement.\n            \nOnly invoices that have actually been sent to you appear. An invoice the transport company\nis still preparing is their working state, not your liability, and it is filtered out\nrather than shown in a draft status you would have to learn to ignore.\n            \nCredit notes are included alongside sales invoices, since a credit note changes what you\nowe and omitting it would make the totals wrong.",
        "operationId": "PartnerInvoices_List",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Only invoices in this status: Sent, Paid, PartiallyPaid, Overdue or Disputed. Any other value answers 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Free text over the invoice number, your reference and the external reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "invoiceDateFrom",
            "in": "query",
            "description": "Invoice date on or after this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "invoiceDateTo",
            "in": "query",
            "description": "Invoice date on or before this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dueDateFrom",
            "in": "query",
            "description": "Due date on or after this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dueDateTo",
            "in": "query",
            "description": "Due date on or before this day, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, 1-based.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Invoices per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerInvoiceListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/invoices/{id}": {
      "get": {
        "tags": [
          "PartnerInvoices"
        ],
        "summary": "One invoice, with its lines.",
        "description": "The line detail is what makes this worth calling rather than reading the PDF: each line\ncarries the shipment and leg it came from, so a queried charge can be traced back to the\nmovement that produced it instead of being argued about in the abstract.\n            \nAn invoice belonging to another customer of the same transport company answers 404 rather\nthan 403, so the endpoint cannot be used to discover which invoice ids exist.",
        "operationId": "PartnerInvoices_GetById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The invoice, from the list.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerInvoiceDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/invoices/{id}/pdf": {
      "get": {
        "tags": [
          "PartnerInvoices"
        ],
        "summary": "The invoice as a PDF.",
        "description": "The same document the transport company would send you, generated on request rather than\nstored, so it always reflects the current state of the invoice.\n            \nReturns `application/pdf` as a file download, not JSON. Everything else on this API\nanswers JSON, so a client that assumes a JSON body everywhere needs a branch here.",
        "operationId": "PartnerInvoices_GetPdf",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The invoice, from the list.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice as a PDF file download.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/{transportId}/charges": {
      "get": {
        "tags": [
          "PartnerInvoices"
        ],
        "summary": "The charges making up what a single transport costs you.",
        "description": "Sits under `/invoices` rather than `/transports` because it is billing data and is\ngated by the invoice permission, not the transport one. A key issued to move freight does\nnot get to read prices.\n            \nCharges appear here before they are invoiced, which is the point: you can see what a\nmovement is costing while it is still running rather than discovering it on a statement\nweeks later. Each line says whether it has been invoiced yet, and which invoice took it.\n            \nAmounts follow the same rule the rest of Orin uses: once a charge has been actualised the\nactual amount is authoritative, and until then the estimate stands. `isEstimate` tells\nyou which one you are looking at, so an estimate is never mistaken for a final figure.",
        "operationId": "PartnerInvoices_GetTransportCharges",
        "parameters": [
          {
            "name": "transportId",
            "in": "path",
            "description": "The transport, as returned by the transport endpoints.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerTransportChargesDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports": {
      "post": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Create a new transport request.",
        "description": "Creates an FMS Shipment for the API key's tenant.\nSupports idempotency via the Idempotency-Key header.",
        "operationId": "PartnerTransport_Create",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Your unique key for this request. A repeat with the same key and body returns the original response; a different body answers 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "The transport to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportRequestDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportRequestDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TransportRequestDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      },
      "get": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "List transports for the authenticated partner.",
        "description": "Pagination: page (1-based, default 1), pageSize (1-100, default 20).\nStatus values: Pending, Confirmed, InProgress, Completed, Cancelled.\nAll timestamps in response are UTC.",
        "operationId": "PartnerTransport_List",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Only transports in this status: Pending, Confirmed, InProgress, Completed or Cancelled.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Planned pickup on or after this time, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Planned pickup on or before this time, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "externalReference",
            "in": "query",
            "description": "Exactly this external reference of yours.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updatedFrom",
            "in": "query",
            "description": "Changed on or after this time, UTC. The field to poll on.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedTo",
            "in": "query",
            "description": "Changed on or before this time, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number, 1-based.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Transports per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportListResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/{id}": {
      "get": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Get a transport by its internal ID.",
        "description": "Returns the transport as Orin currently holds it, including both stops and the current\nstatus. If your system stores its own identifier rather than ours, use\n`GET /transports/by-reference/{reference}` instead: it avoids having to persist an\nOrin ID alongside your own.\n            \nA transport belonging to a different partner account answers 404 rather than 403, so the\nendpoint cannot be used to probe which IDs exist.",
        "operationId": "PartnerTransport_GetById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Orin's identifier for the transport, from the create response or the list.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Update a transport (limited fields).",
        "description": "Changes contact details, driver instructions, time windows and the planner note. Omitted\nfields are left alone, so send only what changed.\n            \nAddresses are deliberately not editable: a different address is a different journey, and\nsilently moving it under a planner who has already routed the trip is worse than refusing.\nCancel and create a new transport instead.\n            \nSend an `Idempotency-Key` header to make a retry safe. A repeat with the same key and\nthe same body returns the original response rather than applying the change twice.",
        "operationId": "PartnerTransport_Update",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Orin's identifier for the transport.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Your unique key for this request. A repeat with the same key and body returns the original response; a different body answers 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "The fields to change. Omitted fields are left alone.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportUpdateDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportUpdateDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TransportUpdateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/by-reference/{reference}": {
      "get": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Get a transport by its external reference.",
        "description": "Looks the transport up by the `externalReference` you supplied when creating it. This\nis the recommended lookup: your system keeps its own identifier and never has to store ours.\n            \nReferences are unique within your partner account, so this returns a single transport. If\nyou have not sent an `externalReference`, this endpoint cannot find anything and the\nonly route back to the record is the Orin ID from the create response.",
        "operationId": "PartnerTransport_GetByReference",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "description": "The external reference you sent on creation.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/{id}/tracking": {
      "get": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Get tracking events for a transport.",
        "description": "Returns what has actually happened to the goods: arrivals, departures, delivery and any\nreported exception, oldest first.\n            \nOrder events by `occurredAt`, not by arrival. That field is the real-world time of the\nevent, and a driver who was offline at the time syncs the event later, so a batch can land\nout of order relative to when you receive it.\n            \nAn empty list is a valid answer for a transport that has not started yet; it is not an\nerror, and it is distinct from a 404 for a transport that does not exist.",
        "operationId": "PartnerTransport_GetTracking",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Orin's identifier for the transport.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TransportTrackingEventResponseDto"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/{id}/pod": {
      "get": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Get proof of delivery for a transport.",
        "description": "Returns the delivery timestamp, who signed, and download links to the evidence. Both sources\nare included: photos the transport company's own driver captured in the app, and any POD\ndocument a subcontractor uploaded against the shipment.\n            \nAvailable once the transport is `Completed` and some evidence exists; otherwise the\nendpoint answers 404 with `partner_api.not_found`, which is the expected response\nrather than a fault.\n            \nThe links are time-limited, around twenty minutes. Fetch the bytes and store them on your\nside if you need to keep the evidence; do not persist a URL and expect it to resolve later.\n            \n`signedBy` and `signatureUrl` come from the delivery signature on the CMR and are\nnull when the delivery was proven by photo alone, which is normal on domestic work.",
        "operationId": "PartnerTransport_GetPod",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Orin's identifier for the transport.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportPodResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/api/partner/v1/transports/{id}/cancel": {
      "post": {
        "tags": [
          "PartnerTransport"
        ],
        "summary": "Cancel a transport.",
        "description": "Moves the transport to `Cancelled` and records the reason against it, where the planner\nsees it. The record is kept rather than deleted, so a cancelled transport still answers on\nthe read endpoints and still appears in the list results.\n            \nA transport that has already been delivered cannot be cancelled: `Completed` answers\n403. Cancelling one that is already cancelled answers 409 rather than succeeding quietly, so\na duplicate call is visible to you instead of silent.\n            \nCancelling a transport that is already in execution is accepted by the API, but the truck\nmay already be on its way. Anything time-critical is worth a phone call as well.",
        "operationId": "PartnerTransport_Cancel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Orin's identifier for the transport.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "Optional reason for the planner.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportCancelDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TransportCancelDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TransportCancelDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransportResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked. No body. While the transport company's kill switch is on: Retry-After: 300."
          },
          "403": {
            "description": "The key lacks the permission this endpoint needs, the transport company has disabled API access, or the caller's IP is not on the key's allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests per minute per key by default; 300 on the EDI gateway). Retry-After says how many seconds to wait.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AcknowledgeDispatchRequest": {
        "required": [
          "response"
        ],
        "type": "object",
        "properties": {
          "response": {
            "minLength": 1,
            "type": "string",
            "description": "`accepted`, `accepted_with_exception` or `rejected`.",
            "example": "accepted"
          },
          "comment": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Why, in words. Required for `rejected`; the planner sees it as the reason.",
            "nullable": true,
            "example": "No tail-lift vehicle available on that day"
          },
          "partnerTripRef": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Your reference for the whole trip.",
            "nullable": true,
            "example": "OUR-JOB-88421"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AcknowledgeLegMetadata"
            },
            "description": "Per-leg plate, driver and reference. The first non-empty value of each rolls up to the trip.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Your answer to a dispatch."
      },
      "AcknowledgeLegMetadata": {
        "required": [
          "legId"
        ],
        "type": "object",
        "properties": {
          "legId": {
            "type": "string",
            "description": "The leg the details are for.",
            "format": "uuid",
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "supplierReference": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Your reference for this leg.",
            "nullable": true,
            "example": "OUR-JOB-88421-1"
          },
          "vehiclePlateNumber": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Plate of the vehicle that will run it.",
            "nullable": true,
            "example": "12-ABC-3"
          },
          "driverName": {
            "maxLength": 200,
            "minLength": 0,
            "type": "string",
            "description": "Driver's name.",
            "nullable": true,
            "example": "P. Nowak"
          },
          "driverPhone": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Driver's phone.",
            "nullable": true,
            "example": "+31 6 11223344"
          }
        },
        "additionalProperties": false,
        "description": "Plate, driver and reference for one leg, sent with the acknowledgement."
      },
      "ApiContractsDispatchExceptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The exception.",
            "format": "uuid",
            "example": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
          },
          "legId": {
            "type": "string",
            "description": "The leg it was reported on. Null for a trip-wide exception.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "exceptionType": {
            "type": "string",
            "description": "One of delay, failed_pickup, delivery_issue, damage, missing_goods, no_show, reschedule_request.",
            "nullable": true,
            "example": "delay"
          },
          "notes": {
            "type": "string",
            "description": "What was reported.",
            "nullable": true,
            "example": "Queue at the gate, 45 minutes"
          },
          "estimatedResolution": {
            "type": "string",
            "description": "When the reporter expected it to be resolved, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T10:00:00Z"
          },
          "isResolved": {
            "type": "boolean",
            "description": "True once the planner closed it.",
            "example": false
          },
          "resolution": {
            "type": "string",
            "description": "How it was closed.",
            "nullable": true,
            "example": "Customer accepted late delivery"
          },
          "reportedAt": {
            "type": "string",
            "description": "When it was reported, UTC.",
            "format": "date-time",
            "example": "2026-04-02T09:14:00Z"
          },
          "resolvedAt": {
            "type": "string",
            "description": "When it was closed, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T11:30:00Z"
          },
          "source": {
            "type": "string",
            "description": "Who reported it: `partner-api` for you, otherwise the transport company's own channel.",
            "nullable": true,
            "example": "partner-api"
          }
        },
        "additionalProperties": false,
        "description": "An exception on the trip, open or resolved."
      },
      "DispatchAcknowledgeResponse": {
        "type": "object",
        "properties": {
          "tripId": {
            "type": "string",
            "description": "The trip.",
            "format": "uuid",
            "example": "8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f"
          },
          "tripNumber": {
            "type": "string",
            "description": "The trip number.",
            "nullable": true,
            "example": "TR-2026-000318"
          },
          "batchReference": {
            "type": "string",
            "description": "Batch the trip was dispatched in, when it was.",
            "nullable": true,
            "example": "BATCH-0042"
          },
          "status": {
            "type": "string",
            "description": "The trip's status after your answer.",
            "nullable": true,
            "example": "Dispatched"
          },
          "acknowledgmentResult": {
            "type": "string",
            "description": "`Accepted`, `AcceptedWithException` or `Rejected`.",
            "nullable": true,
            "example": "Accepted"
          },
          "acknowledgedAt": {
            "type": "string",
            "description": "When the acknowledgement was recorded, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T16:45:00Z"
          }
        },
        "additionalProperties": false,
        "description": "Result of an acknowledgement."
      },
      "DispatchCompleteResponse": {
        "type": "object",
        "properties": {
          "tripId": {
            "type": "string",
            "description": "The trip.",
            "format": "uuid",
            "example": "8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f"
          },
          "tripNumber": {
            "type": "string",
            "description": "The trip number.",
            "nullable": true,
            "example": "TR-2026-000318"
          },
          "status": {
            "type": "string",
            "description": "The trip's status afterwards, normally `Completed`.",
            "nullable": true,
            "example": "Completed"
          },
          "completedAt": {
            "type": "string",
            "description": "When the trip was completed, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T15:10:00Z"
          }
        },
        "additionalProperties": false,
        "description": "Result of completing a dispatch."
      },
      "DispatchErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable code to branch on.",
            "nullable": true,
            "example": "ALREADY_ACKNOWLEDGED"
          },
          "message": {
            "type": "string",
            "description": "What went wrong, in words.",
            "nullable": true,
            "example": "This dispatch was already acknowledged on 2026-04-01T16:20:11.0000000Z"
          },
          "tripId": {
            "type": "string",
            "description": "The trip concerned. Sent with `ALREADY_ACKNOWLEDGED`.",
            "format": "uuid",
            "nullable": true,
            "example": "8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f"
          },
          "tripNumber": {
            "type": "string",
            "description": "The trip's number. Sent with `ALREADY_ACKNOWLEDGED`.",
            "nullable": true,
            "example": "TR-2026-000318"
          },
          "unresolved": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TripCompletionUnresolved"
            },
            "description": "What still blocks completion. Sent with `TRIP_COMPLETION_BLOCKED`. Each entry carries a\nstable `code` (`stop_not_complete`, `action_not_complete`,\n`equipment_no_disposition`, `no_final_stop`), a `message`, and the id of the stop,\naction or equipment it refers to.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Error body of the dispatch endpoints. Here `error` is the stable code and `message`\nthe explanation, the reverse of Orin.Api.Contracts.PartnerErrorResponse."
      },
      "DispatchExceptionListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiContractsDispatchExceptionDto"
            },
            "description": "Every exception on the trip, newest first.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "The exception history of one trip."
      },
      "DispatchExceptionSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The exception.",
            "format": "uuid",
            "example": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
          },
          "legId": {
            "type": "string",
            "description": "The leg it was reported on. Null for a trip-wide exception.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "exceptionType": {
            "type": "string",
            "description": "One of delay, failed_pickup, delivery_issue, damage, missing_goods, no_show, reschedule_request.",
            "nullable": true,
            "example": "delay"
          },
          "notes": {
            "type": "string",
            "description": "What was reported.",
            "nullable": true,
            "example": "Queue at the gate, 45 minutes"
          },
          "reportedAt": {
            "type": "string",
            "description": "When it was reported, UTC.",
            "format": "date-time",
            "example": "2026-04-02T09:14:00Z"
          },
          "estimatedResolution": {
            "type": "string",
            "description": "When the reporter expected it to be resolved, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T10:00:00Z"
          }
        },
        "additionalProperties": false,
        "description": "An open exception, as embedded in the trip detail."
      },
      "DispatchLegDto": {
        "type": "object",
        "properties": {
          "legId": {
            "type": "string",
            "description": "The leg. Use it on the status and POD endpoints.",
            "format": "uuid",
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "shipmentId": {
            "type": "string",
            "description": "The shipment the leg belongs to.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "shipmentNumber": {
            "type": "string",
            "description": "The transport company's shipment number.",
            "nullable": true,
            "example": "SH-2026-000042"
          },
          "sequence": {
            "type": "integer",
            "description": "Position in the shipment's route, starting at 1.",
            "format": "int32",
            "example": 1
          },
          "status": {
            "type": "string",
            "description": "One of Planned, AssignedToTrip, HandedOver, Accepted, Dispatched, InTransit, Completed, Cancelled.",
            "nullable": true,
            "example": "Dispatched"
          },
          "pickup": {
            "$ref": "#/components/schemas/DispatchLegEndpointDto"
          },
          "delivery": {
            "$ref": "#/components/schemas/DispatchLegEndpointDto"
          },
          "exceptions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchExceptionSummary"
            },
            "description": "Open exceptions reported on this leg.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A leg on a dispatched trip: one shipment between two points."
      },
      "DispatchLegEndpointDto": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "City.",
            "nullable": true,
            "example": "Amsterdam"
          },
          "address": {
            "type": "string",
            "description": "Full address on one line.",
            "nullable": true,
            "example": "Industrieweg 42, 1234 AB Amsterdam, NL"
          },
          "plannedDate": {
            "type": "string",
            "description": "Requested window start at this end, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T08:00:00Z"
          }
        },
        "additionalProperties": false,
        "description": "One end of a leg."
      },
      "DispatchLegStatusResponse": {
        "type": "object",
        "properties": {
          "legStatus": {
            "type": "string",
            "description": "The leg's status after the update, in the vocabulary of `DispatchLegDto.status`.",
            "nullable": true,
            "example": "InTransit"
          },
          "shipmentStatus": {
            "type": "string",
            "description": "The shipment's status after the update, when it changed.",
            "nullable": true,
            "example": "InTransit"
          },
          "shipmentCompleted": {
            "type": "boolean",
            "description": "True when this update completed the whole shipment.",
            "nullable": true,
            "example": false
          },
          "exceptionId": {
            "type": "string",
            "description": "The exception that was recorded.",
            "format": "uuid",
            "nullable": true,
            "example": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
          },
          "exceptionType": {
            "type": "string",
            "description": "The exception type that was recorded.",
            "nullable": true,
            "example": "delay"
          },
          "reportedAt": {
            "type": "string",
            "description": "When the exception was recorded, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T09:14:00Z"
          },
          "accepted": {
            "type": "boolean",
            "description": "Always true on a 200.",
            "example": true
          }
        },
        "additionalProperties": false,
        "description": "What a leg status call did. A progress status fills `legStatus`, `shipmentStatus` and\n`shipmentCompleted`; an exception type fills `exceptionId`, `exceptionType` and\n`reportedAt`. The other group is null."
      },
      "DispatchLegStatusUpdateRequest": {
        "required": [
          "status"
        ],
        "type": "object",
        "properties": {
          "status": {
            "minLength": 1,
            "type": "string",
            "description": "A progress status, in order: `acknowledged`, `en_route_pickup`, `picked_up`,\n`en_route_delivery`, `delivered`. Or an exception type, which records an exception\ninstead of moving the leg: `delay`, `failed_pickup`, `delivery_issue`,\n`damage`, `missing_goods`, `no_show`, `reschedule_request`.",
            "example": "picked_up"
          },
          "eventTime": {
            "type": "string",
            "description": "When it happened, UTC. Send the real time, not the upload time. Defaults to now.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T09:14:00Z"
          },
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Anything the planner should know. For an exception, what went wrong.",
            "nullable": true,
            "example": "Loaded, 24 pallets confirmed"
          },
          "supplierReference": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Your reference for the trip. Stored on the trip if it has none yet.",
            "nullable": true,
            "example": "OUR-JOB-88421-1"
          },
          "vehiclePlateNumber": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Plate of the vehicle. Stored on the trip if it has none yet.",
            "nullable": true,
            "example": "12-ABC-3"
          },
          "driverName": {
            "maxLength": 200,
            "minLength": 0,
            "type": "string",
            "description": "Driver's name. Stored on the trip if it has none yet.",
            "nullable": true,
            "example": "P. Nowak"
          },
          "driverPhone": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Driver's phone. Stored on the trip if it has none yet.",
            "nullable": true,
            "example": "+31 6 11223344"
          },
          "estimatedArrival": {
            "type": "string",
            "description": "Expected arrival at the next point, UTC. For an exception, when you expect it resolved.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T13:30:00Z"
          }
        },
        "additionalProperties": false,
        "description": "A progress update, or an exception, on one leg."
      },
      "DispatchListItemDto": {
        "type": "object",
        "properties": {
          "tripId": {
            "type": "string",
            "description": "The trip. Use it on every other dispatch endpoint.",
            "format": "uuid",
            "example": "8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f"
          },
          "tripNumber": {
            "type": "string",
            "description": "The transport company's trip number.",
            "nullable": true,
            "example": "TR-2026-000318"
          },
          "batchReference": {
            "type": "string",
            "description": "Batch the trip was dispatched in, when it was.",
            "nullable": true,
            "example": "BATCH-0042"
          },
          "dispatchMethod": {
            "type": "string",
            "description": "`Agent` for an outside subcontractor, `TradingPartner` when both sides run Orin.",
            "nullable": true,
            "example": "Agent"
          },
          "status": {
            "type": "string",
            "description": "One of Planned, Dispatched, InProgress, Completed, Cancelled. Drafts are never listed.",
            "nullable": true,
            "example": "Dispatched"
          },
          "partnerTripRef": {
            "type": "string",
            "description": "Your own reference for the trip, once you have sent one.",
            "nullable": true,
            "example": "OUR-JOB-88421"
          },
          "instructions": {
            "type": "string",
            "description": "Instructions from the planner for the whole trip.",
            "nullable": true,
            "example": "Two drops, both tail lift."
          },
          "tripDate": {
            "type": "string",
            "description": "The day the trip runs, UTC midnight.",
            "format": "date-time",
            "example": "2026-04-02T00:00:00Z"
          },
          "createdAt": {
            "type": "string",
            "description": "When the trip was created, UTC.",
            "format": "date-time",
            "example": "2026-04-01T15:02:00Z"
          },
          "dispatchedAt": {
            "type": "string",
            "description": "When it was dispatched to you, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T16:20:11Z"
          },
          "acknowledgedAt": {
            "type": "string",
            "description": "When you acknowledged it, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T16:45:00Z"
          },
          "completedAt": {
            "type": "string",
            "description": "When it was completed, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T15:10:00Z"
          },
          "cancelledAt": {
            "type": "string",
            "description": "When it was cancelled, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T18:00:00Z"
          },
          "shipmentCount": {
            "type": "integer",
            "description": "Distinct shipments on the trip.",
            "format": "int32",
            "example": 2
          },
          "legCount": {
            "type": "integer",
            "description": "Legs on the trip. Each leg takes its own status and POD calls.",
            "format": "int32",
            "example": 2
          },
          "requiredActions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What Orin is waiting for from you: any of `acknowledge`, `update_status`,\n`upload_pod`, `complete`. Build your UI on this rather than on the status.",
            "nullable": true,
            "example": [
              "acknowledge"
            ]
          }
        },
        "additionalProperties": false,
        "description": "A dispatched trip as it appears in your inbox."
      },
      "DispatchListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchListItemDto"
            },
            "description": "The trips on this page, newest first.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "How many trips match in total.",
            "format": "int32",
            "example": 7
          },
          "page": {
            "type": "integer",
            "description": "The page returned, 1-based.",
            "format": "int32",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "Page size applied, after clamping to 1..100.",
            "format": "int32",
            "example": 20
          }
        },
        "additionalProperties": false,
        "description": "A page of your dispatches."
      },
      "DispatchPodUploadResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string",
            "description": "The document.",
            "format": "uuid",
            "example": "4c5d6e7f-8a9b-4c0d-8e1f-2a3b4c5d6e7f"
          },
          "fileName": {
            "type": "string",
            "description": "The file name you sent.",
            "nullable": true,
            "example": "cmr-signed.pdf"
          },
          "documentType": {
            "type": "string",
            "description": "The document type it was filed as. `ProofOfDelivery` unless you sent another known type.",
            "nullable": true,
            "example": "ProofOfDelivery"
          },
          "uploadedAt": {
            "type": "string",
            "description": "When it was stored, UTC.",
            "format": "date-time",
            "example": "2026-04-02T13:52:00Z"
          }
        },
        "additionalProperties": false,
        "description": "The stored proof-of-delivery file."
      },
      "DispatchShipmentDto": {
        "type": "object",
        "properties": {
          "shipmentId": {
            "type": "string",
            "description": "The shipment.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "shipmentNumber": {
            "type": "string",
            "description": "The transport company's shipment number.",
            "nullable": true,
            "example": "SH-2026-000042"
          },
          "customerReference": {
            "type": "string",
            "description": "The end customer's reference.",
            "nullable": true,
            "example": "PO-88421"
          },
          "goodsDescription": {
            "type": "string",
            "description": "What is being moved.",
            "nullable": true,
            "example": "6 pallets consumer electronics"
          },
          "totalPieces": {
            "type": "integer",
            "description": "Number of pieces.",
            "format": "int32",
            "example": 6
          },
          "totalGrossWeightKg": {
            "type": "number",
            "description": "Gross weight in kilograms.",
            "format": "double",
            "example": 1200
          },
          "totalVolumeM3": {
            "type": "number",
            "description": "Volume in cubic metres.",
            "format": "double",
            "example": 4.8
          }
        },
        "additionalProperties": false,
        "description": "A shipment carried on a dispatched trip."
      },
      "DispatchStopDto": {
        "type": "object",
        "properties": {
          "stopId": {
            "type": "string",
            "description": "The stop.",
            "format": "uuid",
            "example": "3e4f5a6b-7c8d-4e9f-8a0b-1c2d3e4f5a6b"
          },
          "sequence": {
            "type": "integer",
            "description": "Position in the trip, starting at 1.",
            "format": "int32",
            "example": 1
          },
          "stopType": {
            "type": "string",
            "description": "Pickup, Dropoff, CrossDock, Hub, Customs, Inspection, Port, Airport, RailTerminal, Border, Waypoint, Origin, Destination or Other.",
            "nullable": true,
            "example": "Pickup"
          },
          "locationName": {
            "type": "string",
            "description": "Name of the place.",
            "nullable": true,
            "example": "Warehouse Amsterdam"
          },
          "city": {
            "type": "string",
            "description": "City.",
            "nullable": true,
            "example": "Amsterdam"
          },
          "country": {
            "type": "string",
            "description": "Country, ISO 3166-1 alpha-2.",
            "nullable": true,
            "example": "NL"
          },
          "plannedArrivalUtc": {
            "type": "string",
            "description": "Planned arrival, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T08:00:00Z"
          },
          "plannedArrivalWindowEndUtc": {
            "type": "string",
            "description": "Same value as `plannedArrivalUtc`. Kept so integrations that deserialise it do not break.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T08:00:00Z"
          },
          "instructions": {
            "type": "string",
            "description": "Instructions for the driver at this stop.",
            "nullable": true,
            "example": "Loading dock B, call 15 min before arrival"
          }
        },
        "additionalProperties": false,
        "description": "A stop on a dispatched trip."
      },
      "DispatchWorkItemDto": {
        "type": "object",
        "properties": {
          "tripId": {
            "type": "string",
            "description": "The trip.",
            "format": "uuid",
            "example": "8f14e45f-ceea-467a-9ba5-4b1b2c3d4e5f"
          },
          "tripNumber": {
            "type": "string",
            "description": "The transport company's trip number.",
            "nullable": true,
            "example": "TR-2026-000318"
          },
          "batchReference": {
            "type": "string",
            "description": "Batch the trip was dispatched in, when it was.",
            "nullable": true,
            "example": "BATCH-0042"
          },
          "dispatchMethod": {
            "type": "string",
            "description": "`Agent` or `TradingPartner`.",
            "nullable": true,
            "example": "Agent"
          },
          "status": {
            "type": "string",
            "description": "One of Planned, Dispatched, InProgress, Completed, Cancelled.",
            "nullable": true,
            "example": "Dispatched"
          },
          "partnerTripRef": {
            "type": "string",
            "description": "Your own reference for the trip, once you have sent one.",
            "nullable": true,
            "example": "OUR-JOB-88421"
          },
          "instructions": {
            "type": "string",
            "description": "Instructions from the planner for the whole trip.",
            "nullable": true,
            "example": "Two drops, both tail lift."
          },
          "tripDate": {
            "type": "string",
            "description": "The day the trip runs, UTC midnight.",
            "format": "date-time",
            "example": "2026-04-02T00:00:00Z"
          },
          "createdAtUtc": {
            "type": "string",
            "description": "When the trip was created, UTC.",
            "format": "date-time",
            "example": "2026-04-01T15:02:00Z"
          },
          "dispatchedAt": {
            "type": "string",
            "description": "When it was dispatched to you, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T16:20:11Z"
          },
          "acknowledgedAt": {
            "type": "string",
            "description": "When you acknowledged it, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T16:45:00Z"
          },
          "completedAt": {
            "type": "string",
            "description": "When it was completed, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T15:10:00Z"
          },
          "cancelledAt": {
            "type": "string",
            "description": "When it was cancelled, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T18:00:00Z"
          },
          "vehicleLicensePlate": {
            "type": "string",
            "description": "Plate you reported, shown to the end customer on the tracking page.",
            "nullable": true,
            "example": "12-ABC-3"
          },
          "driverName": {
            "type": "string",
            "description": "Driver name you reported.",
            "nullable": true,
            "example": "P. Nowak"
          },
          "driverPhone": {
            "type": "string",
            "description": "Driver phone you reported.",
            "nullable": true,
            "example": "+31 6 11223344"
          },
          "requiredActions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What Orin is waiting for from you: any of `acknowledge`, `update_status`, `upload_pod`, `complete`.",
            "nullable": true,
            "example": [
              "acknowledge"
            ]
          },
          "shipments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchShipmentDto"
            },
            "description": "The shipments on the trip.",
            "nullable": true
          },
          "stops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchStopDto"
            },
            "description": "The stops, in driving order.",
            "nullable": true
          },
          "legs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchLegDto"
            },
            "description": "The legs you report progress and POD against.",
            "nullable": true
          },
          "openExceptions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DispatchExceptionSummary"
            },
            "description": "Exceptions that are still open on the trip.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Everything needed to run one dispatched trip."
      },
      "InboundMessageStatusResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The message.",
            "format": "uuid",
            "example": "8d2f1e3c-4b5a-4c6d-9e7f-0a1b2c3d4e5f"
          },
          "status": {
            "type": "string",
            "description": "One of Received, Processing, Processed, Failed, DeadLettered, Duplicate.",
            "nullable": true,
            "example": "Failed"
          },
          "processingPhase": {
            "type": "string",
            "description": "How far processing got: Received, Parsed, Mapped, Validated, Executed, AckSent or Failed.",
            "nullable": true,
            "example": "Failed"
          },
          "messageType": {
            "type": "string",
            "description": "The `X-Message-Type` you sent.",
            "nullable": true,
            "example": "EDIFACT_IFTMIN"
          },
          "receivedAt": {
            "type": "string",
            "description": "When the gateway stored the message, UTC.",
            "format": "date-time",
            "example": "2026-05-20T14:31:00Z"
          },
          "processedAt": {
            "type": "string",
            "description": "When processing finished, UTC. Null while it has not.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-05-20T14:31:04Z"
          },
          "retryCount": {
            "type": "integer",
            "description": "How many times processing has been retried.",
            "format": "int32",
            "example": 1
          },
          "errorCode": {
            "type": "string",
            "description": "Why the last attempt failed. See the EDI gateway error codes in the guide.",
            "nullable": true,
            "example": "mapping-failed"
          },
          "lastError": {
            "type": "string",
            "description": "The last failure, in words.",
            "nullable": true,
            "example": "Required field 'externalReference' has no value at source path 'BGM[1][0]'"
          }
        },
        "additionalProperties": false,
        "description": "Current state of a message you posted."
      },
      "InboundReceiveResponse": {
        "type": "object",
        "properties": {
          "messageId": {
            "type": "string",
            "description": "Orin's identifier for the message. Quote it when asking about the message.",
            "format": "uuid",
            "example": "8d2f1e3c-4b5a-4c6d-9e7f-0a1b2c3d4e5f"
          },
          "status": {
            "type": "string",
            "description": "One of Received, Processing, Processed, Failed, DeadLettered, Duplicate.",
            "nullable": true,
            "example": "Processed"
          },
          "processingPhase": {
            "type": "string",
            "description": "How far processing got: Received, Parsed, Mapped, Validated, Executed, AckSent or Failed. Null until processing starts.",
            "nullable": true,
            "example": "Executed"
          },
          "duplicate": {
            "type": "boolean",
            "description": "True when the `Idempotency-Key` had been seen before and this describes the original message.",
            "example": false
          },
          "statusUrl": {
            "type": "string",
            "description": "Where to poll for the outcome. Relative to the API origin.",
            "nullable": true,
            "example": "/api/inbound/messages/8d2f1e3c-4b5a-4c6d-9e7f-0a1b2c3d4e5f/status"
          },
          "processedDocumentId": {
            "type": "string",
            "description": "The transport the message produced, once `status` is `Processed`.",
            "format": "uuid",
            "nullable": true,
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "errorCode": {
            "type": "string",
            "description": "Why processing failed. See the EDI gateway error codes in the guide.",
            "nullable": true,
            "example": "mapping-failed"
          },
          "errorMessage": {
            "type": "string",
            "description": "The failure, in words.",
            "nullable": true,
            "example": "Required field 'externalReference' has no value at source path 'BGM[1][0]'"
          }
        },
        "additionalProperties": false,
        "description": "The gateway's answer to a posted message. Which fields are filled depends on the path the\nmessage took; `messageId`, `status` and `statusUrl` are always there."
      },
      "Module": {
        "enum": [
          1
        ],
        "type": "integer",
        "format": "int32"
      },
      "PartnerAppointmentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Orin's identifier for the appointment.",
            "format": "uuid",
            "example": "7d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a"
          },
          "transportId": {
            "type": "string",
            "description": "The transport it belongs to.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "legId": {
            "type": "string",
            "description": "The leg it belongs to. Always set for Pickup and Delivery.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "appointmentType": {
            "type": "string",
            "description": "Pickup, Delivery, CrossDock, Warehouse, TerminalPickup or TerminalDropoff.",
            "nullable": true,
            "example": "Delivery"
          },
          "status": {
            "type": "string",
            "description": "Requested, Confirmed, Arrived, Completed, Cancelled, Missed or Rescheduled. A new booking starts as Requested.",
            "nullable": true,
            "example": "Requested"
          },
          "locationId": {
            "type": "string",
            "description": "Site the appointment is at.",
            "format": "uuid",
            "nullable": true,
            "example": "5b6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e"
          },
          "locationName": {
            "type": "string",
            "description": "Site name, for display.",
            "nullable": true,
            "example": "Distribution Center Rotterdam"
          },
          "dockResourceId": {
            "type": "string",
            "description": "The dock, when the site models docks as resources.",
            "format": "uuid",
            "nullable": true,
            "example": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f"
          },
          "dockDoor": {
            "type": "string",
            "description": "Door label.",
            "nullable": true,
            "example": "Dock 4"
          },
          "windowStartUtc": {
            "type": "string",
            "description": "Slot start you asked for, UTC.",
            "format": "date-time",
            "example": "2026-04-02T09:00:00Z"
          },
          "windowEndUtc": {
            "type": "string",
            "description": "Slot end you asked for, UTC.",
            "format": "date-time",
            "example": "2026-04-02T10:00:00Z"
          },
          "scheduledStartUtc": {
            "type": "string",
            "description": "Start the site committed to, UTC. Set once the appointment is confirmed.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T09:00:00Z"
          },
          "scheduledEndUtc": {
            "type": "string",
            "description": "End the site committed to, UTC. Set once the appointment is confirmed.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-02T10:00:00Z"
          },
          "reference": {
            "type": "string",
            "description": "Your reference, echoed back.",
            "nullable": true,
            "example": "WMS-APPT-4471"
          },
          "notes": {
            "type": "string",
            "description": "Notes for the site.",
            "nullable": true,
            "example": "Driver speaks Polish and English. Tail lift."
          },
          "cancelReason": {
            "type": "string",
            "description": "Why it was cancelled, when it was.",
            "nullable": true,
            "example": "Load not ready, rebooking for tomorrow"
          }
        },
        "additionalProperties": false,
        "description": "An appointment on one of your transports."
      },
      "PartnerAppointmentListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerAppointmentDto"
            },
            "description": "The appointments on this page, earliest window first.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "How many appointments match in total.",
            "format": "int32",
            "example": 3
          },
          "page": {
            "type": "integer",
            "description": "The page returned, 1-based.",
            "format": "int32",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "Page size applied, after clamping to 1..100.",
            "format": "int32",
            "example": 20
          }
        },
        "additionalProperties": false,
        "description": "A page of appointments."
      },
      "PartnerAssignedShipmentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Orin's identifier for the shipment. Use it on the detail and events endpoints.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "shipmentNumber": {
            "type": "string",
            "description": "The owner's shipment number.",
            "nullable": true,
            "example": "SH-2026-000042"
          },
          "status": {
            "type": "string",
            "description": "The shipment's status.",
            "nullable": true,
            "example": "Confirmed"
          },
          "customerReference": {
            "type": "string",
            "description": "The end customer's reference.",
            "nullable": true,
            "example": "PO-88421"
          },
          "requestedPickupUtc": {
            "type": "string",
            "description": "Requested pickup, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "requestedDeliveryUtc": {
            "type": "string",
            "description": "Requested delivery, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T14:00:00Z"
          },
          "createdAtUtc": {
            "type": "string",
            "description": "When the owner created the shipment, UTC.",
            "format": "date-time",
            "example": "2026-03-30T10:02:00Z"
          }
        },
        "additionalProperties": false,
        "description": "A shipment on your work list."
      },
      "PartnerAssignedShipmentListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerAssignedShipmentDto"
            },
            "description": "The shipments on this page, newest first.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "How many shipments are assigned to you in total.",
            "format": "int32",
            "example": 12
          },
          "page": {
            "type": "integer",
            "description": "The page returned, 1-based.",
            "format": "int32",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "Page size as requested.",
            "format": "int32",
            "example": 20
          },
          "totalPages": {
            "type": "integer",
            "description": "Number of pages at this page size.",
            "format": "int32",
            "example": 1
          }
        },
        "additionalProperties": false,
        "description": "A page of the shipments assigned to you."
      },
      "PartnerBookAppointmentRequest": {
        "required": [
          "appointmentType",
          "transportId",
          "windowEndUtc",
          "windowStartUtc"
        ],
        "type": "object",
        "properties": {
          "transportId": {
            "type": "string",
            "description": "The transport this appointment is for. Must be one of yours.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "legId": {
            "type": "string",
            "description": "Which leg the appointment belongs to. Required for Pickup and Delivery.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "appointmentType": {
            "minLength": 1,
            "type": "string",
            "description": "Pickup, Delivery, CrossDock, Warehouse, TerminalPickup or TerminalDropoff.",
            "example": "Delivery"
          },
          "locationId": {
            "type": "string",
            "description": "Site the appointment is at.",
            "format": "uuid",
            "nullable": true,
            "example": "5b6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e"
          },
          "dockResourceId": {
            "type": "string",
            "description": "The specific dock, from the availability search.",
            "format": "uuid",
            "nullable": true,
            "example": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f"
          },
          "dockDoor": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Free-text door label, when the site does not model docks as resources.",
            "nullable": true,
            "example": "Dock 4"
          },
          "windowStartUtc": {
            "type": "string",
            "description": "Slot start, UTC.",
            "format": "date-time",
            "example": "2026-04-02T09:00:00Z"
          },
          "windowEndUtc": {
            "type": "string",
            "description": "Slot end, UTC.",
            "format": "date-time",
            "example": "2026-04-02T10:00:00Z"
          },
          "reference": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Your own reference for this booking.",
            "nullable": true,
            "example": "WMS-APPT-4471"
          },
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Anything the site needs to know on arrival.",
            "nullable": true,
            "example": "Driver speaks Polish and English. Tail lift."
          }
        },
        "additionalProperties": false,
        "description": "Request to book a dock slot against a transport."
      },
      "PartnerCancelAppointmentRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "maxLength": 500,
            "minLength": 0,
            "type": "string",
            "description": "Why the slot is being given back. Shown to the site.",
            "nullable": true,
            "example": "Load not ready, rebooking for tomorrow"
          }
        },
        "additionalProperties": false,
        "description": "Request to cancel an appointment."
      },
      "PartnerChargeLineDto": {
        "type": "object",
        "properties": {
          "chargeType": {
            "type": "string",
            "description": "Charge type code from the transport company's catalogue.",
            "nullable": true,
            "example": "FRT"
          },
          "chargeTypeName": {
            "type": "string",
            "description": "Charge type name.",
            "nullable": true,
            "example": "Freight"
          },
          "description": {
            "type": "string",
            "description": "What the charge is for.",
            "nullable": true,
            "example": "Amsterdam to Rotterdam"
          },
          "quantity": {
            "type": "number",
            "description": "Quantity in `unitOfMeasure`.",
            "format": "double",
            "example": 1
          },
          "unitOfMeasure": {
            "type": "string",
            "description": "Unit the quantity is in.",
            "nullable": true,
            "example": "shipment"
          },
          "currencyCode": {
            "type": "string",
            "description": "ISO 4217 currency of `amount`.",
            "nullable": true,
            "example": "EUR"
          },
          "amount": {
            "type": "number",
            "description": "The actual amount once the charge is actualised, the estimate until then.",
            "format": "double",
            "example": 720.00
          },
          "isEstimate": {
            "type": "boolean",
            "description": "True while `amount` is an estimate.",
            "example": false
          },
          "legId": {
            "type": "string",
            "description": "The leg the charge is for, when it is per leg.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "invoicedAt": {
            "type": "string",
            "description": "When the charge was put on an invoice, UTC. Null while not yet invoiced.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-30T09:12:00Z"
          }
        },
        "additionalProperties": false,
        "description": "One charge on a transport."
      },
      "PartnerDockAvailabilityResponse": {
        "type": "object",
        "properties": {
          "fromUtc": {
            "type": "string",
            "description": "Start of the window searched, UTC.",
            "format": "date-time",
            "example": "2026-04-02T06:00:00Z"
          },
          "toUtc": {
            "type": "string",
            "description": "End of the window searched, UTC.",
            "format": "date-time",
            "example": "2026-04-02T18:00:00Z"
          },
          "slots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerDockSlotDto"
            },
            "description": "Slots that can be booked right now.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Free dock slots in a window. A snapshot, not a hold."
      },
      "PartnerDockSlotDto": {
        "type": "object",
        "properties": {
          "dockResourceId": {
            "type": "string",
            "description": "The dock. Send it as `dockResourceId` when booking.",
            "format": "uuid",
            "example": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f"
          },
          "dockResourceCode": {
            "type": "string",
            "description": "The dock's short code.",
            "nullable": true,
            "example": "D1"
          },
          "dockResourceName": {
            "type": "string",
            "description": "The dock's name.",
            "nullable": true,
            "example": "Dock 1"
          },
          "locationId": {
            "type": "string",
            "description": "The site the dock belongs to.",
            "format": "uuid",
            "example": "5b6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e"
          },
          "startUtc": {
            "type": "string",
            "description": "Slot start, UTC.",
            "format": "date-time",
            "example": "2026-04-02T09:00:00Z"
          },
          "endUtc": {
            "type": "string",
            "description": "Slot end, UTC.",
            "format": "date-time",
            "example": "2026-04-02T10:00:00Z"
          },
          "remainingCapacity": {
            "type": "integer",
            "description": "How many more bookings the dock takes in this slot. Above one on docks that run several trailers per door.",
            "format": "int32",
            "example": 1
          }
        },
        "additionalProperties": false,
        "description": "One bookable slot on one dock."
      },
      "PartnerErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, in words.",
            "nullable": true,
            "example": "Invoice not found."
          },
          "code": {
            "type": "string",
            "description": "Stable code to branch on. Absent on endpoints that only send a message.",
            "nullable": true,
            "example": "partner_api.not_found"
          },
          "allowed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The values that would have been accepted. Only on an unknown enum value.",
            "nullable": true,
            "example": [
              "Sent",
              "Paid",
              "PartiallyPaid",
              "Overdue",
              "Disputed"
            ]
          },
          "message": {
            "type": "string",
            "description": "Longer explanation. Only the execution endpoints send it.",
            "nullable": true,
            "example": "You must specify the partner tenant ID you are connected to"
          }
        },
        "additionalProperties": false,
        "description": "Error body of the invoice, appointment, execution and EDI gateway endpoints."
      },
      "PartnerExecutionEventDto": {
        "required": [
          "eventType"
        ],
        "type": "object",
        "properties": {
          "eventType": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "One of `picked_up`, `in_transit`, `delivered`, `exception`.",
            "example": "picked_up"
          },
          "legId": {
            "type": "string",
            "description": "The leg the event applies to. Omit to let Orin pick the current leg.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "eventTime": {
            "type": "string",
            "description": "When it happened, UTC. Defaults to now. Send the real time, not the upload time.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:14:00Z"
          },
          "podReference": {
            "maxLength": 200,
            "minLength": 0,
            "type": "string",
            "description": "Your POD reference, on delivery events.",
            "nullable": true,
            "example": "POD-2026-04-01-0042"
          },
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Notes from the driver.",
            "nullable": true,
            "example": "Signed by J. de Vries at goods-in."
          },
          "data": {
            "type": "object",
            "additionalProperties": { },
            "description": "Any additional event data, stored with the event as-is.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "An execution event you report on a shipment."
      },
      "PartnerExecutionEventResponse": {
        "type": "object",
        "properties": {
          "accepted": {
            "type": "boolean",
            "description": "Always true on a 2xx. Present so a client can treat every 200 the same way.",
            "example": true
          },
          "shipmentId": {
            "type": "string",
            "description": "The shipment the event was applied to.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "shipmentStatus": {
            "type": "string",
            "description": "The shipment's status after the event.",
            "nullable": true,
            "example": "InTransit"
          },
          "legId": {
            "type": "string",
            "description": "The leg the event landed on. Null when no leg could be resolved.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "legStatus": {
            "type": "string",
            "description": "The leg's status after the event, in the same vocabulary as `PartnerShipmentLegDto.status`.",
            "nullable": true,
            "example": "InTransit"
          }
        },
        "additionalProperties": false,
        "description": "What Orin did with the event you reported."
      },
      "PartnerInvoiceDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Orin's identifier for the invoice. Use it for the detail and PDF endpoints.",
            "format": "uuid",
            "example": "0f8fad5b-d9cb-469f-a165-70867728950e"
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The number printed on the invoice.",
            "nullable": true,
            "example": "INV-2026-000731"
          },
          "invoiceType": {
            "type": "string",
            "description": "`Sales` or `CreditNote`. A credit note reduces what you owe.",
            "nullable": true,
            "example": "Sales"
          },
          "status": {
            "type": "string",
            "description": "One of `Sent`, `Paid`, `PartiallyPaid`, `Overdue`, `Disputed`. Drafts never appear.",
            "nullable": true,
            "example": "Sent"
          },
          "invoiceDate": {
            "type": "string",
            "description": "Invoice date, UTC.",
            "format": "date-time",
            "example": "2026-04-30T00:00:00Z"
          },
          "dueDate": {
            "type": "string",
            "description": "Payment due date, UTC.",
            "format": "date-time",
            "example": "2026-05-30T00:00:00Z"
          },
          "currencyCode": {
            "type": "string",
            "description": "ISO 4217 currency of every amount on the invoice.",
            "nullable": true,
            "example": "EUR"
          },
          "netAmount": {
            "type": "number",
            "description": "Total excluding tax.",
            "format": "double",
            "example": 845.50
          },
          "taxAmount": {
            "type": "number",
            "description": "Tax total.",
            "format": "double",
            "example": 177.56
          },
          "grossAmount": {
            "type": "number",
            "description": "Total including tax.",
            "format": "double",
            "example": 1023.06
          },
          "payableAmount": {
            "type": "number",
            "description": "What is payable after any prepaid or rounding amounts.",
            "format": "double",
            "example": 1023.06
          },
          "paidAmount": {
            "type": "number",
            "description": "What has been received so far.",
            "format": "double",
            "example": 0
          },
          "amountDue": {
            "type": "number",
            "description": "Gross amount minus what has been paid.",
            "format": "double",
            "example": 1023.06
          },
          "yourReference": {
            "type": "string",
            "description": "The buyer reference you gave the transport company, if any.",
            "nullable": true,
            "example": "PO-88421"
          },
          "purchaseOrderReference": {
            "type": "string",
            "description": "Purchase order reference printed on the invoice.",
            "nullable": true,
            "example": "PO-88421"
          },
          "externalReference": {
            "type": "string",
            "description": "The transport company's external reference for this invoice.",
            "nullable": true,
            "example": "WMS-2026-00001"
          },
          "notes": {
            "type": "string",
            "description": "Free-text note printed on the invoice.",
            "nullable": true,
            "example": "Thank you for your business."
          },
          "paymentTerms": {
            "type": "string",
            "description": "Payment terms as printed, for example `30 days net`.",
            "nullable": true,
            "example": "30 days net"
          },
          "precedingInvoiceNumber": {
            "type": "string",
            "description": "For a credit note: the number of the invoice it corrects.",
            "nullable": true,
            "example": "INV-2026-000702"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerInvoiceLineDto"
            },
            "description": "The invoice lines, in line-number order.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "One invoice with its lines, as returned by `GET /api/partner/v1/invoices/{id}`."
      },
      "PartnerInvoiceLineDto": {
        "type": "object",
        "properties": {
          "lineNumber": {
            "type": "integer",
            "description": "Position on the invoice, starting at 1.",
            "format": "int32",
            "example": 1
          },
          "description": {
            "type": "string",
            "description": "What the line is for.",
            "nullable": true,
            "example": "Freight Amsterdam to Rotterdam, 6 pallets"
          },
          "quantity": {
            "type": "number",
            "description": "Quantity in `unitCode` units.",
            "format": "double",
            "example": 1
          },
          "unitCode": {
            "type": "string",
            "description": "UN/ECE Recommendation 20 unit code (`EA`, `KGM`, `KMT`, ...).",
            "nullable": true,
            "example": "EA"
          },
          "netAmount": {
            "type": "number",
            "description": "Line total excluding tax.",
            "format": "double",
            "example": 720.00
          },
          "shipmentId": {
            "type": "string",
            "description": "The transport this line bills, when it bills one.",
            "format": "uuid",
            "nullable": true,
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "legId": {
            "type": "string",
            "description": "The leg of that transport, when the charge is per leg.",
            "format": "uuid",
            "nullable": true,
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "orderLineReference": {
            "type": "string",
            "description": "Your order-line reference, when it was supplied on the order.",
            "nullable": true,
            "example": "PO-88421-1"
          }
        },
        "additionalProperties": false,
        "description": "One line on an invoice, traceable to the movement that produced it."
      },
      "PartnerInvoiceListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerInvoiceSummaryDto"
            },
            "description": "The invoices on this page, newest first.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "How many invoices match the filter in total.",
            "format": "int32",
            "example": 7
          },
          "page": {
            "type": "integer",
            "description": "The page returned, 1-based.",
            "format": "int32",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "Page size applied, after clamping to 1..100.",
            "format": "int32",
            "example": 20
          }
        },
        "additionalProperties": false,
        "description": "A page of invoices."
      },
      "PartnerInvoiceSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Orin's identifier for the invoice. Use it for the detail and PDF endpoints.",
            "format": "uuid",
            "example": "0f8fad5b-d9cb-469f-a165-70867728950e"
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The number printed on the invoice.",
            "nullable": true,
            "example": "INV-2026-000731"
          },
          "invoiceType": {
            "type": "string",
            "description": "`Sales` or `CreditNote`. A credit note reduces what you owe.",
            "nullable": true,
            "example": "Sales"
          },
          "status": {
            "type": "string",
            "description": "One of `Sent`, `Paid`, `PartiallyPaid`, `Overdue`, `Disputed`. Drafts never appear.",
            "nullable": true,
            "example": "Sent"
          },
          "invoiceDate": {
            "type": "string",
            "description": "Invoice date, UTC.",
            "format": "date-time",
            "example": "2026-04-30T00:00:00Z"
          },
          "dueDate": {
            "type": "string",
            "description": "Payment due date, UTC.",
            "format": "date-time",
            "example": "2026-05-30T00:00:00Z"
          },
          "currencyCode": {
            "type": "string",
            "description": "ISO 4217 currency of every amount on the invoice.",
            "nullable": true,
            "example": "EUR"
          },
          "netAmount": {
            "type": "number",
            "description": "Total excluding tax.",
            "format": "double",
            "example": 845.50
          },
          "taxAmount": {
            "type": "number",
            "description": "Tax total.",
            "format": "double",
            "example": 177.56
          },
          "grossAmount": {
            "type": "number",
            "description": "Total including tax.",
            "format": "double",
            "example": 1023.06
          },
          "payableAmount": {
            "type": "number",
            "description": "What is payable after any prepaid or rounding amounts.",
            "format": "double",
            "example": 1023.06
          },
          "paidAmount": {
            "type": "number",
            "description": "What has been received so far.",
            "format": "double",
            "example": 0
          },
          "amountDue": {
            "type": "number",
            "description": "Gross amount minus what has been paid.",
            "format": "double",
            "example": 1023.06
          },
          "yourReference": {
            "type": "string",
            "description": "The buyer reference you gave the transport company, if any.",
            "nullable": true,
            "example": "PO-88421"
          },
          "purchaseOrderReference": {
            "type": "string",
            "description": "Purchase order reference printed on the invoice.",
            "nullable": true,
            "example": "PO-88421"
          },
          "externalReference": {
            "type": "string",
            "description": "The transport company's external reference for this invoice.",
            "nullable": true,
            "example": "WMS-2026-00001"
          },
          "notes": {
            "type": "string",
            "description": "Free-text note printed on the invoice.",
            "nullable": true,
            "example": "Thank you for your business."
          }
        },
        "additionalProperties": false,
        "description": "An invoice issued to you, as listed by `GET /api/partner/v1/invoices`."
      },
      "PartnerRescheduleAppointmentRequest": {
        "required": [
          "windowEndUtc",
          "windowStartUtc"
        ],
        "type": "object",
        "properties": {
          "windowStartUtc": {
            "type": "string",
            "description": "New slot start, UTC.",
            "format": "date-time",
            "example": "2026-04-02T13:00:00Z"
          },
          "windowEndUtc": {
            "type": "string",
            "description": "New slot end, UTC.",
            "format": "date-time",
            "example": "2026-04-02T14:00:00Z"
          },
          "dockResourceId": {
            "type": "string",
            "description": "Move to a different dock. Omit to keep the current one.",
            "format": "uuid",
            "nullable": true,
            "example": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f"
          },
          "dockDoor": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Change the door label. Omit to keep the current one.",
            "nullable": true,
            "example": "Dock 5"
          },
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Replacement notes. Omit to keep the current ones.",
            "nullable": true,
            "example": "Load ready from 12:30."
          }
        },
        "additionalProperties": false,
        "description": "Request to move an appointment to a different slot."
      },
      "PartnerShipmentDto": {
        "type": "object",
        "properties": {
          "shipmentId": {
            "type": "string",
            "description": "Orin's identifier for the shipment.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "shipmentNumber": {
            "type": "string",
            "description": "The number the owning transport company uses on paperwork.",
            "nullable": true,
            "example": "SH-2026-000042"
          },
          "status": {
            "type": "string",
            "description": "The shipment's own status, as the owner sees it.",
            "nullable": true,
            "example": "InTransit"
          },
          "shipmentType": {
            "type": "string",
            "description": "Shipment type in the owner's system.",
            "nullable": true,
            "example": "Road"
          },
          "customerReference": {
            "type": "string",
            "description": "The end customer's reference.",
            "nullable": true,
            "example": "PO-88421"
          },
          "goodsDescription": {
            "type": "string",
            "description": "What is being moved.",
            "nullable": true,
            "example": "6 pallets consumer electronics"
          },
          "totalPieces": {
            "type": "integer",
            "description": "Number of pieces.",
            "format": "int32",
            "example": 6
          },
          "totalGrossWeight": {
            "type": "number",
            "description": "Gross weight in kilograms.",
            "format": "double",
            "example": 1200
          },
          "totalVolume": {
            "type": "number",
            "description": "Volume in cubic metres.",
            "format": "double",
            "example": 4.8
          },
          "requestedPickupUtc": {
            "type": "string",
            "description": "Requested pickup, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "requestedDeliveryUtc": {
            "type": "string",
            "description": "Requested delivery, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T14:00:00Z"
          },
          "actualPickupUtc": {
            "type": "string",
            "description": "When the goods were actually collected, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:14:00Z"
          },
          "actualDeliveryUtc": {
            "type": "string",
            "description": "When the goods were actually delivered, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T13:52:00Z"
          },
          "legs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerShipmentLegDto"
            },
            "description": "The legs, in sequence. Events are reported against a leg.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A shipment assigned to you for execution, with its legs."
      },
      "PartnerShipmentLegDto": {
        "type": "object",
        "properties": {
          "legId": {
            "type": "string",
            "description": "The leg. Send it as `legId` when reporting an event.",
            "format": "uuid",
            "example": "2c1a6f3e-4b1d-4a0e-9c2b-7d8e9f0a1b2c"
          },
          "sequence": {
            "type": "integer",
            "description": "Position in the shipment's route, starting at 1.",
            "format": "int32",
            "example": 1
          },
          "legType": {
            "type": "string",
            "description": "Leg type in the owner's system, for example `Main`, `PreCarriage`, `OnCarriage`.",
            "nullable": true,
            "example": "Main"
          },
          "transportMode": {
            "type": "string",
            "description": "Mode of transport.",
            "nullable": true,
            "example": "Road"
          },
          "status": {
            "type": "string",
            "description": "One of Planned, AssignedToTrip, HandedOver, Accepted, Dispatched, InTransit, Completed, Cancelled.",
            "nullable": true,
            "example": "Dispatched"
          },
          "plannedDepartureUtc": {
            "type": "string",
            "description": "Requested window start at the origin, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "plannedArrivalUtc": {
            "type": "string",
            "description": "Requested window start at the destination, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T14:00:00Z"
          },
          "actualDepartureUtc": {
            "type": "string",
            "description": "Actual departure from the origin, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:20:00Z"
          },
          "actualArrivalUtc": {
            "type": "string",
            "description": "Actual arrival at the destination, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T13:45:00Z"
          }
        },
        "additionalProperties": false,
        "description": "One leg of a shipment you are executing."
      },
      "PartnerTransportChargesDto": {
        "type": "object",
        "properties": {
          "transportId": {
            "type": "string",
            "description": "The transport the charges belong to.",
            "format": "uuid",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "currencyCode": {
            "type": "string",
            "description": "Currency of `totalAmount`. Null when there are no charges yet.",
            "nullable": true,
            "example": "EUR"
          },
          "totalAmount": {
            "type": "number",
            "description": "Sum of every line's `amount`.",
            "format": "double",
            "example": 845.50
          },
          "containsEstimates": {
            "type": "boolean",
            "description": "True while at least one line is still an estimate, so the total can still move.",
            "example": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerChargeLineDto"
            },
            "description": "The charge lines.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "What one transport is costing you, invoiced or not yet."
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "StructuredAddressDto": {
        "type": "object",
        "properties": {
          "street": {
            "type": "string",
            "description": "Street name, without the number.",
            "nullable": true,
            "example": "Industrieweg"
          },
          "houseNumber": {
            "type": "string",
            "description": "House or building number, including any suffix.",
            "nullable": true,
            "example": "42A"
          },
          "postalCode": {
            "type": "string",
            "description": "Postal code in the local format.",
            "nullable": true,
            "example": "1234 AB"
          },
          "city": {
            "type": "string",
            "description": "City or town.",
            "nullable": true,
            "example": "Amsterdam"
          },
          "country": {
            "type": "string",
            "description": "Country name.",
            "nullable": true,
            "example": "Netherlands"
          },
          "countryCode": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code. Send this rather than relying on the country\n            name being spelled the way Orin expects.",
            "nullable": true,
            "example": "NL"
          },
          "latitude": {
            "type": "number",
            "description": "Latitude in decimal degrees. Supply it if you hold a surveyed gate position;\n            it is used as-is and skips geocoding.",
            "format": "double",
            "nullable": true,
            "example": 52.3676
          },
          "longitude": {
            "type": "number",
            "description": "Longitude in decimal degrees.",
            "format": "double",
            "nullable": true,
            "example": 4.9041
          }
        },
        "additionalProperties": false,
        "description": "An address in components rather than one line. Preferred over a concatenated string when\nthe sending system already holds the parts: it geocodes more reliably, and a bad geocode\nis what puts a driver at the wrong gate."
      },
      "TransportCancelDto": {
        "type": "object",
        "properties": {
          "reason": {
            "maxLength": 500,
            "minLength": 0,
            "type": "string",
            "description": "Why it is being cancelled. Stored on the transport and visible to the planner.",
            "nullable": true,
            "example": "Customer cancelled the underlying sales order"
          }
        },
        "additionalProperties": false,
        "description": "Cancellation of a transport."
      },
      "TransportCargoRequestDto": {
        "required": [
          "description"
        ],
        "type": "object",
        "properties": {
          "description": {
            "maxLength": 500,
            "minLength": 0,
            "type": "string",
            "description": "What the goods are, in plain language. Required.",
            "example": "24 pallets of packaged food"
          },
          "palletCount": {
            "type": "integer",
            "description": "Number of pallets.",
            "format": "int32",
            "nullable": true,
            "example": 24
          },
          "packageCount": {
            "type": "integer",
            "description": "Number of individual packages or colli.",
            "format": "int32",
            "nullable": true,
            "example": 480
          },
          "weightKg": {
            "type": "number",
            "description": "Gross weight in kilograms.",
            "format": "double",
            "nullable": true,
            "example": 12500
          },
          "volumeCbm": {
            "type": "number",
            "description": "Volume in cubic metres.",
            "format": "double",
            "nullable": true,
            "example": 38.4
          },
          "loadingMeters": {
            "type": "number",
            "description": "Loading metres consumed on the trailer floor. Send this if you know it: it is\n            what capacity planning actually works from, and it beats inferring from pallet count.",
            "format": "double",
            "nullable": true,
            "example": 13.6
          },
          "isAdr": {
            "type": "boolean",
            "description": "True if the load is dangerous goods. Setting this affects vehicle and driver\n            eligibility and can route the trip differently through tunnels.",
            "example": false
          },
          "adrDetails": {
            "maxLength": 500,
            "minLength": 0,
            "type": "string",
            "description": "UN number, class and packing group when `isAdr` is true.",
            "nullable": true,
            "example": "UN1203, Class 3, PG II"
          },
          "vehicleRequirements": {
            "maxLength": 200,
            "minLength": 0,
            "type": "string",
            "description": "Equipment the load needs: tail lift, moffett, box trailer.",
            "nullable": true,
            "example": "Tail lift required"
          },
          "temperature": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Required temperature range for controlled freight.",
            "nullable": true,
            "example": "2-7 C"
          },
          "incoterms": {
            "maxLength": 10,
            "minLength": 0,
            "type": "string",
            "description": "Incoterms 2020 three-letter code, for forwarding movements.",
            "nullable": true,
            "example": "DAP"
          },
          "equipmentType": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Equipment type for containerised or intermodal movements.",
            "nullable": true,
            "example": "40ft High Cube"
          },
          "serviceLevel": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Service level agreed with the transport company. Must match one they have\n            configured; unknown values are rejected rather than silently ignored.",
            "nullable": true,
            "example": "Standard"
          }
        },
        "additionalProperties": false,
        "description": "What is being moved. Quantities drive both planning and pricing, so send whichever of\npallets, packages, weight, volume and loading metres you actually hold."
      },
      "TransportListResponseDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransportResponseDto"
            },
            "description": "The transports on this page.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "Total matching the filter across all pages, not the size of this page.",
            "format": "int32",
            "example": 137
          },
          "page": {
            "type": "integer",
            "description": "Which page this is. One-based.",
            "format": "int32",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "How many items a full page holds.",
            "format": "int32",
            "example": 50
          }
        },
        "additionalProperties": false,
        "description": "A page of transports."
      },
      "TransportPodResponseDto": {
        "type": "object",
        "properties": {
          "completedAt": {
            "type": "string",
            "description": "When delivery was completed, UTC.",
            "format": "date-time",
            "example": "2026-04-01T13:52:00Z"
          },
          "signedBy": {
            "type": "string",
            "description": "Who signed for the goods, taken from the delivery signature on the CMR. Null when the\ndelivery was proven by photo alone, which is common on domestic work where no CMR is signed.",
            "nullable": true,
            "example": "M. Bakker"
          },
          "photoUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Time-limited download links to the delivery evidence: photos captured by the driver in the\napp and any POD document uploaded against the shipment. The links expire about twenty\nminutes after the response is generated, so fetch the bytes and store them your side rather\nthan persisting the URL.",
            "nullable": true
          },
          "signatureUrl": {
            "type": "string",
            "description": "Time-limited download link to the captured delivery signature. Null when no CMR was signed.\nExpires on the same schedule as the photos.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Proof of delivery. Returned once the transport is `Completed` and some evidence exists,\nwhether captured by the transport company's own driver or uploaded by a subcontractor."
      },
      "TransportRequestDto": {
        "required": [
          "cargo",
          "destination",
          "origin"
        ],
        "type": "object",
        "properties": {
          "externalReference": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Your own identifier for this transport, unique within your partner account. Sending it\nmakes creation idempotent and enables lookup via `GET /transports/by-reference/{reference}`.",
            "nullable": true,
            "example": "WMS-2026-00001"
          },
          "plannedPickupAt": {
            "type": "string",
            "description": "When the goods should be collected, UTC, ISO-8601 with a trailing Z.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "plannedDeliveryAt": {
            "type": "string",
            "description": "When the goods should be delivered, UTC, ISO-8601 with a trailing Z.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T14:00:00Z"
          },
          "origin": {
            "$ref": "#/components/schemas/TransportStopRequestDto"
          },
          "destination": {
            "$ref": "#/components/schemas/TransportStopRequestDto"
          },
          "cargo": {
            "$ref": "#/components/schemas/TransportCargoRequestDto"
          },
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Free-text note for the planner. Not shown to the driver; use the stop's\n            `instructions` for anything the driver needs at the address.",
            "nullable": true,
            "example": "Customer prefers morning delivery."
          },
          "warehouseCode": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Which of your warehouses is sending this order. Optional.",
            "nullable": true,
            "example": "WH-AMS-01"
          },
          "departmentCode": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Assign the transport to a specific department. Optional; when omitted Orin\n            resolves the department from its own routing rules.",
            "nullable": true,
            "example": "TRANSPORT-NL"
          }
        },
        "additionalProperties": false,
        "description": "A transport request: one collection and one delivery, with its cargo.\nThis is the body of `POST /api/partner/v1/transports`, the main entry point for\na WMS or ERP handing work to the transport company."
      },
      "TransportResponseDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Orin's identifier for the transport. Stable; safe to store.",
            "nullable": true,
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "sourceModule": {
            "$ref": "#/components/schemas/Module"
          },
          "sourceId": {
            "type": "string",
            "description": "Identifier within the owning module.",
            "nullable": true,
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "transportNumber": {
            "type": "string",
            "description": "Human-readable number the transport company uses on paperwork.",
            "nullable": true,
            "example": "SH-2026-000042"
          },
          "externalReference": {
            "type": "string",
            "description": "The reference you supplied on creation, echoed back.",
            "nullable": true,
            "example": "WMS-2026-00001"
          },
          "statusCode": {
            "type": "integer",
            "description": "Numeric status: 0 Pending, 1 Confirmed, 2 InProgress, 3 Completed, 4 Cancelled.\n            Prefer this over `status` for branching; the string is for display.",
            "format": "int32",
            "example": 1
          },
          "status": {
            "type": "string",
            "description": "Status name matching `statusCode`.",
            "nullable": true,
            "example": "Confirmed"
          },
          "plannedPickupAt": {
            "type": "string",
            "description": "Planned collection time, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "plannedDeliveryAt": {
            "type": "string",
            "description": "Planned delivery time, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T14:00:00Z"
          },
          "origin": {
            "$ref": "#/components/schemas/TransportStopResponseDto"
          },
          "destination": {
            "$ref": "#/components/schemas/TransportStopResponseDto"
          },
          "cargoDescription": {
            "type": "string",
            "description": "Cargo description as supplied on creation.",
            "nullable": true,
            "example": "24 pallets of packaged food"
          },
          "totalWeight": {
            "type": "number",
            "description": "Total gross weight in kilograms.",
            "format": "double",
            "nullable": true,
            "example": 12500
          },
          "totalPieces": {
            "type": "integer",
            "description": "Total piece count across the cargo lines.",
            "format": "int32",
            "nullable": true,
            "example": 480
          },
          "notes": {
            "type": "string",
            "description": "Planner note.",
            "nullable": true,
            "example": "Customer prefers morning delivery."
          },
          "createdAtUtc": {
            "type": "string",
            "description": "When the transport was created in Orin, UTC.",
            "format": "date-time",
            "example": "2026-03-28T09:12:44Z"
          },
          "updatedAtUtc": {
            "type": "string",
            "description": "When it last changed, UTC. Use this to drive incremental polling; see the\n            `updatedSince` query parameter on the list endpoint.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-03-30T16:02:10Z"
          },
          "cancelledAt": {
            "type": "string",
            "description": "When it was cancelled, UTC. Null unless `statusCode` is 4.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-03-31T07:45:00Z"
          },
          "cancelledReason": {
            "type": "string",
            "description": "The reason recorded at cancellation.",
            "nullable": true,
            "example": "Customer cancelled the underlying sales order"
          }
        },
        "additionalProperties": false,
        "description": "A transport as Orin holds it. Returned by every transport endpoint."
      },
      "TransportStopRequestDto": {
        "required": [
          "address"
        ],
        "type": "object",
        "properties": {
          "address": {
            "maxLength": 500,
            "minLength": 0,
            "type": "string",
            "description": "The address as a single line. Required. Orin geocodes it on receipt; supply\n            `structuredAddress` as well if you already hold the components separately, which\n            geocodes more reliably than a concatenated string.",
            "example": "Industrieweg 42, 1234 AB Amsterdam, Netherlands"
          },
          "locationName": {
            "maxLength": 200,
            "minLength": 0,
            "type": "string",
            "description": "Site name, shown to the planner and the driver.",
            "nullable": true,
            "example": "Warehouse Amsterdam"
          },
          "contactName": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Who the driver should ask for on arrival.",
            "nullable": true,
            "example": "Jan de Vries"
          },
          "contactPhone": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Phone number for that contact, in international format.",
            "nullable": true,
            "example": "+31 6 12345678"
          },
          "instructions": {
            "maxLength": 1000,
            "minLength": 0,
            "type": "string",
            "description": "What the driver needs to know at this address: dock number, gate code,\n            call-ahead requirement.",
            "nullable": true,
            "example": "Loading dock B, call 15 min before arrival"
          },
          "windowStart": {
            "type": "string",
            "description": "Earliest the driver may arrive, UTC. Together with `windowEnd` this is a\n            commercial time window and is honoured by the planner.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "windowEnd": {
            "type": "string",
            "description": "Latest the driver may arrive, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T11:00:00Z"
          },
          "structuredAddress": {
            "$ref": "#/components/schemas/StructuredAddressDto"
          }
        },
        "additionalProperties": false,
        "description": "One end of a transport: an address, who to ask for, and when they can be there."
      },
      "TransportStopResponseDto": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string",
            "description": "The address as a single line.",
            "nullable": true,
            "example": "Industrieweg 42, 1234 AB Amsterdam, Netherlands"
          },
          "locationName": {
            "type": "string",
            "description": "Site name.",
            "nullable": true,
            "example": "Warehouse Amsterdam"
          },
          "contactName": {
            "type": "string",
            "description": "Who the driver asks for.",
            "nullable": true,
            "example": "Jan de Vries"
          },
          "contactPhone": {
            "type": "string",
            "description": "Phone number for that contact.",
            "nullable": true,
            "example": "+31 6 12345678"
          },
          "instructions": {
            "type": "string",
            "description": "Driver instructions for this address.",
            "nullable": true,
            "example": "Loading dock B, call 15 min before arrival"
          },
          "windowStart": {
            "type": "string",
            "description": "Earliest arrival, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T08:00:00Z"
          },
          "windowEnd": {
            "type": "string",
            "description": "Latest arrival, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T11:00:00Z"
          },
          "structuredAddress": {
            "$ref": "#/components/schemas/StructuredAddressDto"
          }
        },
        "additionalProperties": false,
        "description": "One end of a transport as Orin holds it."
      },
      "TransportStopUpdateDto": {
        "type": "object",
        "properties": {
          "contactName": {
            "maxLength": 100,
            "minLength": 0,
            "type": "string",
            "description": "Replacement contact name.",
            "nullable": true,
            "example": "Petra Jansen"
          },
          "contactPhone": {
            "maxLength": 50,
            "minLength": 0,
            "type": "string",
            "description": "Replacement contact phone.",
            "nullable": true,
            "example": "+31 6 87654321"
          },
          "instructions": {
            "maxLength": 1000,
            "minLength": 0,
            "type": "string",
            "description": "Replacement driver instructions.",
            "nullable": true,
            "example": "Use gate 3; dock B is closed this week."
          },
          "windowStart": {
            "type": "string",
            "description": "New earliest arrival, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T09:00:00Z"
          },
          "windowEnd": {
            "type": "string",
            "description": "New latest arrival, UTC.",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-01T12:00:00Z"
          }
        },
        "additionalProperties": false,
        "description": "Changes to one stop. Contact, instructions and time window only: an address change means\na different journey, so cancel and re-create rather than editing the address in place."
      },
      "TransportTrackingEventResponseDto": {
        "type": "object",
        "properties": {
          "occurredAt": {
            "type": "string",
            "description": "When it happened, UTC. This is the real-world time of the event, not the time\n            Orin recorded it, so events can arrive out of order after an offline driver syncs.",
            "format": "date-time",
            "example": "2026-04-01T08:34:00Z"
          },
          "status": {
            "type": "string",
            "description": "What happened.",
            "nullable": true,
            "example": "ArrivedAtPickup"
          },
          "location": {
            "type": "string",
            "description": "Where it happened, when known.",
            "nullable": true,
            "example": "Amsterdam, NL"
          },
          "note": {
            "type": "string",
            "description": "Any note the driver or planner attached.",
            "nullable": true,
            "example": "Waiting for dock to free up"
          }
        },
        "additionalProperties": false,
        "description": "One thing that happened to a transport. Returned oldest-first by\n`GET /transports/{id}/tracking`."
      },
      "TransportUpdateDto": {
        "type": "object",
        "properties": {
          "notes": {
            "maxLength": 2000,
            "minLength": 0,
            "type": "string",
            "description": "Replacement planner note.",
            "nullable": true,
            "example": "Customer moved the delivery slot forward."
          },
          "origin": {
            "$ref": "#/components/schemas/TransportStopUpdateDto"
          },
          "destination": {
            "$ref": "#/components/schemas/TransportStopUpdateDto"
          }
        },
        "additionalProperties": false,
        "description": "Changes to an existing transport. Every field is optional; omitted fields are left alone.\nOnly editable while the transport has not yet been dispatched."
      },
      "TripCompletionUnresolved": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          },
          "equipmentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "tripStopId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "actionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "apiKey",
        "description": "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
        "name": "Authorization",
        "in": "header"
      },
      "ApiKey": {
        "type": "apiKey",
        "description": "Partner API key issued by the transport company. Always prefixed with orin_pk_.",
        "name": "X-API-Key",
        "in": "header"
      }
    }
  },
  "tags": [
    {
      "name": "PartnerTransport",
      "description": "The shipper direction: create transports from your WMS or ERP, follow them, and pull the proof of delivery. Requires the transport permissions.",
      "x-displayName": "Transports"
    },
    {
      "name": "PartnerInvoices",
      "description": "What a movement costs you, on the same key that created it. Read-only. Requires ReadInvoice.",
      "x-displayName": "Invoices and charges"
    },
    {
      "name": "PartnerAppointments",
      "description": "Search free dock slots at the transport company's sites and book, move or cancel an appointment against one of your transports. Requires ReadAppointment and BookAppointment.",
      "x-displayName": "Dock appointments"
    },
    {
      "name": "Inbound",
      "description": "Post the file your system already produces (EDIFACT, X12, XML, CSV or JSON) and poll what became of it. The transport company configures the mapping.",
      "x-displayName": "EDI gateway"
    },
    {
      "name": "PartnerDispatch",
      "description": "The carrier and subcontractor direction: the trips the transport company has dispatched to you. Acknowledge, report progress per leg, upload the POD, complete. Requires the Supplier permissions.",
      "x-displayName": "Dispatches"
    },
    {
      "name": "PartnerExecution",
      "description": "For a trading partner that also runs Orin: shipments passed to you for execution, and the events you report back. Every call carries X-Partner-Tenant-Id.",
      "x-displayName": "Execution (tenant to tenant)"
    }
  ]
}