Skip to content

First API

Authentication tokens are environment-specific. Send your token in the Authorization header with the Bearer prefix:

Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json

All requests must use HTTPS and the /api namespace.

  • Production: https://api.firstdelivery.com/api/
  • Sandbox: https://sandbox.firstdelivery.com/api/

Never embed First API keys or webhook receiver secrets in URLs, logs, browser code, or published examples.

  • The Bearer prefix is case-insensitive. Sending the raw token without Bearer is still accepted for older integrations, but new integrations should send Bearer.
  • Tokens are only read from the Authorization header. Query-string tokens are not supported.
  • Treat every token as an opaque string. Don’t parse it or check its length.

All 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.

Prefix What it is Who it is for Status
f1rst_k1_<...> Scoped API-client key Server-to-server integrations Available now
f1rst_s1_<...> Session token A user who signs in with POST /api/login Rolling out
UUID Legacy session token or operator API key Existing integrations Still supported

> 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.

Use 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.

An operator admin creates keys in Dispatch → Developer Settings → API Access:

  1. Create an API client with a name, an optional description, and at least one scope.
  2. 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).

Admins can also manage clients through the API with an admin session:

Method Path Purpose
GET /api/operators/:operatorId/api-clients List clients, their scopes and keys
POST /api/operators/:operatorId/api-clients Create a client. Body: name (required), description, scopes (required, at least one)
PATCH /api/operators/:operatorId/api-clients/:clientId Update name, description, scopes or status (active / disabled)
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
DELETE /api/operators/:operatorId/api-clients/:clientId/keys/:keyId Revoke a key

A key can call only the endpoints its scopes cover. Scopes follow the folders under Platform API and Webhooks in this collection:

Folder Scopes
Territories territories:read, territories:write
Cohorts cohorts:read
Runners runners:write
Merchants merchants:read, merchants:write
Orders orders:read, orders:write. Get Provider Quotes needs quotes:create
Strategies strategies:read, strategies:write
Exports reports:read
Refunds refunds:read, refunds:write
Shifts shifts:read, shifts:write
Webhooks webhooks:read, webhooks:write

Endpoints outside this collection are not available to API-client keys.

  • A key does not expire unless you set expires_at when you create it.
  • 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.
  • Revoking a key, or disabling its API client, stops it from working.

A session token is issued when a user signs in:

POST /api/login
Content-Type: application/json
{ "username": "<YOUR_USERNAME>", "password": "<YOUR_PASSWORD>" }

The 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.

  • A session uses the permissions of the signed-in user’s role.
  • 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.
  • POST /api/logout with the session’s token revokes that session and returns { "revoked": true }.
  • A session stops working when its user is archived. Archived users cannot sign in.
  • A failed login returns HTTP 200 with { "status": <code>, "message": "<reason>" } in the body.
Status Body When
401 Empty The token is missing, unknown, expired or revoked
401 Access restricted to: <roles> (plain text) The token is valid, but the user’s role cannot call this endpoint
403 { "type": "ForbiddenError", "message": "API client requires scope: <scope>" } An API-client key is missing the required scope
403 { "type": "ForbiddenError", "message": "API clients cannot access this endpoint" } An API-client key called an endpoint outside this collection

First 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.

Field Type Behavior
orderId string First order UUID. Primary correlation key for standard order events.
externalId string Your order reference.
status string Legacy normalized status. Use with status_v3 while migrating.
status_v3 string Preferred normalized lifecycle status. Not present on Refunded.
trackerLink string First customer tracking page.
trackerLinkFleet string | null Fleet/DSP tracking page when supplied by the provider; otherwise null.
fleetName string | null Fleet/DSP display name; falls back to provider slug; null when no external provider is attached.
dspTrackerLink string | null Legacy provider-tracker field. Prefer trackerLinkFleet for new integrations.

trackerLinkFleet and fleetName are present on every new webhook payload, including cancellations and refunds, but may be null.

  • Events are signals, not an authoritative event log. They may be delayed, missed, duplicated, or arrive out of order.
  • 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.
  • Changed payloads, retries after failures, events outside the suppression window, and upstream provider behavior can still produce repeated lifecycle statuses.
  • 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.
  • Refunded is also an event rather than a lifecycle status and uses a provider-specific payload shape.
  1. Acknowledge quickly. Validate the request minimally, enqueue it, and return a 2xx response. Keep processing comfortably below First’s 5-second delivery timeout.
  2. 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.
  3. 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.
  4. Treat optional fields as optional. Driver, coordinates, ETAs, tracking URLs, proof-of-delivery images, fees, and cancellation details vary by event and provider.
  5. Reconcile with polling. For fulfillment, customer messaging, billing, compliance, and other critical workflows, poll the order API and reconcile against webhook signals.
  6. 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.
  7. Monitor failures and lag. Alert on non-2xx responses, timeouts, queue depth, stale orders, and reconciliation mismatches.

Fleets 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.

> 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).

  • Retry 429 and 5xx responses with exponential backoff and jitter; honor Retry-After.
  • Do not automatically retry most 4xx responses without correcting the request.
  • Use timeouts and idempotency controls for create/update operations.
  • Keep sandbox and production credentials, URLs, and data strictly separated.
Status Meaning
200 / 201 Success
400 Invalid request or validation failure. Validation errors return type: "validation_error" with field_errors
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>)
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
404 Resource not found
429 Rate limited; back off and honor Retry-After
5xx Server error; retry with bounded backoff and keep the request_id from the error body