{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"49338fb8-9897-5cdf-0ffe-ff7c4bb00c61","name":"First API","description":"# Making a Request\n\nAuthentication tokens are environment-specific. Send your token in the `Authorization` header with the `Bearer` prefix:\n\n```text\nAuthorization: Bearer <YOUR_API_KEY>\nContent-Type: application/json\n```\n\nAll requests must use HTTPS and the `/api` namespace.\n\n- Production: `https://api.firstdelivery.com/api/`\n- Sandbox: `https://sandbox.firstdelivery.com/api/`\n\nNever embed First API keys or webhook receiver secrets in URLs, logs, browser code, or published examples.\n\n# Authentication\n\n- The `Bearer` prefix is case-insensitive. Sending the raw token without `Bearer` is still accepted for older integrations, but new integrations should send `Bearer`.\n- Tokens are only read from the `Authorization` header. Query-string tokens are not supported.\n- Treat every token as an opaque string. Don't parse it or check its length.\n\n## Existing tokens and API keys keep working\n\nAll existing access tokens (UUID format) and operator API keys keep working exactly as they do today, with the same permissions. There is no forced migration and no change you need to make.\n\n## Token types\n\n| Prefix | What it is | Who it is for | Status |\n| --- | --- | --- | --- |\n| `f1rst_k1_<...>` | Scoped API-client key | Server-to-server integrations | Available now |\n| `f1rst_s1_<...>` | Session token | A user who signs in with `POST /api/login` | Rolling out |\n| UUID | Legacy session token or operator API key | Existing integrations | Still supported |\n\n> **Rolling out:** `f1rst_s1_` session tokens are being issued gradually, so `POST /api/login` may still return a UUID session token for a while. The API already accepts both formats.\n\n## Scoped API-client keys (`f1rst_k1_`)\n\nUse these for server-to-server integrations. Each key belongs to a named API client, which is limited to the operator it was created for and the scopes you grant it.\n\n### Create a key\n\nAn operator admin creates keys in **Dispatch → Developer Settings → API Access**:\n\n1. Create an API client with a name, an optional description, and at least one scope.\n2. Create a key for that client. The key is shown **once**. Copy it immediately, because it cannot be retrieved again (First stores only a hash of it).\n\nAdmins can also manage clients through the API with an admin session:\n\n| Method | Path | Purpose |\n| --- | --- | --- |\n| `GET` | `/api/operators/:operatorId/api-clients` | List clients, their scopes and keys |\n| `POST` | `/api/operators/:operatorId/api-clients` | Create a client. Body: `name` (required), `description`, `scopes` (required, at least one) |\n| `PATCH` | `/api/operators/:operatorId/api-clients/:clientId` | Update `name`, `description`, `scopes` or `status` (`active` / `disabled`) |\n| `POST` | `/api/operators/:operatorId/api-clients/:clientId/keys` | Create a key. Optional body: `label`, `expires_at` (ISO 8601, in the future). The response contains `data.secret` once |\n| `DELETE` | `/api/operators/:operatorId/api-clients/:clientId/keys/:keyId` | Revoke a key |\n\n### Scopes\n\nA key can call only the endpoints its scopes cover. Scopes follow the folders under **Platform API** and **Webhooks** in this collection:\n\n| Folder | Scopes |\n| --- | --- |\n| Territories | `territories:read`, `territories:write` |\n| Cohorts | `cohorts:read` |\n| Runners | `runners:write` |\n| Merchants | `merchants:read`, `merchants:write` |\n| Orders | `orders:read`, `orders:write`. **Get Provider Quotes** needs `quotes:create` |\n| Strategies | `strategies:read`, `strategies:write` |\n| Exports | `reports:read` |\n| Refunds | `refunds:read`, `refunds:write` |\n| Shifts | `shifts:read`, `shifts:write` |\n| Webhooks | `webhooks:read`, `webhooks:write` |\n\nEndpoints outside this collection are not available to API-client keys.\n\n### Lifetime, rotation and revocation\n\n- A key does not expire unless you set `expires_at` when you create it.\n- Each API client can have at most **two** active keys at a time. To rotate without downtime, create a second key, deploy it, confirm that its last-used time updates, and then revoke the old key.\n- Revoking a key, or disabling its API client, stops it from working.\n\n## Session tokens (`f1rst_s1_`)\n\nA session token is issued when a user signs in:\n\n```http\nPOST /api/login\nContent-Type: application/json\n\n{ \"username\": \"<YOUR_USERNAME>\", \"password\": \"<YOUR_PASSWORD>\" }\n```\n\nThe token is returned in `token.id`. Send it as `Authorization: Bearer <token.id>`. During the rollout, this value may be a UUID or an `f1rst_s1_<...>` token. Handle both the same way.\n\n- A session uses the permissions of the signed-in user's role.\n- Most sessions expire after **30 days without use**, or **90 days after sign-in**, whichever comes first. Runner and merchant app sessions expire after **90 days without use**.\n- `POST /api/logout` with the session's token revokes that session and returns `{ \"revoked\": true }`.\n- A session stops working when its user is archived. Archived users cannot sign in.\n- A failed login returns HTTP `200` with `{ \"status\": <code>, \"message\": \"<reason>\" }` in the body.\n\n## Authentication errors\n\n| Status | Body | When |\n| --- | --- | --- |\n| `401` | Empty | The token is missing, unknown, expired or revoked |\n| `401` | `Access restricted to: <roles>` (plain text) | The token is valid, but the user's role cannot call this endpoint |\n| `403` | `{ \"type\": \"ForbiddenError\", \"message\": \"API client requires scope: <scope>\" }` | An API-client key is missing the required scope |\n| `403` | `{ \"type\": \"ForbiddenError\", \"message\": \"API clients cannot access this endpoint\" }` | An API-client key called an endpoint outside this collection |\n\n# Webhook Contract\n\nFirst normalizes provider events into the payloads in **Webhooks → Webhook Events**. Production payload analysis confirms that standard order events consistently include `orderId`, `externalId`, `status`, `status_v3`, and `trackerLink`. Driver, location, ETA, proof-of-delivery, cancellation, fee, and provider tracking fields are conditional and must be treated as optional.\n\n## Common fields\n\n| Field | Type | Behavior |\n| --- | --- | --- |\n| `orderId` | string | First order UUID. Primary correlation key for standard order events. |\n| `externalId` | string | Your order reference. |\n| `status` | string | Legacy normalized status. Use with `status_v3` while migrating. |\n| `status_v3` | string | Preferred normalized lifecycle status. Not present on `Refunded`. |\n| `trackerLink` | string | First customer tracking page. |\n| `trackerLinkFleet` | string \\| null | Fleet/DSP tracking page when supplied by the provider; otherwise `null`. |\n| `fleetName` | string \\| null | Fleet/DSP display name; falls back to provider slug; `null` when no external provider is attached. |\n| `dspTrackerLink` | string \\| null | Legacy provider-tracker field. Prefer `trackerLinkFleet` for new integrations. |\n\n`trackerLinkFleet` and `fleetName` are present on every new webhook payload, including cancellations and refunds, but may be `null`.\n\n## Event delivery semantics\n\n- Events are signals, not an authoritative event log. They may be delayed, missed, duplicated, or arrive out of order.\n- First applies best-effort suppression to byte-equivalent payloads for the same operator, destination URL, and order during a short window. This reduces noise; it does **not** provide exactly-once delivery.\n- Changed payloads, retries after failures, events outside the suppression window, and upstream provider behavior can still produce repeated lifecycle statuses.\n- `ready_now` is an event layered on the current order status. It includes `event: \"ready_now\"` and `readyAt`; do not interpret it as a new `status_v3` value.\n- `Refunded` is also an event rather than a lifecycle status and uses a provider-specific payload shape.\n\n# Webhook Consumer Best Practices\n\n1. **Acknowledge quickly.** Validate the request minimally, enqueue it, and return a `2xx` response. Keep processing comfortably below First's 5-second delivery timeout.\n2. **Make processing idempotent.** Use a durable key such as `(orderId, status_v3, event, relevant timestamp)` or a canonical payload hash. Never rely on First's short-window suppression as your dedupe layer.\n3. **Do not assume ordering.** Compare incoming state against your stored state. Accept terminal states safely, while allowing a documented `reassign` to start a new delivery lifecycle.\n4. **Treat optional fields as optional.** Driver, coordinates, ETAs, tracking URLs, proof-of-delivery images, fees, and cancellation details vary by event and provider.\n5. **Reconcile with polling.** For fulfillment, customer messaging, billing, compliance, and other critical workflows, poll the order API and reconcile against webhook signals.\n6. **Secure the receiver.** Use HTTPS and configure a private authorization header in `http_options.headers`. Rotate receiver credentials, redact PII from logs, and restrict replay/debug access.\n7. **Monitor failures and lag.** Alert on non-2xx responses, timeouts, queue depth, stale orders, and reconciliation mismatches.\n\n# Fleet-Specific Behavior\n\nFleets differ in which order `options` they honor (signature, PIN, alcohol/ID check), what can be updated after an order is created, and which courier details they return. See **Platform API → Orders → Create Order → Fleet-Specific Behavior** for details on Uber, DoorDash, Grubhub, Just Eat, Wolt, and Relay.\n\n> **Uber drop-off PIN:** For Uber orders created with `options.requires_pin: true`, the PIN the customer must give the courier is always the last 4 digits of `customer_phone` (for example, `+15106809269` → `9269`).\n\n# API Request Resilience\n\n- Retry `429` and `5xx` responses with exponential backoff and jitter; honor `Retry-After`.\n- Do not automatically retry most `4xx` responses without correcting the request.\n- Use timeouts and idempotency controls for create/update operations.\n- Keep sandbox and production credentials, URLs, and data strictly separated.\n\n# Responses and Status Codes\n\n| Status | Meaning |\n| --- | --- |\n| `200` / `201` | Success |\n| `400` | Invalid request or validation failure. Validation errors return `type: \"validation_error\"` with `field_errors` |\n| `401` | The token is missing, unknown, expired or revoked, including a token from the other environment (empty body), or the token's role cannot call this endpoint (`Access restricted to: <roles>`) |\n| `403` | `ForbiddenError`: an API-client key is missing the required scope or called an endpoint closed to API clients, or the user lacks a required permission |\n| `404` | Resource not found |\n| `429` | Rate limited; back off and honor `Retry-After` |\n| `5xx` | Server error; retry with bounded backoff and keep the `request_id` from the error body |","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":false,"owner":"2850328","team":65790,"collectionId":"49338fb8-9897-5cdf-0ffe-ff7c4bb00c61","publishedId":"RW87qVSS","public":true,"publicUrl":"https://docs.firstdelivery.com","privateUrl":"https://go.postman.co/documentation/2850328-49338fb8-9897-5cdf-0ffe-ff7c4bb00c61","customColor":{"top-bar":"1B273F","right-sidebar":"303030","highlight":"52AE68"},"documentationLayout":"classic-double-column","customisation":{"metaTags":[{"name":"description","value":""},{"name":"title","value":""}],"appearance":{"default":"light","themes":[{"name":"dark","logo":"https://content.pstmn.io/1034129f-eca2-47ef-86f1-0966c6f709b0/Zmlyc3QtbG9nby4wNTljYWM4Mi5wbmc=","colors":{"top-bar":"212121","right-sidebar":"303030","highlight":"FF6C37"}},{"name":"light","logo":"https://content.pstmn.io/1034129f-eca2-47ef-86f1-0966c6f709b0/Zmlyc3QtbG9nby4wNTljYWM4Mi5wbmc=","colors":{"top-bar":"1B273F","right-sidebar":"303030","highlight":"52AE68"}}]}},"version":"8.12.11","publishDate":"2024-02-12T17:54:07.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{"title":"","description":""},"logos":{"logoLight":"https://content.pstmn.io/1034129f-eca2-47ef-86f1-0966c6f709b0/Zmlyc3QtbG9nby4wNTljYWM4Mi5wbmc=","logoDark":"https://content.pstmn.io/1034129f-eca2-47ef-86f1-0966c6f709b0/Zmlyc3QtbG9nby4wNTljYWM4Mi5wbmc="}},"statusCode":200},"environments":[{"name":"Sandbox","id":"d150415d-9479-4511-97a2-9e51a9907954","owner":"2850328","values":[{"key":"BASE_HABITAT_URL","value":"https://sandbox.firstdelivery.com","enabled":true,"type":"default"},{"key":"ACCESS_TOKEN_ADMIN","value":"0adf1500-8bf2-11ea-8896-01957a882901","enabled":true,"type":"default"},{"key":"$merchantId","value":"f254d581-3a2d-11ef-a2e0-35b1dbd3cd29","enabled":true,"type":"default"},{"key":"ORDER_ID","value":"b4abc590-a009-11ee-95cd-878c5a7c0cc8","enabled":true,"type":"default"},{"key":"DELIVERTHAT_ROOT","value":"https://api-test.deliverthat.com","enabled":true,"type":"default"},{"key":"DELIVERTHAT_KEY","value":"6c0d75046945451e.9mbolMtq0RwHlIPYdCKBWMYK/wPN/vr4HL6IOqISbIM=","enabled":true,"type":"default"},{"key":"lastQuoteId","value":"","enabled":true,"type":"any"},{"key":"$phoneNumber","value":"+15705261709","enabled":true,"type":"default"}],"published":true}],"user":{"authenticated":false,"permissions":{"publish":false}},"run":{"button":{"js":"https://run.pstmn.io/button.js","css":"https://run.pstmn.io/button.css"}},"web":"https://www.getpostman.com/","team":{"logo":"https://res.cloudinary.com/postman/image/upload/t_team_logo_pubdoc/v1/team/62fc265ab3be5fffb9b3c8bc400e8f62ac0a80e81758d8310c2307e171e2fe8d","favicon":"https://firstdelivery.com/favicon.ico"},"isEnvFetchError":false,"languages":"[{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"HttpClient\"},{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"RestSharp\"},{\"key\":\"curl\",\"label\":\"cURL\",\"variant\":\"cURL\"},{\"key\":\"dart\",\"label\":\"Dart\",\"variant\":\"http\"},{\"key\":\"go\",\"label\":\"Go\",\"variant\":\"Native\"},{\"key\":\"http\",\"label\":\"HTTP\",\"variant\":\"HTTP\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"OkHttp\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"Unirest\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"Fetch\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"jQuery\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"XHR\"},{\"key\":\"c\",\"label\":\"C\",\"variant\":\"libcurl\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Axios\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Native\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Request\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Unirest\"},{\"key\":\"objective-c\",\"label\":\"Objective-C\",\"variant\":\"NSURLSession\"},{\"key\":\"ocaml\",\"label\":\"OCaml\",\"variant\":\"Cohttp\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"cURL\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"Guzzle\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"HTTP_Request2\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"pecl_http\"},{\"key\":\"powershell\",\"label\":\"PowerShell\",\"variant\":\"RestMethod\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"http.client\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"Requests\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"httr\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"RCurl\"},{\"key\":\"ruby\",\"label\":\"Ruby\",\"variant\":\"Net::HTTP\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"Httpie\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"wget\"},{\"key\":\"swift\",\"label\":\"Swift\",\"variant\":\"URLSession\"}]","languageSettings":[{"key":"csharp","label":"C#","variant":"HttpClient"},{"key":"csharp","label":"C#","variant":"RestSharp"},{"key":"curl","label":"cURL","variant":"cURL"},{"key":"dart","label":"Dart","variant":"http"},{"key":"go","label":"Go","variant":"Native"},{"key":"http","label":"HTTP","variant":"HTTP"},{"key":"java","label":"Java","variant":"OkHttp"},{"key":"java","label":"Java","variant":"Unirest"},{"key":"javascript","label":"JavaScript","variant":"Fetch"},{"key":"javascript","label":"JavaScript","variant":"jQuery"},{"key":"javascript","label":"JavaScript","variant":"XHR"},{"key":"c","label":"C","variant":"libcurl"},{"key":"nodejs","label":"NodeJs","variant":"Axios"},{"key":"nodejs","label":"NodeJs","variant":"Native"},{"key":"nodejs","label":"NodeJs","variant":"Request"},{"key":"nodejs","label":"NodeJs","variant":"Unirest"},{"key":"objective-c","label":"Objective-C","variant":"NSURLSession"},{"key":"ocaml","label":"OCaml","variant":"Cohttp"},{"key":"php","label":"PHP","variant":"cURL"},{"key":"php","label":"PHP","variant":"Guzzle"},{"key":"php","label":"PHP","variant":"HTTP_Request2"},{"key":"php","label":"PHP","variant":"pecl_http"},{"key":"powershell","label":"PowerShell","variant":"RestMethod"},{"key":"python","label":"Python","variant":"http.client"},{"key":"python","label":"Python","variant":"Requests"},{"key":"r","label":"R","variant":"httr"},{"key":"r","label":"R","variant":"RCurl"},{"key":"ruby","label":"Ruby","variant":"Net::HTTP"},{"key":"shell","label":"Shell","variant":"Httpie"},{"key":"shell","label":"Shell","variant":"wget"},{"key":"swift","label":"Swift","variant":"URLSession"}],"languageOptions":[{"label":"C# - HttpClient","value":"csharp - HttpClient - C#"},{"label":"C# - RestSharp","value":"csharp - RestSharp - C#"},{"label":"cURL - cURL","value":"curl - cURL - cURL"},{"label":"Dart - http","value":"dart - http - Dart"},{"label":"Go - Native","value":"go - Native - Go"},{"label":"HTTP - HTTP","value":"http - HTTP - HTTP"},{"label":"Java - OkHttp","value":"java - OkHttp - Java"},{"label":"Java - Unirest","value":"java - Unirest - Java"},{"label":"JavaScript - Fetch","value":"javascript - Fetch - JavaScript"},{"label":"JavaScript - jQuery","value":"javascript - jQuery - JavaScript"},{"label":"JavaScript - XHR","value":"javascript - XHR - JavaScript"},{"label":"C - libcurl","value":"c - libcurl - C"},{"label":"NodeJs - Axios","value":"nodejs - Axios - NodeJs"},{"label":"NodeJs - Native","value":"nodejs - Native - NodeJs"},{"label":"NodeJs - Request","value":"nodejs - Request - NodeJs"},{"label":"NodeJs - Unirest","value":"nodejs - Unirest - NodeJs"},{"label":"Objective-C - NSURLSession","value":"objective-c - NSURLSession - Objective-C"},{"label":"OCaml - Cohttp","value":"ocaml - Cohttp - OCaml"},{"label":"PHP - cURL","value":"php - cURL - PHP"},{"label":"PHP - Guzzle","value":"php - Guzzle - PHP"},{"label":"PHP - HTTP_Request2","value":"php - HTTP_Request2 - PHP"},{"label":"PHP - pecl_http","value":"php - pecl_http - PHP"},{"label":"PowerShell - RestMethod","value":"powershell - RestMethod - PowerShell"},{"label":"Python - http.client","value":"python - http.client - Python"},{"label":"Python - Requests","value":"python - Requests - Python"},{"label":"R - httr","value":"r - httr - R"},{"label":"R - RCurl","value":"r - RCurl - R"},{"label":"Ruby - Net::HTTP","value":"ruby - Net::HTTP - Ruby"},{"label":"Shell - Httpie","value":"shell - Httpie - Shell"},{"label":"Shell - wget","value":"shell - wget - Shell"},{"label":"Swift - URLSession","value":"swift - URLSession - Swift"}],"layoutOptions":[{"value":"classic-single-column","label":"Single Column"},{"value":"classic-double-column","label":"Double Column"}],"versionOptions":[],"environmentOptions":[{"value":"0","label":"No Environment"},{"label":"Sandbox","value":"2850328-d150415d-9479-4511-97a2-9e51a9907954"}],"canonicalUrl":"https://documenter.gw.postman.com/view/metadata/RW87qVSS"}