> ## Agent Instructions > Nash exposes two MCP servers. The docs MCP server at https://docs.usenash.com/mcp searches this documentation and needs no credentials. The Nash MCP server at https://mcp.usenash.com/mcp operates an organization's deliveries and needs a Nash API key. See https://docs.usenash.com/reference/build-with-ai. > Use the Sandbox environment (https://api.sandbox.usenash.com/v1) for anything that creates or dispatches deliveries during development. # Nash API Source: https://docs.usenash.com/api-reference/api-overview The Nash API is the gateway to the delivery orchestration platform. Find the right resource group, learn how to authenticate, pick an environment, and get started. The Nash API is the gateway to our delivery orchestration platform. This page is the on-ramp: find the resource group you need, then learn how to authenticate, which environment to target, and the rate limits that apply. ## Available APIs Create orders, compare quotes, and dispatch deliveries programmatically. Choose providers and configure how jobs (deliveries) get dispatched. Send notifications to your customers or internal systems. Manage delivery origins and the geographic areas they serve. Track and update a job (delivery) once it has been dispatched. Track and update jobs (deliveries) with multiple packages. Get the providers available in your organization. Create, update, and dispatch ordered multi-stop routes. Manage driver shifts. Upsert reusable templates. Manage products and store inventory. Submit delivery feedback. Manifest and dispatch orders for shipping. The inbound surface a fleet uses to report delivery state to Nash. Server health check. ## Conventions The API follows RESTful conventions and uses JSON for both requests and responses. Set the `Content-Type` header to `application/json` on requests that carry a payload. All requests are strictly HTTPS. Every Nash API response includes a request ID, whether the request succeeded or failed (see [Errors](/reference/errors)). Include it when contacting support about a specific API call — it's the fastest way for us to find your request. ## Authentication Nash APIs use API keys. Send your key as a Bearer token in the `Authorization` header. If your key has access to more than one Nash organization, also send the `Nash-Org-Id` header to indicate which org the request is for. ```sh theme={"dark"} curl --request GET \ --url https://api.sandbox.usenash.com/v1/jobs \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" ``` See [Authentication](/reference/authentication) for full details, including how to create an API key and find your organization ID. ## Environments Nash provides separate Production and Sandbox environments, each with US and AU endpoints. Sandbox is a free, fully-featured environment for testing your integration without dispatching to real drivers. For the full list of base URLs and Portal links, see [Environments](/reference/environments). ## Rate limits Each organization is limited to 20 requests per second across all Nash API endpoints. Requests beyond the limit receive an HTTP `429` response, so build in a backoff strategy. See [Rate limits & throttling](/reference/rate-limits-throttling) for guidance. ## Using LLMs If you're integrating with the help of an LLM, Nash publishes documentation in LLM-friendly formats and a few best practices for sharing context safely. See [Using LLMs with Nash API](/api-reference/using-llms-with-nash-api). ## Next steps Create an API key and authenticate your requests. Pick the right base URL for Sandbox or Production. Start managing delivery operations with the Orders API. Integrate faster with LLM-friendly documentation. # Cancel Batch Job Source: https://docs.usenash.com/api-reference/batch-job/cancel-job-by-job-id-and-task-or-job-configuration-id post /v1/jobs/{jobId}/cancel Cancel a batch job by job ID and task or job configuration ID. # Create Batch Job Source: https://docs.usenash.com/api-reference/batch-job/create-batch-job post /v1/batch_job Create a job with multiple packages ### What's a batch job? A batch job is a group of packages delivered by the same courier, and so by the same provider. A batch job holds N packages where N > 1, in contrast with single jobs (non-batch), which hold a single package. ### One pickup, many dropoffs Every package in a batch job shares the same pickup: the same address, contact name, and business name. A request whose packages pick up from different locations is rejected with a `400` and the message `Batch jobs must share a single pickup location across all packages`. To move packages from several pickup points, create one job per pickup. The response resembles [Create Job](/api-reference/job/create-job), with two added fields: `isBatch` and `batch`. The `batch` field contains: * `batch.id`: unique ID for this batch * `batch.type`: `SINGLE_PICKUP_MULTIPLE_DROPOFFS` for every batch created through this endpoint * `batch.objectSequence`: list of `jobConfigurations` (which carry the package information) in order * `batch.stopTypes`: list of stops representing the action sequence, each `PICKUP` or `DROPOFF` * `batch.quotes`, `batch.winnerQuote`, `batch.failedQuotes`: the same as `task.quotes`, `task.winnerQuote`, and `task.failedQuotes` on non-batch jobs ```json theme={"dark"} { "batch": { "id": "bth_...", "type": "SINGLE_PICKUP_MULTIPLE_DROPOFFS", "objectSequence": [], "stopTypes": [ "PICKUP", "DROPOFF", "DROPOFF" ], "providerId": "FleetSimulatorFast", "winnerQuote": { "id": "qot_..", "providerId": "FleetSimulatorFast", "providerName": "FleetSimulator (4TestRunner)" }, "quotes": [], "failedQuotes": [] }, ... } ``` For this and all other endpoints, Nash processes fields from different providers — assume that any field in the response may be `null`. # Refresh Quotes (Batch) Source: https://docs.usenash.com/api-reference/batch-job/refresh-quotes-by-job-id-and-batch-id post /v1/jobs/{jobId}/batch/{batchId}/refresh_quotes Refresh the quotes for a batch job. # Select Quote (Batch) Source: https://docs.usenash.com/api-reference/batch-job/select-quote post /v1/select_quote Select the quote you would like to execute from a specific job. # Update Batch Job Source: https://docs.usenash.com/api-reference/batch-job/update-job-by-job-id-and-job-configuration-id patch /v1/jobs/{id}/{jobConfigurationId} Update fields on a Nash job. Updating a job should always work before a quote is selected; once one has been selected, Nash returns an error if the fleet rejects the change. ## Pre-dispatch updates Updating a job should always work before a quote is selected. Some updates may result in pricing changes. You can only update the package delivery times and the pickup or dropoff addresses before an order has been dispatched to a fleet. If any task in a job is dispatched and you attempt to update a delivery time or address, the endpoint returns an error. If you update any of the following fields, the quotes returned by the job are refreshed and the endpoint returns a new set of quotes. Any existing quotes (and quote IDs) become invalid for dispatch. The fields that force a quote refresh: 1. `pickupAddress` 2. `dropoffAddress` 3. `packagePickupStartTime` 4. `packagePickupEndTime` 5. `packageDropoffStartTime` 6. `packageDropoffEndTime` 7. `packageDeliveryMode` 8. `packageRequirements` 9. `packageMinimumVehicleSize` 10. `packageValue`: this field can also be updated in the [post-dispatch stage](/api-reference/batch-job/update-job-by-job-id-and-job-configuration-id#post-dispatch-updates) ## Updating times To update the times of a job, provide at least one of `packagePickupStartTime`, `packagePickupEndTime`, `packageDropoffStartTime`, and `packageDropoffEndTime`. Times you don't specify are calculated by Nash, and any previous times left out of the update request are overwritten. So to change one time field and keep the rest, send the others in the same request. If you change `packageDeliveryMode` from `SCHEDULED` to `NOW`, you don't need to specify any other time fields. ## Post-dispatch updates If a quote has been selected or the job was dispatched by auto-dispatch, Nash returns an error if any of the assigned fleets in the job reject the update. In the post-dispatch stage, updating the package value of the job may result in a price change, depending on how the providers calculate their pricing. Below is a list of fields that can be updated, with more detail on pre-dispatch and post-dispatch restrictions. # Book a delivery window for order Source: https://docs.usenash.com/api-reference/delivery-windows/book-a-delivery-window-for-order post /v1/order/external-identifier/{externalId}/delivery-window/book Reserve a delivery window for an order identified by external ID. The booking holds capacity in the window until confirmed or revoked. # Confirm a delivery window for order Source: https://docs.usenash.com/api-reference/delivery-windows/confirm-a-delivery-window-for-order post /v1/order/external-identifier/{externalId}/delivery-window/confirm Confirm a previously booked delivery window for an order. Finalizes the booking and commits the capacity reservation. # Create a single delivery window Source: https://docs.usenash.com/api-reference/delivery-windows/create-a-single-delivery-window post /v1/delivery-window Create a delivery window defining an available time slot for scheduled deliveries. Delivery windows are associated with a store location and have capacity limits. # Get a delivery window Source: https://docs.usenash.com/api-reference/delivery-windows/get-a-delivery-window get /v1/delivery-window/{id} Retrieve a single delivery window by ID, including its time slot, capacity, and status. # Get eligible delivery windows for order Source: https://docs.usenash.com/api-reference/delivery-windows/get-eligible-delivery-windows-for-order get /v1/eligible_delivery_windows Find delivery windows available for a specific order based on the order's store location and requirements. Requires either order_id or external_order_id. Optionally filter by time range and requested capacity. # Revoke a delivery window for order Source: https://docs.usenash.com/api-reference/delivery-windows/revoke-a-delivery-window-for-order post /v1/order/external-identifier/{externalId}/delivery-window/revoke Revoke a previously booked delivery window for an order. Releases the capacity reservation and disassociates the window from the order. # Create dispatch strategy Source: https://docs.usenash.com/api-reference/dispatch-strategies/create-dispatch-strategy post /v1/options_group Create a new dispatch strategy (options group) with provider selection rules, fallback logic, and quoting configuration. # Automations Source: https://docs.usenash.com/api-reference/dispatch-strategies/dispatch-automations Apply dispatch strategies dynamically with rule-based automations evaluated against each incoming job. Dispatch automation is a way to dynamically manage your dispatch strategies. An automation is composed of several rules; when a rule's conditions are met, its action applies a dispatch strategy to the job. Let's assume you have three dispatch strategies: * Individual Order Dispatch Strategy (*DS\_1*): comprised of short-distance gig providers, lowest price strategy * High Value Dispatch Strategy (*DS\_2*): contains a list of long-range providers that specialize in white-glove delivery * Default Dispatch Strategy (*DS\_3*): a mix of all providers You may want to assign different dispatch strategies based on the content of each delivery. One way to do that is to have the logic live on your server and attach the appropriate dispatch strategy for each new order. Another way to do this is by creating an automation at the account level. An automation is a set of IF / ELIF / ELSE conditions: ```py theme={"dark"} # pseudo code if condition_1: apply dispatch_strategy_1 else if condition2: apply dispatch_strategy_2 else: apply dispatch_strategy_3 ``` Dispatch automations apply to any job that doesn't have a dispatch strategy assigned. If a job meets the defined criteria then it will have the relevant dispatch strategy applied, otherwise, the default dispatch strategy will be applied. The fallback default dispatch strategy can also be `None`, if you do not want any dispatch strategy to apply (not common). ### Properties A rule's conditions test properties of the incoming job. Text properties support equals, does not equal, in list, not in list, starts with, ends with, contains, and does not contain. Numeric properties support greater than, less than, and the other comparison operators. Weight and dimension properties let you pick the unit in the rule editor and Nash converts it for you; driving distance uses your organization's distance unit. **Where** | Property | What it checks | | - | - | | Pickup Business Name, Pickup City, Pickup State, Pickup Zip, Pickup Country | The pickup address fields. Country is a two-letter ISO code. | | Dropoff Business Name, Dropoff City, Dropoff State, Dropoff Zip, Dropoff Country | The dropoff address fields. | | Pickup Store Location | The store location the job picks up from. | | Driving Distance | The driving distance from pickup to dropoff, in miles or kilometers. | **When** | Property | What it checks | | - | - | | Minutes Until Pickup, Minutes Until Dropoff | Minutes from now until the scheduled pickup or dropoff. | | Pickup Time, Dropoff Time | The time of day of the scheduled pickup or dropoff, in that location's local time zone. Comparison operators only. | | Day of Week | The weekday of the scheduled pickup, in the pickup location's local time. | **What's in the package** | Property | What it checks | | - | - | | Package / Order Value | The declared value of the order, entered in dollars. | | Driver's Tip | The tip on the order, entered in dollars. | | Items Count | The number of items in the package. | | Number of Packages | The number of packages in the job. Always `1` when the automation evaluates an order. | | Package Weight | The declared weight of the package, in pounds or kilograms. | | Package Total Weight | The weights of the package's items added up, each multiplied by its quantity. Falls back to Package Weight when no item carries a weight. Comparison operators only. | | Package Width, Package Height, Package Length | The declared package dimensions, in inches or centimeters. | | Package Max Dimension | The largest single dimension across the package's items. Comparison operators only. | | Package Cube | The volume of the package, width × height × length, in cubic inches or cubic centimeters. Comparison operators only. | | Package Requirements | The requirements set on the package, such as a signature or age verification. Contains operators only. | | Delivery Mode | The order's delivery mode: `now` or `scheduled`. | **Your own signals** | Property | What it checks | | - | - | | Tags | The tags on the order. Contains operators only. | | Metadata | A key you set in `jobMetadata` (see below). | | Random Grouping | A stable number from 0 to 99 derived from the order's ID, so you can send a share of jobs down a different path. "Less than 20" routes about a fifth of your jobs. | **Matching postal codes.** Pickup Zip and Dropoff Zip compare against the postal code exactly as it's stored on the address, with no normalization. An address Nash geocodes carries the five-digit US ZIP, while an address you send as separate components keeps whatever you sent, including a ZIP+4 such as `94103-1234`. To match both forms, use **starts with** and the five-digit code rather than **equals**. **Don't see the property you need?** We are continuously adding to this list based on customer needs — reach out to [support@usenash.com](mailto:support@usenash.com) and we can help you add a new property. ### Using job metadata You can query fields, such as tags, that you set in `jobMetadata` when applying dispatch automations. For example, ```js theme={"dark"} jobMetadata: { "tags": ["catering"] } ``` And then when creating your Dispatch Rule you would query that the `jobMetadata` "tags" key contains the value "catering". ### Configuration Dispatch automations can be configured from the [Nash Portal](https://portal.usenash.com/settings/dispatch-automation/active). If you plan to create several Nash Organizations to manage as part of a partnership with Nash, you can get in touch with your Nash point of contact to configure it at the Org Creation level. Dispatch automation configuration in the Nash Portal # Dispatch strategies Source: https://docs.usenash.com/api-reference/dispatch-strategies/dispatch-strategy How dispatch strategies (options groups) control which providers can fulfill a job and how a provider is selected. Dispatch Strategies, or Options Groups, are sets of configurations that can be attached to Nash jobs during creation. For example, you can set up a job to dispatch to the most affordable provider. In another scenario, you might want to dispatch the job to your internal fleet, and if they cannot fulfill it then you may want to send it to a third party provider. Dispatch Strategies allow all of these customizations to happen. ### Allowed providers The list of allowed providers allows you to limit the number of delivery providers available to a job. Through this list, you can add (or remove) any provider that is currently enabled for your account. That way, when creating a job with that dispatch strategy, only the providers you choose will show availability for that delivery. ### Selection strategy Jobs that are assigned via auto-dispatch are assigned to a delivery provider based on one of four selection strategies: **Price** (`lowest_price`): Choosing a price-based selection strategy will compare all of the available delivery provider's quotes, and dispatch the job to the one with the lowest price. **Ranked** (`preferred_order`): Choosing a selection strategy based on rank allows you to set your preferred delivery providers. Jobs will be dispatched in order, based on which providers are actually available for each job. **Reliability**: Choosing a reliability-based dispatch will engage with Nash's proprietary algorithm that ranks each delivery provider's reliability. This is determined by a number of factors including completion rate, cancellation rate, and on-time rate. **Dynamic Dispatch**: Weighs cost, reliability, and speed together for each delivery and picks the best provider for that specific job — see [Dynamic Dispatch](/reference/dispatch-strategies#dynamic-dispatch). The [create](/api-reference/dispatch-strategies/create-dispatch-strategy) and [update](/api-reference/dispatch-strategies/update-dispatch-strategy) endpoints accept `lowest_price` and `preferred_order` as the `providerSelectionStrategy`; Reliability and Dynamic Dispatch are configured in the [Nash Portal](https://portal.usenash.com). When auto-dispatch is enabled, the provider will be selected and job dispatched to them immediately upon job creation. If your use case requires user confirmation before dispatch (for example, confirming the price), you can use the [autodispatch](/api-reference/order/autodispatch-order) API endpoint to trigger the outcome of the strategy when you are ready for dispatch. Dispatch strategy configuration in the Nash Portal ## When to enable auto-dispatch ### Possible applications where you might want to enable this option * You want to send only one command to Nash to simultaneously create and dispatch a Job (set it and forget it). * You offer free or fixed price delivery and don't want to show any dynamic delivery options or prices to your customers. * You will always want the delivery to occur, you just want the lowest price option without having to write logic to figure this out (you can set a max possible fee). This is popular with restaurants. ### Possible applications where you might **not** want to enable this option * You want to allow a customer to choose their own delivery options like provider or price which is dynamically calculated by Nash. * You want to know how much a particular delivery will cost before committing to the delivery, either for yourself or to show your customer. Very common in customer checkout workflows. # Get Dispatch Strategies Source: https://docs.usenash.com/api-reference/dispatch-strategies/get-dispatch-strategies get /v1/options_group List your organization's dispatch strategies. # Update dispatch strategy Source: https://docs.usenash.com/api-reference/dispatch-strategies/update-dispatch-strategy patch /v1/options_group/{id} Update an existing dispatch strategy's provider selection rules, fallback logic, or quoting configuration. Only provided fields are updated. # Add Delivery Feedback Source: https://docs.usenash.com/api-reference/feedback/add-delivery-feedback post /v1/feedback/{delivery_id} Record a customer's rating and comment against a delivery. `score` is a number from 0 to 5. The tracking page's own feedback form sends the same range whichever scale you've chosen for it: thumbs up or down sends 5 or 1, a five-star rating sends 1 to 5, and a 0–10 NPS answer is halved, so it can arrive in half steps. The scale that produced a score isn't stored alongside it. # Add Delivery Feedback by External Identifier Source: https://docs.usenash.com/api-reference/feedback/add-delivery-feedback-by-external-identifier post /v1/feedback/external_identifier/{external_identifier} Record a customer's rating and comment against a delivery, addressed by your own identifier. `score` is a number from 0 to 5. The tracking page's own feedback form sends the same range whichever scale you've chosen for it: thumbs up or down sends 5 or 1, a five-star rating sends 1 to 5, and a 0–10 NPS answer is halved, so it can arrive in half steps. The scale that produced a score isn't stored alongside it. # Simulate Delivery Status Transition Source: https://docs.usenash.com/api-reference/fleet-simulator/simulate-delivery-status-transition post /v1/deliveries/{delivery_id}/simulate Advance a delivery to a given status for testing in sandbox. This endpoint is intended to simulate delivery status transitions in **sandbox only**. It is available in dev and sandbox environments, and only for deliveries dispatched to the Nash sandbox test provider. Use it to test how your integration reacts to delivery status changes without needing a real courier. ## Supported statuses Pass one of the following values as the `status` field in the request body: | Status | Description | | - | - | | `assigned_driver` | A driver has been assigned to the delivery | | `pickup_enroute` | Driver is en route to the pickup location | | `pickup_arrived` | Driver has arrived at the pickup location | | `pickup_complete` | Driver has picked up the order | | `dropoff_enroute` | Driver is en route to the dropoff location | | `dropoff_arrived` | Driver has arrived at the dropoff location | | `dropoff_complete` | Order has been delivered to the customer | | `return_in_progress` | Driver is returning the order | | `return_arrived` | Driver has arrived back at the return location | | `returned` | Order has been returned | | `canceled_by_provider` | Provider canceled the delivery | | `failed` | Delivery failed | # Bulk Update Deliveries Source: https://docs.usenash.com/api-reference/fleet/bulk-update-deliveries patch /v1/fleet/deliveries Apply updates to up to 100 deliveries in a single request. Each item is keyed by exactly one of `deliveryId` or `packageExternalIdentifier` (your own id set on the package at creation), plus the same field set as the single-delivery endpoint. The per-item result echoes back the key you sent under `deliveryId`. Per-item failures come back as a structured error in the response and do not affect the rest of the batch. # Get Driver Group Source: https://docs.usenash.com/api-reference/fleet/get-driver-group get /v1/fleet/driver-groups/{first_version_contract_id} Read one driver group and the drivers in it. Returns the group and its `drivers`: each driver's `id`, your `externalIdentifier` for them, their name, and whether they are `enabled` and on shift (`isShiftActive`). The path takes the group's `firstVersionContractId`, the ID that stays the same however many times the group is edited. # List Driver Groups Source: https://docs.usenash.com/api-reference/fleet/list-driver-groups get /v1/fleet/driver-groups List the driver groups in your internal fleet, newest first. Use this to read the driver groups your organization has, with the stable `firstVersionContractId` each one is known by. Disabled groups are left out unless you send `activeOnly=false`. Results come back newest first, up to 500 a page. Pass the `nextCursor` from one page as `cursor` on the next call; a missing `nextCursor` means you've reached the end. To read who is in a group, call [Get Driver Group](/api-reference/fleet/get-driver-group). # Outbound integration Source: https://docs.usenash.com/api-reference/fleet/outbound-integration Synchronous HTTP calls Nash makes to a fleet for quoting, dispatch, modifications, cancellations, and refresh. When a fleet is integrated with Nash, the runtime contract has two halves: Nash makes **synchronous HTTP calls** to the fleet for everything that happens to a route, and the fleet reports state back through the [Update Delivery](/api-reference/fleet/update-delivery) endpoint. This page covers the outbound half — the `fleet.route.*` calls Nash POSTs (or GETs) on the fleet's API. These are synchronous HTTP requests, not async webhook fanout. Nash blocks on the response and acts on it: `200` = accept, `4xx` = permanent reject (Nash tries another fleet), `5xx`/timeout = retryable failure. ## Wire model: every dispatch is a route A single delivery is a degenerate route of one pickup stop + one dropoff stop; a multi-order job carries N stops. Same envelope, same endpoint (`/fleet/routes`), same response shape — the fleet implements **one schema** and Nash handles the single-vs-multi collapse internally. ## Integration shape ```mermaid theme={"dark"} sequenceDiagram autonumber participant Nash participant Fleet participant Courier Note over Nash,Fleet: Quoting (optional) Nash->>Fleet: POST /fleet/routes/quote Fleet-->>Nash: 200 { priceCents, etas, expireTime } Note over Nash: Nash ranks quotes per merchant strategy
and picks a winner Nash->>Fleet: POST /fleet/routes (event: dispatched) Fleet-->>Nash: 200 { partnerRouteId, stops: [{ partnerStopId, deliveryId }] } Fleet->>Fleet: route to fulfillment, assign courier Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { courier, externalDeliveryId } Nash-->>Fleet: 200 { ...full delivery payload } Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: pickup_enroute, coordinates } Courier->>Fleet: arrives at pickup Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: pickup_arrived } Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: pickup_complete, proofOfDelivery } Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: dropoff_enroute, coordinates } Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: dropoff_arrived } Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: dropoff_complete, proofOfDelivery } ``` ## Onboarding & authentication At onboarding, Nash provisions an organization for the fleet and records: * The **API base URL** Nash sends route calls to. * The **outbound auth token** Nash includes on every call. * An **org API key** the fleet uses to authenticate inbound calls into Nash. **Outbound auth (Nash → fleet).** Nash sends a bearer token in the `Authorization: Bearer ` header on every outbound request. The token is issued at onboarding. The fleet validates the token before processing the body. **Inbound auth (fleet → Nash).** The fleet sends every inbound request with the org API key as a bearer token: ``` Authorization: Bearer ``` Updates only land on deliveries owned by the fleet's organization. Anything else returns not-found — Nash never leaks existence across organization boundaries. ## Events | Event | Method + path | When | | - | - | - | | `fleet.route.quote` | `POST /fleet/routes/quote` | Nash is requesting a price/ETA on a route. The fleet responds with a quote in the response body. Only sent if the fleet participates in quoting. | | `fleet.route.dispatched` | `POST /fleet/routes` | Nash committed the route to this fleet for fulfillment. The body carries the full route snapshot — stops with embedded order data and per-order `deliveryId`. | | `fleet.route.updated` | `POST /fleet/routes/{routeId}` | A field on an in-flight order changed, or a stop was added mid-flight. The body carries the **full updated route**; an optional `fieldChanges[]` list names the paths that changed. | | `fleet.route.canceled` | `POST /fleet/routes/{routeId}/cancel` | The merchant or Nash canceled the route (or one delivery on it). Body carries `cancelReason` and `deliveryIds[]`. | | (refresh) | `GET /fleet/routes/{routeId}` | Nash polls for current state. Fleet returns per-stop updates keyed by `deliveryId`. | The minimum set required to participate is `dispatched`, `updated`, `canceled`. `quote` is required only for fleets participating in quoting; refresh is required only when the fleet doesn't push state inbound on its own. ## Wire envelope Every event has the same top-level shape: | Field | Meaning | | - | - | | `type` | Always `"fleet.route"`. | | `event` | One of `quote`, `dispatched`, `updated`, `canceled`. | | `routeId` | Nash's stable handle for this route. Use it on `GET /fleet/routes/{routeId}`, `POST /fleet/routes/{routeId}`, `POST /fleet/routes/{routeId}/cancel`. For a single-delivery dispatch with no Nash-side route, the delivery id is used as the routeId. | | `occurredAt` | ISO 8601 timestamp of when the event happened in Nash. | | `data` | The route snapshot: `{ id, stops: [...] }`. Update calls add `fieldChanges[]`; cancel calls replace `stops` with `{ deliveryIds, cancelReason }`. | ## Acknowledgement and retries Nash expects `200 OK`. Other responses are handled as follows: * **`4xx`** — treated as a permanent rejection. For `fleet.route.dispatched`, Nash will route to a different fleet. * **`5xx` or timeout** — retried with exponential backoff. * **`404`** on refresh — soft no-op. Fleet doesn't know about this route (possibly pruned after completion). ## `fleet.route.dispatched` Sent when Nash commits the route to the fleet. The body carries the full route snapshot. Each stop has an `orders[]` array with **full per-order snapshots** embedded — pickup-side AND dropoff-side fields are included on every embedding, so the fleet has everything it needs without follow-up lookups. ### Multi-order route ```json theme={"dark"} { "type": "fleet.route", "event": "dispatched", "routeId": "rte_abc123", "occurredAt": "2026-05-09T14:00:00Z", "data": { "id": "rte_abc123", "externalId": "merchant-route-7", "stops": [ { "stopType": "pickup", "sequence": 0, "location": { "lat": 37.7749, "lng": -122.4194 }, "arrivalTime": "2026-05-09T15:00:00Z", "endTime": "2026-05-09T15:15:00Z", "serviceTime": 300, "orders": [ { "id": "ord_1", "deliveryId": "dlv_1", "externalId": "merchant-order-42", "referenceId": "ACM-42", "deliveryMode": "scheduled", "pickupAddress": "123 Main St, San Francisco, CA 94102, US", "pickupBusinessName": "Acme Bakery", "pickupFirstName": "Manager", "pickupPhoneNumber": "+14155551212", "pickupInstructions": "Use the side door", "pickupStartTime": "2026-05-09T15:00:00Z", "pickupEndTime": "2026-05-09T15:15:00Z", "pickupBarcodes": ["ACME-PKG-001"], "dropoffAddress": "456 Market St, San Francisco, CA 94103, US", "dropoffFirstName": "Jane", "dropoffLastName": "Customer", "dropoffPhoneNumber": "+14155553434", "dropoffInstructions": "Leave at door", "dropoffStartTime": "2026-05-09T15:45:00Z", "dropoffEndTime": "2026-05-09T16:15:00Z", "items": [ { "name": "Sourdough Loaf", "quantity": 2 }, { "name": "Croissant", "quantity": 6 } ], "valueCents": 4200, "tipAmountCents": 500, "currency": "USD", "weight": 3.2, "requirements": ["age_verification_on_delivery"], "tags": ["catering"] } ] }, { "stopType": "dropoff", "sequence": 1, "location": { "lat": 37.7895, "lng": -122.4001 }, "arrivalTime": "2026-05-09T15:45:00Z", "endTime": "2026-05-09T16:15:00Z", "orders": [ { "id": "ord_1", "deliveryId": "dlv_1", "...": "same per-order snapshot as above" } ] } ] } } ``` Key shape decisions: * **Stops are flat and sequenced** (0..N-1), mirroring Nash's `Route.stops`. * **Each stop carries `orders[]`** — full per-order snapshots embedded right there. No follow-up lookups. * **A stop with multiple `orders[]`** means a co-pickup or co-dropoff (multiple orders touched at the same location). * **`stopType`** is `pickup`, `dropoff`, or other (e.g. `driver_break`) — driver-break stops pass through verbatim from Nash's optimizer. ### Single delivery (degenerate route of one) A dispatch for one merchant order ships as a route with exactly two stops. When the delivery has no Nash-side `Route` (single-order dispatch), the delivery id is used as the wire `routeId`: ```json theme={"dark"} { "type": "fleet.route", "event": "dispatched", "routeId": "dlv_zyx987", "occurredAt": "2026-05-09T14:00:00Z", "data": { "id": "dlv_zyx987", "stops": [ { "stopType": "pickup", "sequence": 0, "orders": [{ "id": "ord_1", "deliveryId": "dlv_zyx987", "...": "..." }] }, { "stopType": "dropoff", "sequence": 1, "orders": [{ "id": "ord_1", "deliveryId": "dlv_zyx987", "...": "..." }] } ] } } ``` The fleet doesn't have to special-case single vs. batch — both are routes. ### Dispatch response On accept: ```json theme={"dark"} { "partnerRouteId": "fp-route-99", "stops": [ { "sequence": 0, "deliveryId": "dlv_1", "partnerStopId": "fp-s-1" }, { "sequence": 1, "deliveryId": "dlv_1", "partnerStopId": "fp-s-2" } ] } ``` Nash stores `partnerStopId` (per-delivery) as the fleet-side delivery identifier for cross-referencing in logs. Falls back to the Nash delivery id if the fleet doesn't return one. On reject: ```json theme={"dark"} { "error": { "code": "no_capacity", "message": "Out of vehicles in this zone" } } ``` `4xx` with an optional structured body. Nash treats the route as failed and tries another fleet (or alerts the merchant if no fallback). ## `fleet.route.updated` Sent when a field on an in-flight order changes (pickup or dropoff window, address, instructions, items), or when a stop is added mid-flight. The body is a **full re-send** of the route snapshot plus a `fieldChanges[]` list naming the paths that changed. ```json theme={"dark"} { "type": "fleet.route", "event": "updated", "routeId": "rte_abc123", "occurredAt": "2026-05-09T14:30:00Z", "data": { "id": "rte_abc123", "fieldChanges": ["dropoffStartTime", "dropoffEndTime"], "stops": [ "...full stops array..." ] } } ``` Sending the full updated route on every change means the fleet can diff against what they had — no need to track add / remove deltas themselves. ```mermaid theme={"dark"} sequenceDiagram autonumber participant Merchant participant Nash participant Fleet Merchant->>Nash: PATCH /v1/order/{id} (new dropoff time) Nash->>Nash: validate, update order Nash->>Fleet: POST /fleet/routes/{routeId}
{event: updated, data.fieldChanges: ["dropoffStartTime"], data.stops: [...]} Fleet-->>Nash: 200 OK Note over Fleet: Fleet re-routes courier
or 4xx if uncommittable ``` ## `fleet.route.canceled` Sent when the merchant or Nash cancels the route, or one delivery on it. The body carries the route id, the affected `deliveryIds[]`, and a `cancelReason`. ```json theme={"dark"} { "type": "fleet.route", "event": "canceled", "routeId": "rte_abc123", "occurredAt": "2026-05-09T14:45:00Z", "data": { "id": "rte_abc123", "deliveryIds": ["dlv_1"], "cancelReason": "canceled_by_merchant" } } ``` `deliveryIds[]` lists the specific deliveries being canceled. For a whole-route cancellation, it's every delivery on the route; for a single-delivery cancellation on a multi-delivery route, only that one delivery. ```mermaid theme={"dark"} sequenceDiagram autonumber participant Merchant participant Nash participant Fleet Merchant->>Nash: cancel order Nash->>Nash: mark order canceled Nash->>Fleet: POST /fleet/routes/{routeId}/cancel
{event: canceled, data.deliveryIds: [...], cancelReason: ...} Fleet-->>Nash: 200 OK Fleet->>Nash: PATCH /v1/fleet/deliveries/{deliveryId} { status: canceled_by_provider } Note over Nash,Fleet: Nash terminates the lifecycle;
further PATCHes are no-ops ``` After a cancellation, any further `PATCH /v1/fleet/deliveries/{deliveryId}` calls for that delivery are no-ops. ## Refresh Nash periodically polls the fleet for the current state of an in-flight route: ``` GET /fleet/routes/{routeId} ``` Fleet returns per-stop state in the shape of the inbound `PATCH /v1/fleet/deliveries/{id}` body, keyed by `deliveryId`: ```json theme={"dark"} { "stops": [ { "deliveryId": "dlv_1", "status": "pickup_complete", "coordinates": { "latitude": 37.78, "longitude": -122.41 }, "courier": { "name": "Sam", "phoneNumber": "+14155550000" } }, { "deliveryId": "dlv_2", "status": "pickup_enroute" } ] } ``` Refresh and inbound PATCH share the **exact same field set**. The fleet can implement one `DeliveryUpdate` JSON shape and reuse it for both pushing state to Nash (inbound) and returning state to Nash (the refresh GET response). Nash applies the entry whose `deliveryId` matches the delivery being refreshed and ignores the rest — they're siblings on the same route refreshed in their own poll. `404` is a soft no-op (fleet doesn't know about this route). ```mermaid theme={"dark"} sequenceDiagram autonumber participant Nash participant Fleet Note over Nash: periodic refresh from tracker Nash->>Fleet: GET /fleet/routes/{routeId} Fleet-->>Nash: 200 { stops: [{ deliveryId, status, coordinates, ... }, ...] } Nash->>Nash: apply each stop entry via the same code path as inbound PATCH ``` ## `fleet.route.quote` Quoting lets the fleet compete on price and ETA against other providers configured by the merchant. It's opt-in at onboarding — fleets that don't participate are still eligible to receive dispatches directly when the merchant picks them; they just don't compete on price. ### Flow 1. Nash receives an order (or multi-stop batch) from the merchant and identifies the fleets eligible to fulfill it. 2. For each eligible fleet that participates in quoting, Nash POSTs `POST /fleet/routes/quote` with the route snapshot. 3. The fleet responds with a quote within the agreed latency budget. Quotes that arrive too late or fail are skipped — they don't block other fleets' quotes. 4. Nash filters quotes against the merchant's constraints (max fee, max ETA) and the merchant picks a winner. 5. The winning fleet receives `POST /fleet/routes` (dispatch). Losing fleets receive nothing — they don't need to track the outcome. ### Quote request Same wire envelope as the other `fleet.route.*` calls; `event: "quote"`. Same route-shaped `data` as dispatch, but each order's `deliveryId` is `null` (the delivery isn't committed yet). ```json theme={"dark"} { "type": "fleet.route", "event": "quote", "routeId": "ord_abc123", "occurredAt": "2026-05-09T14:00:00Z", "data": { "stops": [ { "stopType": "pickup", "sequence": 0, "location": { "lat": 37.7749, "lng": -122.4194 }, "orders": [{ "id": "ord_abc123", "deliveryId": null, "pickupBusinessName": "Acme Bakery", "pickupPhoneNumber": "+14155551212", "pickupStartTime": "2026-05-08T15:00:00Z", "pickupEndTime": "2026-05-08T15:15:00Z", "...": "..." }] }, { "stopType": "dropoff", "sequence": 1, "location": { "lat": 37.7895, "lng": -122.4001 }, "orders": [{ "id": "ord_abc123", "deliveryId": null, "dropoffFirstName": "Jane", "dropoffPhoneNumber": "+14155553434", "dropoffStartTime": "2026-05-08T15:45:00Z", "dropoffEndTime": "2026-05-08T16:15:00Z", "...": "..." }] } ] } } ``` For batch quoting, the route has N orders with sequenced pickup stops followed by sequenced dropoff stops. ### Quote response Respond with `200 OK` and a JSON body, or `4xx` to decline: ```json theme={"dark"} { "externalQuoteId": "", "priceCents": 1500, "currency": "USD", "pickupEta": "2026-05-08T15:55:00Z", "dropoffEta": "2026-05-08T16:20:00Z", "expireTime": "2026-05-08T15:30:00Z" } ``` Decline responses (`4xx`) are interpreted as ineligible for this route. Nash captures the decline reason from the response body if present (`{ "reason": "out_of_service_area" }`) but doesn't retry. ### Latency budget The fleet's quote endpoint should respond within the budget configured at onboarding (typically 1–3 seconds). Quotes that exceed the budget are dropped for that route. Consistent timeouts move the fleet to a degraded state and Nash will route around it until quote latency recovers. ## Rejecting a dispatch If the fleet can't accept the route (no capacity, ineligible), it can reject in two ways: * **Reject in the response.** Respond `4xx` to `POST /fleet/routes` (dispatched). Nash treats the dispatch as failed and tries a different fleet. * **Reject after acceptance.** Ack `200`, then `PATCH /v1/fleet/deliveries/{deliveryId}` with `status: failed` and `failure.code: "no_capacity"`. Nash treats it as a runtime failure and tries a different fleet. ```mermaid theme={"dark"} sequenceDiagram autonumber participant Nash participant Fleet participant FleetB as Backup Fleet Nash->>Fleet: POST /fleet/routes (event: dispatched) Fleet--xNash: 4xx { error: { code: "no_capacity" } } Nash->>Nash: mark dispatch failed, try another fleet Nash->>FleetB: POST /fleet/routes (event: dispatched, same routeId) FleetB-->>Nash: 200 OK ``` ## Order field reference The per-order snapshot embedded under each stop's `orders[]` is flat camelCase, with ISO 8601 timestamps and monetary values in minor units. ### Required | Field | Meaning | | - | - | | `id` | Nash order id. Stable across modifications. | | `deliveryId` | Identifier the fleet uses on subsequent updates via `PATCH /v1/fleet/deliveries/{deliveryId}`. Present on dispatch / update / cancel; `null` on quote. | | `externalId` | Merchant's order identifier — useful for fleet-side reconciliation. | | `pickupAddress` | Full address string. | | `pickupBusinessName` | Store / restaurant name. | | `pickupPhoneNumber` | E.164 — for courier-to-store contact. | | `pickupStartTime` / `pickupEndTime` | When the order will be ready (window). | | `dropoffAddress` | Customer address. | | `dropoffFirstName` | Recipient first name. | | `dropoffPhoneNumber` | E.164 — for SMS tracking and courier contact. | | `dropoffStartTime` / `dropoffEndTime` | Promised delivery window. | | `items` | List of `{ name, quantity, ... }`. | | `valueCents` | Subtotal in minor units. | | `currency` | ISO 4217. | | `deliveryMode` | `now` or `scheduled`. | ### Recommended * `pickupInstructions` / `dropoffInstructions` — gate codes, "leave at door," etc. * `requirements` — alcohol, age verification, signature, photo POD, barcode scans. * `weight` / `height` / `width` / `depth` — improves vehicle fit. * `tipAmountCents` — passed through to the courier where the fleet supports it. * `referenceId` — short identifier shown to the courier in the fleet's app. * `tags` — partner-specific routing flags. ### Stop-level fields | Field | Meaning | | - | - | | `stopType` | `pickup`, `dropoff`, `driver_break`, etc. | | `sequence` | 0-indexed position in the route. | | `location` | `{ lat, lng }`. | | `arrivalTime` / `endTime` | Window for this stop. | | `serviceTime` | Seconds the courier is expected to spend at the stop. | | `orders[]` | Per-order snapshot — see "Order field reference" above. | # Update Delivery Source: https://docs.usenash.com/api-reference/fleet/update-delivery patch /v1/fleet/deliveries/{delivery_id} Apply a partial update — such as status, courier location, proof of delivery, courier identity, or ETAs — to a delivery your organization owns. Only available for deliveries fulfilled by your own fleet (not by a third-party courier integration). Returns the updated delivery. # Upsert Driver Source: https://docs.usenash.com/api-reference/fleet/upsert-driver put /v1/fleet/drivers/upsert Create or update a driver in your internal fleet, keyed on your own `externalIdentifier`. There is no separate create vs. update call — sending the same `externalIdentifier` again updates the existing driver. The response's `created` field tells you which happened. To disable a driver, upsert with `isShiftActive: false` (or `enabled: false`) — there is no separate delete/disable call. # Upsert Driver Group Source: https://docs.usenash.com/api-reference/fleet/upsert-driver-group put /v1/fleet/driver-groups/upsert Create or update a driver group, keyed on your own identifier. Your `externalIdentifier` is the upsert key. Send a new value to create a group, which needs a `displayName`; send the same value again to update that group. `created` in the response says which happened. Fields you leave out keep their current values. `driverIds` is the group's full roster. Sending it replaces the list, `[]` removes everyone, and leaving it out changes nothing. To add or remove one driver without resending the roster, use `addFirstVersionContractIds` and `removeFirstVersionContractIds` on [Upsert Driver](/api-reference/fleet/upsert-driver). There is no delete call. To disable a group, upsert it with `isActive: false`. A disabled group can lose members but can't gain new ones. # Sync store inventory catalogs to SFTP Source: https://docs.usenash.com/api-reference/inventory/sync-store-inventory-catalogs-to-sftp post /v1/inventory/sync Trigger a full catalog export+upload for all synced stores (`all: true`) or a specific list of store external ids. Returns the run activity id; per-store export runs asynchronously. # Autodispatch Job Source: https://docs.usenash.com/api-reference/job/autodispatch-job post /v1/jobs/{id}/autodispatch Automatically select a quote based on dispatch strategy For a given job, you can set up automatic dispatch by creating [Dispatch Strategies](/reference/dispatch-strategies). In a checkout experience, you often create a job first so you can show delivery quotes to your customer. Once the customer places the order, call this endpoint to dispatch the job using the Dispatch Strategy attached to it — instead of selecting a quote yourself. To autodispatch a job, the job must have a Dispatch Strategy attached, with a provider selection strategy configured. ### How autodispatch selects a quote When you call `/autodispatch`, Nash selects the quote tagged `autodispatch_preferred_quote`. Use this tag to know which price to display to your customer at checkout. You can read the full list of quotes at `create_job_response["jobConfigurations"][0]["advancedTask"]["quotes"]` and filter for the one with `"autodispatch_preferred_quote"` in `quote.tags`. Make sure **Enable Autodispatch** is switched off on the Dispatch Strategy you are using to autodispatch the job. # Autodispatch Job by External Identifier Source: https://docs.usenash.com/api-reference/job/autodispatch-job-by-external-identifier post /v1/jobs/external_identifier/{externalIdentifier}/autodispatch Automatically select a quote based on dispatch strategy For a given job, you can set up automatic dispatch by creating [Dispatch Strategies](/reference/dispatch-strategies). In a checkout experience, you often create a job first so you can show delivery quotes to your customer. Once the customer places the order, call this endpoint to dispatch the job using the Dispatch Strategy attached to it — instead of selecting a quote yourself. To autodispatch a job, the job must have a Dispatch Strategy attached, with a provider selection strategy configured. ### How autodispatch selects a quote When you call `/autodispatch`, Nash selects the quote tagged `autodispatch_preferred_quote`. Use this tag to know which price to display to your customer at checkout. You can read the full list of quotes at `create_job_response["jobConfigurations"][0]["advancedTask"]["quotes"]` and filter for the one with `"autodispatch_preferred_quote"` in `quote.tags`. Make sure **Enable Autodispatch** is switched off on the Dispatch Strategy you are using to autodispatch the job. # Cancel Job by External Identifier Source: https://docs.usenash.com/api-reference/job/cancel-job-by-external-identifier post /v1/jobs/external_identifier/{externalIdentifier}/cancel Cancels a Nash Job by the Partner's External ID if one was supplied at job creation. # Cancel Job Source: https://docs.usenash.com/api-reference/job/cancel-job-by-job-id-and-job-configuration-id post /v1/jobs/{jobId}/{objectId}/cancel Cancel a specific task or job configuration within a batch job. The objectId can be a task ID (tsk_ prefix) or job configuration ID (cfg_ prefix). # Create Delivery Incident Source: https://docs.usenash.com/api-reference/job/create-delivery-incident post /v1/jobs/{jobId}/tasks/{taskId}/delivery_incident Create a new delivery incident for a task #### Requirements At least one of the following fields must be provided: * `requestAmountDeliveryFeeCents` * `requestAmountOrderValueCents` * `requestAmountTipCents` All provided values must be non-negative integers (≥ 0), and at least one must be greater than zero. # Create Job Source: https://docs.usenash.com/api-reference/job/create-job post /v1/jobs Create a new job (delivery) This endpoint is deprecated. Use the [Create Order](/api-reference/order/create-order) endpoint instead. ### Scheduled job time calculations When creating a scheduled job, you must include at least one of the following fields: * `packagePickupStartTime` * `packagePickupEndTime` * `packageDropoffStartTime` * `packageDropoffEndTime` Times you don't specify are calculated by Nash. "Now" or ASAP jobs don't require any of these fields. If the specified pickup time of a scheduled job is very close to the current time (within 5 minutes in the past or 10 minutes in the future), it becomes an ASAP job automatically. For this and all other endpoints, Nash processes fields from different providers — assume that any field in the response may be `null`. ### Package items An array of item objects, each describing an item within the package for delivery. Each object should include the item's name, dimensions (length, width, height), and count, giving a detailed inventory of the package's contents. Nash uses this to handle the package correctly and allocate space for transport. A package containing three labeled items, showing how items nest inside a package. Include both **pickup** and **dropoff times** wherever you can: our estimates are based on historical time data rather than real-time metrics. The fields `selectedConfiguration` and `jobSpecification` returned in the job response are deprecated and should not be used. **Migration guide, replace:** `jobSpecification->package` with `data->jobConfiguration[0]->package` and `selectedConfiguration` with `jobConfiguration[0]` # Create or Update Job by External Identifier Source: https://docs.usenash.com/api-reference/job/create-or-update-job-by-external-identifier post /v1/jobs/external_identifier/{externalIdentifier} Create a new job or update an existing one using your external identifier. If a job with the given external identifier exists, it is updated; otherwise a new job is created. Set autodispatch=true to automatically dispatch after creation. This endpoint is deprecated. Please use the [Create or Update Order by External Identifier](/api-reference/order/create-or-update-order-by-external-identifier) endpoint instead. # Get Job Source: https://docs.usenash.com/api-reference/job/get-job get /v1/jobs/{id} Retrieve a job by its ID. # Get Job by External Identifier Source: https://docs.usenash.com/api-reference/job/get-job-by-external-identifier get /v1/jobs/external_identifier/{externalIdentifier} Retrieve a job by its external identifier. # Get Refund Request Source: https://docs.usenash.com/api-reference/job/get-refund-request get /v1/refund-request/{id} Retrieve a refund request by its ID. # Get Refund Requests Source: https://docs.usenash.com/api-reference/job/get-refund-requests get /v1/refund-requests Retrieve a list of refund requests by task ID or job ID. # Jobs overview Source: https://docs.usenash.com/api-reference/job/jobs What a job (delivery) is in Nash, its data model, and the most common paths in the job object. When one or more Orders are dispatched, they become a Job. Jobs represent the actual delivery process. A Job holds the package(s) to be delivered, the quotes from providers willing to deliver the package(s), and, once a quote has been selected, the current delivery status. Within each job, there's a structure that includes **Job Configurations**, **Tasks**, and **Deliveries** that manage the details and processes of delivery. ## Job data model The figure here draws the job data model as a tree and gives the path to each part of the job object: * **Order**: the delivery details before dispatch. When one or more orders are dispatched, they become a job. * **Job**: the primary object for a delivery. Path: `id`. * **Job configuration**: the dispatch strategy, special instructions, package, and tasks for this job. Path: `jobConfigurations[0]`. * **Package**: what is carried, with its pickup and dropoff locations. Path: `jobConfigurations[0].package`. * **Task**: one attempt at the delivery. Path: `jobConfigurations[0].tasks[0]`. * **Quotes**: the offers for that attempt. Path: `jobConfigurations[0].tasks[0].quotes`. * **Delivery**: status, ETA, courier details, and proof of delivery. It is `null` until a provider is assigned. Path: `jobConfigurations[0].tasks[0].delivery`. * **A second task**: a reassigned job keeps its old task and gains a new one with its own quotes and delivery. Path to the latest: `jobConfigurations[0].advancedTask`. * **Batch**: a batch job carries several packages in one multi-stop delivery, each with its own pickup and dropoff. A job that is not a batch has no `batch` key, and its `isBatch` is false. Here's how the data model relates to each other: * **Job**: This is the primary object that represents a delivery. It contains all the specifications for the package(s) to be delivered, such as the size, value, and the pickup and dropoff locations. * **Job Configuration**: Within each Job, there is a JobConfiguration. It includes information on the dispatch strategy, special instructions, and other configurations specific to this job. * **Task**: Under each Job Configuration, there can be a list of Tasks. A Task is where the action happens; it involves a set of Quotes and a Delivery object. When you create a new Task, you're seeking quotes for a new delivery to occur. After requesting a Task, you will receive a set of quotes from different providers, and upon selecting one, you will initiate a Delivery object that tracks the job's delivery lifecycle. * **Delivery**: The Delivery object holds information on the actual progress of the delivery, such as the status, estimated delivery time, courier details, and proof of delivery. It is created after you choose a quote for a Task and it becomes active when a delivery is underway. Nash provides standardized statuses ([Delivery Statuses](/reference/delivery-status)) that help you understand the delivery's progress, regardless of the provider. This simplicity lets you focus on a single integration with Nash rather than deal with the idiosyncrasies of different providers' status naming conventions. ## Putting it into practice Here's an example scenario: You create a Job to deliver an order using your internal fleet (this would be your first Task). If you later realize that you don't have an internal driver available, you might create a new Task for the same Job to get quotes from third-party providers within Nash. Once you select a new quote, this initiates another Delivery object associated with the second Task. ## Common paths in the job object The Job object contains various nested structures. Here are the most commonly used paths to access important information: ### Basic job information * **Job ID**: `Job["id"]` ### Quotes and tasks * **All quotes for the first task**: `Job["jobConfigurations"][0]["tasks"][0]["quotes"]` * **ID of a specific quote** (replace `Index` with the quote's index): `Job["jobConfigurations"][0]["tasks"][0]["quotes"][INDEX]["id"]` ### Advanced tasks When a job has multiple tasks (e.g. it has been reassigned to a different delivery provider), use this to get the most advanced (aka latest) task in the process: `Job["jobConfigurations"][0]["advancedTask"]` ## Delivery status * **Check if a provider has been assigned**: `Job["jobConfigurations"][0]["tasks"][0]["delivery"]` If this value is `null`, no provider has been assigned yet. * **Access delivery information**: `Job["jobConfigurations"][0]["tasks"][0]["delivery"]` This object contains useful attributes such as: * `status` * `dropoffEta` * `courierName` * `courierPhoneNumber` * `courierLocation` * **Fees**: the same object carries the delivery's fee fields (`priceCents`, `waitFeeCents`, `totalPriceCents`, and the rest). What each one means, what the total includes, and when each fee is final is covered in [Delivery fees and pricing](/reference/delivery-fees-and-pricing). ### Example job object Note: All timestamps are in UTC and in this format `%Y-%m-%dT%H:%M:%S.%f` The Job object returned for most endpoints looks like this: ```json theme={"dark"} { "id": "job_R7FXJrfB99bpvpzf6CriJC", "createdAt": "2022-03-05T01:11:51.078927", "portalUrl": "https://portal.usenash.com/active/job_R7FXJrfB99bpvpzf6CriJC", "externalIdentifier": null, "jobMetadata": { "key": "val" }, "isActive": false, "jobConfigurations": [ { "package": { "description": "Test package description", "pickupEndTime": null, "pickupStartTime": "2022-03-05T01:16:51", "dropoffStartTime": null, "dropoffEndTime": null, "valueCents": 100, "taxCents": 950, "serviceFeeCents": null, "itemsCount": 1, "pickupLocation": { "id": "loc_dB4x4pToexhgRvGhZSTHaQ", "address": "185 University Ave, Palo Alto, CA 94301", "formattedAddress": "185 University Ave, Palo Alto, CA 94301, USA", "addressNumber": "185", "addressFormattedStreet": "University Ave", "addressCity": "Palo Alto", "addressCountry": "US", "addressState": "CA", "addressZip": "94301", "addressCounty": "Santa Clara County", "businessName": "Test pickup Business", "instructions": "Test pickup Instructions", "firstName": "Test pickup FirstName", "lastName": "Test pickup LastName", "phoneNumber": "+12345679012", "email": null }, "dropoffLocation": { "id": "loc_5heYem2xKGfNt9twg33kcT", "address": "401 San Antonio Rd, Mountain View, CA 94040", "formattedAddress": "401 San Antonio Rd, Mountain View, CA 94040, USA", "addressNumber": "401", "addressFormattedStreet": "San Antonio Rd", "addressCity": "Mountain View", "addressCountry": "US", "addressState": "CA", "addressZip": "94040", "addressCounty": "Santa Clara County", "businessName": "Test dropoff Business", "instructions": "Test dropoff Instructions", "firstName": "Test dropoff FirstName", "lastName": "Test dropoff LastName", "phoneNumber": "+12345679019", "email": null } }, "tasks": [ { "id": "tsk_7xKGR6PW2pBtuTeRg2b7Bw", "createdAt": "2022-03-05T01:11:51.078927", "status": "COMPLETED", "providerId": "FleetSimulator", "tipAmountCents": 250, "winnerQuote": { "id": "qot_nh5VSwxr4trHsuKHpQbGW6", "providerId": "FleetSimulator", "providerName": "FleetSimulator" }, "quotes": [ { "id": "qot_DSv9NrbiNMjQw556H33LkY", "providerId": "uber_partner", "providerName": "Uber", "providerLogo": "https://nash-provider-logos.s3.us-west-1.amazonaws.com/uber-logo.png", "createdTime": "2022-03-05T01:11:52.912584", "expireTime": "2022-03-05T01:16:52.913410", "priceCents": 1209, "nashFeeCents": 100, "currency": "USD", "pickupWindow": "2022-03-05T01:29:08", "dropoffEta": "2022-03-05T02:03:39", "tollFeeCents": 0, "insuranceFeeCents": 0, "taxAmountCents": 0, "totalPriceCents": 1209 }, { "id": "qot_Fz8tQbiNMjQw556H33LkZ", "providerId": "roadie", "providerName": "Roadie", "providerLogo": "https://nash-provider-logos.s3.us-west-1.amazonaws.com/roadie-logo.png", "createdTime": "2022-03-05T01:11:52.912584", "expireTime": "2022-03-05T01:16:52.913410", "priceCents": 699, "nashFeeCents": 100, "currency": "USD", "pickupWindow": "2022-03-05T01:29:08", "dropoffEta": "2022-03-05T02:03:39", "tollFeeCents": 0, "insuranceFeeCents": 0, "taxAmountCents": 0, "totalPriceCents": 699 }, { "id": "qot_nh5VSwxr4trHsuKHpQbGW6", "providerId": "FleetSimulator", "providerName": "FleetSimulator", "providerLogo": "https://nash-provider-logos.s3.amazonaws.com/FleetSimulator.png", "createdTime": "2022-03-05T01:11:52.417462", "expireTime": "2022-03-05T02:11:52.417463", "priceCents": 0, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-03-05T01:17:52.417416", "tollFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 0 }, { "id": "qot_mfqxvKhCK8jqioCiVZBnXt", "providerId": "frayt_partner", "providerName": "Frayt", "providerLogo": "https://nash-provider-logos.s3.us-west-1.amazonaws.com/frayt-logo.jpeg", "createdTime": "2022-03-05T01:11:52.870453", "expireTime": "2022-03-05T03:11:52.870457", "priceCents": 3239, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": null, "tollFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 3239 }, { "id": "qot_5DyiesH2CXYXunoavBuBux", "providerId": "skipcart_partner", "providerName": "Skipcart", "providerLogo": "https://nash-provider-logos.s3.us-west-1.amazonaws.com/skipcart-logo.png", "createdTime": "2022-03-05T01:11:52.900834", "expireTime": "2022-03-05T01:21:52", "priceCents": 699, "nashFeeCents": 100, "currency": "USD", "pickupWindow": "2022-03-05T01:26:51", "dropoffEta": "2022-03-05T02:11:51", "tollFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 699 }, { "id": "qot_F48QHqWtNdEeZWNGJH9MzJ", "providerId": "senpex_partner", "providerName": "Senpex", "providerLogo": "https://nash-provider-logos.s3.us-west-1.amazonaws.com/senpex-logo.png", "createdTime": "2022-03-05T01:11:53.875782", "expireTime": "2022-03-05T02:11:53.875797", "priceCents": 3450, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-03-05T02:41:53.875791", "tollFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 3450 } ], "failedQuotes": [ { "id": "qot_n7pBYDYawaEFb83VL9C6oc", "providerId": "Entree_OnFleet", "providerName": "Entree", "errorMessage": "Provider Entree_OnFleet is ineligible because of location coverage: pickup_state=CA and dropoff_state=CA. Provider location_coverage=['IL']" }, { "id": "qot_cQWPW48ZDkHLpcR5Bof6Y8", "providerId": "caterwheels_partner", "providerName": "CaterWheels", "errorMessage": "Provider caterwheels_partner is ineligible because of location coverage: pickup_state=CA and dropoff_state=CA. Provider location_coverage=['NY']" }, { "id": "qot_JzcNUsiqrBpDptZpDeuNwk", "providerId": "deluxe_delivery_partner", "providerName": "Deluxe Delivery", "errorMessage": "Provider deluxe_delivery_partner is ineligible because of location coverage: pickup_state=CA and dropoff_state=CA. Provider location_coverage=['NY', 'CT', 'PA', 'NJ', 'DE', 'MD', 'VA']" }, { "id": "qot_gkXDGdT5wJvVbafHvLRrGf", "providerId": "senpex_batch_partner", "providerName": "Senpex (Batch)", "errorMessage": "Provider senpex_batch_partner is ineligible because of PackageDeliveryMode is not scheduled" }, { "id": "qot_FoidPBMEvuFcs6rdVXmDvJ", "providerId": "dropcar_partner", "providerName": "Dropcar", "errorMessage": "Provider dropcar_partner is ineligible because of location coverage: pickup_city='Palo Alto, CA' and dropoff_city='Mountain View, CA'. Provider location_coverage=['Chicago, IL', 'New York, NY']" }, { "id": "qot_QYjd23Rbo7Ca452pL6ACyt", "providerId": "snap_partner", "providerName": "Snap", "errorMessage": "Provider snap_partner is ineligible because of location coverage: pickup_city='Palo Alto, CA' and dropoff_city='Mountain View, CA'. Provider location_coverage=['Chicago, IL']" } ], "delivery": { "id": "dlv_MwQDy3s6ajvw5aQ7eQPt7i", "status": "DROPOFF_COMPLETE", "isActive": false, "pickupEta": null, "dropoffEta": "2022-03-05T01:17:52.417416", "dropoffDeadline": null, "priceCents": 0, "nashFeeCents": 100, "currency": "USD", "documents": [], "courierName": "Tjy Ifulu", "courierPhoneNumber": "+13408480904", "courierLocation": { "lat": 37.4043247, "lng": -122.1112215 }, "proofOfDelivery": [ { "type": "photo_proof_of_delivery", "url": "https://nash-proof-of-delivery-dev.s3-us-west-1.amazonaws.com/dlv_ZkHf4mLVCXy3avKDJE77jj_J2YHkGbRN8kTdjBrgu8gaQ==.jpg" }, { "type": "photo_proof_of_delivery", "url": "https://nash-proof-of-delivery-dev.s3-us-west-1.amazonaws.com/dlv_ZkHf4mLVCXy3avKDJE77jj_AKapMndrIp2DDW5YUnWa9w==.jpg" }, { "type": "signature_proof_of_delivery", "url": "https://nash-proof-of-delivery-dev.s3-us-west-1.amazonaws.com/dlv_ZkHf4mLVCXy3avKDJE77jj_toG5E7I-BZrvXqnIZAVyWw==.jpg" } ], "statusHistory": [ { "createdAt": "2022-03-05T01:12:00.984078", "status": "created" }, { "createdAt": "2022-03-05T01:12:16.067396", "status": "not_assigned_driver" }, { "createdAt": "2022-03-05T01:13:02.459762", "status": "assigned_driver" }, { "createdAt": "2022-03-05T01:14:32.532602", "status": "pickup_enroute" }, { "createdAt": "2022-03-05T01:15:17.533789", "status": "pickup_complete" }, { "createdAt": "2022-03-05T01:16:02.598233", "status": "dropoff_enroute" }, { "createdAt": "2022-03-05T01:17:32.690484", "status": "dropoff_arrived" }, { "createdAt": "2022-03-05T01:18:17.945368", "status": "dropoff_complete" } ], "tollFeeCents": 0, "waitFeeCents": 0, "waitTimeMinutes": 0, "cancellationFeeCents": 0, "returnFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 0 } } ] } ] } ``` ## Public tracking URL Nash provides your end customers with a way to track deliveries without having to be logged into the Nash Portal. Nash will automatically send text alerts which include the tracking link for a live delivery to the end recipient, unless you would like to disable this feature for your organization. The easiest way to get the link is to read the `publicTrackingUrl` field from the job or order response; the URL takes this form: `https://tracking.usenash.com/{jobId}` `https://tracking.sandbox.usenash.com/{jobId}` See [Embed the Nash live-tracking experience](/reference/nash-embedding) for embedding the tracking view in your own app. # List/Search Jobs Source: https://docs.usenash.com/api-reference/job/listsearch-jobs get /v1/jobs List jobs (deliveries) with filters, sorting, and pagination. Returns a slim payload suitable for discovery; use GET /v1/jobs/ for full details. # Reassign a Task Source: https://docs.usenash.com/api-reference/job/reassign-task-by-job-id-and-task-id post /v1/jobs/{jobId}/{taskId}/reassign Create a new task in a job based on a given job ID and task ID. Reassign a task within an existing job. You can update task parameters along the way, including pickup and dropoff times — which is how you reschedule a task. ### Rescheduling a task When reassigning a task, you can update the following time fields to reschedule it: * `pickupStartTime` * `pickupEndTime` * `dropoffStartTime` * `dropoffEndTime` This lets you change the timing of an existing task without creating a new job or task from scratch. Times you don't specify are calculated by Nash, based on the existing job configuration and current conditions. # Refresh Quotes Source: https://docs.usenash.com/api-reference/job/refresh-quotes-by-job-id-and-task-id post /v1/jobs/{jobId}/tasks/{taskId}/refresh_quotes Regenerate quotes for a given job's task. # Select Quote Source: https://docs.usenash.com/api-reference/job/select-quote post /v1/select_quote Select the quote you would like to execute from a specific job. # Update Job Source: https://docs.usenash.com/api-reference/job/update-job-by-id patch /v1/jobs/{id} Update fields on a Nash job. Updating a job should always work before a quote is selected; once one has been selected, Nash returns an error if the fleet rejects the change. ## Pre-dispatch updates Updating a job should always work before a quote is selected. Some updates may result in pricing changes. You can only update the package delivery times and the pickup or dropoff addresses before an order has been dispatched to a fleet. If any task in a job is dispatched and you attempt to update a delivery time or address, the endpoint returns an error. If you update any of the following fields, the quotes returned by the job are refreshed and the endpoint returns a new set of quotes. Any existing quotes (and quote IDs) become invalid for dispatch. The fields that force a quote refresh: 1. `pickupAddress` 2. `dropoffAddress` 3. `packagePickupStartTime` 4. `packagePickupEndTime` 5. `packageDropoffStartTime` 6. `packageDropoffEndTime` 7. `packageDeliveryMode` 8. `packageRequirements` 9. `packageMinimumVehicleSize` 10. `packageValue`: this field can also be updated in the [post-dispatch stage](/api-reference/job/update-job-by-id#post-dispatch-updates) ## Updating times To update the times of a job, provide at least one of `packagePickupStartTime`, `packagePickupEndTime`, `packageDropoffStartTime`, and `packageDropoffEndTime`. Times you don't specify are calculated by Nash, and any previous times left out of the update request are overwritten. So to change one time field and keep the rest, send the others in the same request. If you change `packageDeliveryMode` from `SCHEDULED` to `NOW`, you don't need to specify any other time fields. ## Post-dispatch updates If a quote has been selected or the job was dispatched by auto-dispatch, Nash returns an error if any of the assigned fleets in the job reject the update. In the post-dispatch stage, updating the package value of the job may result in a price change, depending on how the providers calculate their pricing. Below is a list of fields that can be updated, with more detail on pre-dispatch and post-dispatch restrictions. # Update job by job id and job configuration id Source: https://docs.usenash.com/api-reference/job/update-job-by-job-id-and-job-configuration-id patch /v1/jobs/{jobId}/{jobConfigurationId} # Server Health Check Source: https://docs.usenash.com/api-reference/miscellaneous/server-health-check get /v1/health-check Check the health of the Nash API server. # Add Contact to the Deny List Source: https://docs.usenash.com/api-reference/notifications/add-a-contact-to-the-deny-list post /v1/organization-management/notifications/deny-list Adds a contact to the deny list to opt the user out of communications. # Get Contacts in the Deny List Source: https://docs.usenash.com/api-reference/notifications/get-contacts-in-the-deny-list get /v1/organization-management/notifications/deny-list Retrieves a list of contacts that have opted out of communications. # Get Notification Trigger Catalog Source: https://docs.usenash.com/api-reference/notifications/get-notification-trigger-catalog get /v1/notification-triggers/catalog The full notification-trigger catalog. Each entry describes a trigger's `type` slug, human `title`, the `value` used to subscribe automations, the `entityType` (route/delivery/task/order/activity), its `statuses` vocabulary, the JSON Schema of its create `parameters`, and the `supportedFilters` / `supportedActions`. The shared `filterCriteria` (dispatch strategies, contracts, zones, metadata conditions) and `actions` are described once at the top level. Designed for agents and humans to discover what can be automated and how. # List Notification Trigger Templates Source: https://docs.usenash.com/api-reference/notifications/list-notification-trigger-templates get /v1/notification-triggers/templates List pre-defined notification trigger templates. Each template has a `type` slug (e.g. 'tasks_attempts_exhausted') and a human-readable `title` (e.g. 'Tasks Attempts Exhausted'). Use the `type` when creating a trigger via POST /v1/notification-triggers. # Notifications Source: https://docs.usenash.com/api-reference/notifications/notifications Configure notification triggers, channels, and message variables to keep customers and internal teams informed about deliveries. Nash has a built-in, fully customizable notifications platform. This platform enables you to own the end-to-end customer experience and keep your internal teams up to speed on events and potential issues. Each notification is broken down into two primary parts: defining the **trigger** and **message**, and identifying the **audience**. The trigger and message are defined in each Notification Trigger, and the audience is defined by the Destination. By separating the trigger and message from the audience, Nash makes it easy to send multiple notifications to the same audience. Within the message section, you will have access to delivery-specific variables that you can use to customize the message to your end customer or internal team. Notification Triggers ## Supported channels Nash can send notifications across the following channels: * SMS * Email * Slack * Webhook * WhatsApp A single Destination can mix channels. Triggers can also hand off to Nash Agent — starting a custom agent to investigate and resolve an issue, or pushing an alert to your team. ## Default notifications By default, your account will have 3 customer-facing SMS notifications enabled, and 2 internal notifications that are disabled. To see these notifications and edit them if necessary, go to **Settings > Notifications** in the Nash Portal. ### Customer-facing notifications 1. **Status transitions to "Pickup Complete"** Message: `Your order from {{pickup_business_name}} has been picked up. Stay tuned for more text updates and track the delivery here {{job_tracking_link}}` 2. **Status transitions to "Dropoff Enroute"** Message: `The driver is on their way. They should arrive by {{dropoff_eta}}.` 3. **Status transitions to "Dropoff Complete"** Message: `Your order from {{pickup_business_name}} has been delivered.` ### Internal notifications (disabled by default) To activate internal notifications, you need to create a Destination and add it to each notification. You can select to receive notifications via SMS, email, Slack, and/or webhook. 1. **Delivery Booked** Message: `A new delivery has been scheduled with Nash. Track here: {{job_tracking_link}}.` 2. **Task Reassigned** Message: `Task reassigned on job {{job_id}}. Track here: {{job_tracking_link}}.` ## Recommendations ### Setting up flags Flags allow teams to be proactive during live delivery operations. You can enable flags for any notification, and flagged deliveries appear in the **Flagged** view in the Portal. ### Recommended triggers We strongly recommend setting up and enabling these triggers: 1. **Autodispatch Failed**: Alerts when a delivery fails to auto-dispatch. 2. **Address Validation Failed**: Notifies when there's an issue with the customer's address. ## Trigger options Triggers are grouped by what they watch. Enable, disable, and fine-tune any of them under **Settings > Notifications** in the Portal. For the authoritative, machine-readable list, call [List notification trigger templates](/api-reference/notifications/list-notification-trigger-templates). | Trigger | Fires when | | :- | :- | | **Delivery** | | | Delivery Status Transition | A delivery moves to any of the statuses you choose. One trigger can watch several statuses, each firing on its own. | | Delivery Incident Report | A courier files an incident report; can filter to created or resolved reports. | | Task Reassignment | A delivery is reassigned — every time, or after a set number of reassignments. | | Autodispatch Failed | Automatic dispatch can't assign a provider. | | Delivery Cancellation Failed | A cancellation attempt fails. | | Job Creation Failed | A job can't be created. | | Internal Driver Not Assigned | An internal-fleet delivery still has no driver after a set time. | | Delivery Completed Too Early | A delivery is marked complete before its scheduled window. | | Tasks Attempts Exhausted | Every delivery attempt has been used. | | Status Triggered Too Early | A status is set earlier than expected. | | Driver Stacking | Multiple deliveries are stacked on one driver. | | Time Elapsed Since Status | A delivery holds a status for a set time without moving on. | | Proof of Delivery Uploaded | A proof-of-delivery photo is uploaded. | | Courier Updated | The assigned courier or provider changes. | | Timing Updated | A delivery's ETA or timing changes. | | AI Conversation Completed | A Nash AI conversation about a delivery finishes. | | Delivery Note Added | A note is added to a delivery. | | **Driver** | | | Driver Geolocation | A driver is within, beyond, or stopped for a set distance or time from pickup or dropoff. | | Zone Geolocation | A driver enters or leaves a configured zone. | | Driver Location Inconsistent with Delivery Status | A driver's location doesn't match the reported delivery status. | | Pickup Delayed | The pickup ETA runs too far past the scheduled pickup time. | | Dropoff Delayed | The dropoff ETA runs too far past the scheduled dropoff time. | | **Status** | | | "Stuck" Status | A delivery holds a status longer than expected. | | Statuses Changed Too Quickly | Delivery statuses change faster than they should. | | Live Monitoring Status | A delivery's live-monitoring health changes. | | Activity Status Transition | A scheduled activity changes status. | | **Order** | | | Nash Order Needs Attention | An order needs manual attention. | | Nash Order Ready to Fulfill | An order is ready to fulfill. | | Order Not Dispatched Close to Pickup | An order still isn't dispatched as pickup approaches. | | Address Validation Failed | An address can't be validated. | | Order Modified | An order's details change. | | Delivery Window Booked Timeout | A booked delivery window times out without confirmation. | | Optimization Unassigned Order | Optimization leaves an order unassigned. | | Optimization Action Status | An optimization action succeeds, fails, or is skipped. | | API Based | You trigger it directly from the API. | | **Refund** | | | Refund Request | A refund request moves through review — under review, needs attention, approved, denied, and more. | ## Dynamic text options Here are variables you can use in notification messages. The message editor in the Portal autocompletes the full, current list as you type `{{`, and some triggers expose extra variables specific to their event (for example, refund or optimization details). Nash adds to the list regularly. | Variable | Description | | :- | :- | | **Customer** | | | `{{customer.first_name}}` | The customer's first name (in dropoff address) | | `{{customer.last_name}}` | The customer's last name (in dropoff address) | | `{{customer.phone_number}}` | The customer's phone number | | `{{customer.email}}` | The customer's email address | | `{{customer_feedback_link}}` | The URL for the customer to provide feedback | | **Pickup** | | | `{{org_name}}` | Name of your organization in Nash | | `{{pickup_business_name}}` | The business name from where the order(s) were picked up | | `{{pickup_business.phone_number}}` | The phone number associated with pickup location | | `{{pickup_location.email}}` | The email address associated with the pickup location | | `{{pickup_eta}}` | The pickup ETA | | `{{pickup_datetime}}` | The actual pickup datetime localized to the pickup location | | `{{pickup_address}}` | The pickup address | | `{{pickup_instructions}}` | Instructions for the pickup | | **Dropoff** | | | `{{dropoff_eta}}` | The dropoff ETA | | `{{dropoff_datetime}}` | The dropoff datetime localized to the dropoff location. | | `{{dropoff_business_name}}` | The dropoff business name. | | `{{dropoff_location.address_first_line}}` | The first line of the dropoff address | | `{{proof_of_delivery_photo_url}}` | The URL link to the photo proof of delivery | | `{{dropoff_address}}` | The dropoff address | | `{{dropoff_instructions}}` | Instructions for the dropoff | | `{{pod_upload_url}}` | A link to upload proof of delivery | | **Job** | | | `{{job_id}}` | The job ID e.g. job\_oBDDbYnKFCHAfivtjMoabt | | `{{job_cfg_id}}` | The job configuration ID e.g. cfg\_SuC7dAU8VErdhzx9Wa345k | | `{{job_metadata.X}}` | Pulls from the key X in job\_metadata. X can be any field | | `{{job_tracking_link}}` | The link to track the job | | `{{job_details_url}}` | The URL to view job details | | `{{job_external_identifier}}` | The external ID of a job | | `{{job_feedback_url}}` | The URL for providing job feedback | | `{{delivery_id}}` | The Nash delivery ID | | `{{description}}` | The package description | | **Delivery Provider** | | | `{{provider_delivery_id}}` | The delivery ID provided by the delivery provider | | `{{provider_name}}` | The name of the delivery provider | | `{{provider.slack_channel}}` | The Slack channel configured for the assigned provider | | `{{courier.phone_number}}` | The courier's phone number | | `{{driver.name}}` | The name of the driver/courier | | `{{driver.phone_number}}` | The phone number of the driver/courier | | `{{delivery_incident_report_type}}` | The type of the delivery incident | | `{{delivery_incident_report_message}}` | The message contained in the delivery incident | | **Nash** | | | `{{nash_order_id}}` | The Nash order ID | | `{{nash_order_portal_url}}` | The URL to view the order in the Nash Portal | | `{{nash_order_validation_errors}}` | Any validation errors associated with the Nash order | | `{{nash_order_external_id}}` | The external ID of the Nash order | | `{{nash_order_packing_slip_url}}` | The packing slip URL (ZPL) for the Nash order | | `{{nash_order_packing_slip_pdf_url}}` | The packing slip PDF URL for the Nash order | | `{{nash_order_pickup_location.email}}` | The email associated with the pickup location for the Nash order | | `{{nash_order_dropoff_location.email}}` | The email associated with the dropoff location for the Nash order | | `{{nash_order_pickup_business_name}}` | The business name of the pickup location for the Nash order | | `{{nash_order_dropoff_business_name}}` | The business name of the dropoff location for the Nash order | | `{{nash_order_pickup_time}}` | The pickup time for the Nash order | | `{{reference_id}}` | The order's reference ID | | **Refund** | | | `{{refund_request_id}}` | The refund request ID | | `{{refund_request_status}}` | The refund request status | | `{{refund_request_amount}}` | The refund request amount | | `{{refund_request_reason}}` | The reason given for the refund request | | **Misc** | | | `{{request_id}}` | The ID of the request as generated by Nash's API service. Helpful for debugging purposes. | ## Opt contacts out of notifications through Nash Nash maintains a communication "deny list" for contacts who have opted out of communications. The deny list includes a type of communication (e.g., email or SMS) and the value for the type (e.g., [customer@usenash.com](mailto:customer@usenash.com)). This way, your organization can have fine-grained control of who receives notifications via the Nash platform and who doesn't. # Remove Contact from the Deny List Source: https://docs.usenash.com/api-reference/notifications/remove-a-contact-from-the-deny-list delete /v1/organization-management/notifications/deny-list Removes a contact from the deny list to opt the user back into communications. # Create Optimization Strategy Source: https://docs.usenash.com/api-reference/optimization-strategies/create-optimization-strategy post /v1/optimization-strategies Create a new optimization strategy for the authenticated organization. A strategy configures how orders are grouped into routes (service times, pickup-time windows, contracts, store locations, zones, and optional cron schedules). # Delete Optimization Strategy Source: https://docs.usenash.com/api-reference/optimization-strategies/delete-optimization-strategy delete /v1/optimization-strategies/{id} Soft-delete an optimization strategy. Fails if the strategy is referenced by an automation logic or an active automation rule. # Get Optimization Strategy Source: https://docs.usenash.com/api-reference/optimization-strategies/get-optimization-strategy get /v1/optimization-strategies/{id} Returns a single optimization strategy by id. # List Optimization Strategies Source: https://docs.usenash.com/api-reference/optimization-strategies/list-optimization-strategies get /v1/optimization-strategies Returns all optimization strategies for the authenticated organization (including soft-deleted). # Update Optimization Strategy Source: https://docs.usenash.com/api-reference/optimization-strategies/update-optimization-strategy patch /v1/optimization-strategies/{id} Update an existing optimization strategy. Only provided fields are updated. Supplying `optimization_schedules` replaces the existing schedules. # Add Custom Event to Order Source: https://docs.usenash.com/api-reference/order/add-custom-event-to-order post /v1/order/{id}/events Adds a customizable event to an existing Nash order. Custom order events will be shown in the order events page alongside other Nash-generated events. ### Limits * **Rate limits:** Max 3 requests/second * **Size limit:** Request body must not exceed 1kb # Archive order Source: https://docs.usenash.com/api-reference/order/archive-order delete /v1/order/{id} Archive (soft-delete) an order. Archived orders are hidden from default listings but can be restored using the unarchive endpoint. Nash never archives an order on a timer. An order you don't dispatch keeps its status until you archive it here or in the Portal, or until a cancellation from a connected demand source such as Shopify or Toast archives it for you. Leaving it active has one practical cost: its quotes carry an `expireTime`, and dispatching with an expired quote returns `400`. [Refresh Quotes](/api-reference/order/refresh-order-quotes) before you dispatch, or let auto-dispatch fetch new quotes on its own. Archived orders can be restored with [Unarchive Order](/api-reference/order/unarchive-order). # Autodispatch Order Source: https://docs.usenash.com/api-reference/order/autodispatch-order post /v1/order/{id}/autodispatch Automatically select a quote based on the order's dispatch strategy. This is similar to [Autodispatch Job](/api-reference/job/autodispatch-job) Autodispatch selects a quote for you instead of you choosing one by hand. Set the selection rules up front by creating a [dispatch strategy](/api-reference/dispatch-strategies/dispatch-strategy) and attaching it to the order; Nash then picks the winning quote when you call this endpoint. This fits a checkout flow. You create the order and show the customer their delivery options while the order sits unfulfilled. When the customer places the order, you call this endpoint instead of selecting a quote yourself, and the order goes out under the dispatch strategy you configured. ## Autodispatch preferred quote When you call `/autodispatch`, Nash selects the quote tagged `autodispatch_preferred_quote`. Read that tag ahead of time to know which price to show the customer during checkout: take the full list at `order["quotes"]` and filter for the one with `"autodispatch_preferred_quote"` in `quote.tags`. Keep `Enable Autodispatch` switched off, so the order isn't dispatched the moment it's created. # Confirm order Source: https://docs.usenash.com/api-reference/order/confirm-order patch /v1/order/{id}/confirm Confirm a valid order, changing its status from 'valid' to 'confirmed'. # Create or Update Order by External Identifier Source: https://docs.usenash.com/api-reference/order/create-or-update-order-by-external-identifier post /v1/order/external-identifier/{externalId} Create an order or update an existing one, keyed by your own external identifier. Create an order or update an existing one by your own identifier. This suits checkout workflows: create the order from the customer's cart or order ID, then update it once the customer completes checkout. If two requests arrive with the same `externalId`, Nash blocks the second until the first has been committed to the database and its response returned to the client. The second request is then processed as an update, since the first already created the order. Only one order is created per `externalId`, even when requests are made simultaneously. Updating an archived order will also unarchive it. If `quotes_only` is included in the `tags` field, the response will include quotes for the order. # Create or Update Orders by External Identifier Source: https://docs.usenash.com/api-reference/order/create-or-update-orders-by-external-identifier-v2 post /v2/orders/upsert/bulk Create or update multiple orders by external identifier in Nash Create or update multiple orders in one call, keyed by your own external identifiers. Use it for batch work: importing orders from an external system, or updating many orders at once by their external IDs. Create or Update Orders by External Identifier behavior: For each order with an `external_id`, Nash will check if an order with that external ID already exists. If it exists, the order will be updated; if not, a new order will be created. A hard failure on one order does not fail the batch; the rest still commit. The response returns a `results` array (one entry per input order, in the same order as the request) with `success` or `failed` status, plus a `summary` of the counts. Updating archived orders will also unarchive them. Common uses: * Bulk importing orders from external systems * Synchronizing order data between systems * Batch updating multiple orders by external identifier # Create Order Source: https://docs.usenash.com/api-reference/order/create-order post /v1/order Create an order in Nash See [Order Address Validation](/api-reference/order/order-address-validation-geocoding) for more info on how Nash validates addresses and how you can use your own geocoding service. # Create Orders Source: https://docs.usenash.com/api-reference/order/create-orders post /v1/orders Create multiple pre-dispatch orders in a single request. Each order is created independently with its own pickup and dropoff details. # Bulk Order Actions Source: https://docs.usenash.com/api-reference/order/create-update-upsert-or-delete-orders-asynchronously-in-bulk post /v1/orders/bulk Create, update, upsert, or delete orders asynchronously in bulk. The **upsert** (update or insert) action will update existing orders that match the provided `externalId`, and create new orders for those that don't. The **delete** action will archive the order, akin to the [Archive Order endpoint](/api-reference/order/archive-order). Status field returned is for the bulk operation, not the individual orders and can be one of the following: * `QUEUED`: The request has been received and is waiting to be processed in the bulk operation queue * `IN_PROGRESS`: The request is currently being worked on * `SUCCESS`: The request was successfully processed without any errors to the overall async process (individual orders might have failed) * `FAILED`: The request encountered an error and could not be completed * `CANCELLED`: The request processing was manually cancelled or terminated before completion * `SKIPPED`: The request was intentionally not processed due to validation issues or business rules * `SCHEDULED`: The bulk action is set to be processed at a future specified time (n/a) Bulk order actions are asynchronous operations. You can either poll the [GET endpoint](/api-reference/order/get-response-from-asynchronous-bulk-order-operation) using the returned `id`, and/or opt-in to receive the results via webhook. The webhook event is `order.bulk_operation.completed` and will be sent once the bulk operation is complete. # Get order Source: https://docs.usenash.com/api-reference/order/get-order get /v1/order/{id} Retrieve a single order by ID, including its pickup/dropoff details, status, and metadata. # Get Order Documents Source: https://docs.usenash.com/api-reference/order/get-order-documents get /v1/order/{id}/documents Get the documents for an order # Create Quote Source: https://docs.usenash.com/api-reference/order/get-order-quotes post /v1/order/get_quotes Create a new quote for an order This endpoint always creates a new order from the payload you send, tags it `quotes_only`, and returns it with its provider quotes. It doesn't look up an existing order, even when the payload carries an `externalId` you've used before; if that `externalId` already belongs to another order, the request fails with `400 VALIDATION_ERRORS`. To get fresh quotes for an order that already exists, call [Refresh Quotes](/api-reference/order/refresh-order-quotes) with its `orderId` or `externalOrderId`. See [Choose an order creation flow](/api-reference/order/order#choose-an-order-creation-flow) for how the quote-first flow fits together. # Get Bulk Order Response Source: https://docs.usenash.com/api-reference/order/get-response-from-asynchronous-bulk-order-operation get /v1/orders/bulk/{id} Get the response from an asynchronous bulk order operation. # List/Search Orders Source: https://docs.usenash.com/api-reference/order/listsearch-orders get /v1/orders List orders with filters, sorting, and pagination. Returns a slim payload suitable for discovery; use GET /v1/order/ for full details. # Orders overview Source: https://docs.usenash.com/api-reference/order/order What an order is, how to choose an order creation flow, and how orders relate to jobs (deliveries). An Order represents the delivery details for package(s) in the real world. It's the most flexible way to initiate deliveries in Nash. Orders enable you to: * Update the delivery details before a delivery is created (contact details, addresses, times, and requirements) *e.g. E-Commerce Checkout Experience* * Understand whether the order meets the criteria to become a delivery (validation) *e.g. Error handling as early as possible* * Batch orders through Nash Platform (not optimized or batched on your end) *e.g. Optimizing catering routes* When one or more Orders are dispatched, they become a Job. ## Choose an order creation flow The order endpoints return similar response objects, but they create, validate, quote, and dispatch Orders differently. ### Recommended checkout flow For most checkout integrations, start with [Create Quote](/api-reference/order/get-order-quotes), then update the returned Order after checkout. This separates the eligibility and price check from the final fulfillment details. Send the details needed to evaluate delivery eligibility and price. Create Quote always starts a new Order; it does not look up an existing Order by `externalId`. If order-level validation fails, Nash returns `400 VALIDATION_ERRORS` and does not save the new Order. A successful response includes the saved Order, its provider quotes, and the `quotes_only` tag. Call [Update Order](/api-reference/order/update-order) with the returned Order ID. If the Create Quote request included `externalId`, you can use that identifier in the same update path instead. Send the final fulfillment details and the complete `tags` list without `quotes_only`. Nash runs full order validation. If the Order is valid, it can auto-dispatch when its dispatch strategy has auto-dispatch enabled. Using `externalId` in the Update Order path does not turn it into an upsert. The Order must already exist. Update Order refreshes an existing quote set after the update. [Create or Update by External Identifier](/api-reference/order/create-or-update-order-by-external-identifier) creates the Order when no match exists and requests fresh quotes on each call while `quotes_only` remains. Use a different creation flow when you do not need quotes before saving an Order or need one request that creates or updates by your own identifier. | Flow | Use when | Behavior | | - | - | - | | [Create Order](/api-reference/order/create-order) | You need a new Order without quotes in the same call. | Runs order-creation workflows and can auto-dispatch a valid Order. | | [Create Quote](/api-reference/order/get-order-quotes) | You need to check delivery eligibility and price before checkout. | Always attempts a new Order; it does not look up `externalId`. On success, it saves the Order, requests quotes, and adds `quotes_only`. | | [Create or Update by External Identifier](/api-reference/order/create-or-update-order-by-external-identifier) | You need one request to create the Order when your identifier is new and update it when it already exists. | Creates once, then updates. With `quotes_only`, each request replaces the previous quotes. | `quotes_only` is persistent Order state, not a response option. When an Order created by external identifier remains tagged `quotes_only`, each later create-or-update request asks providers for fresh quotes. Omitting `tags` preserves the existing tag. To return the Order to its normal dispatch flow, send the complete tags list without `quotes_only`. ### Validation behavior Create Order and Create Quote use normal order-creation validation when `quotes_only` is absent from the request. Create Quote adds the tag only after it validates the Order and requests quotes. If `externalId` already belongs to another Order, Create Quote returns `400 VALIDATION_ERRORS` and does not save the new attempt. Later updates use quote-specific validation while the tag remains, so Nash can price an Order that has enough information for quoting but is not ready for fulfillment. | Flow | Response | Order state | | - | - | - | | Create Order | `200` with `validationErrors` | Saved as `needs_attention`. | | Create Quote | `400 VALIDATION_ERRORS` | Not saved. | | Create or Update by External Identifier | Without `quotes_only`: `200` with saved errors. With it: quote generation can return `400`. | The Order may already exist after a `400`, so retrieve it by external identifier before retrying. | The outcomes in the table describe order-level validation, which runs after Nash accepts the request body. Request-shape errors, such as a field with the wrong type, return `422 VALIDATION_ERRORS` before the Order is created or updated. When moving from checkout to dispatch, update the Order with the remaining fulfillment fields and remove `quotes_only` before relying on normal validation or auto-dispatch. The Orders page in the Nash Portal, with orders grouped into status tabs ## Order data model The figure here shows where an order goes. When it is dispatched, an order becomes a job. The job has a job configuration, which holds the package and one or more tasks. Each task is one attempt at the delivery, with its own quotes and, once a quote is selected, its own delivery. See [Jobs overview](/api-reference/job/jobs) for the path to each part. **Order status** * `needs_attention`: The order requires attention due to some data issues (e.g. invalid address) * `valid`: The order is valid and is ready to be dispatched to a Nash Job & Delivery. * `dispatched`: The order has been dispatched and a job (delivery) created for it. Open the job from the **view dispatched job** action in the Portal. * `archived`: The order has been archived and is moved to the archived tab in the Nash Portal. Note: All timestamps are in UTC and in this format `%Y-%m-%dT%H:%M:%S.%f` ```json theme={"dark"} { "id": "ord_PewiSWzxmCFPzW62Ry3A6V", "createdAt": "2024-01-25T21:10:43.722214", "externalId": null, "externalGroupId": null, "referenceId": null, "portalUrl": "https://portal.sandbox.usenash.com/orders/ord_PewiSWzxmCFPzW62Ry3A6V", "publicTrackingUrl": "https://tracking.sandbox.usenash.com/ord_PewiSWzxmCFPzW62Ry3A6V", "status": "valid", "tags": null, "orderMetadata": null, "pickupAddress": null, "pickupPhoneNumber": "+15005550005", "pickupFirstName": "pickupFName", "pickupLastName": "pickupLName", "pickupBusinessName": "pickupBusinessName", "pickupInstructions": "Ask for the manager at the counter", "pickupBarcodes": null, "dropoffAddress": "401 San Antonio Rd, Mountain View, CA 94040", "dropoffPhoneNumber": "+15005550006", "dropoffFirstName": "dropoffFName", "dropoffLastName": "dropoffLName", "dropoffBusinessName": "dropoffBusinessName", "dropoffInstructions": "Leave the package at the front desk", "dropoffBarcodes": null, "deliveryMode": "now", "pickupStartTime": null, "pickupEndTime": null, "dropoffStartTime": null, "dropoffEndTime": null, "description": "Test package description", "itemsCount": null, "requirements": ["photo_proof_of_delivery"], "minimumVehicleSize": null, "currency": null, "documents": [ { "id": "doc_ELre5GCELQ6w3UHKAgbm6F", "type": "shipping_label", "url": "https://nash-documents.s3-us-west-1.amazonaws.com/dlv_bR7NWwhWSQRohyjs7UZd7D_fEavuZda787M70sJEhtCAA==.png", "data": null, "contentType": "image/png" }, { "id": "doc_nuKEL5W8mMgnXuRrFqwwsc", "type": "pickup_label", "url": "https://nash-documents.s3-us-west-1.amazonaws.com/dlv_bR7NWwhWSQRohyjs7UZd7D_QPiARbKwfJRkC94KVQrT8w==.png", "data": null, "contentType": "image/png" } ], "valueCents": "5000", "tipAmountCents": "100", "width": null, "depth": null, "height": null, "volume": null, "weight": null, "dispatchStrategyId": null, "validationErrors": {} } ``` # Order address validation (geocoding) Source: https://docs.usenash.com/api-reference/order/order-address-validation-geocoding How Nash validates addresses, and how to bring your own address components instead. By default, Nash validates an address on every request when passed a pickup or dropoff address or Google Place ID. When provided with a raw address, Nash uses several geocoding services and selects the most appropriate result based on the region of the address. Once geocoded, Nash will populate the componentized pickup/dropoff address in the corresponding component fields, which will be returned in the API response, providing you with structured and standardized address data. ### Bypassing Nash's address validation If you prefer to use your own geocoding service, you can bypass Nash's address validation by passing the componentized address fields directly. To do this, provide the following fields **instead of** the `Address` or `PlaceId` when creating or updating an order. **Pickup** * `pickupAddressNumber` * `pickupAddressSecondarynumber` * `pickupAddressFormattedStreet`\* * `pickupAddressCity`\* * `pickupAddressCounty` * `pickupAddressState` * `pickupAddressZip`\* * `pickupAddressCountry`\* * `pickupLat`\* * `pickupLng`\* **Dropoff** * `dropoffAddressNumber` * `dropoffAddressSecondarynumber` * `dropoffAddressFormattedStreet`\* * `dropoffAddressCity`\* * `dropoffAddressCounty` * `dropoffAddressState` * `dropoffAddressZip`\* * `dropoffAddressCountry`\* * `dropoffLat`\* * `dropoffLng`\* *\* = Required components of the address if using address components* When using componentized address fields, do not pass `Address` or `PlaceId` in the request to create or update an order. ### Using a store location as a pickup or dropoff point As an alternative to providing an address, address components, or a Google Place ID, you can specify a store location as the pickup or dropoff point for an order. To do this, pass the Store Location ID (starting with `stl_`) or the Store Location External ID in the request when creating or updating an order. When a Store Location ID or External ID is provided without any accompanying address information, Nash will automatically populate the pickup or dropoff address fields using the address associated with the specified store location. This eliminates the need to manually input the address details for frequently used store locations. If a Store Location ID or External ID is provided along with address information, the address information (including name, phone number, email, instructions) will take precedence over the store location's address. # Order requirements Source: https://docs.usenash.com/api-reference/order/order-requirements Requirements you can attach to a Nash order, such as proof of delivery, age verification, and barcode scans. When creating a Nash order, Nash allows specifying different requirements to suit your order/package delivery. Available options are: | Requirement | Description | | - | - | | `photo_proof_of_delivery` | Require a picture of the order after drop off | | `signature_proof_of_delivery` | Require the customer's signature after drop off | | `photo_proof_of_pickup` | Require a picture of the order after picking it up | | `signature_proof_of_pickup` | Require the customer's signature after picking up the order | | `age_verification_on_delivery` | Requires driver to verify recipient's age from a government-issued ID.

minimum\_age depends on country:
US: 21
AU: 18
NZ: 18
GB: 18
CA: 18 | | `alcohol` | Lets the provider know there's alcohol in the order and requires driver to verify recipient's age from a government-issued ID.

minimum\_age depends on country:
US: 21
AU: 18
NZ: 18
GB: 18
CA: 18 | | `id_verification_on_delivery` | Requires driver to verify the recipient's ID when dropping off the order (for pharmaceutical delivery) | | `barcode_scan_on_pickup` | Requires driver to scan a barcode on pickup.
Barcode values are provided in `pickupBarcodes` | | `barcode_scan_on_dropoff` | Requires driver to scan a barcode on dropoff.
Barcode values are provided in `dropoffBarcodes` | | `barcode_scan_on_return` | Requires driver to scan a barcode when returning an order | | `display_barcode_on_pickup` | Requires driver to display a barcode at pickup (for example, for store staff to scan).
Barcode values are provided in `pickupBarcodes` | | `meet_on_delivery` | Instructs driver to meet customer on delivery and hand off order.
Note: Supported by select providers only, and best effort rather than guaranteed. | | `shipping_label` | Requires driver to check or scan the shipping label at the pickup and drop-off location.

Note: The shipping label sometimes should be printed and stamped to the order depending on the provider. | | `two_person_team` | Requires a two-person team to assist in the delivery | | `pincode_verification` | Requires a pincode entry at dropoff. The pincode will be shared with the dropoff customer through SMS.

Note: The pincode will also be added to the proofOfDelivery object and will trigger a webhook event of type `delivery.pincode_verification`.
example:
`"proofOfDelivery": [{"type": "pincode_verification","pincode": "1578"}]` | | `parking_check_in` | Requires courier to check in upon arrival while waiting in their vehicle. The courier will provide the parking location information.

Note: The parking location can be found in the `delivery.courier_vehicle.parking_location` field. | | `schedule_ii_controlled_substances` | Require the order to be handled by a provider that is certified to handle Schedule II Controlled Substances (US) | | `tobacco` | Lets the provider know there's tobacco in the order and requires driver to verify recipient's age from a government-issued ID.

minimum\_age depends on country:
US: 21
AU: 18
NZ: 18
GB: 18
CA: 18 | | `vapes` | Lets the provider know there are vape products in the order | | `pick_and_pack` | Enables item-level fulfillment with store picking before delivery. See [Pick and Pack](/api-reference/store-catalog/pick-and-pack). | | `custom:{requirement_name}` | Custom requirement that will filter any provider/contract without this specific requirement. The requirement must be added to the wanted contract before being used. Example: "custom:refrigerated" will only match providers that explicitly support refrigerated transport. | ### Notes **Multiple requirements are allowed.** Example: `requirements: ["photo_proof_of_delivery", "signature_proof_of_delivery"]` **Not all providers support all requirements.** Providers that don't support provided order/package requirements will be ineligible and will show in the failedQuotes section. A quote rejected for that reason carries a machine-readable `errorCode` and an `errorDetails` object naming the requirements the provider wouldn't take, so you can branch on the cause instead of reading the message. When *every* eligible provider rejects a package on its requirements, the delivery carries a `quoteFailure` summary and a normalized `failureCode`, and dispatching it returns a `422` rather than a generic failure. By default, `requirements: ["photo_proof_of_delivery"]` `requirements=[]` means no requirements are expected. # Order validations Source: https://docs.usenash.com/api-reference/order/order-validations The validations Nash applies when you create an order, covering addresses, required fields, data formats, enums, and datetimes. When you create a Nash order, these validations run before the order is accepted. ### Address & location validation * **Address input exclusivity**: * An error is raised if you provide both `pickupAddress` and `pickupPlaceId` (or the dropoff equivalents). You must use one or the other. * An error is raised if you provide address components (e.g., `pickupAddressFormattedStreet`) along with `pickupAddress` or `pickupPlaceId`. * **Address input requirement**: If no store location ID is provided, Nash requires one of the following for both pickup and dropoff: 1. A single-line address string (`pickupAddress` / `dropoffAddress`). 2. A Google Place ID (`pickupPlaceId` / `dropoffPlaceId`). 3. A set of address components (street, city, zip, etc.). * If none of these are present, a "required field" error is added for the address. * **Store location logic**: * If a `pickupStoreLocationId` or `pickupExternalStoreLocationId` is provided, Nash looks up the corresponding store location. * **Existence check**: Fails if the store location ID doesn't exist for that organization, or if the store has been deleted. * **Automatic store creation**: If an external store location ID is given but not found, Nash checks an organization preference for automatically creating store locations. If enabled, it creates a new store location from the order's pickup details. * **Default field population**: If a valid pickup store location is found, Nash populates the order's pickup fields (name, phone, email, instructions) from the store, unless those fields were already provided in the request. * **Geocoding and parsing**: * If a raw `pickupAddress` or `dropoffAddress` string is provided, Nash parses and geocodes it to get coordinates (latitude/longitude). Parsing failure raises an error (for example, "Address not found"). * Nash performs the same parsing and geocoding for `pickupPlaceId` and `dropoffPlaceId`. * **Identical location check**: * Nash raises an error if `pickupPlaceId` and `dropoffPlaceId` are the same. * After parsing, Nash compares the final parsed pickup and dropoff address components and raises an error if they're identical. * **Cross-country check**: Fails if the parsed `pickupAddressCountry` and `dropoffAddressCountry` are not the same. * **Distance check**: * Nash calculates the straight-line distance between the pickup and dropoff coordinates. * That distance is checked against an organization-specific maximum delivery distance preference, and fails if it exceeds the maximum. ### Required & conditional fields * **Core required fields**: Checks that a value is present for: `valueCents`, `pickupPhoneNumber`, `dropoffPhoneNumber`, `deliveryMode`. * **Contact name requirement**: * Requires either `pickupFirstName` or `pickupBusinessName` to be present. * Requires either `dropoffFirstName` or `dropoffBusinessName` to be present. ### Data type & format validation * **Integer fields**: Verifies that `valueCents`, `tipAmountCents`, and `itemsCount` are valid integers. * **Float/decimal fields**: Verifies that dimensional fields (`weight`, `height`, `width`, `depth`, `volume`) are valid decimal numbers and are greater than 0. * **Units**: Nash stores weight in kilograms and dimensions in centimeters. Send those units and you don't need to say so. To send imperial values instead, declare the unit and Nash converts on ingest: * `weightUnit` accepts `lb` or `lbs` (case-insensitive) and converts `weight` to kilograms. Omitting it, or sending `kg`, stores the value as given. Any other value is rejected with `ORDER_INVALID_WEIGHT_UNIT`. * `lengthUnit` accepts `in`, `inch`, or `inches` (case-insensitive) and converts `height`, `width`, and `depth` to centimeters. `volume` is read as cubic inches and converted to cubic centimeters, since one unit covers all four fields. Omitting it, or sending `cm`, stores the values as given. Any other value is rejected with `ORDER_INVALID_LENGTH_UNIT`. * Declaring a unit without the matching value is harmless — `weightUnit` with no `weight` converts nothing. * **Order value**: Checks that `valueCents` is greater than 0. * **Item count**: Checks that `itemsCount`, if provided, is at least 1. * **Name fields**: Checks that all name fields (`pickupFirstName`, etc.) are strings and do not exceed a maximum length (80 characters). * **Phone numbers**: * Checks that `pickupPhoneNumber` and `dropoffPhoneNumber` are valid phone numbers for the corresponding country. * If the number is invalid but the organization has a default backup phone number configured, Nash uses the backup number instead of failing. * **Items**: If the `items` field is provided, Nash validates that each entry in the list is a valid item structure. * **Order metadata**: * Nash validates that `orderMetadata` has no more than 15 key-value pairs, and that all values are simple scalar types (string, int, float, bool). ### Enum & ID validation * **Delivery mode**: * Ensures `deliveryMode` is one of the allowed values (`now` or `scheduled`). * If mode is `now`, Nash nullifies all datetime fields. * If mode is `scheduled`, Nash requires at least one of the datetime fields (`pickupStartTime`, etc.) to be set. * **Currency**: * If a `currency` is provided, Nash checks it against the list of supported currencies. * If no currency is provided, Nash infers it from the `pickupAddressCountry`. * **Requirements**: Ensures all provided `requirements` are supported (see [Order requirements](/api-reference/order/order-requirements)), unless it's a `custom:` requirement. * **Vehicle size**: Ensures `minimumVehicleSize` is one of the supported vehicle sizes. * **Dispatch strategy**: If `dispatchStrategyId` is provided, Nash confirms it exists, belongs to the organization, and isn't deleted. * **Delivery window**: If `deliveryWindowId` is provided, Nash checks that it's a valid, active window. * **External ID uniqueness**: If an `externalId` is given, Nash ensures no other order in the same organization already uses that ID. ### Datetime validation * **Parsing**: Parses all provided datetime strings (`pickupStartTime`, `pickupEndTime`, `dropoffStartTime`, `dropoffEndTime`) into datetime objects, handling both UTC and timezone-localized inputs. Fails if a string is not a valid date format. * **Chronological order**: * `pickupStartTime` must be earlier than `pickupEndTime`. * `dropoffStartTime` must be earlier than `dropoffEndTime`. * `pickupStartTime` must be earlier than `dropoffEndTime`. * `pickupEndTime` must be earlier than `dropoffEndTime`. # Refresh order quotes Source: https://docs.usenash.com/api-reference/order/refresh-order-quotes post /v1/order/refresh_quotes Request fresh price quotes from delivery providers for an existing order. Replaces any existing quotes with updated pricing from available providers. # Select Quote Source: https://docs.usenash.com/api-reference/order/select-quote post /v1/select_quote Select the quote you would like to execute from a specific order. # Unarchive order Source: https://docs.usenash.com/api-reference/order/unarchive-order patch /v1/order/{id}/unarchive Restore a previously archived order, making it visible in default listings again. Unarchives a specific order, changing its status to either `valid` or `needs_attention` based on validation results. An order that has been dispatched to a job cannot be unarchived. # Undispatch an order Source: https://docs.usenash.com/api-reference/order/undispatch-an-order post /v1/orders/external-identifier/{external_id}/undispatch Un-dispatch a route-based order: cancel its active task and provider delivery, remove it from its route (re-syncing the linked batch), and revert it to the `valid` status. Returns the updated order. Errors if the order is not dispatched, or is not associated with a route. # Undispatch an order by id Source: https://docs.usenash.com/api-reference/order/undispatch-an-order-by-id post /v1/orders/{order_id}/undispatch Un-dispatch a route-based order by id: cancel its active task and provider delivery, remove it from its route (re-syncing the linked batch), and revert it to the `valid` status. Returns the updated order. Errors if the order is not dispatched, or is not associated with a route. # Update order Source: https://docs.usenash.com/api-reference/order/update-order patch /v1/order/{id} Update an existing pre-dispatch order's details such as addresses, times, or metadata. Only provided fields are updated. Updating an archived order will also unarchive it. # Upsert order by external identifier with delivery window Source: https://docs.usenash.com/api-reference/order/upsert-order-by-external-identifier-with-delivery-window post /v1/order/external-identifier/{externalId}/delivery-window Create or update an order by external identifier and associate it with a delivery window. The order is linked to the specified delivery window for scheduled delivery. # Get Providers Source: https://docs.usenash.com/api-reference/provider/get-providers get /v1/providers List all delivery providers configured for your organization, including their status and capabilities. # Assign or Reassign a Route Source: https://docs.usenash.com/api-reference/route/assign-route post /v1/routes/{id}/assign Assign a route to a provider, driver group, courier, or vehicle — before or after dispatch. Use this endpoint to set (or change) who fulfills a route. You can target any of these: * **Provider** (`providerId`) — an external delivery provider (e.g. `uber`). Selects the route's successful quote for that provider. * **Driver group** (`driverGroupId`) — a contract. Selects the route's successful quote for that contract. * **Courier** (`courierId`) — an individual driver. Must belong to the route's driver group. * **Vehicle** (`vehicleId`) — a vehicle. Must be assigned to the route's driver group (contract). `driverGroupId`, `courierId`, and `vehicleId` accept either an internal Nash id or your own external id. Provide at least one target; you may combine a courier and a vehicle. ## Pre-dispatch vs. post-dispatch The endpoint behaves differently depending on whether the route has already been dispatched: * **Pre-dispatch** — the route's assignment is updated in place. * **Post-dispatch** — the active assignment is cancelled, live deliveries are cancelled, tasks are recreated and re-quoted, a fresh route assignment is created, and the requested target is applied. In both cases the updated route is returned. ## Requirements * Provide at least one target (`providerId`, `driverGroupId`, `courierId`, or `vehicleId`). * Assigning by **provider** or **driver group** selects that target's successful quote on the route — a successful quote for it must already exist. * A **courier** must belong to the route's driver group. * A **vehicle** must be assigned to the route's driver group (contract). ## Errors Failed assignments return `400 Bad Request` with a body of shape `{ "error": { "code", "message", "details": { "reason" } }, "response_status", "RequestID" }`. Common reasons: | `reason` | Meaning | | - | - | | `courier_not_assigned_to_driver_group` | The courier isn't a member of the route's driver group. Move the route to a group the courier belongs to, or add the courier to that group. | | `vehicle_not_assigned_to_contract` | The vehicle isn't assigned to the route's driver group (contract). | A request with no target fails validation with `422 Unprocessable Content`. To assign a route using your own identifier instead of the Nash route id, use [Assign or Reassign a Route by external id](/api-reference/route/assign-route-by-external-identifier). # Calculate Route Timing Source: https://docs.usenash.com/api-reference/route/calculate-route-timing post /v1/routes/{id}/calculate-timing Calculate per-stop ETAs and polyline for a route using HERE truck routing. Updates arrival/departure times for each stop and returns the route polyline. # Create or Update Route Source: https://docs.usenash.com/api-reference/route/create-or-update-route post /v1/routes Create or update a route in Nash A `Route` object represents a planned sequence of stops for a vehicle or delivery agent. The core of the `Route` is the `stops` array, which lists the specific locations to visit in order. Each stop within this array details: * The actions to be performed (like `stopType`). * Associated items (`objectIds`). * Timing estimates (`arrivalTime`, `departTime`, `serviceTime`). * Travel details from the prior stop (`distanceFromPrevious`, `durationFromPrevious`). * The physical coordinates (`location`). Create a new route or update an existing one by providing the route configuration and its stop details. # Dispatch Optimized Orders Source: https://docs.usenash.com/api-reference/route/dispatch-optimized-orders post /v1/activities/{id}/dispatch Dispatch orders from an optimization activity to delivery providers. Creates jobs and assigns them to providers based on the optimized routes. # Dispatch Routes Source: https://docs.usenash.com/api-reference/route/dispatch-routes post /v1/routes/dispatch Dispatch one or more routes to delivery providers. Creates jobs for each route and assigns them based on the organization's dispatch strategy. # Find Nearby Couriers Source: https://docs.usenash.com/api-reference/route/find-nearby-couriers get /v1/routes/nearby-couriers Find the drivers on active routes nearest to a point. Use this when a route needs help: a van has broken down, or a driver is running out of hours, and you need to know who else is close. Send the point as `lat` and `lng`, and Nash returns the nearest drivers on routes that are assigned, started, or in progress, nearest first, each with their route and their distance from the point in meters. Distance is a straight line from each driver's latest GPS position, not a driving distance. A driver with no recorded position is left out, and so is one whose position is older than `max_location_age_minutes` (30 by default). Narrow the search to one depot with `store_location_id`, and leave out the route you're rescuing, and its drivers, with `exclude_route_ids`. Unlike most of the API, this endpoint's query parameters and response fields are snake\_case. # Get Route Source: https://docs.usenash.com/api-reference/route/get-route get /v1/routes/{id} Retrieve a route by ID, including its stops, travel metrics, and associated orders. A `Route` object represents a planned sequence of stops for a vehicle or delivery agent. The core of the `Route` is the `stops` array, which lists the specific locations to visit in order. Each stop within this array details: * The actions to be performed (like `stopType`). * Associated items (`objectIds`). * Timing estimates (`arrivalTime`, `departTime`, `serviceTime`). * Travel details from the prior stop (`distanceFromPrevious`, `durationFromPrevious`). * The physical coordinates (`location`). **Planned times, not live telemetry.** `arrivalTime`, `departTime`, `serviceTime`, `distanceFromPrevious`, and `durationFromPrevious` are the values stored with the route. Nash fills them in when it plans the route for you or when you call [Calculate Route Timing](/api-reference/route/calculate-route-timing); a route you build by hand carries whatever you supplied at creation. This endpoint reads them back and doesn't recompute them. To follow what the driver actually did, read each stop's `status` and `statusHistory`, which record every transition (`ENROUTE`, `ARRIVED`, `IN_PROGRESS`, `COMPLETED`, and the failure states) with a timestamp. # List Routes Source: https://docs.usenash.com/api-reference/route/list-routes get /v1/routes List and search routes with filters and pagination. Use this to find routes — by date, status, driver, or free-text search — when you have no route ID to start from. The payload is deliberately slim: enough to identify a route and show it in a list, not the whole plan. Read [Get Route](/api-reference/route/get-route) once you know which one you want. `startDate` defaults to 30 days ago, so a call with no date range returns the recent past rather than everything you have ever run. # Save Optimization Routes Source: https://docs.usenash.com/api-reference/route/save-optimization-routes post /v1/activities/{id}/save-routes Save the optimized routes from an optimization activity. Creates route records with stops and travel metrics based on the optimization results. # Create a shift Source: https://docs.usenash.com/api-reference/shifts/create-a-shift post /v1/shift Create a courier shift with start/end times and an assigned store location. # Get a shift Source: https://docs.usenash.com/api-reference/shifts/get-a-shift get /v1/shift/{id} Retrieve a single courier shift by ID, including its times, assigned courier, and store location. # Get all shifts Source: https://docs.usenash.com/api-reference/shifts/get-all-shifts get /v1/shifts List courier shifts with optional filtering by time range and store location. Filter by start_time, end_time, store_location_id, or external_store_location_id. # Upsert shifts Source: https://docs.usenash.com/api-reference/shifts/upsert-shifts post /v1/shifts/upsert Create or update multiple courier shifts in a single request. Existing shifts are matched by ID for updates; new shifts are created. # Dispatch and Manifest Orders Source: https://docs.usenash.com/api-reference/shipping/dispatch-and-manifest-orders post /v1/dispatch-and-manifest Dispatch a batch of orders and manifest them with the carrier in one call, with per-shipment failures reported rather than the batch refused. When a carrier rejects a batch as a whole, Nash doesn't leave every order in it stranded. Each delivery is noted as resubmitted individually and sent again one shipment at a time, so a single shipment the carrier won't take — a bad receiver contact, an instruction it can't parse — fails alone. Each such failure is its own entry in `errors[]`, with the order's `identifier` and `orderId`, `stage: "manifest"`, and the carrier's own message, while `manifests[].delivery_ids` lists only the shipments that were accepted. A shipment that fails stays dispatched and untagged, so a later call retries it once you've fixed the order. If nothing in the batch gets through, the original batch error is returned. The single-purpose [Manifest Orders](/api-reference/shipping/manifest-orders) call keeps its all-or-nothing behavior. # Manifest Orders Source: https://docs.usenash.com/api-reference/shipping/manifest-orders post /v1/manifest Create manifests for dispatched shipping orders. Groups orders by store location and provider, then generates manifest documents (e.g. AusPost order summaries). # Create or update inventory Source: https://docs.usenash.com/api-reference/store-catalog/create-or-update-inventory post /v1/inventory Create or update inventory levels for products at specific store locations. Rows referencing an unknown product or store location are skipped and returned in the same array with `errorCode`/`errorMessage` set; the rest of the batch still commits. # Create or update products Source: https://docs.usenash.com/api-reference/store-catalog/create-or-update-products post /v1/products Create new products or update existing ones in the store catalog. Products are matched by external identifier for upsert logic. # Get inventory Source: https://docs.usenash.com/api-reference/store-catalog/get-inventory get /v1/inventory List inventory levels with pagination. Filter by external store location ID or external product ID. # Get products Source: https://docs.usenash.com/api-reference/store-catalog/get-products get /v1/products List products in the store catalog with pagination and optional filtering. Filter by IDs, SKUs, text search, or semantic search. # Pick and Pack overview Source: https://docs.usenash.com/api-reference/store-catalog/pick-and-pack How Pick and Pack works across orders, the product catalog, and store inventory. Pick and Pack enables item-level fulfillment by coordinating products, store-level inventory, and delivery providers. ## Quick start Define your master product data with names, images, categories, and identifiers Track per-store availability, pricing, and aisle locations for efficient picking Handle substitutions, track picking status, and manage item-level fulfillment ## Product catalog Your product catalog serves as the master data source for all items across your stores. It defines the core product information that drives inventory management, order processing, and picking operations. Product Catalog Products are managed in bulk and referenced throughout your fulfillment workflow by inventory and orders. ### Key fields * **Required fields**: `name`, `categories[]`, `weight`, `dimensions{depth,height,width}`, `identifiers[{type,value}]`. * **Optional fields**: `imageUrls[]`, `externalIdentifier`, `sku`, `description`, `attributes[]` (e.g., `WEIGHTED`), and `details` (`sizeSpecification`, `packSizeSpecification`, `weightedItemInfo`). ### Rate limits * **10 requests per second** per organization * **1,000 products** per request ### API endpoints * **[Create/Update Products](/api-reference/store-catalog/create-or-update-products)**: `POST /v1/products` - Bulk create or update products * **[List Products](/api-reference/store-catalog/get-products)**: `GET /v1/products` - Retrieve products with pagination Ready-to-use sample payloads for common product types ## Store inventory Store inventory tracks the real-time availability, pricing, and location of products within each of your store locations. This data powers accurate order fulfillment by ensuring pickers know exactly what's available and where to find it. Store Inventory Each inventory entry links a product to a specific store location using your external identifiers, enabling precise per-store inventory management. ### Key fields * **Required field**: `available` (boolean). * **Common fields**: `externalProductId`, `externalStoreLocationId`, `quantity`, `valueCents`, `currency`, `location{aisle,bay,shelf}`. ### Weighted items For variable-weight products (produce, deli items, etc.): 1. Set the product attribute to `WEIGHTED` in your product catalog 2. Set per-unit pricing on the store inventory entry's `details.weightedItemInfo.valueCentsPerMeasurementUnit` (e.g., price per pound) ### Rate limits * **10 requests per second** per organization * **10,000 inventory items** per request ### API endpoints * **[Create/Update Inventory](/api-reference/store-catalog/create-or-update-inventory)**: `POST /v1/inventory` - Bulk inventory updates * **[Get Inventory](/api-reference/store-catalog/get-inventory)**: `GET /v1/inventory` - Query inventory with filters: * `externalStoreLocationId`: Filter by store location * `externalProductId`: Filter by product * Pagination: `pageIndex`, `numResultsPerPage` ## Pick and Pack orders Orders with Pick and Pack enabled use your product catalog and store inventory to enable precise item-level fulfillment. The system tracks exactly what was requested, what was picked, and any substitutions made during the process. To enable Pick and Pack functionality, include `pick_and_pack` in the `order.requirements` array when creating orders. See the complete list in [Order Requirements](/api-reference/order/order-requirements). ### Order lifecycle 1. **Order Creation**: Customer places order with specific items with substitution preferences 2. **Picking**: 3rd party courier picks items, handling substitutions as needed 3. **Status Updates**: Real-time updates on picking progress 4. **Delivery**: Order handed off to delivery provider once picking is complete ### Sub-item fields * **`item.subItems[].sku`**: optional SKU for the specific sub-item being picked. * **`item.subItems[].substitution`**: object describing substitution preferences and chosen replacements. * **`preference`**: what to do when the item can't be picked — `refund`, `substitute`, or `contact`. With `contact`, the shopper reaches the customer to agree a replacement instead of deciding for them, so the order needs a contactable dropoff. Support for `contact` depends on the pick-and-pack provider. * **`source`**: who made the decision (e.g., merchant, customer). * **`substituteItems[]`**: list of `{ id, sku, quantity }` representing the chosen replacements. ### Status * **`items_pick_complete`**: emitted when item picking for the order has finished and it is ready for handoff to delivery. Track this status via your order retrieval endpoints and/or subscribe to status change events in [Webhooks](/reference/webhooks). ### Picked items Each delivery carries a `pickedItems` array describing what the picker actually put in the bag. The REST response always emits the same fields per item; values are nullable, but fields are never omitted. | Field | Type | Notes | | - | - | - | | `id` | string \| null | Order line ID this pick fulfills. `null` when the SKU wasn't a pre-existing line (substitution). | | `sku` | string | SKU of what was actually picked. | | `name` | string | Display name of the picked item. | | `status` | string | Per-item fulfillment outcome at pick time. One of `picked`, `partially_picked`, `not_picked`, `substituted`. | | `quantity` | int | Units picked. | | `requestedQuantity` | int \| null | Units originally requested. | | `weight` | number \| null | Net weight in kilograms for random-weight goods. | | `priceCents` | int | Line price in cents (already reflects `weight × unit price` for weighted items). | | `requestedId` | string | ID of the original order line. | | `requestedSku` | string | Originally requested SKU. Equals `sku` on straight picks. | | `scannedBarcode` | string \| null | Raw scan at pick time. `null` for loose produce; 8/12/13 digits for EAN/UPC; ≥24 for GS1 in-store labels (GTIN + weight + price + use-by). | | `scans` | array \| null | Per-scan detail for multi-scan items (e.g. a variable-weight item scanned multiple times). Each entry has its own parsed `barcodes[]` payload. | A substitution is identifiable by `sku != requestedSku` (or equivalently, `id == null`). #### Example — straight pick, packaged item ```json theme={"dark"} { "id": "11111111-1111-1111-1111-111111111111", "sku": "100001", "name": "Dried Fruit Snack 130g", "status": "picked", "quantity": 2, "requestedQuantity": 2, "weight": null, "priceCents": 880, "requestedId": "11111111-1111-1111-1111-111111111111", "requestedSku": "100001", "scannedBarcode": "1234567890128", "scans": null } ``` #### Example — straight pick, weighted item (single scan) `weight` is populated; `scannedBarcode` is a GS1 in-store label encoding GTIN + weight + price. `scans` is `null` because the item was captured in a single scan. ```json theme={"dark"} { "id": "22222222-2222-2222-2222-222222222222", "sku": "200002", "name": "Free Range Chicken Tenderloins 450g-650g", "status": "picked", "quantity": 1, "requestedQuantity": 1, "weight": 0.608, "priceCents": 1307, "requestedId": "22222222-2222-2222-2222-222222222222", "requestedSku": "200002", "scannedBarcode": "0112345678901234152601013103000608392200130700", "scans": null } ``` #### Example — substitution ```json theme={"dark"} { "id": null, "sku": "300099", "name": "Immune Support Shot 60ml", "status": "substituted", "quantity": 1, "requestedQuantity": 1, "weight": null, "priceCents": 330, "requestedId": "33333333-3333-3333-3333-333333333333", "requestedSku": "300003", "scannedBarcode": "2345678901235", "scans": null } ``` #### Example — loose produce (no scan) Items sold "each" have no physical barcode. Identify by `sku`. ```json theme={"dark"} { "id": "66666666-6666-6666-6666-666666666666", "sku": "600006", "name": "Fresh Bananas Each", "status": "picked", "quantity": 2, "requestedQuantity": 2, "weight": null, "priceCents": 186, "requestedId": "66666666-6666-6666-6666-666666666666", "requestedSku": "600006", "scannedBarcode": null, "scans": null } ``` #### Example — weighted item with multiple scans Variable-weight items picked across multiple physical units produce one `scans[]` entry per scan, each with a parsed GS1 payload. For richer parsed output, read `scans[].barcodes[]` instead of re-parsing `scannedBarcode` yourself. ```json theme={"dark"} { "id": "77777777-7777-7777-7777-777777777777", "sku": "700007", "name": "Bananas Loose", "status": "picked", "quantity": 1, "requestedQuantity": 1, "weight": 1.45, "priceCents": 450, "requestedId": "77777777-7777-7777-7777-777777777777", "requestedSku": "700007", "scannedBarcode": "0112345678901234310300072039220000225", "scans": [ { "substitutionType": "no_substitution", "barcodes": [ { "barcode": "0112345678901234310300072039220000225", "format": "gs1_databar", "isVariableWeight": true, "weight": 0.72, "weightUnit": "kg", "priceCents": 225, "productCode": "12345678901234", "expirationDate": null } ] }, { "substitutionType": "no_substitution", "barcodes": [ { "barcode": "0112345678901234310300073039220000225", "format": "gs1_databar", "isVariableWeight": true, "weight": 0.73, "weightUnit": "kg", "priceCents": 225, "productCode": "12345678901234", "expirationDate": null } ] } ] } ``` # Product catalog examples Source: https://docs.usenash.com/api-reference/store-catalog/product-catalog-examples Sample product payloads for common product types to help you structure your catalog data correctly. This guide provides ready-to-use sample payloads for the [Create or Update Products](/api-reference/store-catalog/create-or-update-products) endpoint (`POST /v1/products`). Each example covers a common product type you'll encounter when building your catalog. Before sending products, review the required and optional fields in the [Pick and Pack overview](/api-reference/store-catalog/pick-and-pack#product-catalog). ## Required fields checklist Every product **must** include the following fields: | Field | Type | Description | | - | - | - | | `name` | string | Display name of the product | | `categories` | string\[] | Category hierarchy (e.g., `["Dairy & Eggs", "Milk"]`) | | `weight` | number | Total weight in kilograms | | `dimensions` | object | `depth`, `width`, `height` in centimeters | | `identifiers` | array | At least one identifier (e.g., UPC barcode) | **Commonly used optional fields:** | Field | Type | Description | | - | - | - | | `imageUrls` | string\[] | Product image URLs, shown to pickers during fulfillment | | `sku` | string | Your internal SKU | | `externalIdentifier` | string | Your system's unique product ID | | `description` | string | Product description | | `details.sizeSpecification` | object | Size value and unit (e.g., `200.0` / `"G"`) | | `details.packSizeSpecification` | object | Pack description (e.g., `"140g x 4 Pack"`) | | `attributes` | string\[] | Special attributes like `"WEIGHTED"` for variable-weight items | ## Fixed-weight packaged goods Standard shelf products sold at a fixed weight — canned goods, boxed items, packaged proteins, etc. **What to look for:** * `weight` reflects the total packaged weight (not just the net contents) * `details.sizeSpecification` captures the labeled net weight (e.g., 200g) * A single UPC identifier is typically sufficient ```json theme={"dark"} { "products": [ { "name": "Skinless & Boneless Salmon 200g", "description": "Skinless & Boneless Salmon 200g", "sku": "3001012", "externalIdentifier": "3001012", "imageUrls": [ "https://cdn.example.com/products/3001012/main.jpg" ], "categories": [ "Pantry", "Canned & Prepared Meals", "Canned Fish" ], "weight": 0.225, "dimensions": { "depth": 3.5, "width": 10.2, "height": 7.8 }, "identifiers": [ { "type": "UPC", "value": "9300657100810" } ], "details": { "sizeSpecification": { "value": 200.0, "description": "G" } } } ] } ``` For fixed-weight items, `weight` (in kg) should include packaging. The `sizeSpecification` value represents the labeled net contents. ## Count / piece items (EA) Products sold by count or number of pieces — tea bags, tablets, individual items, etc. **What to look for:** * `details.sizeSpecification` uses `"EA"` (each) as the unit * `details.packSizeSpecification` describes the pack (e.g., `"25 Pack"`) * Multiple `imageUrls` can be provided for different product views ```json theme={"dark"} { "products": [ { "name": "English Breakfast Tea Bags 25 Pack", "description": "English Breakfast Tea Bags 25 Pack", "sku": "3002023", "externalIdentifier": "3002023", "imageUrls": [ "https://cdn.example.com/products/3002023/main.jpg", "https://cdn.example.com/products/3002023/side.jpg" ], "categories": [ "Pantry", "Tea & Coffee", "Tea" ], "weight": 0.095, "dimensions": { "depth": 7.0, "width": 8.5, "height": 13.0 }, "identifiers": [ { "type": "UPC", "value": "70177020095" } ], "details": { "sizeSpecification": { "value": 25.0, "description": "EA" }, "packSizeSpecification": { "value": "25 Pack", "description": "pack size" } } } ] } ``` ## Multi-pack products Bundled products containing multiple individual units — yoghurt packs, drink multipacks, snack boxes, etc. **What to look for:** * `details.sizeSpecification` reflects the **total** combined weight/volume (e.g., 4 x 140g = 560g) * `details.packSizeSpecification` describes the bundle (e.g., `"140g x 4 Pack"`) * Multiple UPC `identifiers` may be needed — one for the outer packaging and one for the individual units * `weight` is the total weight of the entire multi-pack including packaging ```json theme={"dark"} { "products": [ { "name": "Greek Yoghurt Strawberry 140g x 4 Pack", "description": "Greek Yoghurt Strawberry 140g x 4 Pack", "sku": "3003034", "externalIdentifier": "3003034", "imageUrls": [ "https://cdn.example.com/products/3003034/main.jpg", "https://cdn.example.com/products/3003034/back.jpg", "https://cdn.example.com/products/3003034/nutrition.jpg" ], "categories": [ "Dairy & Eggs", "Yoghurt", "Multipacks" ], "weight": 0.612, "dimensions": { "depth": 7.5, "width": 24.0, "height": 9.0 }, "identifiers": [ { "type": "UPC", "value": "9343587000824" }, { "type": "UPC", "value": "9343587000831" } ], "details": { "sizeSpecification": { "value": 560.0, "description": "G" }, "packSizeSpecification": { "value": "140g x 4 Pack", "description": "pack size" } } } ] } ``` For multi-packs, always include the outer package barcode as an identifier. If the individual units have a different barcode, include that as a second identifier — this helps pickers scan either code during fulfillment. ## Liquid / volume-based products Products where the primary measurement is volume — milk, juice, cleaning products, etc. **What to look for:** * `details.sizeSpecification` uses volume units (`"ML"` or `"L"`) instead of weight * `weight` still reflects the physical weight in kg (important for delivery logistics) * For liquids, weight will typically be close to the volume (1L of water \~ 1kg) plus packaging ```json theme={"dark"} { "products": [ { "name": "Full Cream Milk 2L", "description": "Full Cream Milk 2L", "sku": "3005034", "externalIdentifier": "3005034", "imageUrls": [ "https://cdn.example.com/products/3005034/main.jpg" ], "categories": [ "Dairy & Eggs", "Milk", "Full Cream" ], "weight": 2.06, "dimensions": { "depth": 9.5, "width": 9.5, "height": 27.0 }, "identifiers": [ { "type": "UPC", "value": "9310073000206" } ], "details": { "sizeSpecification": { "value": 2.0, "description": "L" } } } ] } ``` ## Size specification units reference Use the correct `description` value in `sizeSpecification` for your product type: | Unit | Description Value | Use For | | - | - | - | | Grams | `"G"` | Solid food, packaged goods | | Kilograms | `"KG"` | Bulk or heavy items | | Millilitres | `"ML"` | Small liquid products | | Litres | `"L"` | Large liquid products | | Each | `"EA"` | Count-based items (bags, tablets, pieces) | ## Common mistakes to avoid `weight` must be in **kilograms** and reflect the total packaged weight. A 200g product should have `"weight": 0.2`, not `"weight": 200`. This field is critical for delivery logistics and bag packing optimization. The `dimensions` object is required. Provide all three fields (`depth`, `width`, `height`) in **centimeters**. These are used for delivery capacity planning. Include at least one image URL. Images are shown to pickers during fulfillment — without them, pickers cannot visually verify they have the correct product. Categories should form a hierarchy from broad to specific (e.g., `["Dairy & Eggs", "Yoghurt", "Multipacks"]`). This powers search and browse functionality in the storefront. `sizeSpecification` is the **labeled product size** customers see (e.g., 2L, 500g, 25 pack). `weight` is the **physical weight in kg** for logistics. These serve different purposes. ## Next steps Send your product catalog via the API Set up per-store availability and pricing # Create or update store location Source: https://docs.usenash.com/api-reference/store-locations/create-or-update-store-location post /v1/store_locations/upsert Create a new store location or update an existing one. Matches on external identifier or store location ID for upsert logic. # Create or update store location by external identifier Source: https://docs.usenash.com/api-reference/store-locations/create-or-update-store-location-by-external-identifier post /v1/store_locations/external-identifier/{externalIdentifier} Create a new store location or update an existing one using your external identifier. If a store location with the given external identifier exists, it is updated. # Create store location Source: https://docs.usenash.com/api-reference/store-locations/create-store-location post /v1/store_locations Create a new store location (pickup point) with address, hours, and capacity settings. # Delete store location Source: https://docs.usenash.com/api-reference/store-locations/delete-store-location delete /v1/store_locations/{id} Delete a store location by ID. Orders referencing this store location will no longer have a valid pickup point. # Get Store Location Source: https://docs.usenash.com/api-reference/store-locations/get-store-location get /v1/store_locations/{id} Get a store location by ID. # Get store location by external identifier Source: https://docs.usenash.com/api-reference/store-locations/get-store-location-by-external-identifier get /v1/store_locations/external_identifier/{externalIdentifier} # Get Store Locations Source: https://docs.usenash.com/api-reference/store-locations/get-store-locations get /v1/store_locations Get a list of store locations. # Get store locations that cover specified location (latitude & longitude, city_zipcode) Source: https://docs.usenash.com/api-reference/store-locations/get-store-locations-that-cover-specified-latitude-and-longitude get /v1/store_locations/coverage Find store locations whose delivery zones cover a given geographic point. Provide latitude/longitude, city_zipcode, or zipcode to find matching store locations. # Update Store Location Source: https://docs.usenash.com/api-reference/store-locations/update-store-location patch /v1/store_locations/{id} Update a store location by ID. # Upsert multiple store locations Source: https://docs.usenash.com/api-reference/store-locations/upsert-multiple-store-locations post /v1/store_locations/upsert/bulk Create or update multiple store locations in a single request. Each store location is matched by external identifier for upsert logic. # Upsert multiple templates Source: https://docs.usenash.com/api-reference/templates/upsert-multiple-templates post /v1/templates/upsert Create or update multiple delivery window or shift templates in a single request. Templates generate their instances on a recurring schedule. On update, a shift template's payload is merged: a field you omit keeps its stored value, and clearing one requires sending it explicitly as null. The external references are the exception, because they are resolved to internal ids only when supplied: sending externalStoreLocationId or externalZoneIds as null leaves the stored store location or zones unchanged. # Using LLMs with Nash API Source: https://docs.usenash.com/api-reference/using-llms-with-nash-api Point an LLM at the Nash API reference and let it speed up your integration An LLM — Claude, ChatGPT, Cursor, or your own agent — can speed up your Nash integration considerably: generating request code, explaining endpoints, and debugging responses. This page covers the formats we publish for exactly that. If your assistant supports MCP, connect the [docs MCP server](/reference/docs-mcp-server) at `https://docs.usenash.com/mcp` instead of pasting links — it searches and reads the current docs on its own. [Build with AI](/reference/build-with-ai) covers every option. ## Nash API documentation for LLMs Give the LLM the API documentation in a machine-readable format: * **Complete API Documentation**: [https://docs.usenash.com/llms-full.txt](https://docs.usenash.com/llms-full.txt) * **Condensed Documentation**: [https://docs.usenash.com/llms.txt](https://docs.usenash.com/llms.txt) * **Any single page as Markdown**: append `.md` to the page URL — for example [https://docs.usenash.com/reference/webhooks.md](https://docs.usenash.com/reference/webhooks.md) * **OpenAPI Specification**: [https://docs.usenash.com/api-reference/openapi.json](https://docs.usenash.com/api-reference/openapi.json) * **Docs MCP Server**: let an assistant search these docs itself — see [Docs MCP server](/reference/docs-mcp-server) * **Nash MCP Server**: connect an agent directly to Nash — see [Nash MCP server](/reference/mcp-integration) These specially formatted text files contain the Nash API endpoints, schemas, and implementation details in a format that's optimized for LLMs to process. They're generated from the public Nash API specification, so they describe only the endpoints available to you — no internal surfaces. ## Example prompt ``` I'm working on integrating with Nash's delivery API. Here's the relevant documentation: https://docs.usenash.com/llms-full.txt I need to implement [SPECIFIC FUNCTIONALITY]. Can you help me understand how to structure this request? ``` ## Best practices for using LLMs with the Nash API 1. **Provide clear context**: Share the relevant Nash API documentation links with the LLM 2. **Be specific**: Clearly describe your use case and requirements 3. **Include complete information**: When troubleshooting, share error messages and API responses ## Key integration areas where LLMs can help * **Understanding Nash Concepts**: Orders, Jobs, Batch Jobs, Dispatch Strategies * **API Request/Response Formatting**: Generate code samples for API calls * **Webhook Implementation**: Set up secure webhook handlers for delivery updates * **Error Handling**: Understand and resolve API error responses ## Keep sensitive data out of the prompt The documentation links above are safe to share — they're public. Your operational data is not. Treat anything you paste into a third-party LLM as if it could be retained or used for training, and keep these out of the prompt: * **Credentials** — API keys, tokens, `Authorization` headers, webhook signing secrets. * **Customer and recipient PII** — names, dropoff addresses, phone numbers, email addresses. * **Identifiers that map back to your operation** — organization IDs, `Nash-Org-Id`, order/job IDs, request IDs. * **Raw API responses and webhook payloads** — these bundle several of the above together. Redact before sharing. When you need an LLM to debug a real request or response, **replace the sensitive values with placeholders** (``, ``, `123 Main St`) before pasting. The structure is what the model needs; the real values are not. Always review and test LLM-generated code before deploying it to production — verify endpoints, fields, and error handling against the [API reference](/api-reference/api-overview). For comprehensive integration guidance, refer to our [Plan Your Integration](/reference/plan-your-integration) documentation. # Count Workflow Executions Source: https://docs.usenash.com/api-reference/workflow-execution/count-workflow-executions get /v1/workflows/{id}/executions/count Returns the total count of workflow executions matching the given filters. Useful for pagination or dashboards without fetching full execution data. # Get Workflow Execution Detail Source: https://docs.usenash.com/api-reference/workflow-execution/get-workflow-execution-detail get /v1/workflow-executions/{id} Returns a single workflow execution with all its execution steps, including step status, node references, and node variables. # List Workflow Executions Source: https://docs.usenash.com/api-reference/workflow-execution/list-workflow-executions get /v1/workflows/{id}/executions Returns paginated workflow execution history with optional filtering by status, date range, and search. Supports sorting by created_at, completed_at, or status. # Create Workflow Source: https://docs.usenash.com/api-reference/workflow/create-workflow post /v1/workflows Create a new workflow with nodes, edges, and a trigger. The workflow is created in draft status by default. Nodes define the actions and filters, edges define the execution flow between nodes, and the trigger defines when the workflow is activated. First fetch GET /workflows/schema for current action config schemas and branch rules. Bounded AI decisions use nodeType=action, nodeAction=ai_decision; a workflow may contain multiple decision nodes, each with its own config and named decision edges. # Get Workflow Source: https://docs.usenash.com/api-reference/workflow/get-workflow get /v1/workflows/{id} Returns a single workflow by ID, including its latest version with nodes, edges, and trigger. # Get Workflow Schema Source: https://docs.usenash.com/api-reference/workflow/get-workflow-schema get /v1/workflows/schema Returns self-describing workflow metadata including triggers, action config schemas, filter operators/fields, published node outputs, and supported workflow node, edge, and status types. Fetch before authoring a workflow. Action-specific edge_rules override the ordinary node-type rules. ai_decision includes all three decision modes, template syntax, availability, and test semantics. # List Workflows Source: https://docs.usenash.com/api-reference/workflow/list-workflows get /v1/workflows Returns all non-archived workflows for the authenticated organization. # Set Workflow Status Source: https://docs.usenash.com/api-reference/workflow/set-workflow-status post /v1/workflows/{id}/status Change the status of a workflow (draft, active, inactive, archived, template). Archiving a workflow disables its notification trigger. # Test Workflow Source: https://docs.usenash.com/api-reference/workflow/test-workflow post /v1/workflows/{id}/test Run a workflow against a real entity without activating it. Requires jobId or orderId — the entity the run is evaluated against. By default this is a dry run: filters are evaluated against the entity and each action returns a preview of what it would do, without performing it. With live=true, messaging actions (send_slack, send_email, send_sms) are sent with a [Test] prefix and ai_decision calls the model; every other action stays preview-only and its preview carries a note saying so, so a live test can never apply a state change such as cancel_order or add_tag. Returns per-node status, so a caller can confirm which filters passed before activating the workflow. # Trigger Workflow Source: https://docs.usenash.com/api-reference/workflow/trigger-workflow post /v1/workflows/{id}/trigger Trigger an active or inactive workflow with optional entity references and input data. Supports sync and async execution modes. # Update Workflow Source: https://docs.usenash.com/api-reference/workflow/update-workflow patch /v1/workflows/{id} Update an existing workflow. Only provided fields are updated. If nodes, edges, and trigger are all provided, a new workflow version is created. These three structure fields must be provided together or not at all. # Create a zone Source: https://docs.usenash.com/api-reference/zones/create-a-zone post /v1/zones Create a geographic zone defined by a polygon boundary. Zones are used to define delivery areas for store locations and apply zone-based restrictions. # Delete a zone Source: https://docs.usenash.com/api-reference/zones/delete-a-zone delete /v1/zones/{id} Delete a zone by ID. Removes the zone and its store location associations. # Get zone Source: https://docs.usenash.com/api-reference/zones/get-zone get /v1/zones/{id} Retrieve a single zone by ID, including its polygon boundary and associated store locations. # Get zone by external identifier Source: https://docs.usenash.com/api-reference/zones/get-zone-by-external-identifier get /v1/zones/external_identifier/{externalIdentifier} Retrieve a zone using your external identifier instead of the Nash zone ID. # Get zones Source: https://docs.usenash.com/api-reference/zones/get-zones get /v1/zones List zones for the organization with pagination. Use size and offset parameters to paginate. Optionally include deleted zones with is_deleted. # Get zones that cover specified location (latitude & longitude, city_zipcode) Source: https://docs.usenash.com/api-reference/zones/get-zones-that-cover-specified-location-latitude-and-longitude-city_zipcode get /v1/zones/coverage Find zones whose polygon boundaries contain a given geographic point. Provide latitude/longitude, city_zipcode, or zipcode to find matching zones. # Query zones Source: https://docs.usenash.com/api-reference/zones/query-zones post /v1/zones/query Return full zone details for many zones in a single call. Filter by `ids` and/or `externalIds` (combined with OR); omit both to page through all of the organization's zones. Results come back under `data` with pagination under `meta`; each `data` item matches the shape of `GET /v1/zones/`. # Update a zone Source: https://docs.usenash.com/api-reference/zones/update-a-zone patch /v1/zones/{id} Update an existing zone's name, polygon boundary, or associated store locations. Only provided fields are updated. # Overview Source: https://docs.usenash.com/fleet/overview Manage drivers and fulfill Nash deliveries with your own fleet: how driver setup, dispatch, delivery-state updates, and the Fleet API fit together. The Fleet API is the inbound surface a fleet uses to manage its drivers and report state on deliveries Nash has dispatched to it: courier location, status transitions, proof of delivery, courier identity, ETAs, and notes. Use it whether you're a merchant operating your own couriers or a third-party fleet receiving Nash demand. The contract is the same in both cases: Nash dispatches a job (delivery) to your organization, you fulfill it, and you `PATCH` updates back as the delivery progresses. ## Manage your drivers Use [Upsert Driver](/api-reference/fleet/upsert-driver) (`PUT /v1/fleet/drivers/upsert`) to create or update a driver in your internal fleet. Your `externalIdentifier` is the upsert key: send the same value again to update that driver, and inspect `created` in the response to tell whether Nash created a new record. Every request must include `phoneNumber` as an international number with a leading country code. Nash stores it in E.164 form, links the driver to a Nash user with your organization's driver role, and provisions SMS sign-in for the Driver App. The linked driver also appears in your internal-driver list in the Portal. You can include `email` to add email sign-in too. Creating a driver through this endpoint does not send a welcome message. Keep these identity rules in mind: * A driver's stored phone number cannot be changed through this endpoint. * A phone number already linked to another driver in your organization cannot be reused with a different `externalIdentifier`, even when that driver is disabled. * A phone number belonging to an active user outside your organization is rejected. * Omitting, clearing, or sending a blank `email` leaves the existing email unchanged. There is no separate delete call. To make a driver unavailable, upsert the driver with `isShiftActive: false`; to disable the driver, use `enabled: false`. ## Manage your driver groups A driver group is a named set of your drivers that dispatch can target. If your roster already lives in a workforce or HR system, three endpoints keep the groups in Nash matched to it, so a change of depot or team doesn't wait for someone to edit a list in the Portal. | Endpoint | Use it to | | - | - | | [List Driver Groups](/api-reference/fleet/list-driver-groups) (`GET /v1/fleet/driver-groups`) | Read your groups, newest first. Disabled groups are left out unless you send `activeOnly=false`. | | [Get Driver Group](/api-reference/fleet/get-driver-group) (`GET /v1/fleet/driver-groups/{first_version_contract_id}`) | Read one group and the drivers in it. | | [Upsert Driver Group](/api-reference/fleet/upsert-driver-group) (`PUT /v1/fleet/driver-groups/upsert`) | Create or update a group, keyed on your own `externalIdentifier`. | A group has two identifiers. `externalIdentifier` is yours and is the upsert key. `firstVersionContractId` is Nash's, stays the same however often the group is edited, and is what the read endpoints and Upsert Driver take. There are two ways to change who is in a group, and they suit different sources of truth: * **You hold the whole roster.** Send `driverIds` on Upsert Driver Group. It replaces the group's members with the list you send; `[]` removes everyone, and leaving the field out changes nothing. * **You hear about one driver at a time.** Send `addFirstVersionContractIds` or `removeFirstVersionContractIds` on [Upsert Driver](/api-reference/fleet/upsert-driver). The driver's other memberships are kept. These two can't be combined with `firstVersionContractIds`, which replaces all of a driver's memberships. A new group needs a `displayName` and starts active. There is no delete call: upsert with `isActive: false` to disable a group. A disabled group can lose members but can't gain new ones. ```bash theme={"dark"} curl -X PUT https://api.sandbox.usenash.com/v1/fleet/driver-groups/upsert \ -H "Authorization: Bearer $NASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "externalIdentifier": "depot-north-am", "displayName": "North depot, morning" }' ``` The response is the group with its `drivers` and a `created` flag that says whether the call created it. Send the same `externalIdentifier` again with `driverIds`, your drivers' Nash IDs, to set its roster. ## Report delivery state ## When updates start Each dispatch event Nash sends carries a `deliveryId`. That's the identifier you use on every update for that delivery's lifetime. Updates only land on deliveries owned by your organization; anything else returns not-found. ## Two endpoints for delivery state | Endpoint | Use when | | - | - | | `PATCH /v1/fleet/deliveries/{delivery_id}` | A single delivery changed state. | | `PATCH /v1/fleet/deliveries` | You want to flush updates for up to 100 deliveries at once. Per-item failures don't affect the rest of the batch. | Both accept partial bodies — only the fields you want to change need to be present. ## Lifecycle A typical happy path moves through: ``` pickup_enroute → pickup_arrived → pickup_complete → dropoff_enroute → dropoff_arrived → dropoff_complete ``` Terminal failures use `failed` or `canceled_by_provider`, paired with a structured `failure.code` (e.g. `customer_unavailable`, `address_not_found`, `no_capacity`). Returns flow through `return_in_progress → return_arrived → returned_to_store`. Sending the same status twice is a no-op, so it's safe to retry. ## What you can include in a single call A single update can carry any combination of: * **`status`** — lifecycle transition. * **`coordinates`** — `{ latitude, longitude }`. Append-only; safe to send on every tick. * **`courier`** — name, phone, vehicle, profile image. Send on first assignment, or any subset on a mid-flight swap. * **`proofOfDelivery`** — image artifacts (photo / signature at pickup or dropoff) or barcode scans. * **`failure`** — structured `{ code, reason }` for `failed` / `canceled_by_provider`. * **`pickupEta` / `dropoffEta`** — provider-supplied ETAs. * **`pickupNote` / `dropoffNote`** — free text the courier captured. * **`parkingLocation` / `returnParkingLocation`** — where the courier parked at the stop or when returning the package. * **`externalDeliveryId`** — your own identifier for this delivery, for cross-system reconciliation. ## Validation * At least one mutating field is required per update — empty bodies return `422`. * `coordinates.latitude` must be in `[-90, 90]`, `longitude` in `[-180, 180]`. * Wire format is camelCase; snake\_case is also accepted. ## Response shape Both endpoints return the same delivery object you'd see nested under a job's task in `GET /v1/jobs/{id}`. The bulk response wraps each item with a per-item `success` flag and either the updated delivery or a structured error. ## Next steps Walk through reporting status, location, and proof of delivery. Create or update a driver and provision their sign-in identity. Create a group or replace its roster from your own system. PATCH a single delivery as it progresses. Flush updates for up to 100 deliveries at once. # Report delivery state Source: https://docs.usenash.com/fleet/report-delivery-state How a fleet reports status, location, proof of delivery, and ETAs back to Nash as a job (delivery) progresses — for single and bulk updates, and how to test transitions with Fleet simulators. When Nash dispatches a job (delivery) to your organization, the Fleet API is how you report state back as the courier works through it — status transitions, courier location, proof of delivery, courier identity, ETAs, and notes. This guide walks through reporting state for a single delivery, flushing many updates at once, testing transitions in sandbox, and the lifecycle and validation rules that apply. Every dispatch event Nash sends carries a `deliveryId`. That identifier is what you use on every update for that delivery's lifetime. Updates only land on deliveries owned by your organization — anything else returns not-found. ## Reporting state for a single delivery Use [Update Delivery](/api-reference/fleet/update-delivery) (`PATCH /v1/fleet/deliveries/{delivery_id}`) to report a change on one delivery. The body is partial — include only the fields that changed. When Nash dispatches the job (delivery) to you, store the `deliveryId` it carries. You'll send it on every subsequent update. On assignment, send the `courier` object (name, phone, vehicle, and profile image as available) and a status such as `assigned_driver`. You can include `coordinates` and an ETA in the same call. As the courier advances, `PATCH` each transition — `pickup_enroute`, `pickup_arrived`, `pickup_complete`, and so on. `coordinates` are append-only and safe to send on every location tick, alongside `pickupEta` / `dropoffEta` and any `pickupNote` / `dropoffNote`. At pickup or dropoff, include `proofOfDelivery` — image artifacts (photo or signature) or barcode scans — discriminated by type. Send `dropoff_complete` on success. For a terminal failure, send `failed` or `canceled_by_provider` together with a structured `failure` object. Returns flow through `return_in_progress → return_arrived → returned_to_store`. A single update can carry any combination of the following fields: | Field | What it carries | | - | - | | `status` | A lifecycle transition (see [Lifecycle & validation](#lifecycle--validation)). | | `coordinates` | `{ latitude, longitude }` for the courier's current position. Append-only; safe to resend. | | `courier` | Courier identity and vehicle — name, phone, vehicle, profile image. | | `proofOfDelivery` | Image (photo or signature) or barcode artifacts captured at a stop. | | `failure` | Structured `{ code, reason }` when the delivery enters `failed` or `canceled_by_provider`. | | `pickupEta` / `dropoffEta` | Provider-supplied ETAs, ISO 8601. | | `pickupNote` / `dropoffNote` | Free-text notes the courier captured at a stop. | | `parkingLocation` / `returnParkingLocation` | Where the courier parked at the stop or when returning the package. | | `externalDeliveryId` | Your own identifier for the delivery, for cross-system reconciliation. | The endpoint returns the updated delivery object — the same shape you'd see nested under a job's task in [Get Job](/api-reference/job/get-job). ## Bulk updates When you have updates for many deliveries to flush at once, use [Bulk Update Deliveries](/api-reference/fleet/bulk-update-deliveries) (`PATCH /v1/fleet/deliveries`). Send a `deliveries` array of up to 100 items, where each item is the same partial update you'd send to the single endpoint, keyed by its `deliveryId`. The response returns a `results` array with one entry per request item, in order. Each result carries: * `deliveryId` — the Nash delivery id. * `success` — whether that item's update was applied. * `delivery` — the updated delivery, on success. * `errorCode` / `errorMessage` — failure detail, when `success` is `false`. Per-item failures don't affect the rest of the batch, so a single bad item won't roll back the others. Inspect each result's `success` flag rather than relying on the overall HTTP status. ## Testing with Fleet simulators In sandbox you can exercise your integration without a real courier. Two complementary tools: * **fleet simulators** — mock fleets that emulate real-world scenarios. `FleetSimulator` cycles a job through statuses from driver-not-assigned up to dropoff-complete, `FleetSimulatorFast` does the same on a shorter interval, and `FailingFleetSimulator` sends a failed status. Simulators start the job based on its scheduled pickup time. See [Environments](/reference/environments) for the full list. * **Simulate Delivery Status Transition** — [this endpoint](/api-reference/fleet-simulator/simulate-delivery-status-transition) (`POST /v1/deliveries/{delivery_id}/simulate`) advances a delivery to a specific status on demand, so you can drive your handlers through each state deliberately. The simulate endpoint is available in dev and sandbox only, and only for deliveries dispatched to the Nash sandbox test provider. The behavior of fleets and fleet simulators is not an indication of how real fleets behave in production. ## Lifecycle & validation A typical happy path moves through: ``` pickup_enroute → pickup_arrived → pickup_complete → dropoff_enroute → dropoff_arrived → dropoff_complete ``` The full set of status values you can send is: `not_assigned_driver`, `assigned_driver`, `pickup_enroute`, `pickup_arrived`, `pickup_complete`, `dropoff_enroute`, `dropoff_arrived`, `dropoff_complete`, `failed`, `canceled_by_provider`, `return_in_progress`, `return_arrived`, `returned_to_store`. Terminal failures use `failed` or `canceled_by_provider`, paired with a structured `failure.code`. Valid codes are `customer_unavailable`, `address_not_found`, `out_of_service_area`, `damage_in_transit`, `item_unavailable`, `age_verification_failed`, `no_capacity`, and `other`, with an optional free-text `reason`. Validation rules to keep in mind: * **At least one mutating field is required** per update — empty bodies return `422`. * `coordinates.latitude` must be in `[-90, 90]` and `coordinates.longitude` in `[-180, 180]`. * Wire format is camelCase; snake\_case is also accepted. * Sending the same status twice is a no-op, so updates are safe to retry. * `proofOfDelivery` artifacts must be reachable URLs — a `file://` reference is rejected. A delivery holds at most 50 photos and signatures and, counted apart from those, at most 200 barcode records across pickup, dropoff, and return; the same barcode recorded at pickup and again at dropoff is two records. Any of those problems fails the whole update rather than dropping the artifact quietly, so a rejected proof is something you find out about. ## Next steps How the inbound Fleet API surface fits together. PATCH a single delivery's state. Flush updates for up to 100 deliveries at once. Drive a sandbox delivery to a given status. # API tutorial: an example checkout workflow with Nom Nom Goods Source: https://docs.usenash.com/guides/api-tutorial-workflow-nom-nom-foods A basic Nash integration through an example app checkout workflow. This guide demonstrates how to integrate Nash into the checkout and delivery process. It uses our fictional company Nom Nom Goods as an example, following our customer Sammy through the process. ## Introduction Imagine you built a business and app called Nom Nom Goods. Nom Nom Goods manages hundreds of stores across the country and uses Nash to orchestrate its deliveries. ## The typical customer experience Here's what Sammy's experience looks like in the Nom Nom app: 1. Sammy adds items to his cart and selects a nearby store location. 2. At checkout, he chooses to have his order delivered immediately. 3. Nash, integrated into the checkout flow, provides delivery quotes and manages the delivery. 4. Sammy receives his order quickly and reliably, along with status updates about his delivery. ## Integration with Nash on checkout To integrate Nash into your Nom Nom Goods checkout flow, you'll follow these steps: 1. **Build the dispatch strategy**: Configure how Nash selects the best delivery provider for each order. 2. **Set up automations**: Automatically apply the appropriate dispatch strategy based on predefined rules. 3. **Create an order**: When Sammy completes checkout, create a new order in Nash for delivery coverage and pricing. 4. **Autodispatch the order**: After Sammy places his order, let Nash dispatch it to the best provider automatically. 5. **Track the delivery**: Use live updates from Nash to keep Sammy informed about his order status. The examples below use the sandbox host. You'll need a sandbox API key, and the `Nash-Org-Id` header if your key has access to multiple organizations — see [API Key and Org ID](/reference/generating-api-key-and-org-id). Here's a diagram of the process we'll build: The figure here is a sequence diagram between Sammy, the Nom Nom app, Nash, and the delivery providers: 1. **Checkout.** Sammy fills a cart and picks delivery. The app creates an order (`POST /v1/order`). Nash collects quotes from the allowed providers, the automation applies `NomNomCheckoutStrategy`, and the response returns the quotes with one tagged `autodispatch_preferred_quote`. The app shows Sammy the delivery price. 2. **Order placed.** Sammy places the order. The app refreshes the quotes if they have expired (`POST /v1/order/refresh_quotes`), then autodispatches (`POST /v1/order/{id}/autodispatch`). Nash books the winning quote, and the order becomes a job (delivery). 3. **On the way.** The provider reports status changes, Nash sends them to the app as webhooks, and the app shows Sammy live order status. ## 1. Building the dispatch strategy Dispatch strategies are configurations that determine how Nash selects delivery providers for your orders. You may prefer selecting the lowest-cost provider or have a ranked list of providers. To create a dispatch strategy, you can use either the Nash API or the Nash Portal. Below is an example of using the API to create a dispatch strategy with the following configuration: * **Allowed providers**: `NomNomDrive`, `HotRodWheels`, `FastFleet` * **Provider selection strategy**: `lowest_price` (Nash chooses the provider with the lowest delivery cost.) * **Reassign on failure**: `true` (Nash seeks an alternative provider if the first choice cannot fulfill the order.) * **Auto dispatch**: `false` (We manage the autodispatch of orders ourselves.) For more details, refer to [Dispatch strategies](/api-reference/dispatch-strategies/dispatch-strategy). Here's a sample curl command to create the dispatch strategy: ```shell create dispatch strategy theme={"dark"} curl --request POST \ --url https://api.sandbox.usenash.com/v1/options_group \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" \ --header 'Content-Type: application/json' \ --data '{ "label": "NomNomCheckoutStrategy", "allowedProviders": [ "NomNomDrive", "HotRodWheels", "FastFleet" ], "enableAutoDispatch": false, "providerSelectionStrategy": "lowest_price", "reassignOnFailure": true }' ``` ## 2. Setting up automations Automations apply dispatch strategies to your orders based on predefined rules, such as package value or delivery location. The right dispatch strategy applies without manual intervention. You can configure automations in the Nash Portal. For example, create a rule that applies the "NomNomCheckoutStrategy" dispatch strategy to any order with a package value over \$50, so higher-value deliveries get the right treatment. ## 3. Creating an order When customer Sammy completes checkout in your Nom Nom Goods app, you'll create a new order in Nash using the [`/v1/order`](/api-reference/order/create-order) endpoint. The order payload should include details such as: * Pickup address * Dropoff address * Package description * Package value * Any special instructions Below is an example of an order creation payload: ```json payload for creating an order theme={"dark"} { "pickupAddress": "185 University Ave, Palo Alto, CA 94301", "pickupBusinessName": "Nom Nom Goods - Palo Alto", "pickupPhoneNumber": "+17201234567", "pickupInstructions": "Pickup 10 boxes of glazed for Sammy. Order Number 1234", "dropoffAddress": "401 San Antonio Rd, Mountain View, CA 94040", "dropoffBusinessName": "Sammy's House", "dropoffFirstName": "Sammy", "dropoffLastName": "Camchaw", "dropoffPhoneNumber": "+12345679019", "dropoffInstructions": "Leave at the doorstep, plz!", "deliveryMode": "now", "description": "10 boxes of donuts", "valueCents": 8990, "tipAmountCents": 1000, "currency": "USD", "itemsCount": 10 } ``` Notice the payload doesn't set a dispatch strategy: the automation from step 2 applies `NomNomCheckoutStrategy` on its own, because the order's \$89.90 value clears the \$50 rule. (You *can* set `dispatchStrategyId` directly on an order, but letting automations decide keeps that logic in Nash.) After creating the order, Nash will return a list of quotes from available delivery providers, including the price, estimated delivery time, and a tag indicating the preferred quote for autodispatch. ```json quote list in create order response theme={"dark"} "quotes": [ { "id": "qot_UPVrXwaAt4tQraeY2mDgH2", "providerId": "NomNomDrive", "providerName": "NomNomDrive", "providerLogo": "https://nash-provider-logos.s3.amazonaws.com/FleetSimulator.png", "createdTime": "2022-09-21T22:28:47.533139", "expireTime": "2022-09-21T23:28:47.533139", "priceCents": 1800, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-09-21T22:31:47.533124", "tags": null, "costSplitCustomerCents": null, "costSplitBusinessCents": null }, { "id": "qot_coBkLwCrgvtbAewhUGehf3", "providerId": "FastFleet", "providerName": "FastFleet", "providerLogo": "https://nash-provider-logos.s3.amazonaws.com/FleetSimulator.png", "createdTime": "2022-09-21T22:28:47.533681", "expireTime": "2022-09-21T23:28:47.533682", "priceCents": 650, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-09-21T22:37:47.533667", "tags": [ "autodispatch_preferred_quote" ], "costSplitCustomerCents": null, "costSplitBusinessCents": null }, { "id": "qot_dvwfGGNGSc8yUrQxrp6cgp", "providerId": "HotRodWheels", "providerName": "HotRodWheels", "providerLogo": "https://nash-provider-logos.s3.amazonaws.com/FleetSimulator.png", "createdTime": "2022-09-21T22:28:47.534574", "expireTime": "2022-09-21T23:28:47.534574", "priceCents": 2000, "nashFeeCents": 100, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-09-21T22:34:47.534567", "tags": null, "costSplitCustomerCents": null, "costSplitBusinessCents": null } ] ``` Let's say an hour passes while Sammy lingers on checkout, deciding whether to add an 11th box of donuts. The quotes have expired, so we [refresh them](/api-reference/order/refresh-order-quotes) for the order: ```shell refresh order quotes theme={"dark"} curl --request POST \ --url https://api.sandbox.usenash.com/v1/order/refresh_quotes \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" \ --header 'Content-Type: application/json' \ --data '{ "orderId": "ord_1234567890" }' ``` Finally, Sammy clicks the Place Order button in the Nom Nom app. Since Nom Nom already created the order with the appropriate details in Nash, the only thing left is to autodispatch. ## 4. Autodispatching the order Once you have reviewed the quotes, you can use Nash's autodispatch feature to assign the delivery automatically to the best provider based on your dispatch strategy. To initiate the autodispatch process, you will call the [`/v1/order/{id}/autodispatch`](/api-reference/order/autodispatch-order) endpoint. Here's an example of how to autodispatch an order: ```shell autodispatch order theme={"dark"} curl --request POST \ --url https://api.sandbox.usenash.com/v1/order/ord_1234567890/autodispatch \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" ``` When an order is dispatched, it becomes a job (the fulfillment attempt). The response contains the job, and within its job configurations you'll find two objects worth noting. The `winnerQuote` object on the job's task has the selected quote's ID. ```json autodispatch response - winner quote theme={"dark"} "winnerQuote": { "id": "qot_coBkLwCrgvtbAewhUGehf3", "priceCents": 650 } ``` The `delivery` object indicates the delivery is active. ```json autodispatch response - delivery object theme={"dark"} "delivery": { "id": "dlv_01234567890", "status": "created" } ``` ## 5. Tracking the delivery After dispatching the order, Nash provides live updates on the delivery status via [webhooks](/reference/webhooks). Use these webhooks to keep customers like Sammy informed about their order's progress, such as when the driver is en route or when the delivery is complete. ## Conclusion That's the whole loop: a dispatch strategy, an automation, an order, an autodispatch, and webhooks. Sammy gets his donuts; you get a delivery pipeline that runs the same way on every order. # Sample checkout workflow Source: https://docs.usenash.com/guides/checkout-workflow A step-by-step workflow for embedding Nash delivery quotes, dispatch, and tracking into your checkout. This page outlines a sample workflow for embedding Nash into a custom checkout. It defines a few terms and walks through the Nash endpoints we recommend for fulfilling delivery from your checkout experience. Building for a restaurant? The [fast casual restaurant integration](/guides/fast-casual-restaurant-integration) guide walks through this pattern for ASAP food orders, including kitchen prep time, guest-facing statuses, and live tracking. ## Relevant concepts * [Authentication](/reference/authentication) * [Nash Order](/api-reference/order/order) * [Dispatch automations](/api-reference/dispatch-strategies/dispatch-automations) * [Dispatch strategy](/api-reference/dispatch-strategies/dispatch-strategy) ## Step 1: Fetching order quotes If you want to validate that Nash has a delivery option or if you want to pull the delivery price quotes from Nash for your customer's cart, you should use the [Get Order Quotes](/api-reference/order/get-order-quotes) endpoint to fetch all the valid quotes for the order. Some fields you want to include in this request are: * Pickup location: you should determine the location you would like to fulfill from. Alternatively, Nash can help you determine the closest location to the customer that has inventory of the item, if you use our [Store Locations](/api-reference/store-locations/get-store-location). * Customer Address: the dropoff location for the order. You'll need a full, valid address to obtain delivery quotes. * Estimated Pickup + Dropoff times: pass the time your customer would like to have the order delivered. If only the dropoff time is sent, Nash will compute the pickup time based on the driving distance between the pickup and dropoff. * Order / cart ID (if available): associating a unique identifier to the Nash Order will allow you to recognize it in the Nash Portal. * `dispatchStrategyId` (optional): we recommend using [Dispatch automations](/api-reference/dispatch-strategies/dispatch-automations) to determine the dispatch strategy for an order, but if you want to specify the dispatch strategy at the quote creation step, then you can set up [Dispatch strategies](/api-reference/dispatch-strategies/dispatch-strategy) and pass the ID. ### Processing the quotes response How you process the quotes from Nash depends on the type of integration you are building. For instance, do you plan to show your customers an accurate delivery price in the checkout or will you just charge them a fixed fee for delivery, regardless of the Nash price? Here are a few fields that may be relevant: * **`id`**: This is the order ID that you'll use in the following steps of the checkout. * **`quotes`**: this is a list of quotes that you can process. If using dispatch strategies, look for the quote with `autodispatch_preferred_quote` in `quote.tags`. * **`failedQuotes`**: a list of quotes from providers that can't fulfill the order, along with the failure reason. If your customer abandons the cart, you don't need to cancel the order that was created to obtain quotes. ## Step 2: Updating the order If your customer decides to place the order for local delivery, then you should initially use the [Update Order](/api-reference/order/update-order) endpoint to add any fields that were missing when you first obtained your quotes. For this endpoint, you will use the Order ID returned in Step 1, and you should consider adding any new information that's relevant for the order. For example, if an order is created in your system, then you should add it in the `externalId` field. Similarly, you can update the `description`, `itemsCount`, `pickupInstructions`, and `dropoffInstructions` fields to give the driver what they need to complete the delivery. You can also call Update Order anytime your customer updates their cart and tries to fetch a new delivery quote. To avoid excessive calls, you should only call update order when your customer is ready to check out and wants the latest delivery price. ## Step 3: Dispatching orders for delivery Once your customer has placed the order, you should indicate to Nash that the order should be dispatched to the provider. Nash will forward the delivery information about the order (addresses, pickup + dropoff times, order details, etc.) to the chosen provider. There are two ways you can dispatch the order, and it depends on whether you are using dispatch strategies: 1. With dispatch strategies: use [Autodispatch Order](/api-reference/order/autodispatch-order). Autodispatching an order selects a quote based on the dispatch strategy. 2. Without dispatch strategies: use [Select Order Quote](/api-reference/order/select-quote) to choose the quote your customer has accepted in the checkout page. ## Step 4: Tracking your deliveries Once the customer's order is placed and booked with the provider, you might want to track the status of the delivery in your app. To do that, you can either use the [GET Order](/api-reference/order/get-order) endpoint or [set up webhook](/reference/webhooks) updates. ## FAQ ### (Optional) Zip / postal code validation Some merchants want their customers to validate that delivery is available for them before they reach the checkout page. A common question is "What if I want to check for local delivery availability in the pre-checkout step before a customer inserts their full address?" If you want to add a delivery widget option, we recommend adding a step in your cart page or a banner in your website to ask your customers to check if they are eligible for local delivery. If your system already tracks stores, you can store a list of zip / postal codes that are reachable by local delivery for each store, or you can rely on the Nash [Store Locations](/api-reference/store-locations/get-store-location) to check which areas are serviceable from your stores. Delivery availability widget in a cart page ### Refreshing quotes Quotes have an `expireTime`. You can use the [Refresh Quotes](/api-reference/order/refresh-order-quotes) endpoint to refresh the quotes. The pricing shouldn't change assuming no other information about the order has changed. # Fast casual restaurant integration Source: https://docs.usenash.com/guides/fast-casual-restaurant-integration Quote delivery during checkout, dispatch when payment succeeds, and track every delivery through webhooks. A fast casual delivery integration comes down to three touchpoints. You quote while the guest is in checkout, dispatch that quote when payment succeeds, and listen to webhooks until the food is at the door. Nash picks the provider (a third-party courier network, a local fleet, or your own drivers) and sends every status back in one normalized format. This guide is for a restaurant brand, or its ordering platform, that runs its own web or app checkout and wants on-demand (ASAP) delivery. For the same pattern outside restaurants, see the [sample checkout workflow](/guides/checkout-workflow). ## When to use this * Guests order in your own app or website and choose delivery at checkout. * You want to show a delivery fee and arrival time before the guest pays. * Your kitchen needs couriers to arrive when food is ready, not before. * You want delivery status in your app, your POS, or your kitchen display. If you batch many orders into planned routes for your own drivers, start with [route optimization](/guides/route-optimization) instead. ## How it works | Step | When it happens | Nash call | What you get back | | - | - | - | - | | 1. Quote | Guest enters a delivery address in checkout | [`POST /v1/order/get_quotes`](/api-reference/order/get-order-quotes) | A Nash order `id`, a list of `quotes` (price, dropoff ETA, expiry), and `failedQuotes` | | 2. Dispatch | Payment succeeds and the POS order exists | [`PATCH /v1/order/{id}`](/api-reference/order/update-order), then [`POST /v1/order/{id}/autodispatch`](/api-reference/order/autodispatch-order) | A job (delivery), booked with the provider | | 3. Track | From dispatch until a terminal status | [Webhooks](/reference/webhooks) to your endpoint (type `delivery`) | One event per status change, carrying the full job | The figure here is a sequence diagram in three phases. Your app calls Nash at two moments, once to quote and once to dispatch, and everything after that comes back as webhooks. 1. **Checkout: quote.** The guest enters a delivery address. Your app calls `POST /v1/order/get_quotes`. Nash requests quotes from providers and returns an order `id`, `quotes`, and `failedQuotes`. Your app shows the fee and ETA of the quote tagged `autodispatch_preferred_quote`. 2. **Order placed: dispatch.** The guest pays. Your app updates the order with `PATCH /v1/order/{id}`, then calls `POST /v1/order/{id}/autodispatch`. Nash books the quote your dispatch strategy prefers and returns the job. 3. **Delivery: track.** The provider reports status changes. Nash sends each one to your app as a `delivery` webhook, and your app shows live status to the guest. ## Before you start Build and test in Sandbox first. Sandbox has its own API keys and Portal, and pairs [simulated fleets](/reference/environments) with your test orders, so a test order never sends a real courier. Every request sends `Authorization: Bearer $NASH_API_KEY` and the `Nash-Org-Id` header. See [Authentication](/reference/authentication). Add each restaurant as a [store location](/reference/store-locations), with your own store number as its external ID. A quote then only needs `pickupExternalStoreLocationId`; Nash uses the store's saved address and contact details. A [dispatch strategy](/reference/dispatch-strategies) holds your rules for choosing a provider: cost, speed, a preferred fleet first, a fee cap, auto-reassignment. Apply it with a [dispatch automation](/api-reference/dispatch-strategies/dispatch-automations) so you don't send `dispatchStrategyId` on every call. Keep **Enable Autodispatch** off, so an order isn't booked the moment you quote it. In the Portal, under **Settings > Webhook Management**, add an HTTPS endpoint subscribed to `delivery` events. Copy its signing secret for [verification](/reference/webhooks#verifying-webhooks). ## Step 1: Quote during checkout Call [Create Quote](/api-reference/order/get-order-quotes) once the guest has entered a full delivery address and you know which store will make the food. The response gives the delivery fee and arrival time to show, and a Nash order `id` to keep with the cart. Call it after the address is confirmed and the cart is close to final, not on every keystroke or cart edit. If the guest changes the address or store, quote again. If they change only the cart, update the order (Step 2). ```bash theme={"dark"} curl -X POST https://api.sandbox.usenash.com/v1/order/get_quotes \ -H "Authorization: Bearer $NASH_API_KEY" \ -H "Nash-Org-Id: $NASH_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "externalId": "cart_8812", "pickupExternalStoreLocationId": "store-0142", "dropoffAddress": "350 5th Ave, New York, NY 10118", "dropoffFirstName": "Jordan", "dropoffLastName": "Lee", "dropoffPhoneNumber": "+12125550123", "deliveryMode": "now", "pickupStartTime": "2026-09-30T17:15:00Z", "valueCents": 3450, "itemsCount": 3, "description": "2 bowls, 1 drink" }' ``` | Field | Why it matters for fast casual | | - | - | | `pickupExternalStoreLocationId` | Your store number. Nash looks up the pickup address from the store location. | | `dropoffAddress` (or address components), name, and phone | A full, valid address is required for a quote; see [Send the address](#send-the-address-single-line-or-components). The phone lets the courier reach the guest. | | `deliveryMode` | `now` on the first quote. Switch it to `scheduled` if the guest picks a later delivery time. | | `pickupStartTime` | The earliest the food can be ready: now plus your kitchen prep time, plus any buffer, in UTC. On the first quote, this is the only time you send; see [Choose which time to send](#choose-which-time-to-send). | | `dropoffStartTime` / `dropoffEndTime` | Leave these out of the first quote. Send them only when the guest picks a later delivery time; Nash then works out the pickup time from drive time. | | `valueCents`, `itemsCount`, `description` | Order value and size, passed on to the provider so the courier knows what to expect. | | `externalId` | Your cart or order ID, so you can find the order in the Portal. You can replace it with the POS order number in Step 2. | | `maxDeliveryFeeCents` | Optional. A cap on the delivery fee; it overrides the dispatch strategy's cap. | ### Choose which time to send Quote with the earliest pickup time, and let the provider's ETA set the first delivery time the guest sees. 1. **First quote: send the pickup time only.** Set `pickupStartTime` to the earliest the food can be ready: now plus the restaurant's prep time, plus any buffer you want to give the kitchen. Don't send dropoff times. 2. **Show the ETA as the first available time.** Each quote comes back with a `dropoffEta`: the provider's expected arrival for that pickup time. Show the preferred quote's `dropoffEta` to the guest as the earliest delivery time they can choose. 3. **Update the order to match what the guest chose.** Before you dispatch (Step 2), send the time that matches the guest's choice: | The guest chooses | What to send before dispatch | What Nash does | | - | - | - | | The first available time | Nothing changes. Keep `pickupStartTime` as you quoted it. | Books the delivery for that pickup time. | | A later time | Set `deliveryMode` to `scheduled`, set `pickupStartTime` to `null`, and send the guest's chosen time as `dropoffStartTime` (and `dropoffEndTime` for a window). | Works out when the courier needs to pick up, from drive time, and sends back that pickup time so the restaurant knows when to have the food ready. | Don't send both a pickup time and a guest-chosen dropoff time for the same order. One anchors the delivery; Nash calculates the other. ### Send the address: single line or components Nash needs a deliverable dropoff address to quote. You can send it two ways, and you pick one per request; mixing them returns a validation error. | | Single line | Address components (preferred) | | - | - | - | | What you send | `dropoffAddress`: the whole address as one string, such as `"350 5th Ave, Apt 4B, New York, NY 10118"` | Each part in its own field, plus coordinates: `dropoffAddressNumber`, `dropoffAddressSecondarynumber`, `dropoffAddressFormattedStreet`, `dropoffAddressCity`, `dropoffAddressState`, `dropoffAddressZip`, `dropoffAddressCountry`, `dropoffLat`, `dropoffLng` | | What Nash does | Parses and geocodes the string, then returns the parsed components in the response | Uses your components and coordinates as sent, without geocoding again | | If it goes wrong | An address Nash can't parse fails validation (for example, "Address not found") | You own the accuracy of what you send | | Use it when | Your checkout only has a free-text address | Your checkout already has a structured address, such as from an address autocomplete or a saved guest profile | Send components if you have them. The address the guest confirmed at checkout is the one the courier gets, with the unit number in its own field and the pin where your autocomplete put it, rather than a second geocode of a typed string. Required components are `dropoffAddressFormattedStreet`, `dropoffAddressCity`, `dropoffAddressZip`, `dropoffAddressCountry`, `dropoffLat`, and `dropoffLng`. ```json theme={"dark"} { "pickupExternalStoreLocationId": "store-0142", "dropoffAddressNumber": "350", "dropoffAddressSecondarynumber": "4B", "dropoffAddressFormattedStreet": "5th Ave", "dropoffAddressCity": "New York", "dropoffAddressState": "NY", "dropoffAddressZip": "10118", "dropoffAddressCountry": "US", "dropoffLat": 40.7484, "dropoffLng": -73.9857 } ``` Don't send `dropoffAddress` or `dropoffPlaceId` alongside components. A Google Place ID (`dropoffPlaceId`) works like the single line: Nash looks it up and geocodes it. The pickup side follows the same rules, but with `pickupExternalStoreLocationId` Nash takes the pickup address from the store location. See [Order address validation](/api-reference/order/order-address-validation-geocoding) and [Order validations](/api-reference/order/order-validations). ### Read the response * `id` is the Nash order ID. Store it with the cart; Steps 2 and 3 use it. * `quotes` lists the options, each with `totalPriceCents`, `dropoffEta`, `expireTime`, and `providerName`. Show the guest the quote whose `tags` include `autodispatch_preferred_quote`; that's the one autodispatch will book. * `failedQuotes` lists providers that can't take the order. If `quotes` is empty, delivery isn't available to this address, so offer pickup instead. You don't have to charge the guest the Nash price. You can charge a flat delivery fee and use the quote only to confirm delivery is available and to show an ETA. If the guest abandons the cart, do nothing. An order that was quoted but never dispatched doesn't need to be canceled. ## Step 2: Dispatch after checkout completes Once payment succeeds and the order exists in your POS, update the Nash order with the final details, then dispatch it. Dispatching books the delivery with the provider and returns a job (delivery), which you track in Step 3. ### Update the order Use [Update Order](/api-reference/order/update-order) with the Nash order `id` from Step 1. Swap in the POS order number and add what the courier needs at the counter and at the door: ```bash theme={"dark"} curl -X PATCH https://api.sandbox.usenash.com/v1/order/$NASH_ORDER_ID \ -H "Authorization: Bearer $NASH_API_KEY" \ -H "Nash-Org-Id: $NASH_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "externalId": "POS-20260930-0417", "referenceId": "417", "pickupInstructions": "Order #417 on the pickup shelf by the door", "dropoffInstructions": "Apt 4B, buzz 4B", "tipAmountCents": 500 }' ``` `referenceId` is shown to the courier where the provider supports it, so use the short number your staff call out at the counter. If the guest chose a later delivery time instead of the first available one, send the time change in the same update: switch `deliveryMode` to `scheduled`, clear the pickup time, and send their chosen dropoff time (see [Choose which time to send](#choose-which-time-to-send)). ```json theme={"dark"} { "deliveryMode": "scheduled", "pickupStartTime": null, "dropoffStartTime": "2026-09-30T19:00:00Z" } ``` ### Dispatch the order Call [Autodispatch Order](/api-reference/order/autodispatch-order). Nash books the quote your dispatch strategy prefers, the one tagged `autodispatch_preferred_quote`, which is the quote you showed the guest in Step 1. Your provider rules live in the strategy, so you can change them in the Portal without a code change. [Select Quote](/api-reference/order/select-quote) (`POST /v1/select_quote` with a `quoteId`) books one specific quote. It's available, but use it only when Nash's built-in selection strategies can't express what you need. It hard-codes the provider choice in your code, and you lose strategy features like auto-reassignment and fee caps. If you think you need it, talk to Nash first; a strategy change usually covers it. Autodispatch returns `{ "job": { ... } }`. Save `job.id` on your POS order; webhooks reference it. * **Check the quote hasn't expired.** Every quote has an `expireTime`. If the guest spent a long time in checkout, call [Refresh Quotes](/api-reference/order/refresh-order-quotes) with the `orderId` first. The price shouldn't change unless the order did. * **Dispatch once per order.** Mark the POS order as dispatched when the call succeeds. If the call times out, don't retry blindly: read the order with [Get Order](/api-reference/order/get-order) and dispatch again only if it has no job. * **Plan for a failed dispatch.** If the call returns an error or no provider can take the order, alert the store, then retry after refreshing quotes, switch the order to pickup, or refund the delivery fee. ## Step 3: Receive status updates through webhooks After dispatch, Nash sends a webhook every time the delivery changes status, so you don't need to poll. Every payload has three fields: `type` (`delivery` for status changes), `event` (the new status), and `data` (the full job, the same shape as the dispatch response). ```json theme={"dark"} { "type": "delivery", "event": "dropoff_enroute", "data": { "id": "job_R7FXJrfB99bpvpzf6CriJC", "isBatch": false, "portalUrl": "https://portal.usenash.com/active/job_R7FXJrfB99bpvpzf6CriJC", "jobConfigurations": [ { "tasks": [ { "status": "dropoff_enroute" } ] } ] } } ``` ### Map statuses to what the guest sees Nash normalizes every provider's statuses into [one list](/reference/delivery-status). A fast casual app usually needs only a few guest-facing states: | Nash `event` | Guest-facing message | Store action | | - | - | - | | `created`, `not_assigned_driver` | "Finding a courier" | None | | `assigned_driver` | "Courier assigned", with name and ETA | Get the bag ready | | `pickup_enroute`, `pickup_arrived` | "Courier heading to the restaurant" / "Courier at the restaurant" | Hand off the bag | | `pickup_complete`, `dropoff_enroute` | "On the way", with live tracking | None | | `dropoff_arrived` | "Courier has arrived" | None | | `dropoff_complete` | "Delivered", with proof of delivery if present | Close the order | | `canceled_by_provider`, `canceled_by_nash`, `failed`, `expired` | "We're sorting out your delivery" | Alert the store (see [Edge cases](#edge-cases)) | | `canceled_by_customer` | "Delivery canceled" | Refund per your policy | Every completed delivery passes the seven statuses below in order; Nash infers a status if a provider skipped it. Other statuses can arrive in between, and not every provider sends every status. The figure here shows the seven statuses as stops on a line: `created`, `assigned_driver`, `pickup_enroute`, `pickup_arrived`, `dropoff_enroute`, `dropoff_arrived`, `dropoff_complete`. From any stop before the last, a delivery can end early as `canceled_by_provider`, `canceled_by_nash`, `canceled_by_customer`, `failed`, or `expired`. With auto-reassignment on, a provider cancel isn't the end: Nash books another provider on the same job, and a new task starts again at `created`. ### Build your webhook handler 1. **Verify the signature.** Nash sends webhooks through Svix. Check the `svix-id`, `svix-timestamp`, and `svix-signature` headers against your endpoint's signing secret, ideally with a Svix SDK. See [Verifying webhooks](/reference/webhooks#verifying-webhooks). 2. **Respond with a `2xx` quickly.** Queue the work and acknowledge right away. Failed deliveries are retried on a backoff; see the [retry policy](/reference/webhooks#retry-policy). 3. **Deduplicate.** Retries and multiple tasks can send the same event twice. Key on `svix-id`, and never move an order backward: ignore `dropoff_enroute` once you've seen `dropoff_complete`. 4. **Handle reassignments.** When Nash reassigns a delivery, the old task ends and a new task starts on the same job. A cancel on the old task doesn't mean the delivery is over; read `jobConfigurations[].advancedTask` for the attempt that's live. See [Reassignments and courier changes](/reference/webhooks#reassignments-and-courier-changes). 5. **Match on the job ID.** Look up your order by `data.id`, the `job.id` you saved in Step 2. ### Real-time tracking for the guest You can show the guest a live map two ways: build it yourself from location webhooks, or let Nash host it. The hosted page is the faster path, since it needs no map or backend work on your side. | | Build it yourself | Nash-hosted tracking | | - | - | - | | How it works | Subscribe to [`courier_location`](/reference/webhooks#courier-location-events) webhooks. Nash sends the courier's position every 1 to 2 minutes while the provider reports it. Draw it on your own map. | The job's `publicTrackingUrl` opens a Nash-hosted page with the courier on a live map and real-time status. Link to it, or [embed it](/reference/nash-embedding) in your app or site with an ` ``` ## Reference URL: [https://tracking.usenash.com](https://tracking.usenash.com) | Parameter | Location | Required | Description | | - | - | - | - | | `/{packageId}`, `/{jobId}`, or `/{orderId}` | Path | **Yes** | Unique identifier you receive from the Nash API. Use the order ID for [tracking before dispatch](#tracking-before-dispatch). | | `components` | Query | No | When set to `map`, hides the details panel. | URL: [https://tracking.sandbox.usenash.com/](https://tracking.sandbox.usenash.com/) | Parameter | Location | Required | Description | | - | - | - | - | | `/{packageId}`, `/{jobId}`, or `/{orderId}` | Path | **Yes** | Unique identifier from the Sandbox environment. | | `components` | Query | No | Same behavior as production. | ## Need something richer? The iframe approach covers the majority of use cases, but Nash also supports **deeper, fully customizable embeddings** — for example: * Pushing live status events directly into your own components * Re-skinning the UI to match a bespoke design system * Embedding only selective widgets (ETA chip, progress bar, etc.) These advanced options require a short enablement step on our side. Reach out to your Nash solutions engineer or email [**support@usenash.com**](mailto:support@usenash.com) to learn more. ## Related Keep customers and internal teams updated on delivery events. Push real-time delivery events to your own endpoint. # Notifications Source: https://docs.usenash.com/reference/notifications Nash's built-in notifications platform alerts customers and internal teams on delivery events across SMS, email, WhatsApp, Slack, and webhook — with reusable audiences, fine-grained targeting, and customizable messages. Nash has a built-in, customizable notifications platform that lets you own the end-to-end customer experience and keep internal teams up to speed on delivery events. Every notification separates the **event and message** from the **audience**, so you can send the same update to different audiences — or different messages on the same event — without rebuilding anything. Whether a job (delivery) reaches a new status, a driver runs late, or an order needs attention, you decide who hears about it and how. ## How it works You manage notifications under **Settings > Notifications** in the Portal, built from three pieces: * **Triggers** define *when* a notification fires and *what* it says — an event (a status change, a delay, a failed dispatch) paired with a message. * **Destinations** define *who* receives it and *where* — a reusable audience on one or more channels. * **Settings** control how messages are sent, including sending SMS from your own number. Because the event and message live on the trigger and the audience lives on the destination, one event can notify several audiences at once, and one audience can be reused across many events — no duplicated work. Notification triggers in the Nash Portal ## Channels Notifications can be delivered across multiple channels: A single destination can mix channels. Triggers can also hand off to [Nash Agent](/nash-agent/custom-agents) — starting a custom agent to investigate and resolve an issue, or pushing an alert to your team. ## Events you can notify on Nash ships a broad catalog of triggers, grouped by what they watch: * **Delivery** — a delivery reaching a new status (*Pickup Complete*, *Dropoff Enroute*, *Dropoff Complete*) — one trigger can watch several statuses at once, so a message that should go out at pickup and again at dropoff is one trigger rather than two — incident reports, task reassignments, a failed auto-dispatch or cancellation, delivery attempts exhausted, a proof-of-delivery photo uploaded, and more. * **Driver** — a driver's distance or time from pickup or dropoff, entering or leaving a [zone](/reference/zones), a location that doesn't match the reported status, and pickup or dropoff ETAs running late. * **Status** — a delivery stuck in one status too long, statuses changing faster than expected, and live-monitoring health changes. * **Order** — an order that needs attention, is ready to fulfill, hasn't dispatched as pickup nears, or has an address that won't validate, plus optimization outcomes and an event you fire yourself from the API. **Order Cutoff Time** runs a set number of minutes after an order's booked [delivery window](/reference/delivery-windows) reaches its cutoff, and moves with the order if it's booked into another window. * **Refund** — refund requests as they move through review. By default, your account includes customer-facing SMS notifications on key status transitions — *Pickup Complete*, *Dropoff Enroute*, and *Dropoff Complete* — plus internal notifications such as *Delivery Booked* and *Task Reassigned* that are disabled by default. For the complete catalog of triggers and the events behind them, see the [notifications catalog](/api-reference/notifications/notifications). ## Discover trigger types from the API [Get notification trigger catalog](/api-reference/notifications/get-notification-trigger-catalog) (`GET /v1/notification-triggers/catalog`) returns the trigger definitions your integration can use to build notification tooling. Each response includes: * `triggers`, with each trigger's type, title, entity type, statuses, parameter schema, supported filters, and supported actions * `filterCriteria`, with the shared fields and operators available for narrowing a trigger * `actions`, with the action types a trigger can run The endpoint takes no query parameters. Fetch it when your integration needs to present the current trigger catalog without hard-coding trigger types or their configuration schema. ```bash theme={"dark"} curl --request GET \ --url https://api.sandbox.usenash.com/v1/notification-triggers/catalog \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" ``` ## Customer notifications and the tracking page Customer messages typically include a **tracking link** (`{{job_tracking_link}}`) that opens Nash's live-tracking page, where the customer sees the courier's position on a map and real-time status updates. You can embed that same live-tracking experience directly in your own web or mobile app — see [Embedding the Nash live-tracking experience](/reference/nash-embedding). ## Destinations: choose who hears it A **destination** is a reusable group of recipients. Build as many as you need, mix channels within one, and point any number of triggers at it. Recipients can be: * **Static** — a specific email address, phone number, Slack channel, or webhook URL. * **Dynamic** — a variable resolved per delivery, such as `{{customer.phone_number}}` to text the recipient or `{{provider.slack_channel}}` to post in the assigned provider's channel. Because the message lives on the trigger and the audience on the destination, you can tailor the same event per audience — a detailed Slack message for your ops team and a friendly SMS for the customer. Give each destination its own message override and, for email, its own subject line. You can also limit a trigger to fire **once per job** for a destination, so a chatty event doesn't text a customer over and over. ## Target the right deliveries Not every trigger should fire on every delivery. Each trigger has an **Advanced** section to narrow when it runs: * **Dispatch strategies** — only deliveries that use selected [dispatch strategies](/reference/dispatch-strategies). * **Contracts** — only deliveries on selected contracts. * **Conditions** — only deliveries whose metadata matches conditions you define, combined with AND/OR. Operators include *equals*, *does not equal*, *greater than*, *less than*, *contains*, *starts with*, and *ends with* — for example, fire only when `priority` equals `high`. ## Flag deliveries for live ops A trigger can do more than send a message — it can **flag** the delivery so your team works it from the Flagged view in the Portal, turning passive alerts into a live queue. For triggers that watch an ongoing condition — a stuck status, a late pickup, or a late dropoff — enable **auto-resolve** to clear the flag automatically once the delivery recovers, so the queue only shows problems that are still live. ## Customize your messages Every message supports **dynamic variables** so it reflects the specific delivery. Variables span the customer, pickup, dropoff, job, order, and delivery-provider context — for example `{{customer.first_name}}`, `{{pickup_business_name}}`, `{{dropoff_eta}}`, `{{job_tracking_link}}`, and `{{driver.name}}`. The [notifications catalog](/api-reference/notifications/notifications) lists every variable, and Nash adds to it regularly. By default Nash sends SMS from its own messaging service. Your organization can instead send from its own Twilio account and sender identity, so texts arrive from a number your customers recognize — available on the **Settings** tab under Settings > Notifications. A trigger can also add a **custom event** to the delivery's timeline when it fires — useful for tracking, or to start a [workflow](/reference/workflows). For richer customer-facing customization — re-skinning the live-tracking page or embedding only selected widgets — see the advanced options in [Embedding the Nash live-tracking experience](/reference/nash-embedding). ## Track what Nash sent Open any delivery in the Portal and select the **Notifications** view on its timeline to see every message Nash sent for that job — the trigger, the message, where it went, and when — along with any replies that came back. It's the fastest way to confirm a customer was notified. ## Deny list The **deny list** is how you suppress notifications for a specific contact who has opted out of communications. Each entry records a **type** of communication and the **value** for that type — for example an email address or a phone number — giving you fine-grained, per-contact control over who Nash messages. [Add a contact to the deny list](/api-reference/notifications/add-a-contact-to-the-deny-list) (`POST /v1/organization-management/notifications/deny-list`) opts a contact out. Provide the required `type` and `value`; that contact will no longer receive delivery notifications. [Get contacts in the deny list](/api-reference/notifications/get-contacts-in-the-deny-list) (`GET /v1/organization-management/notifications/deny-list`) returns the suppressed contacts, with `page` / `pageSize` pagination. [Remove a contact from the deny list](/api-reference/notifications/remove-a-contact-from-the-deny-list) (`DELETE /v1/organization-management/notifications/deny-list`) opts the contact back in so they resume receiving notifications. The deny list operates at the organization level. Adding a contact suppresses Nash-sent notifications for that contact across deliveries, not just a single job. ## Next steps Every trigger, channel, and dynamic message variable. Read the current trigger types, filters, actions, and configuration schemas. Suppress notifications for a contact. Embed the Nash tracking page in your own app. Drive automated actions when a trigger fires. # Plan your integration Source: https://docs.usenash.com/reference/plan-your-integration How to plan a Nash API integration: timeline, account setup, dispatch strategies, notifications, and go-live. Plan a Nash API integration: the decisions to make before you build, and what a typical integration timeline looks like. (If you only need occasional deliveries, you can create them manually in the Nash Portal without building an integration.) Familiarizing yourself with Nash's capabilities and configuration choices will help you pick the dispatch strategies and notifications that fit your operation. ## Timeline Implementing a Nash integration typically takes 2 - 4 weeks. This timeline may vary based on the complexity of your delivery requirements and the extent of integration with your existing systems. | Phase | Duration | Tasks | | :- | :- | :- | | **Account Setup and Onboarding** | 1 - 2 days | Familiarize your team with the Nash system | | **Plan Checkout, Dispatch, Automations, and Notifications** | 1 - 2 days | Plan your customer experience, dispatch strategies, and monitoring approach | | **Build and Test** | 1 - 3 weeks | Implement API calls, set up webhook handling, configure dispatch strategies | | **Go-Live** | 1 - 2 days | Conduct final testing and move from Sandbox to Production | ## Account setup and onboarding Get access to the Nash API — see [API key and Org ID](/reference/generating-api-key-and-org-id). Determine who needs access to the API and to the Nash Portal. ## Plan dispatch strategies, automations, and notifications Choose the [dispatch strategy](/reference/dispatch-strategies) that Nash will use to select delivery providers for your orders, including options such as lowest cost, highest reliability, or a tailored ranking. Decide whether or not you want auto-dispatch. Read more about [dispatch strategy considerations](/reference/dispatch-strategies). Then, set [automations](/api-reference/dispatch-strategies/dispatch-automations) to automatically apply the dispatch strategies based on parameters such as order value, delivery location, and product type. Finally, decide what [notifications](/api-reference/notifications/notifications) you want to send to your customers and your internal teams. Read more about the [default enabled and disabled notifications](/reference/notifications). ## Build and test Now it's time to integrate with your e-commerce platform or order management system to ingest orders into Nash. The bulk of the work is building the end-to-end delivery experience. You'll implement the customer checkout experience, the delivery dispatch and automation strategy, and the tracking experience for customers and internal teams. To enable real-time delivery updates, you'll need to configure [webhooks](/reference/webhooks). ## Go-live When you are ready for Production testing, please reach out to **[support@usenash.com](mailto:support@usenash.com)** to enable the API in Production. ## Best practices For a smooth integration and ongoing improvement, follow these practices: 1. **Start with a solid foundation**: Take time upfront to determine your dispatch strategies and automations. Think about your existing customer experience and what you want to optimize for. 2. **Use notifications**: Set up notifications to keep you informed about delivery status, so you can identify and resolve issues quickly. 3. **Continuously improve**: Analyze delivery performance data and adjust your dispatch strategies and automations as needed to keep improving efficiency and customer satisfaction. ## Related Push real-time delivery events to your own endpoint. The key concepts behind the Nash API. Validate your integration before going live. # Rate limits & throttling Source: https://docs.usenash.com/reference/rate-limits-throttling Nash allows 20 requests per second per organization. How 429 responses work and how to back off. Nash's APIs use rate limiting to control how many requests come in. The limit is **20 requests per second per organization**, counted across your whole org, not per API key. If several systems in your organization share the API, they share this budget. A few endpoints carry lower limits of their own — the [store catalog](/reference/store-catalog) endpoints allow 10 requests per second, and custom order events (`POST /v1/order/{id}/events`) allow 3 requests per second per order. The limit applies to **requests you make to the Nash API**. It does not govern Nash's delivery of [webhooks](/reference/webhooks) to your endpoint, which has its own retry behavior. ## When you exceed the limit Requests over the limit receive an HTTP `429 Too Many Requests` response. Treat a 429 as a signal to slow down, not as a failure to retry immediately. * **Back off exponentially, with jitter.** On a 429, wait before retrying and double the wait on each subsequent 429 (for example 0.5s, 1s, 2s, 4s), adding a small random jitter so retries from parallel workers don't resynchronize. * **Honor `Retry-After` when present.** If a 429 response includes a `Retry-After` header, wait at least that long before retrying instead of using your own delay. * **Retry idempotently.** Send your own external identifier — `externalId` on orders, `externalIdentifier` on jobs (deliveries) — so a retried call references the same record instead of creating a duplicate (see [create or update order by external identifier](/api-reference/order/create-or-update-order-by-external-identifier)). * **Smooth your own traffic.** For bulk work, cap your client's outbound rate below the limit (a token-bucket or a small concurrency limit) rather than bursting and relying on retries. ## Need a higher limit? If your organization has a sustained need above 20 requests per second, contact the Nash support team at [support@usenash.com](mailto:support@usenash.com) to discuss an adjustment. Share your expected peak throughput and the integration driving it. # Refunds & incidents Source: https://docs.usenash.com/reference/refunds-and-incidents Report a delivery incident, optionally request a refund, and track the refund request through review. When a delivery goes wrong — a damaged package, missing items, or a late arrival — you can **report a delivery incident** against the affected task. An incident can optionally request a refund, which Nash tracks as a **refund request** you can poll until it's resolved. ## Reporting a delivery incident Report an incident with [Create delivery incident](/api-reference/job/create-delivery-incident) (`POST /v1/jobs/{jobId}/tasks/{taskId}/delivery_incident`). The incident is recorded against a specific task on a job (delivery). The request requires: * `raiseIncident` (boolean) — flag the delivery to be reviewed by Nash's team (reviews aren't real-time — don't rely on this for urgent issues) * `incidentNotes` (string) — details about what happened It also accepts: * `incidentType` — either `REFUND_REQUEST` or `COURIER_NOTES` * `incidentSubtype` — the refund-request type (see below) * `incidentFileUrl` / `incidentFileUrls` — links to supporting file attachments * `requestAmountDeliveryFeeCents`, `requestAmountOrderValueCents`, `requestAmountTipCents` — the amounts you're requesting back To request a refund, at least one of `requestAmountDeliveryFeeCents`, `requestAmountOrderValueCents`, or `requestAmountTipCents` must be provided. All provided values must be non-negative integers (≥ 0), and at least one must be greater than zero. When an incident is a refund request, you classify it with a subtype. The supported refund-request types are: | Subtype | Meaning | | - | - | | `arrived_late` | The delivery arrived later than expected | | `delivered_early` | The delivery arrived earlier than expected | | `delivered_to_wrong_address` | The order went to the wrong place | | `missing_or_incorrect_items` | Items were missing or incorrect | | `never_delivered` | The order was never delivered | | `poor_delivery_experience` | A general quality issue with the delivery | | `other` | Any other reason | ## Refund requests A refund request captures what you asked for, what was granted, and the supporting context. Retrieve them by task or job with [Get refund requests](/api-reference/job/get-refund-requests) (`GET /v1/refund-requests`) — at least one of `taskId` or `jobId` is required — or fetch a single request by its ID with [Get refund request](/api-reference/job/get-refund-request) (`GET /v1/refund-request/{id}`). A refund request includes, among other fields: * Identity and linkage: `id`, `jobId`, `taskId`, `organizationId`, `createdAt`, and `resolutionDate` * The classification and context: `incidentSubtype`, `incidentNotes`, and the courier's `response` * The requested amounts: `requestAmountCents` plus the per-bucket `requestAmountDeliveryFeeCents`, `requestAmountOrderValueCents`, and `requestAmountTipCents` * The granted amounts: `refundAmountCents` plus the per-bucket `refundAmountDeliveryFeeCents`, `refundAmountOrderValueCents`, and `refundAmountTipCents`, in the given `currency` * The current `status` and any `proofOfRefund` Filing is idempotent per task. If a request times out on your side and you send it again, the retry returns the original `refundRequestId` instead of opening a second request for the same delivery — so retrying on a timeout is the right thing to do. Amounts are split into three buckets — delivery fee, order value, and tip — on both the **requested** and **granted** sides, so you can see exactly which part of a delivery was refunded. ## Statuses A refund request carries a `status` that reflects where it is in review. Statuses you may see include: | Status | Meaning | | - | - | | `draft` | The request has been started but not submitted for review | | `under_review` | The request is being reviewed | | `needs_attention` | The request requires manual intervention | | `approved` | The request has been approved | | `nash_approved` | The request has been approved by Nash | | `denied` | The request has been declined | | `cancelled` | The request was cancelled | The plain-language meanings are summarized here for convenience; treat the status string itself as the source of truth when reconciling a request's state in your system. ## Next steps Report a problem with a delivery and optionally request a refund. List refund requests for a task or job. Retrieve a single refund request, including its status. # Roles and access Source: https://docs.usenash.com/reference/roles-and-access How Nash roles and feature permissions work, recommended user groups to start from, and how to keep configuration and credentials restricted. Every person in your Nash organization holds one or more roles. A role is a set of features they can read and features they can change. Nash seeds four roles when your organization is created, and you can build your own on top of them. Getting this right early is cheaper than fixing it later. Access shapes what your operators see on a busy morning, who can change dispatch behavior mid-peak, and how quickly someone loses access when they leave. ## How access works Access is granted by **feature**, not by individual screen. Each feature has a read side and a write side: read lets someone see it, write lets them change it. Granting write always carries its matching read, so a role with write access to a feature can always see that feature too. The features you assign to a role are: Analytics, Monitor, Deliveries, Batch Jobs, Dispatch Strategy, Optimization Strategy, Route Monitor, Delivery Windows, Shifts, Zones, Store Locations, Store Inventory, Products, Templates, Notifications, Webhooks, Workflows, Nash Agent, Billing, Organization Management, Organization Roles, and User Management. Two consequences worth knowing before you design roles: * **Roles are additive.** Someone with two roles gets the union of both. There is no deny rule that claws access back, so you reduce access by removing a role, not by adding a narrower one. * **Roles are per organization.** If your operation spans several Nash organizations, a person's access in one says nothing about their access in another. ## The default roles | Role | What it covers | | - | - | | **Owner** | Every feature in the organization, read and write. | | **Admin** | Every feature in the organization, read and write. | | **Operations** | The delivery day: create and act on orders and deliveries, plus read access to history, tracking, analytics, monitor, store locations, and optimization strategies. No configuration access. | | **Billing Admin** | Everything Operations covers, plus read and write on Billing. | Two things surprise people: **Owner and Admin carry the same permissions.** Choosing Admin instead of Owner does not reduce what someone can do. If you want a lesser administrator, build a custom role rather than assuming Admin is one. **Billing Admin is not billing-only.** It is Operations plus Billing, so it can also act on live deliveries. If your finance team should only see invoices, give them a custom role with Billing alone. **A new custom role starts with read on Organization Management.** The Add role drawer lists Organization Management with the other features, and ticks its read side for you, because signing in to the Portal needs it. You can untick it, but a user whose only role lacks it can't get in. ## Recommended user groups A structure that holds up in most operations. Start from the group that matches the job, then add features by exception rather than by default. | Group | Who is in it | Start from | Add | | - | - | - | - | | **Delivery operations** | Dispatchers, store and depot teams, customer service | Operations | Nothing. This is the majority of your users. | | **Operations lead** | Duty managers who tune behavior during the day | Operations | Write on Dispatch Strategy, Delivery Windows, Shifts, and Zones | | **Integration engineer** | The people who own your API integration | Custom role | Write on Webhooks and Templates, read on Deliveries and Notifications | | **Finance** | Invoice and cost review | Custom role | Billing only | | **Access reviewer** | Security or IT confirming who has what | Custom role | Read on Organization Roles, User Management, and Organization Management | | **Platform administrator** | The few people who configure Nash | Admin | Nothing. Keep this group as small as it can be. | Name roles after the work rather than after seniority, so the next person to join maps cleanly onto one. "Depot dispatcher" and "Finance review" age better than "Level 2". ## Restricting configuration and credentials Two controls come up in almost every security assessment. **Webhook configuration.** Webhooks are their own feature, and the stock Operations role does not include it. Anyone on Operations can work the delivery day without being able to see or change where Nash sends your events. Give write on Webhooks only to the group that owns your integration, and keep read on it for anyone who needs to debug delivery of events without changing endpoints. **API key visibility.** Keys belong to the person who created them. A user sees only their own keys; listing every key in the organization is restricted to Owner and Admin. A key's secret is shown once at creation and never again, so it cannot be recovered from the Portal by anyone, at any access level. A key also carries its own feature grants and can never exceed the access of the user who created it, so a key made by someone on Operations cannot reach Billing or Webhooks no matter which features are ticked. See [API key and Org ID](/reference/generating-api-key-and-org-id). If your security team needs both restrictions in writing: webhook configuration is gated by the Webhooks feature, and organization-wide key visibility is limited to the Owner and Admin roles. **Deleting proof of delivery.** Removing a proof-of-delivery photo from a delivery is its own permission, `delete.proof-of-delivery`, separate from the ability to edit the delivery. Owner and Admin hold it; Operations and the other roles don't, and a user scoped to a store location or driver group doesn't see the action at all. A deletion takes effect in Nash immediately and the stored file is cleaned up in the background — which is what you need when a recipient asks for a photo to be removed, without handing that ability to everyone who can edit a job. ## Best practices **Start everyone on Operations.** It covers the delivery day without exposing configuration. Most people in a logistics operation never need more, and starting narrow means access grows by request instead of by default. **Separate running the day from changing the rules.** Dispatch Strategy, Optimization Strategy, Workflows, Webhooks, and Notifications decide how the system behaves for every order that follows. Keep write access to those with a small, named group. Reading them is different: dispatchers benefit from seeing which strategy is in play, so read access is usually worth granting widely. **Count your Owners and Admins, then cut the number.** Full access is the right answer for a handful of people who configure the platform, and the wrong answer for anyone who was given it once to unblock a launch. Revisit the list after go-live, while the temporary grants from launch week are still recent enough to explain. **Let your directory drive it.** With [single sign-on](/reference/single-sign-on), your identity provider groups map to Nash roles, so a change in your directory shows up in the Portal on the next sign-in. Joiners and leavers become a directory operation rather than a Nash one, which removes the drift that builds up when both sides are maintained by hand. **Set the least-privileged role as your default.** New users provisioned through SSO land on your organization's default role before anyone reviews them. Make that the narrowest role you have, not a convenient one. **Keep API keys on their own budget.** One key per integration, scoped to what that integration does, so you can rotate or revoke one without taking down the rest. **Re-check access when the operation changes shape.** A new region, a new fulfillment model, or a team reorganization usually needs a new role, not a wider existing one. Widening the role everyone already holds is the quiet way an organization ends up with fifty administrators. ## FAQ Yes. A custom role is any set of features with read or write on each, so you can build a dispatcher who cannot change strategies, or a finance reviewer who sees only Billing. Nash's default role names (Owner, Admin, Operations, Billing Admin) are reserved and cannot be edited or reused, which keeps a stock role from quietly meaning something different in one organization. Someone with write access to User Management and Organization Roles, which in practice means an Owner or Admin. Keep that list short: it is the access that can grant every other kind. No. Users see their own keys. Listing every key in the organization is limited to Owner and Admin, and no role can reveal a key's secret after creation. If you use SSO, remove them in your identity provider and they can no longer sign in; the Nash user is deactivated rather than deleted, so their past actions stay attributed. Without SSO, remove their access in the Portal. Either way, check for API keys they created, since a key outlives the browser session. Both. Roles combine, and the person gets everything either role allows. To reduce what someone can do, take a role away rather than adding a narrower one. Talk to your Nash contact about how to scope this for your operation. The right shape depends on how your organizations and store locations are set up, so it is worth a conversation before you build roles around it. ## Related Connect Portal sign-in to your identity provider and map your groups to Nash roles. Create a key in the Portal, scope it to features, and find your Org ID. How API requests authenticate, and why keys are separate from people. # Routes Source: https://docs.usenash.com/reference/routes A route is a planned, ordered sequence of stops for a vehicle or driver. Learn how to create, update, and dispatch routes — and how routes relate to route optimization. A **route** represents a planned sequence of stops for a vehicle or driver. Where a single job is one pickup-to-dropoff delivery, a route bundles many stops into one ordered itinerary — the unit you use when a driver completes several deliveries in a single trip. ## What a route is The core of a route is its `stops` array, which lists the locations to visit in order. Each stop details: * The kind of stop (`stopType`) * Associated items (`objectIds`) * Timing estimates (`arrivalTime`, `departTime`, `serviceTime`) * Travel details from the prior stop (`distanceFromPrevious`, `durationFromPrevious`) * The physical coordinates (`location`) A route also carries travel metrics and the orders associated with each stop. Retrieve a single route, including its stops, travel metrics, and associated orders, with [Get Route](/api-reference/route/get-route) (`GET /v1/routes/{id}`). ## Creating & updating routes Use [Create or Update Route](/api-reference/route/create-or-update-route) (`POST /v1/routes`) to create a new route or update an existing one by providing the route configuration and its stop details. This is the manual path: you supply the ordered stops yourself. In the Portal, you can start a route before its orders are ready. **Create route** on the Routes page asks for a **Route name** (up to 128 characters) and, optionally, a **Start location** and an **End location** from your store locations, then opens the new, empty route so you can add orders to it. It needs Routes turned on for your organization, a role that can edit orders and deliveries, and a user who isn't scoped to a single store location or driver group. An empty route is listed under the day it was created. Give it a start location if you plan to move stops onto it from another route: **Move to route** needs to know where the destination loads (see [Plan routes on the Orders map](#plan-routes-on-the-orders-map)). You can recalculate timing for a route with `POST /v1/routes/{id}/calculate-timing`, which computes per-stop ETAs and a polyline using truck routing, updating each stop's arrival and departure times and returning the route polyline. Timing runs from the route's planned departure from its start, falling back to the first order's pickup window when the route has no planned departure. It works for every organization with Routes; the `route_timing_not_enabled` error some organizations used to get is gone. You don't have to build routes by hand. [Route optimization](/guides/route-optimization) generates routes for you from a set of orders, and can save them as route objects you then dispatch. ### Naming a route A route carries an optional `name` — a label a dispatcher recognizes, as opposed to the generated identifier. Set it on [Create or Update Route](/api-reference/route/create-or-update-route), or from the Portal: the Execute page has a **Rename route** action, and splitting stops into a new route lets you name that route as you create it. Renaming works after dispatch too, so a route can be relabeled once you see how the day is actually running. Clear the name and displays fall back to the route's external identifier. ## Plan routes on the Orders map The Orders and Routes pages each have a map view, and it's a working surface: you can select work on it, put that work on a route, and compare routes without going back to the table between steps. Route changes here apply to routes that haven't been dispatched. They need Routes turned on for your organization, a role that can edit deliveries, and a user who isn't scoped to a single store location or driver group. **Select orders the way you think about them.** Click a pin to select every order at it — a single order, a shared address, or a cluster — and Shift+click to look at the pin's orders without selecting them. **Select all** takes every order behind the current filters, not only the ones drawn. For a shape, open **Tools**, choose **Select area**, and pick **Lasso** or **Polygon**: drag a ring, or click corners and close the shape on the first one. An area adds to the selection you already have, it covers the orders loaded on the map, and Esc cancels it. The bar of bulk actions that appears is the table's own, acting on exactly what the map shows as selected. **Put a selection on a route.** With unassigned orders selected, **Add to route** opens the route chooser for the whole selection and assigns every order in one step. Candidate routes are ranked by how close their stops are to the orders you picked. **Nearby routes** leaves out a route whose vehicle lacks a capability the orders require and says how many it hid; a route whose vehicle meets every preference is marked **Preferred**. The full list of routes stays unfiltered, with a **Can't carry** note on the ones that don't fit. **Move stops between routes.** Select route stops — by click, with the same area tools, or by ticking dropoffs in the route cards to gather them from several routes — and choose **Move to route**. Moved stops join the destination ahead of its route end, and any you selected that are already on the destination stay where they are. You can move every stop off a route that hasn't been dispatched; the emptied route stays in the list, so you can move stops back onto it or archive it. Routes that load at their start (a depot) can exchange stops even when they leave from different depots, while a route that collects from a separate pickup location exchanges stops only with routes that load at the same place. Nash refuses a move from a route that's already dispatched, one onto a route with no start or pickup to load from, and one whose stops changed after you selected them. The chooser behind **Add to route** and **Move to route** lists the routes you've focused on the map first, marked **Focused on map**, and its search matches the text on the route cards. When it can't confirm yet, it says why beside the button. **Resequence without losing stops.** **Resequence** re-solves a route that hasn't been dispatched using the route's own vehicle, routing, service times, and driver breaks, and it keeps every stop you put on the route. To do that it relaxes the limits that would otherwise push a stop off — the length of the day, distance, capacity, vehicle capabilities, the end of the shift, and dropoff windows; pickup windows still hold — and records what the route now breaks. The result reads **Every stop kept · N constraint violations**, the route card shows the count, and the route's details show **Route health**: late stops, time past the shift end, over-capacity loads, and orders needing a capability the vehicle doesn't have, each linked to its stop, with **Acknowledge** to record that you've seen them. If the optimizer still can't place a stop, the route isn't changed and you're told so. A resequenced route keeps its name, number, and vehicle, and a route with no orders can't be resequenced. **Tell the optimizer what order matters.** **Edit sequencing**, in a stop's menu on a route card or in the route's details, sets three things on the order behind that stop: a **Sequence Priority** from 0 to 499, where a smaller number is visited earlier; a **Sequence Group**, which limits that ordering to orders in the same group and keeps the group on one route; and a **Mutually Exclusive Label**. The values show as badges on the stop and take effect the next time the route is optimized. They can't be changed once the route is dispatched. **Act from the route card.** Each route's card carries **Dispatch**, **Resequence**, and **Edit**, which opens the same editor the Routes table does, with **Archive**, **Calculate timing**, **Split**, and the copy actions in its menu. They drop out once the route is dispatched. Provider and driver can be set from the card before dispatch, and the Routes table shows each in its own column. **Recalculate after an edit.** Changing the order of a planning route's stops leaves its arrival times describing a route that no longer exists. The route's card and its details say so, with **Times are out of date**, and **Recalculate** works the times out again from the new sequence. Times aren't recalculated on their own, so a route you've edited keeps the mark until you do. **Switch Routes to a grid.** Under **Settings → Preferences → Routing**, **Routing UI** chooses the layout everyone in your organization gets when they open Routes: **Cards**, the default, with route cards beside the map, or **Grid**, with a table of routes and a table of the selected routes' stops beside it. The grid suits planning by the numbers: the route table carries stops, service time, size, and percent of capacity, with a totals row, and the stop table carries each stop's sequence, time window, and departure. Tick stops to **Move Stops…** or **Set Priority…**, or drag them to reorder or onto another route. The stop panel can also draw each stop as a **Timeline** against its window, showing where a driver waits for a window to open or arrives after it closes. **Compare routes.** Shift+click route cards or route lines to hold several in focus, and the map frames all of them. Hover a route line for its driver, vehicle, planned time, distance, and stop count. Colors are assigned across the routes on screen from a palette of 48, so two routes in view don't share one; past 48, a color repeats only between routes that are far apart. **Choose what the map shows.** The **Layers** control, on every Portal map, switches to a **Satellite** view — imagery with street names — and on the Orders map holds the **Routes**, **Orders**, **Customers**, and **Order labels** layers. **Orders** shows unassigned orders by default and can switch to **Needs Attention**, **Ready to Dispatch**, **Dispatched**, or **All orders**; selecting orders to plan still works on unassigned ones. **Sort routes** orders the route list by **Date created** or by **Route ID**, which reads the numbers in an ID the way you do, so D2 comes before D10. Times and date ranges on the map follow the time zone shown on its toolbar, which defaults to your organization's; **Use organization timezone** at the top of its list switches back to it. **Tools → Filters** filters the map from the map: it holds the table's filters, such as pickup and dropoff store, tags, and dispatch status, and a filter set in either place applies in both. Its **Route metrics** conditions narrow routes by **Boxes**, **Distance**, **Duration**, and **Stops**, in your display units; a route missing a number a condition needs is set aside behind a **Show** link rather than dropped. **Sort routes** can order by the same four numbers. Filtering or sorting on a metric loads every route first, so the result is complete. **Display fields**, set by an owner or admin for the whole organization, picks the order details, metadata, and tags shown on stop cards, with up to two of them printed on the map itself. Those labels appear on every stop that has its own pin, focused route or not; stops merged into one pin are labeled once you zoom in. ## Dispatching routes Once a route is planned, dispatch it with [Dispatch Routes](/api-reference/route/dispatch-routes) (`POST /v1/routes/dispatch`). This creates jobs for each route and assigns them based on your organization's dispatch strategy. Pass the route IDs to dispatch: ```json theme={"dark"} { "routeIds": ["rte_route_id_1", "rte_route_id_2"], "autoDispatch": true } ``` | Parameter | Type | Required | Description | | - | - | - | - | | `routeIds` | String\[] | Yes | The route IDs to dispatch | | `autoDispatch` | Boolean | No | Automatically dispatch to a provider (default: `true`) | When `autoDispatch` is `true`, the resulting jobs are sent to a provider immediately according to your [dispatch strategy](/reference/dispatch-strategies). Set it to `false` to create the jobs without dispatching them right away. ## Watching routes on the Execute page Once routes are dispatched, the Execute page in the Portal is where you watch them run. It lists dispatched routes for a date range, and every part of it exists to be narrowed until it shows the routes you're responsible for. **Pick the day first.** Execute opens on today. The date control leads with your organization's date range presets — **Today**, **Yesterday**, **Tomorrow**, and **This week** unless an admin has chosen others (see [Display preferences](/reference/display-preferences#set-your-organizations-defaults)) — each applying the moment you choose it. **Custom range** opens a calendar, and nothing moves until you apply the range. A link carrying its own dates still wins over the default, and so does a saved view. **Then narrow it.** Filters are grouped by what a dispatcher is deciding — route operations, assignment, orders and locations, and package — rather than by which object each field happens to belong to. Among them are **driver group** and **courier**, so someone who owns one group can work only their own routes. Selections are held in the URL and in saved views, which makes a narrowed queue something you can send to a colleague. **Check whether the driver is moving.** Each assigned driver carries a location freshness reading beneath their name, both in the table and in the route detail panel. A location counts as live through 15 minutes and after that reads as not live, with its age — the same rule the delivery map uses. Select the reading to open a small map at the driver's last known position. **Read the day as a timeline.** The timeline view draws each route as drive, service, waiting, and break segments in proportion to the time they take, with the shift's end marked and any overrun called out — the same drawing the Optimize review uses to show how much of a shift a proposed route consumes. Select a dropoff on it to open the route panel at that stop. **See what the driver did with a stop.** When a driver skips a stop and comes back to it, both show on the route's detail panel, the order drawer, and the delivery timeline as **Skipped** and **Resumed** events, with the driver's reason when they gave one. **Work the exceptions.** **Add exception** records what happened on a route, with a **Severity** you can leave on **Auto** for Nash to rate or set from P0 to P3. In the route's details, each exception's card sets its status — **Needs Triage**, **Acknowledged**, or **Resolved** — or removes it, and resolved exceptions stay listed, read-only. To hand out a batch, select routes in the table and choose **Assign to** or **Clear assignee**; it applies to those routes' open exceptions and skips routes that have none. **Turn off what you don't use.** The map view, the timeline view, and the KPI cards at the top of the page can each be switched off for the whole organization under **Organization settings → Products → Execute**. All three start on, so turning one off is the opt-out — for teams who find the full page busier than their work needs. A link that asks for a view you've turned off still resolves; it falls back to the table. ### Who can open Execute Execute is gated by two permissions — `execute.view` to open the page and read a route, and `execute.act` to change one. An organization admin grants either one on a role, from the Add role and Edit role drawers in the Portal. The two rows appear there once your organization has Execute turned on. A user scoped to a single store location can hold those permissions too. Execute then behaves the same way it does for anyone else, narrowed to the routes that touch their store: the queue, the header counts, and the route detail panel all apply the scope server-side, and a route outside it returns a `FORBIDDEN` error rather than a partly-filled page. What they can *do* still comes from their role — with `execute.act`, a store operator can reassign a route's courier and record what happened on a route (a breakdown, an ad-hoc break, an overweight or oversize load, a refuel, a late departure, a customer not home, a catchment or break-compliance violation) without an org admin doing it for them. Vehicle assignment stays with unscoped users. Driver and vehicle pickers only open on a route that Nash is running with your own fleet. On a route assigned to an external provider, the driver and vehicle are the provider's to set, so Execute shows them read-only; a route with no provider chosen yet reads **Assign provider** until you pick one. ## Remove an order from dispatch You can remove a dispatched, route-based order by either identifier: * [Remove by external ID](/api-reference/order/undispatch-an-order): `POST /v1/orders/external-identifier/{external_id}/undispatch` * [Remove by Nash order ID](/api-reference/order/undispatch-an-order-by-id): `POST /v1/orders/{order_id}/undispatch` Both operations cancel the order's active delivery attempt, remove the order from its route, update the linked batch, and return the order with `status: valid`. The request has no body. These operations apply only to an order that is currently dispatched and associated with a route. A standalone order or an order that has not been dispatched returns an error. ## Routes vs. route optimization It's worth separating two related concepts: * A **route** is the data object — a defined, ordered sequence of stops. You can author routes directly via the API. * **Route optimization** is the engine that *produces* routes. Given a set of orders and your fleet constraints, the optimizer solves the Vehicle Routing Problem (VRP) — balancing capacity, time windows, driver shifts and breaks, vehicle profiles, traffic, and cost — and returns efficient routes. A typical optimized flow is: submit orders for optimization, poll until it completes, then either dispatch the optimized orders directly or save them as route objects and dispatch those routes. For the full walkthrough, see the [Route optimization](/guides/route-optimization) guide. ## Next steps Build or modify a route and its stops. Retrieve a route's stops, metrics, and orders. Turn routes into jobs and dispatch them. Return a route-based order to a valid, pre-dispatch state. Generate optimized routes from a set of orders. # Shift dispatched event Source: https://docs.usenash.com/reference/shift-dispatched-event The shift.dispatched webhook fires when orders from a delivery window (shift) are dispatched in bulk to delivery jobs. Payload structure, scenarios, and error reference. Nash sends the `shift.dispatched` webhook when a [delivery window](/reference/delivery-windows) (shift) passes its cutoff and its confirmed orders are dispatched as a batch — one event that tells you which orders became jobs (deliveries) and which failed, grouped by organization, store location, pick shift name, and cutoff date. **Event Type**: `shift.dispatched`\ **Entity Type**: `shift`\ **Event Name**: `dispatched` The figure here is a sequence diagram between your system, Nash, and a provider: 1. Your system gets the eligible delivery windows for an order (`GET /v1/eligible_delivery_windows`). 2. It books a window (`POST /v1/order/external-identifier/{externalId}/delivery-window/book`), which holds capacity. 3. It confirms the window (`POST /v1/order/external-identifier/{externalId}/delivery-window/confirm`), which commits the reservation. 4. The window's cutoff passes. 5. Nash validates each confirmed order, dispatches the shift to a provider as a batch, and sends your system one `shift.dispatched` webhook listing the jobs that were created and what failed. ## When is this webhook triggered? The webhook is triggered when: 1. **Automatic Dispatch**: The system automatically dispatches orders from delivery windows that have passed their cutoff time 2. **Bulk Processing**: Orders are grouped by organization, store location, pick shift name, and cutoff date 3. **Validation**: Each order in the group is validated before dispatch; failures are reported in the `errors` field while the remaining orders dispatch ## Validation rules Before orders are dispatched, the system validates each order against the following criteria: ### Order-level validations 1. **Delivery Window Status** * Delivery window status must be `confirmed` * Orders with other statuses (e.g., pending, canceled) are skipped 2. **Cutoff Time** * Cutoff time must have passed * Orders with future cutoff times are not dispatched 3. **Order Status** * Order must not already be in `dispatched` or `archived` status * Prevents duplicate dispatches ### Group-level validations * All orders in a store/shift group must meet the criteria above * If any order fails validation, it's reported in the `errors` field * Successful orders still get dispatched; the webhook includes both successes and failures ## Webhook payload structure The webhook payload follows this structure: ```json theme={"dark"} { "type": "shift", "event": "dispatched", "data": { "eventSourceId": "act_", "created_job_ids": ["job_", "job_", "job_"], "errors": { "ord_": "Error message describing the failure", "job_": "job_autodispatch_failure, Error dispatching to provider" } } } ``` ### Payload fields | Field | Type | Description | | - | - | - | | `type` | string | Always `"shift"` for shift-related webhooks | | `event` | string | Always `"dispatched"` for this webhook | | `data.eventSourceId` | string | Activity ID that triggered the dispatch (format: `act_*`) | | `data.created_job_ids` | array | List of successfully created job IDs (format: `job_*`) | | `data.errors` | object | Dictionary mapping order/job IDs to error messages | A job that is created but then fails provider dispatch appears in `errors` keyed by its `job_` ID, not in `created_job_ids` — see [Scenario 3](#scenario-3-partial-success-with-provider-errors). ## Webhook scenarios ### Scenario 1: complete success All orders in the shift are successfully dispatched to jobs. **Example Payload:** ```json theme={"dark"} { "type": "shift", "event": "dispatched", "data": { "eventSourceId": "act_7xK9mP2nQ4wL", "created_job_ids": [ "job_8rY3nK7pL2xM", "job_5tW9mN3kP8yQ", "job_2zX6kL8rM5wN", "job_9vT4nM7pQ3xK" ], "errors": {} } } ``` **Interpretation:** * 4 orders were successfully dispatched * No errors occurred * Each job ID corresponds to one order being delivered * All orders from the shift are now in the delivery system ### Scenario 2: partial success with validation errors Some orders succeed while others fail validation checks. **Example Payload:** ```json theme={"dark"} { "type": "shift", "event": "dispatched", "data": { "eventSourceId": "act_4mK8pN2wL9xQ", "created_job_ids": [ "job_3nL7mK9pW2xQ", "job_6kM2nP8wL5yR" ], "errors": { "ord_5xW9mL3kP7yN": "order_validation_error: Delivery address is incomplete", "ord_8nK2pM7wL4xQ": "order_validation_error: Order has validation_errors: Missing customer phone number" } } } ``` **Interpretation:** * 2 orders successfully dispatched (jobs created) * 2 orders failed validation and were not dispatched * Failed orders need to be corrected and re-dispatched manually * The validation error messages provide details on what needs to be fixed ### Scenario 3: partial success with provider errors Orders pass validation but fail when dispatching to the delivery provider. **Example Payload:** ```json theme={"dark"} { "type": "shift", "event": "dispatched", "data": { "eventSourceId": "act_9pL3nM7kW2xQ", "created_job_ids": [ "job_2kM8nL9pW5xQ", "job_7nK3mP2wL8yR" ], "errors": { "job_4xW9mL3kP7yN": "job_autodispatch_failure, Error dispatching to provider", "ord_8nK2pM7wL4xQ": "order_validation_error: Order has validation_errors: Missing customer phone number" } } } ``` **Interpretation:** * 2 orders successfully dispatched to the provider * 1 order failed at the provider dispatch stage (after job creation) * 1 order failed validation and was not dispatched * Provider errors may indicate capacity issues, service area restrictions, or provider API problems * Orders with provider errors may have jobs created but not accepted by the provider ## Error types reference ### Order validation errors Order validation errors come from Nash's order validation. See the full validation rules in [Order validations](/api-reference/order/order-validations). **Common validation error patterns:** #### Required fields | Field | Error Message | | - | - | | Any required field | `required field` | | `pickupFirstName` / `pickupBusinessName` | `required field if pickupBusinessName is not provided` / `required field if pickupFirstName is not provided` | | `dropoffFirstName` / `dropoffBusinessName` | `required field if dropoffBusinessName is not provided` / `required field if dropoffFirstName is not provided` | #### Store location errors | Error Message | Meaning | | - | - | | `{store_location_id} does not exist` | Store location ID not found in organization | | `{store_location_id} is deleted` | Store location has been deleted | | `Error creating store {external_store_location_id}` | Failed to auto-create store location | #### Address and location errors | Error Message | Meaning | | - | - | | `Address not found` | Address could not be geocoded/parsed | | `Pickup and dropoff places must be the different` | Same place\_id used for pickup and dropoff | | `Pickup and dropoff addresses must be the different` | Identical pickup and dropoff addresses | | `Pickup and dropoff countries must be the same` | Cross-border delivery not allowed | | `Distance between pickup and dropoff is {distance} miles, which exceeds your organization's maximum delivery distance of {max} miles.` | Distance exceeds org-specific limit | | `Distance between pickup and dropoff locations is too large: {distance} miles. Limit is 300 miles.` | Distance exceeds global default limit | #### Phone number errors | Error Message | Meaning | | - | - | | `Invalid phone number` | Phone number format invalid for country | | Phone validation errors | Various errors from phone number validation | #### Value and type errors | Field | Error Message | Meaning | | - | - | - | | `valueCents`, `tipAmountCents`, `itemsCount` | `value must be an integer` | Non-integer value provided | | `weight`, `height`, `width`, `depth`, `volume` | `value must be a decimal number` | Non-numeric value provided | | Numeric fields | `value must be greater than 0` | Zero or negative value | | `valueCents` | `value must be more than 0` | Order value is zero or negative | | `itemsCount` | `value must be more than 0 or null` | Items count is zero | | `maxDeliveryFeeCents` | `value must be greater than 0` | Max fee is zero or negative | | Name fields | `value must be a string` | Non-string value provided | | Name fields | `value is too long: {length} > {max}` | Name exceeds maximum length (80 chars) | #### Delivery mode and datetime errors | Error Message | Meaning | | - | - | | `Valid values are …` | Invalid delivery mode — the message lists the valid modes (`now` or `scheduled`) | | `at least one value is required` | Scheduled mode requires at least one datetime field | | `Time is in the past` | Datetime is earlier than current time (when not allowed) | | `cannot be later than pickupEndTime` | `pickupStartTime` is after `pickupEndTime` | | `cannot be later than dropoffEndTime` | `pickupEndTime` or `pickupStartTime` is after `dropoffEndTime` | | `cannot be later than dropoffStartTime` | `pickupStartTime` is after `dropoffStartTime` | #### Currency and requirements errors | Field | Error Message | Meaning | | - | - | - | | `currency` | `Supported currencies are …` | Invalid currency code — the message lists the supported currencies | | `requirements` | `Supported values are …` | Invalid package requirement — the message lists the accepted values; see [Order requirements](/api-reference/order/order-requirements) | | `minimumVehicleSize` | `Supported values are …` | Invalid vehicle size — the message lists the accepted sizes | #### External ID errors | Error Message | Meaning | | - | - | | `'{external_id}' is used in other order(s): {order_ids}` | External ID already in use | #### Items errors | Error Message | Meaning | | - | - | | `The items data is invalid` | Items array contains invalid data structure | ### Provider dispatch errors | Error Message | Meaning | Action Required | | - | - | - | | `job_autodispatch_failure, Error dispatching to provider` | Provider API failure | Check provider status, retry manually if needed | | `job_autodispatch_failure, Error dispatching to a job` | Job creation failed | Review job configuration and order details | ## Related The bookable slots whose cutoffs trigger this event. Set up endpoints, verify signatures, and handle retries. # Single sign-on Source: https://docs.usenash.com/reference/single-sign-on Nash Portal sign-in can be connected to your own identity provider over OpenID Connect or SAML 2.0. Nash Portal sign-in can be connected to your organization's identity provider (IdP), so your team signs in with the credentials they already have and you keep control of who has access. Nash supports OpenID Connect (OIDC) and SAML 2.0, and configures the connection with you. There is nothing to set up on your own in the Portal. Single sign-on covers people signing in to the Portal. It does not change how your systems authenticate to the API. That stays [API key based](/reference/authentication), and nothing about your keys changes when you turn SSO on. ## When to use this Reach for SSO when: * **You already run an IdP** such as Microsoft Entra ID (formerly Azure AD), Okta, Google Workspace, or Ping, and you want Portal access to follow it. * **Your security policy requires MFA.** Nash defers to your IdP, so whatever factors you require apply to the Portal too. * **Offboarding has to be reliable.** Removing someone in your IdP should end their Portal access, without anyone filing a ticket with Nash. * **You want your groups to decide Nash access.** Your IdP groups can map to Nash roles, so a person's Portal permissions follow their group membership. If you have a handful of Portal users and no central IdP, or your integration is API-only and nobody signs in to the Portal, SSO buys you little. Skip it. ## What Nash supports | | | | - | - | | Protocols | OpenID Connect, SAML 2.0 | | Sign-in directions | SP-initiated from the Portal; SAML also supports IdP-initiated | | Multi-factor authentication | Enforced by your IdP; Nash adds no second factor of its own | | Provisioning | Just-in-time: the Nash user is created on first successful sign-in | | Deprovisioning | Removing a user in your IdP ends Portal access; the Nash user is deactivated, not deleted | | Roles | Your organization's default role, or your IdP groups mapped to Nash roles | | Sign-in routing | By email domain: users on a domain mapped to your IdP are sent there | | Identity attribute | The stable, immutable user identifier from your IdP; for SAML, a persistent identifier or an email address | ## Choose a protocol Both protocols get you to the same place. They differ sharply in how much work the setup is. OIDC needs an issuer, a client ID, and a client secret. SAML needs a sign-in endpoint, a signing certificate, and matching assertion configuration on both sides, and its failure modes are quiet ones: a certificate pasted with the wrong boundaries, an audience that doesn't match, a name identifier format that isn't what the other side expects. Each round trip to find one costs both teams an afternoon. If your IdP supports OIDC, use OIDC. Choose SAML when your IdP only speaks SAML, when your policy mandates it, or when you need users to start from your IdP dashboard. ## How setup works Nash configures the connection; you configure an application for Nash in your IdP. To start one, email [tech@usenash.com](mailto:tech@usenash.com) with the protocol you want, which IdP you run, and the email domains your users sign in with. From there, expect to exchange a small number of values with your Nash contact: the endpoint and identifier details for the application on each side, your IdP's signing certificate or client credentials, and your group-to-role mapping if you want one. Nash provides a secure channel for the certificate and secret handoff; don't send either over email or chat. Once the connection is live, sign in with a single account and confirm the user lands in the right organization with the role you expect before you widen access. ## Users and roles **First sign-in creates the account.** Nash provisions a user the first time someone signs in successfully through your IdP, so you don't pre-create accounts or send invitations. A new user gets your organization's default Nash role. **Roles can follow your groups.** Give Nash a mapping from your IdP groups to Nash roles and Nash applies it on every sign-in, so a change in your directory shows up in the Portal the next time that person signs in. Without a mapping, everyone lands on the default role and permissions are managed in the Portal instead. For which roles to map to, see [Roles and access](/reference/roles-and-access). **Removing access happens in your IdP.** Take someone out of the mapped group or the application in your IdP and they can no longer sign in. Nash deactivates the user rather than deleting them, so the actions they took stay attributed and your history stays intact. **MFA is your policy, not Nash's.** Nash enforces no second factor of its own and adds nothing on top of what your IdP asks for. Whatever your IdP requires is what Portal sign-in requires. ## Identity attributes Nash keys a user on the stable, immutable user identifier from your IdP rather than on their email address. That matters at the moment someone changes their name or their email: the identifier doesn't move, so they keep the same Nash user instead of getting a second one. For SAML, Nash accepts either a persistent identifier or an email address as the name identifier. Prefer the persistent identifier where your IdP offers it. Email addresses change, and when they do, an email-keyed user becomes a new user with none of their history. ## FAQ No. API requests keep using an API key in the `Authorization` header, and your existing keys keep working exactly as they did (see [Authentication](/reference/authentication)). SSO governs people signing in to the Portal, nothing else. No. SSO is an interactive, browser-based flow. For programmatic access, generate an API key scoped to what the script needs and send it in the request headers. See [API key and Org ID](/reference/generating-api-key-and-org-id). Routing is by email domain. Users whose email is on a domain mapped to your IdP are sent there to sign in; users on other domains are not affected. If you need a mixed arrangement inside one domain, raise it when you start setup, since it changes how the connection is configured. Yes. Entra ID (formerly Azure AD) works over either protocol, and OIDC is the faster of the two to stand up. Its group claims can drive Nash roles the same way any other IdP's do. With SAML, yes: IdP-initiated sign-in is supported alongside starting from the Portal. OIDC connections start from the Portal. Yes, and it's worth doing. Standing SSO up in [Sandbox](/reference/environments) shakes out role mapping before it affects your production users. Tell your Nash contact which environments you want enabled. ## Related Which roles to map your groups to, and how to keep configuration and credentials restricted. How API requests authenticate, unchanged by SSO. Create a key in the Portal and find the Org ID your requests need. Sandbox and Production, and what differs between them. # Store catalog Source: https://docs.usenash.com/reference/store-catalog The store catalog combines a master product catalog and per-store inventory to power item-level fulfillment. Learn how products and inventory fit together, how Pick and Pack works, and what item-level details an order can carry. The **store catalog** is the master data behind item-level fulfillment. It pairs a **product catalog** — your canonical list of items — with **store inventory** that tracks what each [store location](/reference/store-locations) actually has on the shelf. Together they let Nash support **Pick and Pack**, where a courier picks the requested items, handles substitutions, and hands off the packed order for delivery. ## Products & inventory The catalog has two layers: * **Products** are the master records for the items you sell — names, images, categories, identifiers, weight, and dimensions. They are organization-wide and referenced by both inventory and orders. * **Store inventory** links a product to a specific store location and records its real-time **availability**, **pricing**, **quantity**, and physical **location** (aisle, bay, shelf) for efficient picking. Manage products in bulk with [Create or update products](/api-reference/store-catalog/create-or-update-products) (`POST /v1/products`) and read them back with [Get products](/api-reference/store-catalog/get-products) (`GET /v1/products`). Required fields include `name`, `categories[]`, `weight`, `dimensions{depth,height,width}`, and `identifiers[{type,value}]`. Optional fields include `imageUrls[]`, `externalIdentifier`, `sku`, `description`, `attributes[]` (such as `WEIGHTED`), and `details`. Limits: up to **1,000 products** per request, **10 requests per second** per organization. Update inventory in bulk with [Create or update inventory](/api-reference/store-catalog/create-or-update-inventory) (`POST /v1/inventory`) and query it with [Get inventory](/api-reference/store-catalog/get-inventory) (`GET /v1/inventory`). Each entry ties a product to a store using your external identifiers (`externalProductId`, `externalStoreLocationId`). The only required field is `available` (boolean); common fields include `quantity`, `valueCents`, `currency`, and `location{aisle,bay,shelf}`. Query by `externalStoreLocationId` or `externalProductId`, with `pageIndex` / `numResultsPerPage` pagination. Limits: up to **10,000 inventory items** per request, **10 requests per second** per organization. **A bad row doesn't cost you the batch.** A row naming a product or store location Nash doesn't have is skipped; everything else in the request still commits. Skipped rows come back in the same array with `errorCode` set to `MISSING_RESOURCE` and an `errorMessage` saying which identifier missed, and their data fields — `id`, `productId`, `storeLocationId`, `quantity`, `available` — null. Rows that were written carry `errorCode: null`. Discriminate on `errorCode`; the array is not in request order, so match rows back by the `externalProductId` / `externalStoreLocationId` pair. A per-store feed carrying one stale SKU therefore updates the rest of the store instead of being rejected whole. For variable-weight products such as produce or deli items, set the product's `attributes` to `WEIGHTED`, and set per-unit pricing on the store-inventory entry's `details.weightedItemInfo.valueCentsPerMeasurementUnit` (for example, price per pound). ## Pick and Pack Pick and Pack turns an order into an item-level fulfillment job (delivery): the catalog and inventory tell a courier what to pick and where to find it, and the system tracks what was requested, what was picked, and any substitutions. Enable it by including `pick_and_pack` in the order's `requirements` array when you create the order — see [Order requirements](/api-reference/order/order-requirements). The lifecycle is: The customer places an order with specific items and substitution preferences. A courier picks the items, handling substitutions as needed. Picking progress is reported in real time. When picking finishes, the order reaches `items_pick_complete` and is ready for handoff to delivery. Once picking is complete, the order is handed to the delivery provider. Each delivery carries a `pickedItems` array describing what the picker actually put in the bag, including the picked `sku`, `quantity`, `requestedQuantity`, `weight`, `priceCents`, and any scanned barcode data. For the full field reference and worked examples — straight picks, weighted items, substitutions, and multi-scan items — see the [Pick and Pack](/api-reference/store-catalog/pick-and-pack) background page. You can track the `items_pick_complete` status via your order retrieval endpoints, or subscribe to status-change events through [Webhooks](/reference/webhooks). ## Item-level details When you create an order, each package's `items` array describes the individual items in that package. Per item you can set: | Field | Type | Description | | - | - | - | | `id` | string | The merchant ID of the item | | `description` | string | The description of the item | | `count` | integer | The quantity of the item | | `dimensions` | object | `height`, `width`, `depth` (in centimeters) | | `weight` | number | The weight of the item in kilograms (kg) | | `valueCents` | integer | The value of the item in cents | | `category` | string | The category of the item | | `requirements` | array | Requirements specific to this item | | `barcode` | string | Barcode value for this item | | `metadata` | object | Key-value data to attach to the item | | `subItems` | array | Nested sub-items (used for Pick and Pack substitutions) | Each item carries a single `barcode` value. The richer parsed barcode payloads (format, variable weight, price, expiration) appear on the `pickedItems` results after picking, not on the items you submit — see the [Pick and Pack](/api-reference/store-catalog/pick-and-pack) page. For Pick and Pack orders, the `subItems` array supports substitution handling: a sub-item can carry a `sku` and a `substitution` object describing the `preference` (such as `refund` or `substitute`), the `source` of the decision, and the chosen `substituteItems[]`. ## Next steps Bulk-manage your master product catalog. Track per-store availability, pricing, and aisle location. The full pick-and-pack model and picked-items reference. Query inventory by store or product. # Store locations Source: https://docs.usenash.com/reference/store-locations A store location is a physical origin — a restaurant, warehouse, or store — that deliveries depart from. Learn how to model store locations, upsert them by your own identifier, and look up which stores cover a location. A **store location** is a physical origin — a restaurant, warehouse, or store — that deliveries depart from. Store locations let you register your pickup points once and then reference them from orders, associate them with [zones](/reference/zones), and answer coverage questions like *"which of my stores serves this address?"* ## Modeling store locations A store location describes a single origin and the information a driver and the dispatch system need to pick up from it. Create one with [Create store location](/api-reference/store-locations/create-store-location) (`POST /v1/store_locations`). A store location can carry: * A **name** (the store's business name) and a **phone number** — the two required fields * A **first name** and **last name** for the contact at the location * An **email** and **pickup instructions** for the courier * An **address** (as a single line) or pre-geocoded **address components** * An **external ID** that maps the location to your own systems * Free-form **tags** for your own classification * **Operating hours** and a **prep time** in minutes (both integration-specific) * **Zone associations** that link the location to one or more [zones](/reference/zones) Retrieve a single store location with [Get store location](/api-reference/store-locations/get-store-location) (`GET /v1/store_locations/{id}`), or list them with [Get store locations](/api-reference/store-locations/get-store-locations) (`GET /v1/store_locations`). Update an existing location with `PATCH /v1/store_locations/{id}` and remove one with `DELETE /v1/store_locations/{id}`. `name` and `phoneNumber` are the only required fields. If you don't know the contact's name, pass `Manager` for `firstName` and `lastName`. ## External identifiers Most teams already have their own identifier for each store. Rather than store and reconcile Nash's IDs, you can address store locations by your **external identifier** and let Nash perform an idempotent upsert. Use [Create or update store location by external identifier](/api-reference/store-locations/create-or-update-store-location-by-external-identifier) (`POST /v1/store_locations/external-identifier/{externalIdentifier}`). If a store location with that external identifier already exists, it is updated in place; if not, a new one is created. This makes it safe to replay the same request — for example when syncing your store catalog on a schedule — without creating duplicates. To read a location back by your own identifier, use [Get store location by external identifier](/api-reference/store-locations/get-store-location-by-external-identifier) (`GET /v1/store_locations/external_identifier/{externalIdentifier}`). There is also a plain upsert endpoint, [Create or update store location](/api-reference/store-locations/create-or-update-store-location) (`POST /v1/store_locations/upsert`), and a bulk variant, [Upsert multiple store locations](/api-reference/store-locations/upsert-multiple-store-locations) (`POST /v1/store_locations/upsert/bulk`), for syncing many locations at once. ## Coverage lookups A coverage lookup answers *"which store locations cover this point?"* by testing a location against the delivery zones associated with your store locations. Use [Get store locations that cover a specified latitude and longitude](/api-reference/store-locations/get-store-locations-that-cover-specified-latitude-and-longitude) (`GET /v1/store_locations/coverage`). You can query by: Provide both `latitude` and `longitude` to find the store locations whose zones cover that exact point. ``` GET /v1/store_locations/coverage?latitude=37.76&longitude=-122.43 ``` Provide a `cityZipcode` pair, or a `zipcode` alone. The location must have coverage set up using the matching field. ``` GET /v1/store_locations/coverage?cityZipcode=sanfrancisco_94114 ``` `latitude` and `longitude` must be supplied together — if you pass one, you must pass the other. ## Store locations vs. zones Store locations and [zones](/reference/zones) are distinct but related: * A **store location** is a physical origin that deliveries depart from. * A **zone** is a geographic area — a polygon boundary or a list of cities, zip codes, states, or countries — where delivery is available. You connect them through **zone associations**: associate a store location with one or more zones, and a coverage lookup can tell you whether a given dropoff point falls inside a store's delivery area. The store location coverage endpoint returns the *stores* serving a point; the [zones coverage endpoint](/api-reference/zones/get-zones-that-cover-specified-location-latitude-and-longitude-city_zipcode) returns the *zones* containing it. ## Next steps Register a pickup origin and its contact details. Create or update a store location idempotently by your own ID. Find the stores that cover a latitude/longitude or city/zip. Define the delivery areas your stores serve. # Webhooks Source: https://docs.usenash.com/reference/webhooks Set up and consume Nash webhooks: event types, payload shape, signature verification, and retries. Webhooks push real-time delivery events to your endpoint, so you don't have to poll for what Nash already knows. If your application reacts to delivery progress — updating a customer, advancing an order, alerting on a problem — subscribe to webhooks. Every webhook payload carries three fields: * `type` — the kind of event: `delivery`, `task`, `courier_location`, `courier_name`, or `shift`. * `event` — the specific status within that type (enumerated below). * `data` — for `delivery` and `task` events, a Job (delivery) object, the same shape as the [Create Job](/api-reference/job/create-job) response (see the example below); for `shift` events, a small payload described in [shift.dispatched](/reference/shift-dispatched-event). Set up endpoints in the [Webhook Management](https://portal.usenash.com/settings/webhook-management) page in the Nash Portal. You can filter by event type and register multiple endpoints — for example, routing different events to different services. The **Event Catalog** tab enumerates every event schema and type, with example payloads for select events. Event schemas assume a batch job. For a non-batch job, the `data` object has no `batch` key and `"isBatch": false`. Webhooks work in both sandbox and production. Failed webhook deliveries are retried per our [retry policy](/reference/webhooks#retry-policy), and every request is [signed so you can verify it](/reference/webhooks#verifying-webhooks). ## Delivery events **Type**: `delivery` **Frequency**: once per state transition (e.g., from `created` -> `assigned_driver`, or `dropoff_arrived`-> `dropoff_complete`) **Guaranteed events**: `created`, `assigned_driver`, `pickup_enroute`, `pickup_arrived`, `dropoff_enroute`, `dropoff_arrived`, `dropoff_complete` (guaranteed events may be inferred from future events) Events: ```text Delivery events theme={"dark"} created not_assigned_driver assigned_driver started pickup_enroute pickup_arrived items_pick_update items_pick_complete pickup_complete dropoff_enroute dropoff_arrived dropoff_complete canceled_by_provider return_in_progress return_arrived returned scheduled expired failed canceled_by_customer canceled_by_nash canceled_by_auto_reassign: this is triggered when using the auto-reassignment functionality in Dispatch Settings / Dispatch Strategies proof_of_delivery: proof(s) of delivery have been added/updated in the `delivery.proofOfDelivery` field pincode_verification: pincode of delivery when pincode is requested. it will be updated in the `delivery.proofOfDelivery` field pickup_label: available if a pickup label is configured to be generated for your account shipping_label: (SHIPPING ONLY) shipping label has been added/updated in the `delivery.documents` field manifested: (SHIPPING ONLY) shipment has been manifested and manifest document has been added/updated in the `delivery.documents` field ``` ### Where a scan happened Shipping carriers that scan a parcel through their network report where each scan took place — a sorting facility, a depot, a suburb. Each entry in the delivery's `statusHistory` carries that as `location`, alongside `status` and `statusDescription`: ```json theme={"dark"} "statusHistory": [ { "createdAt": "2026-08-04T02:55:27.240246", "status": "in_transit", "statusDescription": "Item processed at sorting facility", "location": "WELSHPOOL WA" } ] ``` `location` is `null` when the provider reported no location for that transition, which is the normal case for point-to-point couriers — they move a parcel directly rather than through scan points. Use it to tell a customer where their parcel is between the pickup and the doorstep, rather than only that it's moving. ## Task events **Type**: `task` **Frequency**: once per state transition (e.g., from `running` -> `completed`) Events: ```text theme={"dark"} created updated running completed canceled_by_provider failed canceled_by_customer canceled_by_nash canceled_by_auto_reassign: the task was canceled because a dispatch strategy reassigned the job canceled_by_reassign: the task was canceled because someone reassigned the job by hand dispatch_failure planned_selection refund_created refund_updated ``` ## Courier location events **Type**: `courier_location` Events: * `updated` **Frequency**: once every 1-2 minutes (if courier\_location is available). For organizations with immediate location webhooks turned on, one is sent as each driver position arrives, at most once every 20 seconds per delivery — several times the volume, so the endpoint has to keep up. Here's a sample response that you can expect from our webhook for an ongoing delivery: All timestamps are UTC, ISO 8601 with microseconds and no timezone suffix (for example `2022-03-05T01:11:51.078927`). ```json Webhook response theme={"dark"} { "type": "courier_location", "event": "updated", "data": { "id": "job_R7FXJrfB99bpvpzf6CriJC", "createdAt": "2022-03-05T01:11:51.078927", "portalUrl": "https://portal.usenash.com/active/job_R7FXJrfB99bpvpzf6CriJC", "externalIdentifier": null, "jobMetadata": {}, "isActive": false, "isBatch": false, "jobConfigurations": [ { "package": { "description": "Test package description", "pickupEndTime": null, "pickupStartTime": "2022-03-05T01:16:51", "dropoffStartTime": null, "dropoffEndTime": null, "valueCents": 100, "taxCents": 950, "serviceFeeCents": null, "itemsCount": 1, "pickupLocation": { "id": "loc_dB4x4pToexhgRvGhZSTHaQ", "address": "185 University Ave, Palo Alto, CA 94301", "formattedAddress": "185 University Ave, Palo Alto, CA 94301, USA", "addressNumber": "185", "addressFormattedStreet": "University Ave", "addressCity": "Palo Alto", "addressCountry": "US", "addressState": "CA", "addressZip": "94301", "addressCounty": "Santa Clara County", "businessName": "Test pickup Business", "instructions": "Test pickup Instructions", "firstName": "Test pickup FirstName", "lastName": "Test pickup LastName", "phoneNumber": "+12345679012", "email": null }, "dropoffLocation": { "id": "loc_5heYem2xKGfNt9twg33kcT", "address": "401 San Antonio Rd, Mountain View, CA 94040", "formattedAddress": "401 San Antonio Rd, Mountain View, CA 94040, USA", "addressNumber": "401", "addressFormattedStreet": "San Antonio Rd", "addressCity": "Mountain View", "addressCountry": "US", "addressState": "CA", "addressZip": "94040", "addressCounty": "Santa Clara County", "businessName": "Test dropoff Business", "instructions": "Test dropoff Instructions", "firstName": "Test dropoff FirstName", "lastName": "Test dropoff LastName", "phoneNumber": "+12345679019", "email": null } }, "tasks": [ { "id": "tsk_7xKGR6PW2pBtuTeRg2b7Bw", "createdAt": "2022-03-05T01:11:51.078927", "status": "COMPLETED", "providerId": "FleetSimulator", "tipAmountCents": 250, "winnerQuote": { "id": "qot_nh5VSwxr4trHsuKHpQbGW6", "providerId": "FleetSimulator", "providerName": "FleetSimulator" }, "quotes": [ { "id": "qot_nh5VSwxr4trHsuKHpQbGW6", "providerId": "FleetSimulator", "providerName": "FleetSimulator", "providerLogo": "https://nash-provider-logos.s3.amazonaws.com/FleetSimulator.png", "createdTime": "2022-03-05T01:11:52.417462", "expireTime": "2022-03-05T02:11:52.417463", "nashFeeCents": 100, "priceCents": 0, "currency": "USD", "pickupWindow": null, "dropoffEta": "2022-03-05T01:17:52.417416", "tollFeeCents": 0, "insuranceFeeCents": 0, "totalPriceCents": 0 } ], "failedQuotes": [], "delivery": { "id": "dlv_MwQDy3s6ajvw5aQ7eQPt7i", "status": "DROPOFF_COMPLETE", "isActive": false, "pickupEta": null, "dropoffEta": "2022-03-05T01:17:52.417416", "dropoffDeadline": null, "currency": "USD", "documents": [ { "id": "doc_ELre5GCELQ6w3UHKAgbm6F", "type": "SHIPPING_LABEL", "url": "https://nash-documents-dev.s3-us-west-1.amazonaws.com/dlv_bR7NWwhWSQRohyjs7UZd7D_fEavuZda787M70sJEhtCAA==.png", "data": null, "contentType": "image/png" }, { "id": "doc_nuKEL5W8mMgnXuRrFqwwsc", "type": "PICKUP_LABEL", "url": "https://nash-documents-dev.s3-us-west-1.amazonaws.com/dlv_bR7NWwhWSQRohyjs7UZd7D_QPiARbKwfJRkC94KVQrT8w==.png", "data": null, "contentType": "image/png" } ], "nashFeeCents": 100, "priceCents": 0, "courierName": "Tjy Ifulu", "courierPhoneNumber": "+13408480904", "courierLocation": { "lat": 37.4043247, "lng": -122.1112215 }, "courierProfileImage":"https://nash-proof-of-delivery-sandbox.s3-us-west-1.amazonaws.com/dlv_SXnvEsXY4aEQ5YVY92sLPK_4t9WSOg3Zw-h4BT0fFUWqw==.jpg", "proofOfDelivery": [ { "type": "photo_proof_of_delivery", "url": "https://nash-proof-of-delivery-sandbox.s3-us-west-1.amazonaws.com/dlv_SXnvEsXY4aEQ5YVY92sLPK_4t9WSOg3Zw-h4BT0fFUWqw==.jpg", "size": 12421 }, { "type": "barcode_scan", "text": "pkg_SggEeiudfNRQDvtAF35qPr", "url": "https://nash-proof-of-delivery-dev.s3-us-west-1.amazonaws.com/dlv_JJUqcMDdxDxoj6Zsk8iru3_rp_nNJtdAVItcnBpKxTHBQ==..png", "size": 566 } ], "statusHistory": [ { "createdAt": "2022-03-05T01:12:00.984078", "status": "created" }, { "createdAt": "2022-03-05T01:12:16.067396", "status": "not_assigned_driver" }, { "createdAt": "2022-03-05T01:13:02.459762", "status": "assigned_driver" }, { "createdAt": "2022-03-05T01:14:32.532602", "status": "pickup_enroute" }, { "createdAt": "2022-03-05T01:15:17.533789", "status": "pickup_complete" }, { "createdAt": "2022-03-05T01:16:02.598233", "status": "dropoff_enroute" }, { "createdAt": "2022-03-05T01:17:32.690484", "status": "dropoff_arrived" }, { "createdAt": "2022-03-05T01:18:17.945368", "status": "dropoff_complete" } ], "tollFeeCents": 0, "waitFeeCents": 0, "waitTimeMinutes": 0, "cancellationFeeCents": 0, "returnFeeCents": 0, "insuranceFeeCents": 0, "taxAmountCents": 0, "totalPriceCents": 0 }, "failureReason": null } ] } ] } } ``` ## Reassignments and courier changes When a job is reassigned, whether by hand, by a dispatch strategy's auto-reassignment, or after a provider cancels, Nash creates a **new task** on the same job rather than editing the old one. Three things follow for a webhook consumer: * **The old task ends and a new one starts.** The original task emits its terminal event (`task.canceled_by_auto_reassign`, `task.canceled_by_reassign`, `task.canceled_by_provider`, and so on), and the replacement emits `task.created` followed by its own delivery events. Both tasks stay in `jobConfigurations[].tasks`, and nothing on the old task points at its replacement. Treat a task cancellation as terminal for that task only, and read `jobConfigurations[].advancedTask` for the attempt that's now live. * **Quotes collapse to the winner.** Before a quote is selected, `quotes` lists every offer. Once one is chosen, task and batch payloads carry only the winning quote in `quotes` (`winnerQuote` is unchanged), and `failedQuotes` is never included in a webhook payload. * **A courier change is not a reassignment.** When the same provider swaps drivers, the delivery's `courierName`, `courierPhoneNumber`, and `courierLocation` update in place and you get a `courier_name` event with `event` set to `updated`, along with the usual `courier_location` updates. No new task, no new quotes. Each payload is a snapshot of the whole job taken when the change that raised the event was saved, not a diff. Events raised by the same change, such as the cancellation of one task and the creation of its replacement, share one snapshot, so the `data` on a `task.created` event already shows the old task as canceled. ## Best practices Nash jobs can include multiple delivery attempts (see [Jobs overview](/api-reference/job/jobs)). When processing our webhook updates, make sure a cancellation or failure on one task doesn't disrupt your handling of the job's other tasks. Furthermore, since there are multiple tasks, you should be prepared to handle multiple instances of the same webhook event. For example, if a job has two tasks — one assigned to Provider A and another to Provider B — both providers might send `pickup_arrived` events. Refer to [Delivery events](/reference/webhooks#delivery-events) for more details on the guaranteed statuses we will send. You should also be prepared for some providers erroneously sending statuses such as `pickup_complete` or `dropoff_complete` when their drivers have not completed a delivery yet. ## Verifying webhooks We use a service called [Svix](https://www.svix.com/) to send webhooks. Svix prevents attacks and forgeries by signing webhook requests, and also handles [retries](#retry-policy). ### How to verify To verify the webhook signature and ensure the request genuinely came from Nash via Svix, you have a couple of options: 1. **Use the Svix SDKs:** The recommended approach is to use the official [Svix SDKs](https://docs.svix.com/receiving/verifying-payloads/how) for your language/framework. These libraries handle signature verification and timestamp checks automatically. 2. **Manually Verify:** You can [manually verify](https://docs.svix.com/receiving/verifying-payloads/how-manual) the signature using the request headers (`svix-id`, `svix-timestamp`, `svix-signature`) and your endpoint's signing secret. You will also need to implement the timestamp check yourself to prevent replay attacks. You'll need the **signing secret**, which can be found in the Nash Portal under Settings > Webhook Management > Click on an Endpoint > Signing Secret. Where to find your Svix signing secret. ### Firewall configuration (static IP addresses) If your webhook receiving endpoint is behind a firewall or NAT, you may need to explicitly allow traffic from Svix's IP addresses. Webhooks will originate from the IP addresses listed in [Svix's source IPs documentation](https://docs.svix.com/receiving/source-ips). ## Retry policy A webhook retry is an attempt by Svix to re-send a webhook message that previously failed to be delivered to your endpoint (e.g., due to a network error, server error (5xx), or certain 4xx errors like 408, 409, 429 on your end). Our automatic retry schedule is approximately as follows: * Immediately * 5 seconds * 5 minutes * 30 minutes * 2 hours * 5 hours * 10 hours * Another 10 hours The figure here draws the schedule on three time scales and lets a reader choose how long their endpoint is down. Counted from the event, the tries fall at about: 0 seconds, 5 seconds, 5 minutes, 35 minutes, 2 hours 35 minutes, 7 hours 35 minutes, 17 hours 35 minutes, and 27 hours 35 minutes. An endpoint that is down for 20 minutes misses the first three tries and receives the event on the fourth, about 35 minutes after it happened. If all eight tries fail, the message is marked failed and you can replay it from Webhook Management in the Portal. Reference: [Svix retry documentation](https://docs.svix.com/retries) ### Stop retries or disable an endpoint Include the `webhook-delivery` header in your response when you need to override the normal delivery behavior: * `webhook-delivery: abort-message` stops remaining retries for the current message. It has no effect on a successful response because any `2xx` response already marks the message as delivered. * `webhook-delivery: disable` disables the entire endpoint and stops all future webhook deliveries until the endpoint is re-enabled. Use it only when the endpoint should no longer receive events. Returning HTTP `410 Gone` also disables the endpoint. See [Svix's delivery control documentation](https://docs.svix.com/receiving/delivery-control) for details. You can also manually trigger retries for specific messages or automatically retry ("Recover") all failed messages starting from a given date via the Nash Portal (Webhook Management > Select Endpoint > Attempts). # Workflows Source: https://docs.usenash.com/reference/workflows Nash workflows are an automation engine that responds to delivery events with filters and actions — applying dispatch strategies, modifying orders, sending notifications, and more. A **workflow** is an automation that responds to an event in Nash and runs a series of actions. Workflows let you encode operational logic — "when an order is created, if it's high value, apply the white-glove dispatch strategy and notify the team" — so the platform handles decisioning instead of your application. Workflows are how Nash determines which [dispatch strategy](/reference/dispatch-strategies) an order flows into, and how it automates order-level decisions using attributes like order value, product type, destination, SLA, or merchant-defined metadata. The figure here draws that example as a workflow. A trigger node listens for `order.created`. Its `default` edge leads to a filter node that checks whether the order value is greater than \$200. The filter's `success` edge leads to an action node that applies the white-glove dispatch strategy, followed by an action node that sends a Slack message. Its `negative` edge has nothing attached. A \$240 order follows the `success` edge and both actions run. An \$80 order follows the `negative` edge, and the run ends there. ## What workflows are A workflow is built from **nodes** connected by **edges**, plus a **trigger** that defines when it runs. * **Nodes** are the steps of the workflow. Each node has a type: * `trigger` — the entry point that starts the workflow * `filter` — evaluates conditions and branches the flow * `action` — performs an operation * `switch` — routes execution based on a value * **Edges** connect nodes and define execution order. An edge type of `default` is used for trigger and action nodes. For filter nodes, `success` follows when the condition is met and `negative` follows when it is not. An [AI Decision](/reference/workflows#branch-with-an-ai-decision) node uses `decision` edges, each carrying a `branchKey` that names the outcome it follows. * **A trigger** defines the event that activates the workflow. A workflow moves through a lifecycle of statuses: `draft`, `active`, `inactive`, `archived`, and `template`. New workflows are created in `draft` by default and must be set to `active` to run. Updating a workflow's structure (nodes, edges, and trigger together) creates a new version. ### Actions Action nodes perform operations on the order or delivery, or send notifications. Supported actions include: * Apply a **dispatch strategy** or an **optimization strategy** * **Modify order price or tip** — set a value outright, or bound one: **At least** raises anything under a floor, **At most** caps anything over a ceiling, and both leave a value already inside the bound alone * **Add a tag** or **extract a metadata field** * **Flag a task or delivery**, **auto-reassign**, **cancel a delivery**, **cancel an order**, or **remove an order from a route** * **Raise, update, resolve, or assign a route flag** so a detected condition becomes durable work on the Execute page rather than a notification someone has to catch * **Add requirements** to an order * **Update order text** while an order is being created — add text before or after the order description or the pickup or dropoff instructions, replace it, or fill it only when it's missing — see [Fill in instructions as an order is created](/reference/workflows#fill-in-instructions-as-an-order-is-created) * **Update order metadata** while an order is being created — add, update, or remove one metadata key and leave the rest alone — see [Change one metadata key](/reference/workflows#change-one-metadata-key) * **Reschedule an order** that hasn't been dispatched, moving every pickup and dropoff window by the same amount — see [Reschedule an order](/reference/workflows#reschedule-an-order) * **Mark an order invalid** from a validation workflow, so a rule of your own parks the order alongside Nash's built-in checks — see [Validate orders with your own rules](/reference/workflows#validate-orders-with-your-own-rules) * Send a notification by **email**, **SMS**, **Slack**, or **Teams channel message** * **Summarize**, run an **agent call** or **browser automation**, or **emit a custom event** * **[Send an HTTP request](/reference/workflows#call-your-own-system)** to a system of your own * **[Branch with an AI Decision](/reference/workflows#branch-with-an-ai-decision)** when the question is a judgment — yes or no, one of your named outcomes, or a score against a rubric * **[Hand the step to a Custom Agent](/reference/workflows#hand-a-step-to-an-agent)** and branch on what it reports back The exact set of actions, their configuration schemas, and the available filter fields and operators are self-describing. Call `GET /v1/workflows/schema` to retrieve the current triggers, action config schemas, filter fields and operators, and supported node, edge, and status types. ## Triggers & conditions A workflow's **trigger** specifies what activates it. Trigger types include `event`, `manual`, `webhook`, and `cron`. Event triggers listen for a named event in Nash — for example `order.created` or `order.creating`. (Currently, the REST API for creating workflows supports the `event` trigger type; other trigger types exist in the platform but are not all exposed through that endpoint.) A trigger built on a delivery status transition can name several statuses in `deliveryStatuses` and fires on each of them on its own, so one workflow covers pickup and dropoff without being built twice. **Conditions** are expressed with **filter nodes**. Each filter holds one or more conditions made up of a `field`, an `operator`, and a `value`. When the filter evaluates true, execution follows the `success` edge; otherwise it follows the `negative` edge — letting you branch a workflow based on order attributes. The available fields and operators are returned by `GET /v1/workflows/schema`. ### Fire against a pickup or dropoff window Most triggers react to something that has happened. Four react to a time that is coming. `order_pickup_start_time`, `order_pickup_end_time`, `order_dropoff_start_time`, and `order_dropoff_end_time` fire against the order's own window timestamps, offset by however long you choose — "30 minutes before the dropoff window ends". Each takes a `direction` of `before`, `at`, or `after` and an `offset_minutes`. This is the trigger for the order that *hasn't* moved: a pickup due in twenty minutes with no provider on it, a dropoff window about to close on a store that hasn't packed. Nothing has happened, which is the problem. If the window moves after the timer is armed, Nash arms a new one against the new time and the superseded timer does nothing when it comes around. ### Fire when a delivery window's cutoff passes A booked [delivery window](/reference/delivery-windows) has a cutoff, the moment it stops taking changes and its orders are due to be dispatched. **Delivery Window Cutoff** in the workflow builder fires for each order when the window it's booked into reaches that cutoff, or a number of minutes after it. Notifications has the same trigger as **Order Cutoff Time**, and in the API its slug is `order_cutoff_time`. Use it for the check that only makes sense once the window has closed: an order still not dispatched ten minutes after cutoff, or a store that hasn't started picking. The trigger doesn't look at what state the order is in, so pair it with a condition on **Current Task Status** or **Job Canceled** (see [Fields worth branching on](#fields-worth-branching-on)). The timer follows the order. Move the order to another window and the trigger moves with it. Two limits: an order that was booked before this trigger existed has no timer, and changing a window's cutoff after orders are booked into it doesn't move theirs. ### React to a route A route is a workflow context of its own. Five triggers sit under **Routes** in the builder: **Route Created** (`route.created`), **Route Started** (`route.started`), **Route Stop Completed** (`route.stop_completed`), **Route Completed** (`route.completed`), and **Route Canceled** (`route.canceled`). Every one of them carries the route's own fields: | Field | What it holds | | - | - | | Route Status | The route's current lifecycle status. | | Provider | The provider fulfilling the route. | | Driver | The courier assigned to the route. | | Stop Sequence | The index of the next stop still needing a visit. | | Stops Completed, Total Stops | How many stops are in a completed status, and how many the route has. | | Planned Departure, Actual Departure, Planned Completion | When the route was planned to start, when it did, and when it's planned to end, read in your organization's time zone. | In the API the keys are `route.id`, `route.status`, `route.provider`, `route.driver`, `route.stop_sequence`, `route.stops_completed`, `route.total_stops`, `route.planned_departure`, `route.actual_departure`, and `route.planned_completion`. Delivery, task, and order fields may be present depending on the route, and the field picker marks them as possibly empty. The route-flag actions are the natural pair. A workflow on Route Started that finds Actual Departure well past Planned Departure can raise a route flag, so the late departure is work on the Execute page with an owner rather than a message someone has to catch — or send a Slack message with `{{route.driver}}` in it, or call your own system. ### Match an event from your catalog An `event` trigger matches on the event's name. If your organization keeps a catalog of custom event definitions, point the trigger at a definition instead of typing that name: send `customEventDefinitionId` on the trigger and Nash stamps the definition's key as the event name it matches on. The two can't drift apart afterwards, and a definition something is still listening for can't be archived out from under the workflow — whether the listener is a workflow trigger or a plain notification. In the Portal, a Custom Event Listener trigger carries an **Event context** selector — order, delivery, task, or job — naming the entity the event belongs to. That choice decides which conditions and actions the builder offers downstream, so changing it resets the actions already placed after the trigger. The same reference works on the writing side. `POST /v1/order/{id}/events` and its job equivalent accept `customEventDefinitionId` in place of `name`, and so does the `emit_custom_event` action. Send one or the other; sending both is rejected, as is a definition from another organization or one that has been archived. ### Branching on whether a field is there Two operators take no value at all. **Is set** and **Is missing** ask whether a field or a metadata key is present, which is a different question from what it contains. Only `null` and genuinely absent values count as missing. `0`, an empty string, and an empty list are all *set* — so a condition asking whether someone filled in a tip doesn't quietly treat a deliberate zero as a blank. A presence check against context the workflow doesn't have yet, such as a delivery on an order that hasn't been dispatched, resolves to missing rather than failing the step. ### Fields worth branching on The field catalog spans the order, its packages, the job (delivery), and the delivery attempt, and it is served live by `GET /v1/workflows/schema` — the Portal's workflow editor reads it from there, so new fields appear without a Portal release. Alongside the address, value, tag, and requirement fields, it carries the numbers a routing or escalation decision usually turns on: | Field | Available on | What it holds | | - | - | - | | Drive Time | Order, Package | Expected driving time for the leg, in minutes. | | Minutes Until Pickup | Order, Package | How long remains before the pickup time. | | Package Count | Job | How many packages the job carries. | | Total Package Value | Job | The summed value of every package on the job. For a dispatched route, whose batch job holds every package on the route, that is the value of the whole route. | | Task Attempt Number | Delivery attempt | How many times this delivery has been attempted — the field to read when you're deciding whether a second failure should escalate rather than reassign again. | | Provider Name, Provider Delivery Id | Delivery, Delivery attempt | The provider fulfilling the delivery and the ID it gave the delivery — the fields to branch on when a step depends on which provider won. | | Delivery Fee | Delivery | The base fee the provider charged, in cents, before tolls, wait time, and other fees. | | Current Task Status, Current Delivery Status | Order | Where the order's latest dispatch stands, chosen from the same status lists the task and delivery fields use. Both are empty until the order is dispatched. A failed attempt outranks a later canceled one. | | Job Canceled | Order | Whether the order's job was canceled. The field to check when a workflow should leave canceled work alone. | Order fields are available on every trigger that has an order behind it, including delivery, job, and delivery-attempt triggers, so a delivery-status workflow can branch on the order's value or tags. A condition on an order field in one of those contexts used to resolve to nothing; if you built one earlier and it never took the branch you expected, it does now. In the Portal, the field picker offers only fields whose entity can exist for the trigger you've chosen, and marks a field that may be empty for that trigger — a delivery field on an order-created trigger, say — so a condition that can never match isn't offered as though it could. ### Comparing times Date and time fields carry their own operators rather than being matched as text. A condition can compare a timestamp against a fixed moment, against a relative window — within the last, more than, less than a duration you name — or against another datetime field, which is how you ask whether a dropoff window closes before its pickup window does. Evaluation is time zone aware, and a weekday reads the same regardless of where the person editing the workflow is sitting. A dispatch strategy is a field you can branch on too, so a workflow can ask which strategy an order is already flowing through before it decides to change it. ### Branching on whether an event has happened **Is set** asks whether a field has a value. A different question is whether something has *happened* to this order — has the store sent its `store_ready` event yet? Four filter-only fields answer it: `order.custom_event`, `job.custom_event`, `task.custom_event`, and `delivery.custom_event`, each paired with the operators **Has occurred** and **Has not occurred** and a value that is the exact key of the custom event you mean. Pick it from your catalog, or type a key by hand to match an archived or legacy one. A workflow on a pickup-window trigger can then continue only when the order has no `store_ready` event. The check is taken at the moment the trigger fires and stored with the run, so a worker that picks the run up late, or a retry, can't see an event that arrived in between and flip the branch. If the entity the condition refers to can't be resolved, the step errors rather than treating "not found" as "hasn't happened". These fields exist only to be filtered on; they don't hydrate into a message. ## Validate orders with your own rules Nash checks every order it takes in — an address that won't geocode, a pickup time in the past — and parks an order that fails as needing attention, with the reasons in `validationErrors`. Some rules are yours, not Nash's: an unattended delivery needs dropoff instructions, a reference ID has to match your ERP's format, a barcode has a fixed shape. A workflow on the **Order validation** trigger (`order.validating`) adds those checks to the same pass. The trigger runs synchronously inside every validation of an order — on creation, on every update, and on CSV upload — and the workflow can hold only filters, switches, and the **Mark order invalid** action. It runs before any order-created workflow, so a rule can't depend on a field one of those fills in. **Mark order invalid** (`add_validation_error` in the API) takes a `field` and a `message`. The field is a camelCase key of up to 64 characters: use one of the order's own fields, such as `dropoffInstructions`, and the Portal's order form highlights that field, or use a key of your own. A few keys Nash uses itself are reserved. The message can carry `{{order.*}}` variables. The error lands in `validationErrors` next to Nash's own, the order's `status` becomes `needs_attention`, and the key clears on the next update that satisfies the rule. Every run, including one that marked nothing, appears in the workflow's **Runs** view. Two filter operators arrive with it. **Matches pattern** and **Does not match pattern** (`matches_regex`, `not_matches_regex`) test a string field against a regular expression, matched anywhere in the value — anchor with `^` and `$` to check the whole thing. On a list field, matches pattern needs every item to match, and does not match pattern passes when any item fails. Patterns are compiled when you save, so a broken one is refused there rather than at run time. A validation workflow fails closed. If it can't run, or one of its conditions can't be evaluated, the order is marked with a reserved `workflowValidation` error naming the workflow, rather than slipping through as valid. Two things this trigger can't do: it can't be run by hand, because a validation only makes sense inside an order's own validation pass, and it can't carry any action other than **Mark order invalid**. Nash refuses both when you save or activate the workflow. ## Fill in instructions as an order is created Instructions are the field that most often arrives empty — the ordering system didn't ask, or the customer left it blank — and an empty one is what the driver reads at the door. A workflow on the **Order being created** trigger (`order.creating`) can fill it in before dispatch with the **Update order text** action (`modify_order_instructions` in the API). Pick **Pickup instructions**, **Drop-off instructions**, or **Order description**, then how the text applies: **Add before existing text** (the default), **Add after existing text**, or **Replace existing text**, which overwrites everything there, customer notes and address lines included. **Only when instructions are missing** treats blank text, `N/A`, and an address-only line as missing and leaves anything else alone. For the description the equivalent is **Only when the description is empty**, and only an empty or whitespace-only description counts. Use one action node per field, and the workflow's filters to limit it to a store or a delivery model. The action writes the order, and any package location already built from it, so what dispatch sends carries the text. Combined text longer than 280 characters fails the step rather than being cut — the description included — and a pickup override from a provider or store keeps precedence. It runs after order validation: if a validation rule of yours rejects orders with no instructions, adjust it when the intent is to fill them in instead. ### Change one metadata key The same trigger can write order metadata. **Update order metadata** (`modify_order_metadata` in the API) changes one key per node and leaves every other key as it was: | Metadata change | API `operation` | What it does | | - | - | - | | **Add or update key** | `set` | Writes the value, replacing one that's already there. | | **Add key if missing** | `add` | Writes the value only when the order has no such key. | | **Remove key** | `remove` | Deletes the key. No value needed. | The value is text, a number, a boolean, or null. A value that is one whole `{{variable}}` keeps the type it had where it came from, so a barcode with leading zeroes stays a string rather than becoming a number. Name the key exactly. Variables and wildcards aren't accepted in the key, a node can't clear all metadata at once, and keys Nash manages itself — anything beginning with `nash`, among a few others — are refused. The change is written to the order and to the package record already built from it, so a later sync doesn't undo it. Both actions belong to the **Order being created** trigger only. Nash refuses them on any other trigger when you save or activate the workflow. ## Reschedule an order A store closes early, a truck is a day late, a customer asks for tomorrow: the order's pickup and dropoff windows all need to move, together. The **Reschedule order** action (`reschedule_order` in the API) does that from a workflow, on any order trigger other than the two that run while an order is being created or validated. Choose a **Reschedule method**: * **Delay** moves the order later by a duration — whole minutes, from 1 minute to 365 days. The Portal takes minutes, hours, or days, offers 12-, 24-, and 48-hour presets, and starts at 24 hours. * **Move to date** takes a calendar date (`YYYY-MM-DD`) and keeps the local clock time. An order whose pickup window opens at 9:00 opens at 9:00 on the new date. Either value can come from a workflow variable instead of being typed in. Every window bound the order has moves by the same amount, so each window keeps its length and the gap between pickup and dropoff stays what it was. A bound the order never had stays empty. Quotes on the order are invalidated, since they priced the old times, and if the same run is retried the order isn't delayed twice. An order can be rescheduled when all of these hold: * It's a scheduled order, with at least one pickup or dropoff time. * It hasn't been dispatched or archived — its status is `valid`, `confirmed`, or `needs_attention`. * It isn't on a route. Remove it from the route first. * It has no booked [delivery window](/reference/delivery-windows). Change the booking instead. * Every resulting time is in the future, and the rescheduled order still passes validation. When one doesn't, the step fails with the reason and no times change. ## Writing text with variables Any text a workflow sends — an email subject, an SMS body, a Slack message, the channel it goes to — can carry `{{variables}}` that Nash fills in when the run reaches that step. | Kind | Written as | Holds | | - | - | - | | Notification variables | `{{customer.first_name}}`, `{{dropoff_eta}}`, `{{job_tracking_link}}` | The delivery's own context. The [notifications catalog](/api-reference/notifications/notifications) lists every one. | | Entity fields | `{{order.packing_slip_pdf_url}}`, `{{route.driver}}` | A field on the order, package, job (delivery), attempt, or route the run is about. | | Trigger metadata | `{{trigger_metadata.}}` | Whatever the trigger carried in. | | Node results | `{{node_var___}}` — for an HTTP node keyed `wms`, `{{node_var_wms__status}}` | What a step earlier in the graph produced. | Filters produce results too. A filter node publishes `__result` (`"true"` or `"false"`, absent when the filter couldn't be evaluated) and `__status` (`completed` or `error`), so a message or an [AI Decision](/reference/workflows#branch-with-an-ai-decision) further down can say which way an earlier condition went. Every notification action — email, SMS, Slack, Teams, and voice-agent calls — reads the same catalog the notification triggers use, so a message you write in a workflow and a message you write in Notifications have the same vocabulary. The catalog hydrates the destination and the subject as well as the body: a Teams channel or an SMS recipient can be a variable instead of a hard-coded address. A variable that can't resolve at the node you're editing is refused when you save, with the nearest match suggested, so a typo surfaces before the workflow runs rather than as a blank in a message. At run time a variable that resolves to nothing hydrates to empty text. On message actions a key that doesn't exist at all fails the step; the **Send HTTP request** action is more forgiving, and sends anyway — see [Call your own system](/reference/workflows#call-your-own-system). A **Send email** action takes several addresses, comma-separated. Each is checked against the deny list and delivered on its own, so one refused or bouncing address doesn't take the rest with it. ### Send to a role or a destination A message usually belongs to whoever holds a job, not to a person. In the Portal, **Send SMS** and **Send email** take a **Recipients** list, and each recipient is a contact you type in, a **Role** in your organization, or a notification **Destination**. Nash looks up a role's members and a destination's contacts when the workflow runs, so someone who joins the role next month gets the message without anyone editing the workflow. Deactivated users are left out, and one action sends to at most 250 recipients. When the same message should reach people on different channels, use **Send to destinations**. It notifies every contact on the [destinations](/reference/notifications) you choose, each on the channel that contact is set up for — email, SMS, or Slack — from one step. It's available on workflows that run in the background, with the same cap of 250 recipients. ### Send a notification once per order Some triggers fire more than once for the same order: a status that's reached again after a reassignment, a window that's re-booked. On **Send an email**, **Send an SMS**, **Send a Slack message**, and **Send to destinations**, turn on **Only send once per order** and the step sends the first time and holds back after that. It can send again for the same order after 2 days. The count is kept per step and per order, and editing the workflow doesn't reset it. A send that fails doesn't count, so the next run tries again. A held-back step still completes, and the run's details mark it **Not sent: already sent for this order**. On a trigger that has no order or job behind it, the toggle is unavailable and the step sends every time. In the Portal's editor, destination fields suggest only variables that could plausibly address that channel: phone numbers for SMS and voice, addresses for email, channels for Slack. The message body itself stays unrestricted. ## Call your own system Everything above acts on Nash. Sometimes the next step in your process lives somewhere else — mark the order picked in your WMS, open a ticket, tell your ERP the delivery failed. The **Send HTTP request** action makes that call from inside the workflow, so the handoff happens where the decision was made rather than in a listener you have to run and monitor yourself. It works in two halves, deliberately: * **An administrator creates a connection.** The connection owns the HTTPS origin, the credentials, which methods are allowed, the path prefix requests must sit under, and the names of the request fields a workflow may set. Credentials are write-only: they never appear in the workflow definition, and they are kept out of request logs and error reporting. * **A workflow author references it.** On the action node you set a relative path, query parameters, headers, a JSON body, a timeout, and what the workflow does if the call fails. Templates work here the same as anywhere else, so the body can carry the order's own values. The action can pull a value back out of the response with a JSON Pointer, and downstream filters see that value alongside the HTTP status, how long the call took, and any failure detail — so the branch after it can act on what your system actually said. A body field whose whole value is a date and time, such as a pickup window or a completion ETA, is sent as an ISO 8601 string. A template variable with no value at run time doesn't stop the call. It hydrates to empty text, or `null` for a whole value, and the request goes out, with each miss recorded on the run's step so a request that left with an empty field is still explainable. When the call does fail, the step says at which stage — building the request, connecting, authenticating, transport, the HTTP status, or reading the response — so a request that was never sent isn't reported as one that failed. The action runs in the background and makes one attempt, carrying a stable idempotency key so your endpoint can recognize a repeat. It is not a durable delivery channel: treat it as a signal your system can act on, not as a queue that guarantees arrival. It also can't hold up a synchronous run. ## Running a workflow by hand Triggers cover the routine cases. Sometimes you need to run one yourself — to apply a workflow to orders that predate it, or to check a change before you turn it on. Start a workflow on demand with the Trigger Workflow endpoint (`POST /v1/workflows/{id}/trigger`), passing entity references (such as the ID of a job, i.e. a delivery) and optional input data, in synchronous or asynchronous mode. A manual run works on a workflow in either `active` or `inactive` status. That's the useful part of `inactive`: a workflow you've switched off no longer fires on its own, but you can still run it deliberately. Workflows in `draft`, `archived`, or `template` status are rejected. In the Portal, select the orders you want on the Orders page and choose **Run Workflow** from the action bar. Pick the workflow, add trigger input if it needs any, and Nash starts one run per selected order — each independent, each with its own result you can open. Selecting orders row by row is what's supported here; a workflow run needs a concrete order to reference, so "select all results" across pages isn't. ## Run history Every run of a workflow, whether a trigger fired it, you ran it by hand, or a script started it through the API, is recorded as an execution with one step per node the run reached. Use it to answer "did the workflow fire for this order, and what did it do?" without adding logging of your own. In the Portal, open a workflow and switch from **Editor** to **Runs**; the toggle itself shows the most recent run's status and how long ago it was. A strip above the builder keeps four numbers in view — **Runs today**, **Success rate**, **Errored**, and **Avg run time** — and each person can hide it. Each row in **Runs** shows the run, the orders or deliveries it was about, when it completed, how long it ran, and its status. Open a run to see when it was created and completed, its total runtime, and the steps it took, each with the node it ran, that step's status, and when it started. The same history is available through the API: * `GET /v1/workflows/{id}/executions` lists a workflow's runs. Filter by `status`, `startDate`, `endDate`, or `search`; order with `sortBy` and `sortDirection`; page with `limit` and `offset`. * `GET /v1/workflows/{id}/executions/count` returns how many runs match the same filters. * `GET /v1/workflow-executions/{id}` returns one run with its steps. A run carries `id`, `status`, `createdAt` (when the trigger fired and the run was created), `completedAt`, `runtimeSeconds` (the difference between the two), and the `orderIds`, `jobIds`, and `deliveryIds` it was about. Each step carries `id`, `nodeId`, `status`, `createdAt` (when the step started), and `nodeVariables`, the data the run had in hand when it processed that node, including what earlier nodes produced. ```json theme={"dark"} { "execution": { "id": "wfx_...", "status": "completed", "createdAt": "2026-09-08T14:02:11.418223", "completedAt": "2026-09-08T14:02:13.077410", "runtimeSeconds": 2, "orderIds": ["ord_..."], "jobIds": [], "deliveryIds": [] }, "steps": [ { "id": "wfs_...", "nodeId": "...", "status": "completed_filter_success", "createdAt": "2026-09-08T14:02:11.502961", "nodeVariables": { "order": { "id": "ord_...", "valueCents": 18500 } } }, { "id": "wfs_...", "nodeId": "...", "status": "completed", "createdAt": "2026-09-08T14:02:12.311874", "nodeVariables": { "order": { "id": "ord_...", "valueCents": 18500 } } } ] } ``` Run statuses are `pending`, `queued`, `running`, `completed`, `failed`, `cancelled`, and `paused`. Step statuses say what a filter decided as well as whether an action ran: `completed_filter_success` and `completed_filter_negative` are the two branches of a filter, `completed_filter_error` is a filter that couldn't be evaluated, `completed` and `failed` are the outcomes of an action, and `paused` and `ready_to_resume` appear on a step while the run is waiting to continue. Two limits worth knowing before you build on it. A step records when it started, not when it finished, so per-step durations aren't available; the run's `runtimeSeconds` covers the whole run. And retries aren't recorded: a step shows its final outcome, not how many attempts it took. ## Test a workflow before you activate it Running a workflow tells you what it does. Testing one tells you what it *would* do, which is what you want while you're still writing it. The alternative is activating a workflow against live orders to find out whether a filter matches. Test a saved workflow with `POST /v1/workflows/{id}/test`, passing the `jobId` or `orderId` of the entity to evaluate it against. The response reports per-node status, so you can see which filters passed and which branch execution took. | Mode | What happens | | - | - | | `live: false` (default) | A dry run. Filters are evaluated against the real entity, and every action returns a preview of what it would do without doing it. An AI Decision isn't evaluated; it follows its **Uncertain** branch. | | `live: true` | Messaging actions (`send_slack`, `send_email`, `send_sms`) are performed for real, with their content prefixed `[Test]`, and an AI Decision is evaluated and follows the branch it chose. Every other action stays a preview and says so in its result. | Even a live test can't change state. Actions like `cancel_order` or `add_tag` are preview-only in both modes, so testing against a real order can't cancel or modify it. A live test does two real things: it delivers messages, and it evaluates AI Decisions. Use it to check that a Slack or email action is addressed and worded the way you meant, and that a decision lands on the branch you expected. This is the same dry run behind the **Test** drawer in the Portal's workflow editor, so a workflow an agent or a script builds can be checked the same way a person would check it. ## Branch with an AI Decision Some conditions are a judgment rather than a comparison. Does this delivery note describe a safety problem? Which of four teams should own this failure? How complete is this address? A filter can't ask that, and an agent is more than the question needs. An **AI Decision** node (`ai_decision` in the API) puts one bounded question to a model and sends the run down a branch per outcome. Give it two things. **Question or instructions** is what the decision should determine, up to 8,000 characters. **Information to evaluate** is the context the answer should rest on, up to 32,000 characters — and only what you put there is sent for evaluation. Both take variables, including what upstream actions and filters produced. Then pick a **Decision type**: | Decision type | You configure | Branches | | - | - | - | | **Yes / no** | Two thresholds on the probability of yes: yes at or above one (0.8 by default), no at or below the other (0.2). | **Yes** and **No**. Anything between the thresholds follows **Uncertain**. | | **Choose an outcome** | 2 to 32 named outcomes, each with a description of when to choose it. | One branch per outcome. | | **Score against a rubric** | 2 to 20 levels described from lowest to highest, starting at 0, and a score threshold. | **At or above threshold** and **Below threshold** — two branches, however many levels the rubric has. | Every type has two more branches. **Uncertain** is where an answer goes when the model wasn't sure enough: between the two thresholds for yes / no, or under the **Minimum confidence** (0.7 by default) for an outcome or a score. **Error** is where the run goes when the decision couldn't be made — a variable in the question resolved to nothing, the expanded text was too large, or the evaluation failed or ran out of time. Connect **Error**. Without a step on it, a failed evaluation fails the run. Any other outcome you leave unconnected ends that path quietly. And read confidence for what it is: how sure the model was, not a measured accuracy. **Uncertain** is the branch a person should look at. The node publishes its result for the steps after it: `__branch`, `__status` (`completed`, `uncertain`, `error`, or `preview`), `__value`, `__probability`, and `__confidence`, written as `{{node_var___branch}}` and so on. A downstream filter can compare the confidence, and a Slack message can say which way the decision went. In the API, edges leaving the node use `edgeType: "decision"` with a `branchKey`: `yes` or `no`, one of your `choices[].key` values, `above` or `below`, or `uncertain` or `error`. A branch takes at most one edge. `GET /v1/workflows/schema` returns the full config schema for all three decision types. What it can't do: * **It can't sit on a synchronous run.** A workflow on **Order being created** or **Order validation** would hold the order while the model answers, so Nash refuses the node there when you save, when you activate, and again at run time. * **It can't read ahead.** The question and the context can reference the trigger, the entity's fields, and upstream nodes only. A reference to the node itself or to a later one is refused when you save. * **It doesn't investigate.** It reads what you hand it and answers. When a step needs to look things up, call tools, or write a report, [hand it to an agent](/reference/workflows#hand-a-step-to-an-agent) instead. A dry-run test doesn't call the model; the node follows **Uncertain** with a `preview` status. Turn on **Evaluate with the model** in the Portal's Test drawer, or send `live: true`, to see a real answer. If a step is retried with the same question and context, the earlier decision is reused rather than asked again. ## Hand a step to an agent Filters and actions cover the decisions you can write down in advance. Some steps aren't like that — read the notes on this order and decide whether anyone needs to be called, look at what the provider has done so far and judge whether the delivery is still recoverable. An **agent node** hands that step to one of your [custom agents](/nash-agent/custom-agents) and lets the rest of the graph branch on what comes back. The agent receives the entity the workflow was triggered on, the trigger's own metadata, and the output of every node upstream of it. It does the work — pulls the data it needs, calls the tools it's allowed to use — and returns a written report, plus the structured fields that go with it if you've given the agent an [output format](/nash-agent/custom-agents#structured-output-and-writing-results-back). In the API it's the `invoke_agent` action; in the Portal's workflow editor it's **Run Custom Agent**. Either way you pick the agent, write the task context for this particular step, and choose what the workflow branches on. ### Branching on what it reports The node writes its result into the workflow's variables in two shapes: | Variable | Holds | | - | - | | The node's own key | The agent's full written report | | One key per structured-output field | That field's value, at its own type | The typed per-field variables are the ones worth branching on: a number can be compared, a boolean tested, an enum matched, the same as any other filter field. The full report is the fallback for an agent with no output format — useful to carry into a notification, less useful to filter on. ### What an agent node can't do * **It can't take actions.** Only report-only agents can be selected. An agent that acts would put its actions behind its own confirmation gate, and the workflow would carry on as though they had happened. * **It can't sit on a synchronous run.** Workflows that run synchronously while an order is being created reject agent nodes, because the order would wait on the agent. Nash refuses the combination when you save the workflow, and again if a run attempts it. * **It can't run unbounded.** Each node carries its own timeout and a choice of what the workflow does when the agent fails or runs out of time. ## How workflows relate to dispatch strategies Workflows and dispatch strategies are complementary: * A **dispatch strategy** answers *"given this job, which provider should fulfill it?"* — it defines the eligible providers, selection rule, and failover behavior. * A **workflow** answers *"given this event and these conditions, what should happen?"* — including *which dispatch strategy to apply*. A common pattern is a workflow triggered on order creation, with filter nodes that branch on order value or destination, and `dispatch_strategy` action nodes that apply the right strategy to each branch. This is the same role served by [Automations](/api-reference/dispatch-strategies/dispatch-automations), which map account-level business rules to dispatch strategies; workflows generalize that idea to a broader set of triggers, conditions, and actions. ## Next steps How Nash selects a provider for each delivery. Map business rules to dispatch strategies. Configure delivery notifications to customers and systems. See where workflows fit in the delivery lifecycle. # Zones Source: https://docs.usenash.com/reference/zones A zone is a geographic delivery area defined by a polygon boundary or a list of cities, zip codes, states, or countries. Learn how zones model coverage, how to look up which zones cover a location, and how zones relate to store locations. A **zone** is a geographic area defined by a coverage area — a polygon boundary or a list of cities, zip codes, states, or countries. Zones model where delivery is available and let you apply zone-based rules and associate areas with the [store locations](/reference/store-locations) that serve them. ## What a zone is A zone is created from a coverage area and can be associated with one or more store locations. Each zone can carry: * A **name** and an optional **external ID** for mapping to your own systems * One or more **store location associations**, optionally with start and end dates * A **coverage area** that defines the zone's extent — a polygon boundary or a list of cities, zip codes, states, or countries * Free-form **tags** and **metadata** for your own classification Create a zone with [Create a zone](/api-reference/zones/create-a-zone) (`POST /v1/zones`) and retrieve one — including its coverage area and associated store locations — with [Get zone](/api-reference/zones/get-zone) (`GET /v1/zones/{id}`). You can also list zones with pagination (`GET /v1/zones`) or look one up by your own identifier with [Get zone by external identifier](/api-reference/zones/get-zone-by-external-identifier). **Zone restrictions** are a separate concept layered on top of zones. A zone restriction defines a rule that controls delivery behavior within a specific zone — for example, provider restrictions or time-based rules. List them with `GET /v1/zone-restrictions`. ## Set a polygon boundary [Create a zone](/api-reference/zones/create-a-zone) (`POST /v1/zones`) and [Update a zone](/api-reference/zones/update-a-zone) (`PATCH /v1/zones/{id}`) accept a `polygon` field. Send one closed ring of coordinate pairs to define the zone's geographic boundary. A valid polygon has: * At least four coordinate pairs, including the repeated closing point * Coordinates in `[latitude, longitude]` order * An identical first and last pair, which closes the ring * A boundary that does not cross itself ```bash Create a zone theme={"dark"} curl --request POST \ --url https://api.sandbox.usenash.com/v1/zones \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" \ --header "Content-Type: application/json" \ --data '{ "name": "Downtown", "externalId": "downtown", "polygon": [ [37.7749, -122.4194], [37.8049, -122.4194], [37.8049, -122.3894], [37.7749, -122.3894], [37.7749, -122.4194] ] }' ``` ```bash Replace a boundary theme={"dark"} curl --request PATCH \ --url https://api.sandbox.usenash.com/v1/zones/$ZONE_ID \ --header "Authorization: Bearer $NASH_API_KEY" \ --header "Nash-Org-Id: $NASH_ORG_ID" \ --header "Content-Type: application/json" \ --data '{ "polygon": [ [37.7649, -122.4294], [37.8149, -122.4294], [37.8149, -122.3794], [37.7649, -122.3794], [37.7649, -122.4294] ] }' ``` When you create a zone with a polygon, Nash creates a coverage area unless you also pass an existing `coverageAreaId`. Updating `polygon` replaces the zone's current boundary. If the coverage area is shared with another resource, Nash gives the zone a dedicated coverage area before changing the boundary, so the other resource keeps its geometry. Zone responses return the boundary under `coverageArea.polygons[].polygonData`. ## Read zones in bulk [Query zones](/api-reference/zones/query-zones) (`POST /v1/zones/query`) returns full details for several zones in one request. Filter by Nash zone IDs in `ids`, your identifiers in `externalIds`, or both. The filters use OR, and each list accepts up to 500 values. Omit both filters to page through every zone in your organization. Set `size` from 1 to 500 and use `offset` to advance through the result. The response places zone records under `data` and pagination details under `meta`. ## Coverage lookups Coverage lookups answer the question *"which zones contain this location?"* by testing a location against your zones' coverage areas. Use [Get zones that cover a specified location](/api-reference/zones/get-zones-that-cover-specified-location-latitude-and-longitude-city_zipcode) (`GET /v1/zones/coverage`). You can query by: Provide both `latitude` and `longitude` to find zones whose coverage contains that exact point. ``` GET /v1/zones/coverage?latitude=37.76&longitude=-122.43 ``` Provide a `cityZipcode` pair (for example `sanfrancisco_94114`). The location must have coverage set up using the **City & Zip Code** unique field, not the separate City or Zip Code fields. You can also query by `zipcode` alone. ``` GET /v1/zones/coverage?cityZipcode=sanfrancisco_94114 ``` The endpoint returns the list of zones that cover the requested location. If you query by `latitude`, you must also provide `longitude` (and vice versa) — coordinate lookups require both values. ## Zones vs. store locations Zones and store locations are distinct but related: * A **store location** is a physical origin — a restaurant, warehouse, or store — that deliveries depart from. * A **zone** is a geographic area, defined by its coverage area, where delivery is available. A zone can be associated with one or more store locations. Together they let you describe *which store serves which area*: associate a zone with a store location, and a coverage lookup tells you whether a given dropoff point falls inside that store's delivery area. Store locations have their own coverage lookup, [Get store locations that cover a specified latitude and longitude](/api-reference/store-locations/get-store-locations-that-cover-specified-latitude-and-longitude), which returns the store locations serving a point rather than the zones containing it. ## Next steps Define where delivery is available with a coverage area. Find zones covering a latitude/longitude or city/zip. Retrieve a zone's boundary and store locations. Fetch full zone details in bulk by Nash ID or external ID. Manage the origins that zones serve. # Release notes Source: https://docs.usenash.com/release-notes/change What's new in Nash — features, improvements, and fixes, week over week. ### Week of October 5, 2026 #### Highlight **Driver groups from your own roster.** Who drives from which depot, on which shift, usually lives in a workforce or HR system, and it changes every week. Until now the copy in Nash was kept by hand: someone opened the Portal and edited each group's list. Three new Fleet API endpoints let the system that owns the roster keep Nash's driver groups matched to it. The figure here is a sequence diagram between your workforce system and Nash. Your system upserts a driver group and its full roster with `PUT /v1/fleet/driver-groups/upsert`, sending `externalIdentifier`, `displayName`, and `driverIds`. Nash creates the group and answers with `created: true`, the group's `firstVersionContractId`, and its `drivers`. Later, one driver changes depot. Your system moves that driver with one `PUT /v1/fleet/drivers/upsert` call carrying `addFirstVersionContractIds` and `removeFirstVersionContractIds`, with no need to send either roster again. It then reads the group back with `GET /v1/fleet/driver-groups/{first_version_contract_id}`, and each driver comes back with your `externalIdentifier` for them and whether they are `enabled` and on shift. The first call sets a whole roster; after that, a change to one driver is one call about that driver. * **Create and update groups by your own ID.** `PUT /v1/fleet/driver-groups/upsert` is keyed on your `externalIdentifier`: a new value creates a group, the same value updates it, and `created` in the response says which happened. Fields you leave out keep their values. There's no delete call; send `isActive: false` to disable a group. * **Set a roster in one call, or change one driver.** `driverIds` on the group replaces its whole list, which suits a nightly sync. When you hear about one driver at a time, `addFirstVersionContractIds` and `removeFirstVersionContractIds` on `PUT /v1/fleet/drivers/upsert` add or remove that driver and keep their other memberships. * **Read back what Nash has.** `GET /v1/fleet/driver-groups` lists your groups, newest first, and `GET /v1/fleet/driver-groups/{first_version_contract_id}` returns one group with its drivers, each marked enabled or not and on shift or not. See [Manage your driver groups](/fleet/overview#manage-your-driver-groups). #### New & improved **Portal** * Routes has a grid layout. **Routing UI**, under **Settings → Preferences → Routing**, switches your organization from route cards to **Grid**: a table of routes with stops, service time, size, and percent of capacity, a table of their stops with time windows and departures, and the map. Tick or drag stops to move and reorder them, and switch the stop panel to **Timeline** to see where a driver waits for a window or arrives after it closes. *(For organizations with Routes turned on.)* See [Plan routes on the Orders map](/reference/routes#plan-routes-on-the-orders-map). * **Recalculate** on a route marked **Times are out of date**, and **Calculate timing** in a route's menu, work for every organization. `POST /v1/routes/{id}/calculate-timing` no longer returns `route_timing_not_enabled`. *(For organizations with Routes turned on.)* * **View Routes**, on an optimization run on the Activities page and in the optimize review once routes are saved, opens Routes filtered to the routes that run wrote. *(For organizations with Routes turned on.)* * On a job using a Dynamic Dispatch strategy, the quotes list marks the provider Dynamic Dispatch recommends, on the job's page and when you assign a provider. * Deliveries has three more columns under **Manage Columns**: **Assignment Time**, **Pickup Arrival Time**, and **Dropoff Arrival Time**. **Show Time Between Steps**, in the table's settings, prints the time elapsed under each step. **Actual Pickup Time** and **Actual Dropoff Time** show when the event happened, not when Nash recorded it. * The delivery windows calendar shows each window's maximum capacity on its card, and marks a window **Dispatched** once every order booked into it has been dispatched. The Shifts calendar marks a shift **Dispatched** when its route is. See [Delivery windows](/reference/delivery-windows). * Export shifts or shift templates from **Fleet → Shifts**, edit the file, and upload it to update the same records. The export carries each record's `id` and the columns **CSV Upload** reads, and a template upload changes only the columns in the file. *(For organizations with Fleet turned on.)* * On Execute, a route's exceptions sit in one **Exception** column, with **Add exception** on routes that have none. Hover it for a preview that lists them, with **Acknowledge** on each and **Acknowledge all** for those in **Needs Triage**. *(For organizations with Execute turned on.)* * An activity a driver logs on a route reads by name in the job's route activity and on delivery timelines, such as "Lunch Break started". * On the tracking page, your brand color sets the color of the header. The progress bar and timeline are always green, so progress reads the same on every merchant's page. * The tracking page is easier to use with a screen reader and a keyboard: map markers and controls have names, the timeline says each step's state in words, focus returns to where it was after a dialog, and form borders and error text have more contrast. * Error messages across the Portal are translated in Spanish, Polish, and Arabic, including the common ones the server returns, such as an expired session or a missing permission. **Copy** on an error message includes the request details support asks for. * Reloading the Portal draws the sidebar and top bar straight away and fills the page in, in place of a series of full-page loading screens. **Orchestration** * A new trigger fires when an order's booked delivery window reaches its cutoff, or a number of minutes after: **Delivery Window Cutoff** in the workflow builder and **Order Cutoff Time** in Notifications. It moves with the order if the order is booked into another window. See [Fire when a delivery window's cutoff passes](/reference/workflows#fire-when-a-delivery-windows-cutoff-passes). * **Only send once per order**, on a workflow's email, SMS, Slack, and **Send to destinations** steps, sends a notification the first time and holds it back when the trigger fires again for the same order. A held-back step is marked **Not sent: already sent for this order** in the run. See [Send a notification once per order](/reference/workflows#send-a-notification-once-per-order). * Workflow conditions can read where an order's dispatch stands: **Current Task Status**, **Current Delivery Status**, and **Job Canceled** are order fields, so a workflow on an order trigger can skip work that's already picked up or canceled. **Platform API** * `GET /v1/routes/nearby-couriers` returns the drivers on active routes nearest to a point, by straight-line distance from each driver's latest GPS position. Use it to find who can take over a route that needs help. See [Find Nearby Couriers](/api-reference/route/find-nearby-couriers). * Order responses return `pickupEmail` and `dropoffEmail`. The API accepted and stored both already; now it sends them back. **Nash Agent** * Files on a custom agent keep their history. Replace a document in an agent's **Knowledge base**, or a file attached under its instructions, and the file's version count opens every saved version, with **Restore** to make an earlier one live again. See [Go back to an earlier version of a file](/nash-agent/knowledge#go-back-to-an-earlier-version-of-a-file). * Stopping Nash Agent mid-answer keeps the conversation open. Send your next message and it carries on in the same chat. Runs of a custom agent still end when you cancel them. * While Nash Agent is working on an answer, the chat shows how long it has been at it, such as "Working for 4m 10s". * A confirmation card names what you're approving, such as "Cancel delivery …?" or "Reassign delivery … to a new provider?", in more cases that used to read "Confirm action?". * The Nash MCP server has four tools for working on one provider attempt: `reassign_task`, `refresh_task_quotes`, `select_quote`, and `cancel_task`, which cancels a single attempt and leaves the rest of the job alone. See [Nash MCP server](/reference/mcp-integration#what-the-server-exposes). **Fleet** Driver App version 1.13 is in the App Store and Google Play. Changes marked 1.13 need that version. * When an order's dropoff instructions change while its route is underway, the driver gets a **Delivery instructions updated** notification naming the stop, and the app loads the new instructions. * A newly assigned or newly offered delivery reaches the driver's phone within about 10 seconds, down from up to a minute, and the app shows it without a manual refresh. * The dropoff package list can show each package's description under its category. **Display package list description** is under **My Fleet → Pickup & Delivery**, and can be set per driver group. *(Opt-in.)* * The driver's choice of navigation app includes TomTom GO, DashTag Maps, and Trucker Path when they're installed. *(1.13)* * Driver App 1.13 needs iOS 16.4 or later. #### Fixes **Portal** * Execute's header and KPI cards count the routes inside the page's dates and filters, and the insight bar's headline counts every matching route, not the first page of them. **View details** in a route's panel opens the delivery in the same tab. *(For organizations with Execute turned on.)* * The **Shift Name** filter loads its choices on the **Ready to Dispatch** view of Orders. * Editing or dragging a delivery window with unlimited capacity keeps it unlimited. It used to save a capacity of 0, which closed the window to bookings. * The street field on the create-order and create-job pages suggests addresses when the page is opened directly or reloaded. * Changing a member's Portal role keeps their driver role, so a member who also drives stays signed in to the Driver App. * With the theme set to **System** on a device in dark mode, the Portal uses the dark versions of its default color and integration logos, and follows the device when it changes. **Tracking page** * The headline, progress bar, timeline, and driver card agree for every delivery status. A canceled delivery reads **Delivery canceled**, a delayed delivery keeps the step it had reached, scans at a carrier facility read as in transit, and a return in progress no longer shows an earlier "Delivered" step. * Suggestions a customer ticks on the feedback page arrive with their comment. **Orchestration** * A **Send HTTP request** step whose body carries a date and time on its own, such as a pickup window or a completion ETA, sends it as an ISO 8601 string. The request used to fail before it was sent. * A workflow on an order-created or job-created trigger runs once for an order that arrives through an integration such as Shopify or a point-of-sale system. It used to run twice. * A WhatsApp notification whose message has line breaks is delivered. **Nash Agent** * Images attached to a chat are read again on the Claude model options. * Rejecting one confirmation card while the agent is still working no longer leaves the chat idle after you approve another. **Fleet** * A proof-of-delivery photo, proof-of-pickup photo, or signature stays done when a driver leaves a stop and comes back to it, including offline. *(1.13)* * A GPS position the Driver App sends more than five minutes late is kept in the driver's history but doesn't move their current position or mark them arrived. * A driver is at one stop at a time: starting on a stop sets any other stop they'd started back to pending. * When merging or splitting a batch leaves a job with no orders, its route is removed, so the driver doesn't see a stop that can't be closed. * An order added to a pickup stop the driver is already heading to takes that stop's status straight away. **Integrations** * A Dash order that no driver accepts is treated as canceled by the provider, so it's reassigned when your dispatch settings say to. ### Week of September 28, 2026 #### Highlight **Start from the list you were sent.** The warehouse emails the orders that are packed. A customer's spreadsheet names the ones to pull. Until now, acting on a list like that meant searching for each order, or building a filter that happened to catch them all — and a list rarely lines up with one date or one status. **Paste IDs** on the Orders table takes the list as it is, finds every order on it, and lets you act on them together. The figure here shows the flow in three panels. You paste a list of Nash IDs, external IDs, and reference IDs. Nash finds every matching order whatever its date or status, and lists the IDs it couldn't find. You then run Dispatch, Mark as picked up, Archive, or Unarchive on the orders each action applies to, and read which were done, skipped, or failed. * **Paste what you have.** Up to 1,000 Nash IDs, external IDs, and reference IDs, mixed in one list and separated by new lines, commas, semicolons, or tabs. Matching is exact and case-sensitive, and it ignores the table's date range and filters, so an order from last week and one for tomorrow come back together. Your organization and location access still apply. * **See what matched, and what didn't.** A **Matched ID** column says which identifier found each order, an ID shared by several orders brings back all of them, and the IDs that matched nothing are listed with **Copy IDs**, ready to send back to whoever made the list. * **Act on the ones that qualify.** **Dispatch**, **Mark as picked up**, **Archive**, and **Unarchive** each show how many orders they can change and how many they'll skip, with the reason. Orders an action doesn't apply to are skipped, never changed, and a run changes at most 1,000 orders. * **Know how it went.** A run shows its progress, then which orders were done, skipped, or failed, and why. If it stops partway, it names the orders it couldn't confirm, so you check those before running it again. *(For organizations with the Orders page turned on.)* See [Find and act on a list of orders](/guides/find-and-act-on-orders). #### New & improved **Portal** * Archiving several selected orders, or every order matching your filters, asks for a reason, as archiving one order does. The reason is saved on each order. * Filter the Orders and Routes maps from the map. **Tools → Filters** holds the table's filters, such as pickup and dropoff store, tags, and dispatch status, and a filter set in either place applies in both. Its **Route metrics** conditions narrow routes by **Boxes**, **Distance**, **Duration**, and **Stops** in your display units, in the optimization review as well, and **Sort routes** orders by the same numbers. See [Plan routes on the Orders map](/reference/routes#plan-routes-on-the-orders-map). * When you add orders to a route, **Nearby routes** leaves out routes whose vehicle lacks a capability the orders require and says how many it hid. A vehicle that meets every preference is marked **Preferred**. *(For organizations with Routes turned on.)* * **Edit sequencing**, in a route stop's menu, sets a **Sequence Priority**, **Sequence Group**, and **Mutually Exclusive Label** on the stop's order, for the next time the route is optimized. A smaller priority is visited earlier. *(For organizations with Routes turned on.)* * A route's **Activity** tab lists everything that happened to it in time order, with **Load earlier activity** for older entries. A dispatch entry links to its job, and each optimization run links to its record. * On Execute, set an exception's status from its card — **Needs Triage**, **Acknowledged**, or **Resolved** — and choose its **Severity** when you add one: **Auto**, or P0 through P3. Select routes in the table to **Assign to** someone, or **Clear assignee**, on their open exceptions. *(For organizations with Execute turned on.)* * Open a delivery window on the calendar for a **Booked orders** tab: the orders holding a slot in it, with their addresses and how many slots are used. Calendar blocks are colored by whether a window is open, within an hour of its cutoff, or closed, and a shift that has a route opens with **Route** and **Activity** tabs. See [Delivery windows](/reference/delivery-windows). * Three new preferences. **Business Day Starts At** makes a day on Deliveries, Orders, and Shipping run from the hour you choose to the same hour the next day. **Map Starting Point** opens empty maps on your operating area. And each person can choose their own **Date-range presets** on their Account page. See [Display preferences](/reference/display-preferences#set-your-organizations-defaults). * **Settings → Preferences**, **Organization Settings → Operations**, and tracking customization each have an **Audit log** tab that lists who changed a setting, when, and the old and new values. It appears for roles that can read the audit log. * A **Webhooks** button on a job's details opens the webhook logs filtered to that job. * A delivery's timeline records each masked call when it ends: whether it was completed, unanswered, or failed, who called whom, and how long it lasted. * **Est. Duration**, under **Manage Columns** on Deliveries, shows the planned driving time beside the driving distance. * The Driver Geolocation notification trigger measures distance in **Kilometers** as well as miles. **Orchestration** * Address a workflow's **Send SMS** or **Send email** to a **Role** or a notification **Destination**, as well as to a contact you type in. Members and contacts are looked up when the workflow runs, so a later change to the role or destination needs no edit to the workflow. See [Send to a role or a destination](/reference/workflows#send-to-a-role-or-a-destination). * A new **Send to destinations** action notifies every contact on the destinations you choose, each on its own channel, from one step. It's available on workflows that run in the background. **Nash Agent** * An agent with a structured output says when a run finished but didn't do its job, because data it needed was out of reach or its results are incomplete. The Portal marks that run **Needs attention** rather than completed, separately from a run that failed. See [Structured output and writing results back](/nash-agent/custom-agents#structured-output-and-writing-results-back). * A chat with an agent that acts on child organizations reaches those organizations too, using your own sign-in, so you reach the children where you hold a role. Until now only its scheduled and manual runs did. *(Opt-in.)* * Ask in an agent's chat to add or remove one of its skills, or to put it back on the default set. The change goes through the same review as any other edit to the agent. * **Balanced** and the OpenAI option under Nash Agent **Settings → General → Model** move to each provider's latest model. Organizations and agents already on either option move with them. * In chat, wide tables stay within the chat's width and scroll sideways, timelines connect their steps and mark the current one, and callouts carry an icon for their tone. * The Nash MCP server can add a driver and place them in a driver group: `create_courier` creates a driver from a name and phone number, and `update_driver_group_members` adds or removes a group's drivers. Creating a driver texts them the Driver App links. **Fleet** Driver App changes reach phones on version 1.12 on their own and apply the next time the app starts. * Drivers can pick **Business Closed** as the reason for an attempted dropoff. * The proof-of-delivery camera has a flash control — **Auto**, **On**, or **Off** — and starts on Auto, so it fires in the dark on its own. The torch button is unchanged. * A pickup stop shows the total weight of the orders being loaded. * An activity a driver times on a route, such as a break or refueling, is recorded as it starts and stops, including offline, and a running timer comes back if the phone has lost track of it. **Platform API** * A failed optimization says why. When the engine rejects a request, `GET /v1/activities/{id}` returns its `vendor_message` and `vendor_reasons` in the `result`, and a reason that points at a position in the request ends with the Nash ID it refers to. The Portal shows the same reasons on the failed run. See [Route optimization](/guides/route-optimization#polling-strategy). #### Fixes **Portal** * Rows you selected in the Orders and Routes tables stay selected on the same records when the list refreshes or reorders. * Dispatchers hear the chat sound when a driver sends a message, and get a browser notification when the Portal is in a background tab. * Running an optimization to review it, without saving or dispatching, leaves orders on the routes they're already on when the proposed plan doesn't place them. *(For organizations with Routes turned on.)* * A route card's **Times are out of date** mark survives a reload and shows for an edit a colleague made, until the route is recalculated or re-optimized. * On Execute, the routes table keeps its header in view as you scroll, pickup time, stops, and end ETA line up under their headers, and **Call driver** on the map dials the assigned driver. *(For organizations with Execute turned on.)* * The **Updated** tab of an audit log lists the records that were modified. * Saving delivery window settings under **Organization Settings → Operations** keeps the dropoff cutoff times and automatic reassignment choice already set. * The provider selector on a contract lists active providers only. * Batch rows on Shipping show their locations, progress, times, distance, and package totals, with the batch's last destination as the dropoff. **Orchestration** * **Random Grouping** in dispatch automations keeps an order in the same group when the order is updated. Its number now comes from the order's ID, so orders may land in a different group than they did before the change. **Nash Agent** * A failed run on an agent with a custom notification template sends a message that says the run failed and why, rather than an empty one. * An agent watching a delivery or route on an interval carries on after a Nash service restart. * A run on the OpenAI option whose request gets no response is retried, then falls back to a Claude model if OpenAI stays unreachable. **Fleet** * Photo and signature uploads on a weak connection retry on their own with longer waits each time. A queued action the server rejects shows as failed, for the driver to retry or discard, rather than staying on syncing. * Correcting a failed item to delivered at a dropoff takes the order off the return stop, so finishing the return doesn't mark a delivered order as returned. A return stop left with no orders is removed. * Unassigning a driver from a route leaves stops the merchant already canceled, and stops already delivered, as they are. * Reassigning a dispatched route to a different driver group no longer carries the previous group's driver onto the new deliveries. * Tracking continues on a multi-stop route that runs past its planned window while the driver still has goods on board, so ETAs and statuses keep coming for the remaining stops. * Pickup and dropoff ETAs on your own fleet's point-to-point deliveries hold steady between refreshes, and the dropoff ETA right after pickup is calculated for the dropoff. **Integrations** * Nash's tags and **Additional details** are written back to a Shopify order even when a value, such as a long pickup location name, is over Shopify's 40-character tag limit. The tag is shortened and the full value stays in Additional details. * A Shopify order placed after a shipping rate was quoted at checkout is dispatched with the placed order's line items. * A Leajlak delivery shows the driver as arrived at pickup when Leajlak reports **Reached Shop**. * Frayt receives five-digit US ZIP codes, so an address stored with a ZIP+4, or a ZIP missing its leading zero, dispatches. * When Team Global Express rejects a manifest because the receiver has no email, Nash resubmits it with the default receiver email set for the provider, when there is one. ### Week of September 21, 2026 #### Highlight **Resequence keeps every stop you planned.** A route built by hand carries decisions a dispatcher has already made: these stops go together, and that customer was promised today. Resequence is there to put those stops in a sensible order. But when one stop didn't fit the route's limits — a window it would miss, a day that ran long, a capability the truck doesn't have — the optimizer returned no plan, and the route stayed as it was. Now Resequence keeps every stop, puts them in order, and shows you which limits the result bends, so the call on what to do about them stays with you. You plan a route with the stops you chose and the route's own vehicle, routing, service times, and breaks; Resequence keeps every stop by relaxing day length, distance, capacity, capabilities, shift end, and dropoff windows, and records what breaks; Route health then lists each violation, such as a late stop, a missing capability, or time past the shift end. * **It plans with the route's own setup.** Resequence uses the route's vehicle, routing, service times, and driver breaks, so the order and times it returns are the ones that route will run. * **Nothing falls off.** To place every stop, it relaxes the limits that would push one off: day length, distance, capacity, vehicle capabilities, the end of the shift, and dropoff windows. Pickup windows still hold. If a stop still can't be placed, the route isn't changed, and you're told so. * **It shows what it bent.** The result reads **Every stop kept · N constraint violations**, the route card carries the count, and **Route health** in the route's details lists late stops, time past the shift end, over-capacity loads, and orders that need a capability the vehicle doesn't have, each linked to its stop. **Acknowledge** records that you've seen them. The route keeps its name, number, and vehicle. * **Start from an empty route.** **Create route** on the Routes page makes a named route, with an optional start and end location, and opens it for orders. You can also move every stop off a route that hasn't been dispatched — the emptied route stays, so you can refill it — and routes that load at a depot can trade stops even when they leave from different depots. Resequence and these edits work on routes that haven't been dispatched. *(For organizations with Routes turned on.)* See [Plan routes on the Orders map](/reference/routes#plan-routes-on-the-orders-map). #### New & improved **Portal** * Gather stops from several routes by ticking dropoffs in their route cards, then choose **Move to route**. Stops you picked that are already on the destination stay where they are. The route chooser lists the routes you've focused on the map first, marked **Focused on map**, and says beside the button why it can't confirm yet. *(For organizations with Routes turned on.)* * The Orders map's **Orders** layer can show **Needs Attention**, **Ready to Dispatch**, **Dispatched**, or **All orders**, not only unassigned ones. **Sort routes** orders the route list by **Route ID**, so D2 comes before D10, or by **Date created**, and a route card's pickup collapses to one line with its order count. * The Orders and Routes maps show their time zone on the toolbar. It defaults to your organization's, **Use organization timezone** at the top of the list switches back to it, and every time and date range on the map follows it. * Choose the date range presets your organization sees. **Date Range Presets**, under **Settings → Preferences**, picks from Today, Yesterday, Tomorrow, This week, Last week, the new **Next week**, Last 7 days, Next 7 days, This month, Last month, and Next month, and every date range picker in the Portal offers that list, with **Custom range** always there. Until an admin changes it, pickers offer Today, Yesterday, Tomorrow, and This week. See [Display preferences](/reference/display-preferences#set-your-organizations-defaults). * Cmd+K (Ctrl+K on Windows) opens any page in your sidebar, Execute included, and your **Account** page. **Nash Agent** * A reply that reaches the limit of 50 tool calls in one turn ends with what Nash finished and what's left, plus a **Continue** button that picks up where it stopped. In Slack, reply "continue", or mention Nash with it in a channel. A step Nash hands to a specialist that hits its own limit no longer fails your reply or a scheduled run. * **Most capable** and the OpenAI option under Nash Agent **Settings → General → Model** move to each provider's latest model. Organizations and agents already on either option move with them. * A scheduled or triggered agent run on the OpenAI option that fails because OpenAI is unavailable retries once on a Claude model. * The Nash MCP server's code mode runs up to 300 API calls in one block, up from 50, so a sweep across many orders finishes in one step. Each organization also gets its own share of the server's request rate — 30 requests per second, with bursts up to 75 — so one organization's burst doesn't slow another's calls. **Fleet** Driver App changes reach phones on version 1.12 on their own and apply the next time the app starts. A phone still on 1.11 needs 1.12 from the App Store or Google Play first. * The Driver App reads properly in Spanish, Portuguese, Simplified Chinese, Japanese, and Arabic. About 50 strings per language that still appeared in English are translated, including the screens a driver sees when their account can't load, and wrong words are corrected: in Chinese and Japanese, **Return** no longer reads as "go back" and **Unassign** no longer reads as "cancel", and in Arabic, **Check In** no longer reads as "sign in". * The app tells drivers when the App Store or Google Play has a newer version. **Update recommended** shows the version and opens the store, and **Not now** hides it until the app next starts. It checks when the app opens or comes back to the foreground, at most every four hours, and stays quiet when the store can't be reached. * Starting a route sends the driver's position, and the first stop's deliveries take it as their `courierLocation` when they don't have one yet. When completing a stop moves the route on, the next stop's deliveries take that position the same way. * Route cards show when the route is planned to reach its first pickup, and sit under that day, rather than the first order's requested pickup time. A route without a plan still shows the order's time. * Pickups and returns to the store use the **Alternative Pickup Location** you've set for your internal fleet on a pickup location, as the job view already did. * Scanning a package marked failed or missing picks it up or delivers it, when the stop still allows that. Other failed or missing packages keep their status. * Drivers can fail a dropoff with **Out of Drive Time** after heading to the stop, not only before. **Platform API** * Quotes under **Dynamic Dispatch** carry a `rank`, with 1 for the provider Nash tries first, as quotes under the other strategies do. * `minimumVehicleSize` accepts `cargo_bike` as well as `cargobike`. **Integrations** * Medzoomer deliveries itemize Medzoomer's final invoice. Wait-time, canceled-order, and return-to-pharmacy charges land in `waitFeeCents`, `cancellationFeeCents`, and `returnFeeCents`, and `priceCents` leaves out the courier tip, which you already see as `tipAmountCents`. The invoice is final after the delivery completes; see [When fees are final](/reference/delivery-fees-and-pricing#when-fees-are-final). * Autolane receives changes to a dispatched delivery's pickup and dropoff instructions. * Correct a Team Global Express delivery's structured address — `pickupAddressComponents` or `dropoffAddressComponents` on `PATCH /v1/jobs/{id}` — until the delivery is manifested, so a suburb or postcode Team Global Express rejected can be fixed in place. Other providers still refuse structured address changes after dispatch. * AlShrouq receives your order's `externalId` as its order reference, so AlShrouq's team can match a delivery to your order number; an order without one sends the Nash delivery ID. AlShrouq accepts each reference once, so an order canceled with AlShrouq can't be sent to AlShrouq again. * Uber can receive each item's price on standard deliveries, as it already does for pick-and-pack orders. *(Opt-in, per provider configuration.)* #### Fixes **Portal** * The severity pills on Execute count the routes the table shows for your dates, filters, and search. *(For organizations with Execute turned on.)* * Route search matches what route cards show, and a short term like `D7` no longer brings back routes whose generated IDs happen to contain it. Part of an ID, with its prefix, still finds the route. * **Lasso** and **Polygon** select only the orders drawn on the map. * Routes near each other get clearly different colors, and in dark mode a selected order's path to its dropoff is visible. * The optimization review shows each order's customer promise in the route's time zone. * Going back from a delivery page returns to where you came from, such as a route, rather than Home. * Duplicating a delivery carries its **Reference ID** into the new delivery form. * Searching **Users**, **Providers**, and **Drivers** ignores spaces around the name you type, and **Driver Pay** shows drivers who have only a first or last name on file. * Activating or deactivating a contract from the **Contracts** list keeps its drivers. * When a contract or driver group can't be saved because a store location, depot, coverage area, or driver isn't available to your organization, the error says which kind it was. * Refund requests from organizations in Nash's Australian region update with Nash support's decision, including the status and the refunded amounts. **Platform API** * Paging through `GET /v1/orders` returns each order once, even when many orders share the value you sort by. Orders that tie are ordered by order ID in the same direction, and the Portal's order lists page the same way. * A job created from a route carries the tags every order on the route shares, all of them for a single-order route, so dispatch rules that match on tags apply to route work. The orders keep their own tags. * After `POST /v1/jobs/{id}/rebook`, the job keeps the provider, quote, and delivery the rebook selected. * `GET /v1/webhooks/messages/{msg_id}/attempts` returns a `404` `MISSING_RESOURCE`, with the ID in `details.msg_id`, when the message doesn't exist. Replaying an unknown message returns the same `404` and resends nothing. **Nash Agent** * Text before and after a lookup no longer runs together. A heading that follows a tool call starts on its own line, and new thoughts start new paragraphs, in the Portal, in Slack, and in custom-agent reports. * A chat where Nash runs code keeps working. A message you send while a code step is running is picked up once the step finishes, a chat where Nash saved a file from Python keeps taking messages, and files you uploaded earlier stay open to Nash after you approve an action or answer its setup questions. * When a chat or run grows past the AI model's context limit, Nash says so and suggests starting a new chat or asking for less data at once, rather than offering a retry that would fail the same way. Long analytics sessions reach that limit later, because each query result is sent to the model once. * Custom agent results with a structured output format read cleanly: long reports render as formatted text, wide tables scroll sideways, and a value in an unexpected shape shows as readable data. Status badges are neutral, since a yes or no doesn't mean pass or fail. * On the Nash MCP server, bulk reads wait and retry when the Nash API rate-limits them, so a large sweep comes back complete, and the photo-verification tools report `photo_found: false` for a job with no photo yet. Code-mode calls to operations with an ID in the path — webhook message attempts, delivery feedback, removing an order from dispatch, assigning a route, finding a store location by external ID — send the ID and succeed. **Fleet** * Android drivers can call from the Driver App. Tapping a stop's phone number or **Call Support** opens the dialer with the number filled in. * The Driver App holds up over a long shift. Going back returns to the screen the driver came from instead of opening a fresh copy, and on Android a map loads only while it's on screen, with the driver's position as a blue dot — an arrow while moving — and a **My location** button to re-center. * A route's **Total Weight** in the Driver App shows its decimals, and a route under 1 kg shows its weight. * On a started route, a return stop or the end-of-route stop opened ahead of its turn shows its actions. * Scanning a package label that carries more than one barcode records one scan for the package. * The ID check's signature step shows the minimum age the customer is confirming. * A shift template creates each shift once, including a shift that has already started when the template runs. * **Create Fleet** under **Fleet › Driver Groups** works when another organization already has a fleet with the same name. *(For organizations with Fleet turned on.)* * A provider organization can save a contract whose eligibility lists store locations of a customer organization it supports. **Integrations** * A canceled Roadie delivery stays canceled when Roadie sends a later driver update, and canceling a delivery Roadie has already canceled succeeds. A shipment Roadie lets expire without a driver closes as `CANCELED_BY_PROVIDER` with the failure reason `expired`. * A Toast update that only changes a scheduled order's prep time leaves its delivery in place. When a Toast change does move the delivery time, Nash checks the new order before canceling the old one. * Addison Lee deliveries show a dropoff ETA once the driver has the package, the pickup ETA clears, and Addison Lee's tracking updates move the delivery's status along. ### Week of September 14, 2026 #### Highlight **Judgment calls, as a branch.** A workflow could compare a field, and it could hand a whole step to an agent. The decisions in between — does this delivery note describe a safety problem, which of four teams should own this failure, how complete is this address — were too loose for a filter and too small for an investigation, so they stayed with a person reading a queue. An **AI Decision** node puts one bounded question to a model and sends the run down a branch per outcome. A workflow hands an AI Decision node a question and the information to evaluate; the node answers as yes or no, one of your named outcomes, or a score against a rubric; the run follows the branch for that outcome, the Uncertain branch when the model was not sure enough, or the Error branch when the decision could not be made. * **Three shapes of answer.** **Yes / no**, with the thresholds yours to set. **Choose an outcome**, from 2 to 32 outcomes you name and describe. **Score against a rubric** of up to 20 levels, split at a threshold into at-or-above and below. Each outcome is its own branch, so every path a run can take is drawn in the graph rather than implied by a prompt. * **It says when it isn't sure.** Every decision also has an **Uncertain** branch, for an answer under your minimum confidence or between the yes and no thresholds, and an **Error** branch for a decision that couldn't be made. Uncertain is the branch a person looks at; connect Error, because a failed evaluation with nothing on that branch fails the run. * **It reads only what you hand it.** You write the question and choose the information to evaluate, with variables from the trigger, the entity, and upstream steps. Filters now publish their results, so an earlier condition can be part of the evidence. Nothing else is sent. It runs on asynchronous workflows only, so it can't hold up an order that's being created, and a dry-run test follows Uncertain without calling the model. In the API it's the `ai_decision` action, with `decision` edges that carry a `branchKey`. See [Branch with an AI Decision](/reference/workflows#branch-with-an-ai-decision). #### New & improved **Orchestration** * Reschedule an order from a workflow. **Reschedule order** delays an order that hasn't been dispatched by a duration you set — 12-, 24-, and 48-hour presets, or anything from a minute to 365 days — or moves it to a calendar date at the same local time. Every pickup and dropoff window moves by the same amount, so their lengths and the gap between them hold. An order on a route, or with a booked delivery window, is refused with the reason. See [Reschedule an order](/reference/workflows#reschedule-an-order). * Order-creation workflows can write more of the order. **Update order instructions** is now **Update order text** and covers the **Order description** as well as the pickup and dropoff instructions, and a new **Update order metadata** action adds, updates, or removes one metadata key and leaves the rest of the order's metadata alone. *(For organizations with order-creation workflows turned on.)* See [Change one metadata key](/reference/workflows#change-one-metadata-key). * A workflow on Task Attempts Exhausted can flag the failed task itself when dispatch never produced a delivery. The action is now **Flag task or delivery**, and it flags the final attempt rather than an earlier one. * The workflow builder shows more of what a workflow is doing. An **Editor** / **Runs** toggle carries the latest run's status, a strip above the graph counts **Runs today**, **Success rate**, **Errored**, and **Avg run time**, and the workflows list has a **Draft** tab. A Custom Event Listener trigger takes an **Event context** — order, delivery, task, or job — that decides which conditions and actions follow it. * Writing a workflow takes fewer lookups: typing `#` in an action's text field opens a searchable picker grouped by entity, with upstream steps in a group of their own, and conditions on money fields such as package value and tip take dollars rather than cents. **Portal** * Select work on the Orders map. Click a pin to select every order at it, Shift+click to look without selecting, or **Select all** to take everything behind the current filters. **Tools › Select area** adds a **Lasso** and a **Polygon** for drawing around the orders you want, and the bulk action bar acts on exactly what the map shows selected. * Put a whole selection on a route, or move stops between routes, without leaving the map. **Add to route** assigns every selected unassigned order in one step. Select route stops, by click or by area, and **Move to route** takes them to another route — refusing a move that would empty the route they came from. *(For organizations with Routes turned on.)* See [Plan routes on the Orders map](/reference/routes#plan-routes-on-the-orders-map). * Route cards on the map carry **Edit** and **Archive** alongside **Dispatch** and **Resequence**, and a route's provider and driver can be set from the card before dispatch, with a column each in the Routes table. Shift+click cards or route lines to hold several routes in focus, and hover a line for its driver, vehicle, planned time, distance, and stop count. Route colors come from a palette of 48 assigned across what's on screen, so two routes in view no longer share one. * Every Portal map has a **Layers** control with a **Satellite** view, imagery with street names. Each screen's own overlays — routes, unassigned orders, customers, order labels, live traffic — sit in the same popover. * **Display fields** chooses which order details, metadata, and tags appear on stop cards, with up to two printed on the map itself, set by an owner or admin for the whole organization. The labels show on route stops whether or not a route is focused. * Keep internal notes on an order. Add them from the Orders drawer or the **Notes** tab of the Edit Order drawer, up to 2,000 characters each. They stay with the order through dispatch and show on the delivery with an **Order note** badge. They're internal to your organization: they aren't sent to providers or recipients, and the API doesn't return them. * Archiving an order asks why. Pick a reason, or choose **Other** and type your own, and the Archived tab shows it in an **Archive Reason** column. Archiving several orders at once from the action bar doesn't ask. * A package value over 1,000 asks you to confirm before a job or an order is created, and one over 100,000 is refused in the create and edit forms and in CSV uploads, so an order number typed into the value field is caught there rather than on an invoice. * Organization Management appears in the Add role and Edit role drawers, so a custom role can carry it. A new custom role starts with its read side ticked, because signing in to the Portal needs it. See [Roles and access](/reference/roles-and-access#the-default-roles). * Orders and Routes open on **Today**, as Execute does, rather than on this month. A link or a saved view with its own dates still wins. * The Edit Order drawer keeps the package identifier under the order ID in its header, so it stays in view on every tab. * **Fleet › Drivers** has a map view: every driver in the current search and filters at their last reported location, colored by status and refreshed every 30 seconds. The search there also matches a driver's external ID, in full or in part. *(For organizations with Fleet turned on.)* **Nash Agent** * A custom agent's page has a **Reports** tab that opens on the newest file its recent runs produced. Step between runs with the arrows, pick among a run's reports, and open **Artifacts** to search, sort, and filter the files from all of those runs in a panel beside the page. See [Review reports and files](/nash-agent/custom-agents#review-reports-and-files). * TSV files and XLSX workbooks preview as tables, as CSV already did, with a **Sheet** picker for a workbook that has more than one. A preview shows up to 500 rows and says when it has cut a file short. * A Daily Brief finding can be followed through. Each one shows its **Latest observation** and the result of a read-only check Nash runs on its own, and **Refresh evidence** runs another. **I'll investigate** puts your name on it, with a next action, a due date, and an outcome — and recording an outcome doesn't close a finding the agents are still seeing. **Investigations** keeps claimed work reachable under **My work**, **Unclaimed**, and **Team work** after the insight has left the brief. See [Follow an insight through](/nash-agent/daily-brief#follow-an-insight-through). * In a live chat, a follow-up correction edits the script Nash already wrote instead of starting over, so a revised file comes back sooner. **Platform API** * Collect cash on delivery. Set `dueAmountCents` on an order to the amount the driver must collect from the recipient: equal to `valueCents` for full cash on delivery, less for a part-paid order, `0` or absent for a prepaid one. It can't exceed `valueCents`. Dash honors it today; other providers treat the order as prepaid. The amount comes back on order responses and in the package block of job responses and webhooks. See [Collecting cash on delivery](/reference/delivery-fees-and-pricing#collecting-cash-on-delivery). **Fleet** * Stop details in the Driver App show the business name and the point of contact on separate rows, so a driver sees both the destination and the person to meet. A stop with only one of them still shows one row. * A stop action from the Driver App — arriving, completing — carries the driver's position at that moment, and the delivery's `courierLocation` takes it, so the last known location is where the driver actually did the work. If the phone has no fix, the action goes through without one. **Integrations** * DoorDash reports the Dasher's ID, and a delivery's **Driver Details** shows it as **Driver ID**. #### Fixes **Orchestration** * A **Send HTTP request** body that references a list-like field such as `{{order.tags}}` goes out, with the values as a sorted JSON array. It used to fail while the request was being built, before anything was sent. * Conditions on an order's pickup and dropoff window times read the window Nash calculated for the order, so a relative-time condition such as "pickup starts within the next two hours" evaluates against the real window. * A workflow on the Task Reassignment trigger can branch on order fields. The builder takes each trigger's available fields from the server rather than a list of its own, which had left order fields out. **Platform API** * Removing an order from dispatch keeps its external ID and reference ID, so the order can still be found, and dispatched again, by external ID. * After a large job is split, the original job reports its own items rather than the full list from before the split. * An order created from address components returns an `address` on its pickup and dropoff, not an empty field. * Updating an order without touching an address leaves that address as it was. A saved pickup pin no longer shifts when only the instructions change, so arrival at pickup keeps registering. **Portal** * Large Orders maps stay responsive while you pan, zoom, scroll the list, and focus routes. Markers and pins are reused rather than rebuilt, route lines update without a full redraw, and list rows no longer react to the camera. * Selected orders draw on the map again: each shows its pickup pin and the road path to its dropoff, and opening the **Unassigned** tab turns the order layer on. * Driver call activity appears in order and route timelines, including calls recorded without a provider task. * Webhook entries in delivery and route timelines read **Webhook sent** and name the trigger that fired, instead of listing the webhook's configuration as a recipient. * Quote results label a provider Nash couldn't quote as **Unavailable** rather than **Failed**, with the reason in the tooltip, so a provider your dispatch strategy excluded no longer reads as an outage. * Review & Dispatch weight totals follow your unit preference, as the route table does. * Sorting Pickup Locations by name sorts across every page, and the sort controls that did nothing are gone. * The pickup time on a route-based delivery shows the route plan's first pickup stop, falling back to the requested window when the route has no plan. * A notification contact group takes the same channel type more than once, so a group can hold several email destinations. **Nash Agent** * A run that stops early, or can't save every file it generated, is reported as incomplete rather than successful, and the output and files it did produce are kept. * A long run that pauses partway continues instead of stopping before its files are written. * Spreadsheet, CSV, and JSON files you upload to a chat reach the agent as files it can open rather than as pasted text, and a session holds up to 15. * Switching organizations from a Nash Agent page opens the new organization's agent area instead of asking you to switch back. **Fleet** * The Driver App no longer closes on Android while the skip-stop drawer is opening. The fix is in version 1.12 of the app, which is a new build rather than an in-place update. * Starting a route moves to the first stop that still needs a visit, so a pickup whose orders were all canceled doesn't leave the route stuck on a stop nobody can complete. * Sending an arrival for a stop the driver has already arrived at succeeds and changes nothing, rather than failing. Completing a stop that's already complete behaves the same way. **Integrations** * Tookan driver-location pushes update the courier's live position, name, phone, and vehicle on the delivery. They carry no job status, and were being set aside for that reason. * Gophr receives a package's real weight and dimensions when the package holds several items, instead of having them multiplied by the item count. * Editing a Team Global Express delivery before it's manifested succeeds, and editing one after returns a 422 that says to cancel and re-create it, rather than a server error. * A delivery that has been picked up no longer moves back to an assigned-driver status when a provider re-reports an earlier state. Cancellations and completions still apply. ### Week of September 7, 2026 Action required before your next driver upsert: include `phoneNumber` in every `PUT /v1/fleet/drivers/upsert` request. Send an international number with a leading country code. #### Highlight **Workflows that run on routes.** A workflow could react to an order, a delivery, or an attempt, but not to the route they ride on — the thing a dispatcher actually watches. A route that leaves late, stalls halfway, or gets canceled was something a person noticed on the Execute page, or didn't. Now a route is a workflow context of its own, with triggers for each moment in its life and fields that describe where it stands. A route is created, started, has a stop completed, is completed, or is canceled; a workflow on one of those route triggers filters on the route's own fields and raises a route flag, sends a message, or calls your system; the result is work on the Execute page or a message naming the driver and the route. * **Five triggers, one per lifecycle moment.** A **Routes** category in the builder offers **Route Created**, **Route Started**, **Route Stop Completed**, **Route Completed**, and **Route Canceled** — `route.created`, `route.started`, `route.stop_completed`, `route.completed`, and `route.canceled` in the API. * **Filter on the route's own state.** Route Status, Provider, Driver, Stop Sequence (the next stop still needing a visit), Stops Completed, Total Stops, Planned Departure, Actual Departure, and Planned Completion are all conditions, with the time fields read in your organization's time zone. "A route whose actual departure is twenty minutes past its planned one" is a filter, not a report someone runs later. * **Act on the route, not just about it.** The route-flag actions are the natural pair: raise a flag so the late departure is work on the Execute page with an owner, rather than a message someone has to catch. Notifications and **Send HTTP request** work here too, and messages can carry `{{route.*}}` variables — the driver, the status, the stop counts. See [React to a route](/reference/workflows#react-to-a-route). #### New & improved **Orchestration** * Fill in instructions as an order is created. The **Update order instructions** action on the **Order being created** trigger writes pickup or drop-off instructions before dispatch: add text before what's there (the default), after it, or replace it, and optionally only when the field is missing — blank, `N/A`, or an address-only line all count as missing. Two nodes cover both stops, and the workflow's filters pick the store or delivery model. Text over 280 characters fails the step visibly rather than being cut. *(For organizations with order-creation workflows turned on.)* See [Fill in instructions as an order is created](/reference/workflows#fill-in-instructions-as-an-order-is-created). **Portal** * One map on the Orders page for moving work between routes. **Add to route** and **Move to route** open an editor with the map beside it: search a destination by route, driver, or vehicle, choose from **Nearby** (within 10 km) or all routes, and preview the result before you apply it. Route cards carry **Dispatch**, **Resequence**, **Calculate timing**, **Split**, and copy actions; stops resequence by drag or keyboard; and **Resequence** and **Calculate timing** show stop positions and arrival times before and after. Select an unassigned order to see its pickup-to-dropoff path. * See your customers on the map. A **Customers** layer on the Orders, Routes, and Execute maps — off until you switch it on — plots each customer at their latest delivery address, clustered apart from route stops and refreshed as you pan, up to 500 in view. Filter it by name or contact details, and select a pin for the customer's details. * Highlight orders on the optimization review by any of your configured review fields. **Highlight** sits beside **Filters**: matching stops get a halo on the map and matching rows a tint in the list without hiding anything else, and the unassigned list in map view puts matches first. * Hover a route's pickup counter to see picked and total per dropoff stop, in route order, with a warning on any stop still short — so a driver held up by a missed scan can be traced to the stop. Click to pin it open. * Manifest a single shipping delivery from its job page. A **Manifest** action sits with the other job actions, so you no longer go back to a list to find the delivery you were looking at. *(For organizations with shipping turned on.)* * **Reverse order** in the Execute resequence dialog flips the pending dropoffs in place; depots, pickups, breaks, and completed stops keep their slots, and **Apply new sequence** recomputes the stop ETAs. * The delivery panel's copy menu lists **Pickup barcodes** and **Dropoff barcodes** when a delivery has them. **Nash Agent** * A scheduled agent can run monthly. The schedule trigger's **Monthly** cadence takes a day of the month and a time; a day a month doesn't have is skipped rather than moved to the month's last day. See [Set up a schedule](/nash-agent/scheduling-and-execution#set-up-a-schedule). * Insight cards on the Daily Brief show the evidence summary and, when an agent suggested one, the next check, so you can triage from the list without opening each insight. * Each file an agent produces carries a file-type badge on its thumbnail, so a long name that hides its extension still says what it is. * The Nash MCP server accepts twice the request rate it did — 40 requests per second, with bursts up to 100 — so a scheduled agent's burst of tool calls is no longer turned away. **Platform API** * Barcodes and other proofs are counted separately on a Fleet API update. A delivery holds up to 50 photos and signatures and, apart from those, up to 200 barcode records across pickup, dropoff, and return, so an order with many barcodes no longer crowds out its photos. See [Lifecycle & validation](/fleet/report-delivery-state#lifecycle--validation). * `courier_location.updated` webhooks can be sent as driver positions arrive — at most one every 20 seconds per delivery — instead of on the tracker's cycle of every minute or two. Expect several times the volume. *(For organizations with immediate location webhooks turned on.)* See [Courier location events](/reference/webhooks#courier-location-events). **Integrations** * Added Dash as a delivery option in Saudi Arabia. Orders, status and courier updates, and cancellations go through Dash's API, and pricing comes from your Nash contract. #### Fixes **Dispatch** * Auto-dispatch after an optimization runs when the solver returns a result it has returned before; the orders no longer sit undispatched. * A strategy's earliest-dispatch lead time holds multi-order routes as well as single-order ones, so a strategy set to keep re-optimizing until a set number of minutes before pickup does so instead of dispatching the first result and stopping. The strategy's schedule has to run more often than that lead window is long, or a held route waits for a run that never comes. * Deactivating a selected optimization strategy no longer traps the sidebar on one tab, and a tab you chose by hand survives searches and refreshes. * The Activities page's **Items** column shows a dash for a route re-optimization instead of counting the route as one order. * The optimization review footer counts every numbered stop, pickups included, so it matches the stop badges and the map. * A route restriction with a start and end date saves, and the dates load back into the form when you reopen it. * The dispatch strategy picker in the Optimize flow no longer offers **Select All**. **Portal** * Request logs open promptly for requests with large bodies. The body loads for the request you select, with a message when it can't be fetched. * Notification timelines show only messages sent to people; an agent's internal start and stop signals no longer appear as empty email entries. * New zone and route-restriction editors open at your organization's saved map center, and a center stored as text no longer breaks the map. * Long exception forms on Execute scroll, so **Notes** and the actions at the bottom are reachable at normal zoom, and stop counts and end ETAs in the Execute table stay on one line. * The "stops before you" count on a job's page follows the route's dropoff order, so a driver's last stop no longer reads as the first. *(For organizations with provider batch info turned on.)* **Nash Agent** * Files you upload to a chat stay attached to their message, so the chips are still there after a reload. * A confirmation card appears once, on the message that raised it, after a reload or replay, and a resolved action no longer reopens. * An email or Slack message an agent sends with a large file carries a download link to the file, instead of going out without it. **Fleet** * Drivers created through `PUT /v1/fleet/drivers/upsert` appear in the Portal's internal-driver list and can sign in to the Driver App by SMS. An optional `email` also provisions email sign-in, without sending a welcome message. See [Fleet overview](/fleet/overview#manage-your-drivers). * A parking bay a driver sends stays on the stop; a status refresh no longer clears it while the driver is scanning. * A route whose first stop is a depot start can be started from the driver app: the start marker completes and the pickup becomes the active stop. * After **Complete Loading** on a pickup whose picks had already synced, the route refreshes, so a delivered item shows as delivered and its stop can be completed without restarting the app. * Pickup details list every order's ID, using the external ID for an order with no location or reference ID. * With driver cancellation turned off for your organization, the route flow hides **Cancel Delivery** as the delivery list already did. **Integrations** * A Microlise route dispatches when a task has no time window; the stop's planned arrival and departure stand in for it rather than failing the whole route. * Cancellations sent to AlShrouq complete, and an order AlShrouq reports as already canceled is closed on the Nash side instead of being retried. * Shopify tag sync and fulfillment-quantity sync run for an order that was first created through the API and then matched by the Shopify app. * A delivery already in transit no longer drops back to Manifested when the carrier's stop status lags its carton scans; tracking pages and status webhooks keep the in-transit state. * DeliverEasy quotes succeed for dropoff addresses without a business name. ### Week of August 31, 2026 #### Highlight **Validate orders with your own rules.** Nash checks every order it takes in — an address that won't geocode, a pickup time already in the past — and parks the ones that fail as needing attention, with the reasons on the order. But the rules that catch the most expensive mistakes are usually yours, not ours. An unattended delivery with no dropoff instructions is a delivery that fails at the door. A reference ID in the wrong shape is a delivery your warehouse can't match. Until now those checks lived in your ordering system, or in a person reading the queue after the order was already in. An order arrives or changes; an Order validation workflow filters on the order's own fields and marks it invalid with a key and message of your choosing; the order then carries that error in validationErrors and needs attention until it's fixed. A workflow on the new **Order validation** trigger adds your rules to the same pass. * **It runs inside validation, not after it.** The trigger fires synchronously on creation, on every update, and on CSV upload, before any order-created workflow runs — so a rule can't depend on a field one of those fills in, and an order never sits in a valid state it shouldn't be in. * **Mark order invalid says what's wrong and where.** The action takes a field and a message. Name one of the order's own fields, such as `dropoffInstructions`, and the Portal's order form highlights it; the error lands in `validationErrors` beside Nash's own, the order's status becomes `needs_attention`, and the key clears on the next update that satisfies the rule. Messages can carry the order's own values. * **Formats are a filter.** Two operators arrive with it, **Matches pattern** and **Does not match pattern**, which test a field against a regular expression. Reference IDs, barcodes, and anything else with a shape can be checked without a line of code on your side. A pattern is compiled when you save, so a broken one is refused there rather than at run time. * **It fails closed.** A rule that can't run, or a condition that can't be evaluated, marks the order with a reserved `workflowValidation` error naming the workflow rather than letting the order through. Every run, including one that marked nothing, shows in the workflow's Runs tab. See [Validate orders with your own rules](/reference/workflows#validate-orders-with-your-own-rules). #### New & improved **Orchestration** * Branch on whether something has *happened* to an order, not only on what its fields hold. Four filter-only fields — `order.custom_event`, `job.custom_event`, `task.custom_event`, and `delivery.custom_event` — take **Has occurred** or **Has not occurred** and the key of a custom event from your catalog, so a pickup-window workflow can continue only when the store hasn't sent `store_ready`. The check is taken when the trigger fires and stored with the run, so a late worker or a retry can't flip the branch. See [Branching on whether an event has happened](/reference/workflows#branching-on-whether-an-event-has-happened). * One status trigger, several statuses. A Delivery Status Transition trigger names any number of statuses in `deliveryStatuses` and fires on each on its own, in both Notifications and the workflow builder. A trigger you already have with a single status is untouched. See [Notifications](/reference/notifications). * Order fields are available on delivery, job, and delivery-attempt triggers, so a delivery-status workflow can branch on the order's value or tags and put `{{order.*}}` in what it sends. A condition on an order field in one of those contexts used to resolve to nothing; if you built one that never took the branch you expected, it will now, so it's worth a look at what it does. See [Fields worth branching on](/reference/workflows#fields-worth-branching-on). * Filter on who won and what it cost. `delivery.provider_name` and `delivery.provider_delivery_id` name the provider fulfilling a delivery and the ID it gave it; `task.provider_name` does the same for the attempt; `delivery.delivery_fee_cents` is the base fee the provider charged, before tolls and wait time. * A **Send email** action takes several addresses, and each is checked against the deny list and delivered on its own, so one refused address doesn't take the rest with it. **Fill addresses from destination** in the Portal copies a notification destination's current addresses into the field, once. See [Writing text with variables](/reference/workflows#writing-text-with-variables). * The editor refuses a variable that can't resolve at the node you're on when you save, and suggests the nearest match — so a typo surfaces before the workflow runs rather than as a blank in a message. The field picker offers only fields whose entity can exist for the trigger you've chosen, and marks the ones that may be empty for it. **Portal** * A skipped stop shows as skipped. When a driver skips a stop and comes back to it, **Skipped** and **Resumed** events appear on the route's detail panel, the order drawer, and the delivery timeline, with the driver's reason when they gave one. Before, the Portal showed none of it — and a skip note could land in the stop's failure slot on a stop that hadn't failed. * The route timeline is drawn as drive, service, waiting, and break segments in proportion to the time each takes, with the shift's end marked and any overrun called out, on both the Optimize review and Execute. Select a dropoff on it to open the route panel at that stop. See [Watching routes on the Execute page](/reference/routes#watching-routes-on-the-execute-page). * Open a delivery's request logs from its page. **Request Logs** sits beside Events on the job page and shows every request scoped to that delivery's job and order IDs, with an **API / Provider** toggle in place of a checkbox hidden behind a gear, and a **Source** row on each request so a shared link identifies itself. Provider traffic appears when provider logs are enabled for your organization. * Hover the provider in a delivery's details to see the contract it won under, for external providers as well as your own fleet. * Delete a proof-of-delivery photo with a permission of its own. `delete.proof-of-delivery` is held by Owner and Admin and separate from editing the delivery; the confirmation says what will happen, the photo leaves Nash immediately, and storage cleanup runs in the background. See [Roles and access](/reference/roles-and-access#restricting-configuration-and-credentials). * The Driver Groups page gains a **Provider** column and filter for organizations running more than one internal provider, and creating a second driver group asks for its provider rather than accepting none. **Tracking page** * Give customers somewhere to go. Set a **Support URL** under **Customization › Tracking** and the tracking page shows a **Need Help?** button that opens it with the order's ID appended, so your support desk knows which delivery the customer is looking at. The link must be `https`. See [A support link on the page](/reference/nash-embedding#a-support-link-on-the-page). * The tracking preview is organized by journey: **Before delivery**, **During delivery**, and **After delivery**, each with its own settings, so you can check what a customer sees at each phase. The pre-dispatch switch now reads **Customers can open the link before a driver is assigned**. * Choose the scale customers rate a delivery on — thumbs up or down, one to five stars, or a 0–10 NPS question — under the **After delivery** tab, and the tracking page links to the feedback form once the delivery is done. Every scale reaches the feedback API as a 0–5 score, so NPS answers can arrive in half steps. *(For organizations with delivery feedback turned on.)* See [Add Delivery Feedback](/api-reference/feedback/add-delivery-feedback). **Nash Agent** * Start the day from the Daily Brief. It now leads with one **Agent insights** list — the patterns Nash curated from what your agents recorded, up to twenty at a time, unread first — with the confirmations waiting on you kept apart under **Awaiting your decision**. Each insight carries **Verify in Nash** links to the agent session and message behind every cited observation, so you can check the source before you act, and **Ask Nash** opens a chat with the insight attached as evidence. See [Daily Brief](/nash-agent/daily-brief). * The brief is yours. An insight you've read comes back as **Updated** when new evidence lands, **Dismiss** clears it from your own brief and nobody else's, and the sidebar entry counts what you haven't read. A finding that is really about Nash itself, or about an agent's configuration, is routed to Nash's team rather than to your brief. * Each custom agent's page shows its **Fresh signals**: what it recorded in the last 24 hours and the organization-wide insights it contributed to. * An agent can act on the organizations beneath yours. *(Opt-in.)* An owner or admin of a parent organization turns on **Let agents act on child organizations** under Nash Agent's General settings, and each agent opts in with its own **Acts on child organizations** switch; every call the agent makes then names the organization it's for, and it switches to the child that owns each job. Turning the organization switch off revokes the shared credential immediately. See [Scope](/nash-agent/custom-agent-configuration#scope). * See which routes an agent is watching from Execute. An **Agent watch** column names the agent, whether its watch is live or ended, when it last looked, and when it checks next, and two **Agent coverage** filters narrow the queue to routes an agent has assessed — or hasn't. See [Seeing coverage in Execute](/nash-agent/entity-monitor#seeing-coverage-in-execute). * Files an agent produces render as thumbnails in the chat — an image preview, the first page of a PDF, the head of a CSV — grouped into a strip when a message carries several. * Chatting with a custom agent now uses the same analytics toolkits its scheduled runs do, so a question that comes back as hundreds of rows arrives as a table or file rather than being retyped into the reply. * A run has an hour to finish, up from thirty minutes, so an agent working through a few hundred records isn't stopped mid-write. * The Nash MCP server's most common reads are first-class tools instead of code an agent has to write each time. `find_order` resolves whatever identifier you have in one call; `list_orders`, `list_jobs`, and the bulk gets take typed filters and a `fields` list, and workflows, dispatch strategies, contracts, and providers are readable too. See [What the server exposes](/reference/mcp-integration#what-the-server-exposes). **Fleet** * The driver app is available in Dutch and German, from the device language or the in-app picker. * Loading a pickup no longer waits on the network. Every tap in the package list queues and renders at once, picks sync in the background in batches, and everything else is sent when the driver taps **Complete Loading** — with a plain message if the sync fails, and the driver's work kept. * One push per route change. When a dispatched route is edited or split, the driver gets a single **Route updated** or **Route split** notification, instead of one *Delivery canceled* push for every stop that moved. * An action a driver took offline carries the time they took it, so stop and item timestamps reflect when it happened rather than when the phone reconnected. * Provider organizations choose who receives their payment statements, under **Settings › Provider Profile › Payment statement recipients**, with the Stripe Connect account email shown alongside for reference. **Platform API** * A dispatch-and-manifest batch that a carrier rejects as a whole is resubmitted one shipment at a time, so the single shipment it won't take fails alone. Each failure is its own entry in `errors[]` with the carrier's own message, `manifests[].delivery_ids` lists only what was accepted, and a failed order stays dispatched so a later call retries it. The single-purpose manifest call keeps its all-or-nothing behavior. See [Dispatch and Manifest Orders](/api-reference/shipping/dispatch-and-manifest-orders). * A rejected proof-of-delivery artifact on a Fleet API update — a `file://` reference, or one past the 50-per-delivery cap — fails the update rather than being dropped quietly. See [Lifecycle & validation](/fleet/report-delivery-state#lifecycle--validation). **Integrations** * Gophr receives one parcel per item, each with its own description, weight, dimensions, and quantity, when every item in a package carries dimensions. * Tookan job-status webhooks are processed as they arrive, so status, driver location, courier details, the tracking link, and proof-of-delivery photos land when Tookan sends them rather than on the next poll. #### Fixes **Orchestration** * A **Send HTTP request** whose template has a variable with no value still goes out. The variable hydrates to empty text, each miss is recorded on the run's step, and when a call does fail the step says at which stage — building the request, connecting, authenticating, transport, the HTTP status, or reading the response. See [Call your own system](/reference/workflows#call-your-own-system). * Messages a workflow sends appear on the delivery's Notifications timeline, typed **Workflow** with the workflow's name, so "did we tell them?" has an answer for workflow-sent SMS, email, Slack, and Teams as it already did for triggers. * A workflow built in an older editor validates and saves. Its filter's single unlabeled branch is read as the Yes branch and stored that way — the branch the runtime was already taking. * Editing a notification destination while a search filter is active adds and removes the row you meant, and removing a draft row no longer wipes a neighbor's unsaved edits. **Dispatch** * Archiving an order that sits on a route takes it off the route first. If the route refuses, the order stays valid rather than being archived while the route's stops still name it. * Orders the optimizer can't fit on a shift-based run dispatch through the strategy's unassigned-orders fallback instead of being left behind; the activity stays in progress until that dispatch completes. * The optimization overview map opens for a run that includes break stops. * A strategy link with an unknown ID reports not found instead of opening a different strategy's configuration under that URL. * Opening a past optimization run shows its setup and selected strategy rather than a blank Setup step, and a strategy that names contracts belonging to providers not currently enabled keeps them, labeled as outside the eligible roster, instead of showing zero providers and dropping them on the next edit. The Strategy step links to **Manage strategies**. * A completed activity replay closes with a single **Close** action. **Platform** * Location history keeps every ping on a delivery that runs past a day. A throttle was reading only the sub-day part of the interval, so a long-running delivery dropped a courier location every subsequent day. * A complete-stop the driver app sends twice is accepted as already done, so an offline queue draining after the route finished no longer errors. * Filing a refund is idempotent per task: a retry of `POST /v1/request-refund` returns the original `refundRequestId` instead of opening a second request. See [Refund requests](/reference/refunds-and-incidents#refund-requests). * Photo proof-of-delivery checks count a shipping label affixed to a parcel as evidence of the delivered item, and one valid photo in a multi-photo set is no longer outvoted by a context photo. *(For organizations with photo verification turned on.)* **Portal** * A driver whose vehicle color was saved with a trailing space can be re-enabled; the invisible character was failing validation on the Vehicle tab with no explanation. * The order CSV upload accepts the plural `pickupStoreLocationIds` column, which the API already did, instead of reporting a missing pickup location on every row that used it. * Merchant attribution on NashLink orders shows across the Deliveries, Orders, Shipping, and Execute views and the order drawer's Merchant tab. * The tracking page reads **Delivery attempt failed** for a failed delivery instead of *Delivery canceled*, and names in-transit, ready-for-pickup, partially delivered, being-returned, and expired states instead of falling back to *Tracking your order*. **Fleet** * With item marking skipped, the package list opens read-only, so a driver can still read what's on the shelf; before, it wouldn't open at all. * A loading change the server refused no longer blocks **Complete Loading** for the rest of the route. The list refreshes with a message saying what didn't save, and the driver taps again. * Opening a pickup's package list no longer holds up later dropoff actions on the route, and **Mark as Picked Up** commits queued work the same way **Complete Loading** does. **Integrations** * Team Global Express receives delivery instructions up to 80 characters intact, and parentheses in them no longer reject the manifest. * CBD carton scans register as in transit, so a delivery shows the on-road scan on tracking pages and in status webhooks instead of jumping from manifested to delivered. * Uber proof-of-delivery photos are captured from Uber's delivery API as well as from its webhooks. * Roadie delivery updates carry a five-digit ZIP, so an address stored with a ZIP+4 no longer has its update refused. **Nash Agent** * A chat that exceeds the model's PDF page limit says so, with the limit and what to do about it, instead of failing with a provider error. * An agent with too many strict tools enabled gets a clear error naming the cause instead of a silent retry loop. * Entity Monitor watches complete every scheduled check. * A code-mode step that pages through a long list finishes instead of timing out at 30 seconds and losing what it had computed. * A long chat turn is no longer ended early as abandoned while it's still working. * "Yesterday" and "last 7 days" resolve against one clock for the whole run, including across a daylight-saving change. * A refund the agent files through a delivery incident follows your refund confirmation policy, and confirmations left pending by a run that timed out are retired rather than left waiting for an answer. * A Nash MCP server instance that couldn't load the API catalog at startup is taken out of rotation and retries, instead of answering every call with an unknown-tool error. ### Week of August 24, 2026 #### Highlight **A workflow can call your own system.** Workflows decide and then act — apply a dispatch strategy, tag the order, notify the team, hand a step to an agent. Every one of those actions lands inside Nash. But the next step in an operation usually isn't: mark the order picked in the warehouse system, open a ticket, tell the ERP the delivery failed. That handoff has been yours to build — a webhook subscriber, a queue, a service to keep running — even when the decision that triggered it was already made in Nash. A workflow's Send HTTP request node references a connection an administrator owns; the connection calls your system, and the value extracted from the response becomes the field the next filter branches on. The **Send HTTP request** action makes the call from inside the workflow. It comes in two halves on purpose. * **An administrator owns the connection.** A connection holds the HTTPS origin, the credentials, which methods are allowed, the path prefix requests have to sit under, and the names of the request fields a workflow may set. Credentials are write-only: they never appear in a workflow's definition, and they're kept out of request logs and error reporting. So the person wiring up a workflow doesn't need — and never sees — the secret. * **A workflow author shapes the request.** On the node you set a relative path, query parameters, headers, a JSON body, a timeout, and what the workflow does when the call fails. Templates work here like anywhere else, so the body can carry the order's own values. * **The branch after it reads the answer.** Pull a value out of the response with a JSON Pointer and downstream filters see it alongside the HTTP status, how long the call took, and any failure detail — so the workflow can act on what your system actually said, not just on whether it answered. * **Background and best-effort, deliberately.** One attempt, carrying a stable idempotency key so your endpoint can recognize a repeat. It's a signal your system can act on, not a durable queue, and it can't hold up a synchronous run. See [Call your own system](/reference/workflows#call-your-own-system). #### New & improved **Orchestration** * Four triggers that fire against an order's own windows: `order_pickup_start_time`, `order_pickup_end_time`, `order_dropoff_start_time`, and `order_dropoff_end_time`, each with a direction and an offset in minutes — "30 minutes before the dropoff window ends". These are the triggers for the order that *hasn't* moved: a pickup due shortly with no provider on it, a dropoff window closing on a store that hasn't packed. Move the window and Nash re-arms against the new time. See [Fire against a pickup or dropoff window](/reference/workflows#fire-against-a-pickup-or-dropoff-window). * One vocabulary for every message a workflow sends. Email, SMS, Slack, Teams, and voice-agent actions now read the same template catalog the notification triggers use — customer contact and address, pickup and dropoff ETAs, instructions, proof-of-delivery and tracking links, the assigned provider's Slack destination, the customer feedback link. The catalog hydrates the destination and the subject as well as the body, so a Teams channel or an SMS recipient can be a variable instead of a hard-coded address, and the editor suggests only variables that could plausibly address the channel you're filling in. See [Writing text with variables](/reference/workflows#writing-text-with-variables). * Conditions on dates and times work as dates and times. Datetime fields carry their own operators, including relative windows and comparing one datetime field against another, and evaluation is time zone aware — conditions that had been quietly evaluating false now resolve. A dispatch strategy is a field you can branch on, and a variable with no value hydrates to empty text rather than the literal word `None`. See [Comparing times](/reference/workflows#comparing-times). * Point a workflow at a custom event definition instead of typing its name. `customEventDefinitionId` is accepted on a workflow trigger, on the `emit_custom_event` action, and on `POST /v1/order/{id}/events` and its job equivalent — Nash stamps the definition's key as the event name, so the catalog entry and the string the runtime matches on can't drift apart. A definition something is still listening for can't be archived, whether the listener is a workflow or a plain notification. See [Match an event from your catalog](/reference/workflows#match-an-event-from-your-catalog). **Dispatch** * Hold a delivery's price at the fee you first accepted. A dispatch strategy can cap every later quote for the same work at the fee selected on the first assignment, across single deliveries, batches, and routes. A reassignment can come in cheaper and the ceiling stays where it was, so a delivery reassigned twice can't ratchet its way up; a refused quote is recorded with its reason rather than disappearing. *(Opt-in, per dispatch strategy.)* See [Cost controls](/reference/dispatch-strategies#cost-controls). **Fleet** * Keep your driver roster in step from your own system. `PUT /v1/fleet/drivers/upsert` creates or updates a driver keyed on your own `externalIdentifier` — no separate create and update to choose between, and a field you leave out keeps the value it had. The same call links vehicles, store locations, and driver groups, and provisions a driver-app login when you send an email address. Take a driver off the road by upserting `isShiftActive: false`. See [Upsert Driver](/api-reference/fleet/upsert-driver). * Drivers on routes see their pay. The delivery card in the driver app showed stops, distance, and duration but no pay for drivers on routes, which batch drivers had all along. The same figure is now on the REST job response as `bonusPayCents`, where webhooks already carried it. * A driver's package list says which stop it belongs to. The routes package list carries a `Stop 4 — ` header, so a driver scanning a shelf knows whose order they're holding. * Set the Start Route gate from the Portal. The number of minutes before the earliest pickup a driver can start a route is now editable under **My Fleet** and per driver group, rather than needing someone at Nash to set it. Leave it off and there's no gate. * Set geofence auto-arrival distances per driver group. The pickup and dropoff arrival radiuses now sit under **Fleet › Driver Group › Features › Geofence** in plain meters, instead of arriving through a CSV an engineer had to upload. Leave one blank and it stays unconfigured. * Maps stop working when they aren't on screen. The route map kept rendering and tracking location behind stop details, the item list, the scan view, and navigation — two maps alive at once in the worst case. Drivers reported hot phones and battery drain; the map now freezes when it isn't visible. **Platform API** * `GET /v1/routes` is in the API reference. List and search routes by date, status, driver, or free text, with pagination and a payload slim enough for a list view. See [List Routes](/api-reference/route/list-routes). **Integrations** * Frayt requests are paced to the 60 requests per second Frayt allows, and a throttled response is handled as throttling rather than logged as a failure. * DoorDash receives the weighted price you set for it. A per-provider `valueCentsPerMeasurementUnit` on an inventory item's provider configuration was being read from the base item instead, so DoorDash quoted the base rate for weighted goods. **Portal** * Driver calls appear on the delivery timeline. A call a driver places from a stop — to the customer, the pickup, or support — is recorded as an event and shows on the delivery timeline, so a support conversation about "did anyone try to reach them" has an answer. Calls from the routes stop sheet were the ones going unrecorded. * Edit a job that's been sent to a provider or manifested. Both statuses sit in the same pre-running family as the ones the Portal already let you edit, and the server never refused the edit — only the Portal did. * A dispatch-and-manifest activity row says which store and carrier it was for. The row used to read `61 orders` and nothing else; it now leads with the store location and the provider, and the store filter reaches it. * Choose whether duplicating a job carries its dispatch strategy over. A new organization preference controls it; leave it alone and a duplicate inherits the source job's strategy the way it always has. **Nash Agent** * Send a scheduled report into a Microsoft Teams channel. Pick a connected team and channel as a report destination and Nash uploads the report's files there and posts them as an attachment. Teams connections made before this need to be reconnected once, because their stored permission doesn't cover uploading files. * Answers cite their sources. When Nash reads a web page to answer you, the message carries a **Sources** list of the URLs it used. #### Fixes **Dispatch** * A route's job dispatches on the strategy its contract names. A route optimized onto a shift and pinned to that shift's contract could still be won by a different provider, and a sibling batch that split into single deliveries could find no provider at all — leaving the orders stranded. * A dispatch-and-manifest batch that couldn't manifest every order is reported as failed. One order failing out of sixty-one finished green, and the activity's terminal status notified nobody — so a trigger watching for a failed manifest run had never fired. * Moving an order between routes doesn't blank its route. Detaching an order from a route it had already left cleared the pointer to the route that actually owned it, leaving a dispatched order with no route on it. * An order that reports its own total volume keeps it. A package with no per-item dimensions fell back to Nash's default box, so orders carrying an aggregate volume were measured as that default and failed contract size and weight eligibility. **Platform** * An order that supplied its own pickup address keeps it. Re-validating an order without resending the address — on autodispatch, on a delivery-window booking, on a workflow revalidation — replaced the caller's address block and coordinates with the store location's own, so the job dispatched to the registered store address rather than the one the order named. * A manifest sent late can't move a finished delivery backwards. Re-manifesting a batch after its shipments had been delivered rewrote them to **Manifested**, cleared the delivery description, and emitted a post-delivery `manifested` webhook. * Every delivery in a batch gets an ETA, even when one stop is mid-reassignment. A stop whose task had no delivery yet stopped the ETA refresh for the entire batch, so the Portal kept showing stale times for all of them. * Metadata containing double quotes is stored as you sent it. The REST path rewrote inner `"` to `'` on `jobMetadata`, `orderMetadata`, `packageMetadata`, and several other JSON fields. * Metadata written to a package reaches its order. A key added to a package after dispatch stayed on the package. * Two not-dispatched reminders fire at their own times. An organization with a 60-minute and a 30-minute *order not dispatched* trigger had both fire at the 60-minute mark and nothing at 30. * A workflow filter accepts a fractional number. Comparing a weight or dimension field against a value like `226.8` was legal per the published schema but unrepresentable, so a workflow built through the API took the false branch every time and said nothing about it. Workflows built in the Portal were unaffected. * A shift template saved without a cadence generates daily. It was stored as daily and run hourly, which produced a duplicate shift every hour of every day. * Completing a return closes the route. A return marked complete from the Portal's job page reopened the finished return stop and reopened the route with it, so the driver app never cleared the route and the Portal kept showing the return as pending. **Portal** * Driver groups list for organizations whose internal fleet is run by a separate provider organization. **Fleet › Driver Groups** rendered empty for them, and a group created there vanished on save. * An organization preference can be zero. A numeric preference set to `0` rendered as an empty field, which matters where zero and unset mean different things. * A stop's delay is measured in the right time zone. A stop that arrived 39 minutes early could read as more than two hours late, off by the viewer's own UTC offset — and the late text is now legible in both light and dark themes. * The shift-template CSV preview accepts the columns the upload accepts. A valid file showed a wall of validation errors and then uploaded successfully, which is worse than either outcome on its own. * Expanding **Package details** doesn't remount the page. The content region flashed its loading state and re-rendered, which read as the dashboard reloading. **Integrations** * Couriers Please bookings produce their manifest. The manifest call was failing on a missing pickup time and swallowing the error, so it returned no documents and marked nothing manifested — a wrong answer rather than a visible failure. A booking with no pickup time is now refused outright. * Uber deliveries that end in a timeout after dropoff arrival, or in a merchant cancellation, are marked inactive. Three final-status branches settled the status without clearing the active flag, so the delivery read as still running. * CBD webhooks reach their delivery again. A change to the reference Nash sends on each stop broke both lookup keys at once, so status updates from CBD were dropped from 20 August until this fix. * A split Shopify cart is quoted once. Shopify prices a multi-location cart by asking per group and summing the answers, so a cart split three ways showed the customer three times the fee — and a free-delivery threshold was measured against one group rather than the whole cart, so carts over the threshold were charged. * A Shopify pickup order uses the buyer's phone number. Pickup orders fell back to the store's own number, so notifications went to the merchant instead of the customer. * DeliveryApp deliveries that end in a cancellation, failure, expiry, or return pick up their final pricing. The delayed pricing refresh only ran for completed dropoffs, so later fee changes on every other outcome were missed. **Fleet** * The routes list refreshes after you unassign a delivery. The screen kept showing the route as assigned until something else refetched it. * A stop shows the order's external ID when it has no reference ID. Stop details rendered neither, so an order identified only by its external ID showed nothing to identify it by. **Nash Agent** * Elapsed times are computed against one clock. Two parts of the agent's own instructions disagreed about which timestamp counted as "now", so it could subtract a store-local time from a UTC clock and report an age off by the store's offset — differently from one run to the next. * A scheduled watch serves a check that arrives mid-run. A wake landing while the watch's first run was still working was recorded and never served, so the follow-up waited out the full interval instead of running when it was due; stale ones are now swept up rather than left sitting. * An agent named for the thing it does can finish its run. Guardrail keyword matching read an agent whose name contains a word like *refund* or *cancel* as taking a customer action and parked its own internal submission behind a confirmation nobody could answer, until the run ran out of time. * A question or confirmation card stays at the end of the turn it belongs to, with its explanation above it, and answers you've already submitted survive the card re-rendering. ### Week of August 17, 2026 #### Highlight **Execute, for one store.** Execute shows the routes that are on the road right now, which makes it the page a store manager most wants and has been least able to get. Access to it was organization-wide: to watch the routes leaving your own store, you needed a role that could see every route in the business, and most organizations reasonably decline to hand that out. So the person closest to the problem asked someone further away to look. Store-scoped access is now a role you can grant. * **The page is the same page.** A user scoped to a store location opens the Execute they'd see anywhere else — the queue, the filters, the date control, the route detail panel — narrowed to the routes that touch their store. The scope is applied on the server, including the counts in the header, so a link to a route outside it returns an error rather than a partly-filled screen. * **Two permissions, granted from a role.** `execute.view` opens the page and reads a route; `execute.act` changes one. An admin grants either from the Add role and Edit role drawers, and the rows appear once your organization has Execute turned on. What a scoped user can do is still their role's decision, not a consequence of the scope. * **Reassign the courier without escalating.** A store operator with `execute.act` can move a route to a different courier from Execute. Vehicle assignment stays with unscoped users. * **Record what actually happened.** The route events an operator on the ground is the first to know about — a breakdown, an ad-hoc break, an overweight or oversize load, a refuel, a late departure, a customer not home, a catchment or break-compliance violation — can be logged by the person who saw them instead of relayed to an org admin. See [Who can open Execute](/reference/routes#who-can-open-execute). #### New & improved **Platform API** * `POST /v1/inventory` commits the rows it can. A row naming a product or store location Nash doesn't have is skipped and returned in the same array with `errorCode` and `errorMessage` set, while the rest of the batch writes — so a per-store feed carrying one stale SKU updates the store instead of being rejected whole. Discriminate on `errorCode` being non-null, and match rows back by the `externalProductId` / `externalStoreLocationId` pair. See [Store catalog](/reference/store-catalog). * A quoted or ID search in the Deliveries workspace reaches four more identifiers — the job, task, and delivery IDs, and the job's short reference code — so pasting a reference someone sent you finds the delivery more often than it used to. * Event payload examples render again in the webhook event-type reference. `delivery.*`, `task.*`, and several other families had been showing an empty example box, which made a live schema look like a missing one. **Orchestration** * A workflow action can link to the order's packing slip. `{{order.packing_slip_pdf_url}}` interpolates the rendered PDF — the one a warehouse or partner kitchen can open and print — and `{{order.packing_slip_zpl_url}}` the raw label payload for a ZPL printer. Both resolve to nothing on an order with no slip. **Integrations** * Uber dropoff notes can carry a standing marker. `DROPOFF_INSTRUCTION_PREFIX` sits ahead of the customer's own instructions so it survives truncation when they run long, and `NOTES_SEPARATOR` controls what joins the segments — set it to an empty string when the note carries a token something downstream parses rather than prose a driver reads. *(Opt-in, per provider configuration.)* * Roadie can price a multi-component order as one parcel. A contract option collapses the components into a single manifest item with the package's own dimensions, weight, and value, so an assembled gift is quoted on the box that ships rather than on each piece inside it. *(Opt-in, per contract.)* * Samsara route dispatch binds the vehicle as well as the driver, scoped to the shift window — so the truck arrives already assigned, and nothing outranks a pin an operator set in the Samsara dashboard outside that window. * Shipday deliveries pick up the assigned courier's name, phone, and ID from routine refreshes, not only from webhooks. A courier Shipday assigned quietly now shows on the job. * CBD manifest stops carry your package's external identifier as the source reference, so the stop is filed under the order number you'd quote when you call about it. **Portal** * Driver notes on a route stop show in Execute. A note a driver added on a stop appears beside that stop in the route detail timeline, the same way the batch view already showed it. * Correct a dropoff address before dispatch. Edit Delivery gained Package, Pickup, and Dropoff tabs, and the dropoff address can be edited component by component when geocoding split a rural, PO Box, or otherwise ambiguous address the wrong way — which previously took an API call. Components lock once a task has a winning quote. * Driver and vehicle pickers on Execute open only for routes you're running with your own fleet. A route with an external provider shows the assignment read-only, because it's the provider's to set, and a route with no provider chosen yet reads **Assign provider**. * Searching Deliveries costs one query instead of several. The box waits a full second before it searches, names what you can search by, and stops the 30-second background refresh while a search is on screen. * Cancelling a whole batch looks like what it is: a warning treatment, a button that reads **Cancel batch**, and a line stating how many remaining deliveries it covers. Single-delivery cancels are unchanged. **Nash Agent** * In a Slack channel, Nash answers when you mention it — and only then. Follow-ups inside a thread Nash is already in need `@Nash` too. A thread that turns into a side conversation, or a message tagging a colleague, no longer wakes the agent. Direct messages are unchanged. See [Talk to Nash in Slack](/nash-agent/channels#talk-to-nash-in-slack). * Slack replies render as Slack's own blocks. Tables stay tables, an entity Nash looked up comes back as a card, and a chart renders where the data supports one — each with a plain-text version behind it. * Preview a CSV without leaving the panel, and share where you are. The artifact panel renders a bounded table of a generated CSV, and the open artifact now lives in the URL, so a link you paste to a colleague reopens on the same file. * Attach a gzipped dataset. `.gz` files upload as data and mount in the code sandbox, instead of having to be pasted into the prompt. **Fleet** * Hold **Start Route** until the pickup window is close. A new organization preference sets how many minutes before the earliest pickup a driver can start; until then the button shows a countdown and says when it opens. Drivers who get a route hours early no longer arrive before the store is ready for them. Leave it unset and nothing changes. *(Opt-in.)* * The barcode scanner reads inside the brackets. The scan camera draws a guide on the preview and accepts a barcode only when it sits inside — so a driver scanning one package on a full shelf doesn't pick up its neighbor. Move a rejected barcode into the brackets and it registers immediately. Applies to every scan flow in the app. #### Fixes **Dispatch** * Every order on a route with a mid-route pickup is dispatched. When a route picked up twice, dropoffs belonging to the first pickup but sequenced after the second matched neither leg and were dropped — silently, with the dispatch reported as a success. Dropoffs are now assigned by which pickup's manifest they belong to, and an order that can't be placed fails the dispatch instead of disappearing from it. * A dispatched route reports the distance and duration it actually has. Routes created from a partner's batch could show 0 mi / 0 min because the batch's driving metrics were computed before the route existed to receive them. * An autodispatch that failed is reported as failed. The Orders page treated the returned job identifier as success, announced the order as dispatched, and linked to a delivery that was never created. It now reads the job's activity, and shows the unsupported package requirements when that's the reason. * Dispatch activity results say what went wrong. A failed dispatch lists the orders, routes, and deliveries involved with the reason for each, linked to the records, rather than reporting only that something failed. * Cancelling a batch dispatched through NashLink resolves the items on it and clears its return stop. The cancellation itself went through, so the mutation looked fine, while the items stayed pending. **Platform** * A delivery keeps its ETAs through a pick milestone. `PICKING_COMPLETE` and `PICK_COMPLETE` weren't counted among the running statuses, so the next status change cleared both the pickup and dropoff ETAs on an order whose items had just been picked. On a route, the two pre-departure milestones now read the pickup stop as arrived — consistently, whether the stop carries one order or several — and `PICK_COMPLETE` completes it. See [The three pick milestones](/reference/delivery-status#the-three-pick-milestones). * An order created from a job carries the package's tags. Tags your integration set on the package — the ones naming the storefront an order came from — stopped at the package and never reached the order. * A shift-template upload keeps the hours in the file. A CSV row naming a service window uploaded cleanly and then produced a template on the default 6am–2pm window instead. * An item a merchant removes from a Shopify pickup fulfillment isn't reported as prepared. Setting an item's fulfillment quantity to `0` still marked the whole fulfillment order ready. * A refund request is filed against the delivery attempt that reached a provider. On a job that was reassigned, the request could land on a superseded attempt that never had one and fail — including when the identifier you passed names exactly one package on a multi-package job. * Creating an API key explains what's wrong with the request. A key submitted with organizations selected but no feature groups returned only "an unknown error occurred"; the same applied to clearing every group on an existing key. * Opening an order or job that lives in another region shows the record. A detail read fans out across regions, and the expected "not mine" answer from a region that doesn't own the record could overwrite the answer from the one that does. * Canadian organizations can set up a pre-authorized debit mandate for invoices again. **Integrations** * Uber receives merchant-approved substitutes as a substitution. A preference value Uber's payload spells differently than Nash does fell through to a default that asked the courier to phone the customer — on items where the merchant had already supplied the replacements. * Purolator shipments are dated from the pickup, not the dropoff. Purolator reads that field as the date the shipment is handed over and validates it against a ten-day window, so an order that waited overnight before dispatch was rejected with a date already in the past. * One oversized instruction or underweight carton no longer takes down a TGE manifest. TGE validates a manifest as a single document, so a single bad field rejected every shipment in it; per-item weights are floored and instructions are capped before they're sent. * Australia Post manifest timestamps record when Australia Post created the manifest, rather than when Nash's request landed. **Portal** * A route you renamed shows the name you gave it. The route drawer and the move-stop dialog prefixed every label with "Route", so `WMT Delivery - Dock Door 5` displayed as `Route WMT Delivery - Dock Door 5`. Unnamed routes still fall back to the prefixed identifier. * Contract version history lists newer versions. Opening version 2 of a contract that has a version 3 showed only the versions before it, so a contract's later revisions were unreachable from any but the newest one. * A delivery in a terminal status is locked wherever its status dropdown appears. The organization preference that locks terminal statuses was enforced on the Deliveries table but not on the route drawer's stop timeline. * Shopify settings toggles announce the state they just applied, instead of reporting every change — on or off — as "disabled". **Nash Agent** * A session view survives a card status the browser doesn't recognize. One unmapped status on a pending action took down the whole session view rather than the card. * A download chip comes back after a dropped connection. The chip was sent once and only once, so a viewer whose stream reconnected at the wrong moment lost the download permanently — with the file itself intact the whole time. It's now replayed from the stored artifact. * A table whose values are all null reads as no data, rather than as a blank preview that looks broken. A real `0` is still shown as a `0`. * You can send a message into your own session while it's parked on your confirmation. Doing so returned a conflict, and the banner explaining it blamed a teammate — then disabled the Confirm and Cancel buttons that would have cleared it. * An intake started in Slack can be answered. Nash asked its questions, then closed the turn as done, leaving nothing to answer and no way to resume; the questions now appear in the thread with a link into the session, and answering them continues the run. * A Slack confirmation runs even when the card can't be updated first. A failure updating the message to "Processing" stopped the confirmation itself from executing. * Slack turns that aren't about a report don't come with report tooling attached. Asking about a delivery could return an answer mentioning report template files, because the branded report kit was mounted on every turn that could run code. ### Week of August 10, 2026 #### Highlight **A quieter Execute page.** Execute is where a dispatcher spends the day — the list of routes that are out on the road right now, and the place you go when one of them stops behaving. It has picked up a lot of surface over the last year, and the feedback was consistent: for a team running twelve routes, the page shows the machinery of a team running twelve hundred. This week it got narrower rather than larger. * **The day starts on today.** Execute used to open on the whole month, which meant every visit began by narrowing to the day you actually care about. It now opens on today, matching Deliveries. The date control leads with Today, Yesterday, Tomorrow, and This week, each applying the moment you pick it, and **Custom** opens a calendar that doesn't move the queue until you apply it. A link that carries its own dates, and a saved view, both still win. * **Filters grouped the way a dispatcher decides.** The filter panel used to have one catch-all Route bucket. It's now split into route operations, assignment, and orders and locations, with package filters kept to genuinely package-level things. The question you're asking — who's carrying this, where is it going, what's in it — now maps to a section. * **Filter by driver group or courier.** Two filters that were missing, on the panel, the queue, the downloads, and the URL. An operation that splits its day across driver groups can give each dispatcher a link that opens on their own routes. * **The filter lists match the routes in front of you.** Tags, city, state, business name, shift, and optimization strategy were drawn from a list built for the Plan page, which deliberately excludes orders on dispatched routes — the exact routes Execute lists. So the options and the table disagreed, which read as the filters being broken. They come from the routes on the page now. * **See whether the driver is actually moving.** Each assigned driver carries a location freshness reading under their name, in the table and in the route panel: live through 15 minutes, then shown as not live with its age, the same rule the delivery map already used. Select it and a small map opens at their last known position, so "where are they" doesn't cost you a page change. * **Turn off the parts you don't use.** The map view, the timeline view, and the KPI cards can each be switched off for the whole organization under **Organization settings → Products → Execute**. All three stay on unless you turn them off, and a link to a view you've disabled falls back to the table rather than failing. See [Watching routes on the Execute page](/reference/routes#watching-routes-on-the-execute-page). #### New & improved **Orchestration** * Ask a workflow whether a field is there at all. **Is set** and **Is missing** take no comparison value, so a condition can branch on presence — a metadata key your integration only sometimes sends, a tip nobody entered. Only `null` and genuinely absent values count as missing: `0`, an empty string, and an empty list are all *set*, so a deliberate zero isn't mistaken for a blank. Checking optional context the order doesn't have yet resolves to missing rather than failing the step. See [Branching on whether a field is there](/reference/workflows#branching-on-whether-a-field-is-there). * Five more fields to branch on, for the decisions that turn on a number rather than a status. **Drive Time** and **Minutes Until Pickup** on the order and its packages, **Package Count** and **Total Package Value** on the job (delivery), and **Task Attempt Number** for the case where a second failure should escalate rather than reassign again. The workflow editor reads the field catalog live, so they were there the moment the platform published them. See [Fields worth branching on](/reference/workflows#fields-worth-branching-on). * Hold an order's price or tip inside a bound instead of overwriting it. **Modify order price or tip** gains **At least** and **At most**: a floor raises anything below it and leaves higher amounts alone, a cap does the reverse. Both are idempotent, so a workflow that runs twice doesn't compound — which is what made the old pattern of pairing a filter with a set action awkward to get right. **Nash Agent** * Choose which entity an alert-triggered agent actually works on. The event that starts a run and the entity the run follows are now set separately: a delivery, task, or job alert can run against the parent job, while a route alert runs against the route. An agent that rebooks a failed drop-off can therefore start from the delivery event and still see — and act on — the whole job on every later pass. If the chosen scope can't be resolved from the payload, Nash refuses the run instead of starting the agent against the wrong thing. See [What the run works on](/nash-agent/scheduling-and-execution#what-the-run-works-on). * See which agents are watching, and what they last concluded. An agent that watches an order, route, or job now shows its latest assessment on that record's detail view, with a link back to the run that produced it, and Routes on Execute can carry it as a column. Agent activity is kept out of the ordinary event timeline so it doesn't crowd the operational history. **Portal** * One page for how the Portal reads. Organization defaults, branding, and the Orders and Routes column labels you've overridden now sit together under **Settings → Preferences**, instead of being split across an organization preferences tab and a separate customization screen. Column labels gained a searchable editor, per-row reset, and a count of what you've changed. See [Display preferences](/reference/display-preferences). * Customer and provider analytics live at one address. `/analytics` resolves the view from your organization type, with an explicit switch for organizations that are both. The old provider path redirects. * Stop timing reads in three rows at most. The dropoff window, then a single ETA slot — the planned ETA until the route is running, the live one while it is, back to planned once the driver has arrived — and the arrival time once there is one. Shared by the Execute route panel, the Deliveries drawer, and the Plan route panel. * Organizations billed in Great Britain can reach the Stripe customer portal from an empty billing state, which is where Direct Debit setup lives. **Dispatch** * Split a batch when a provider cancels it — available to every organization now, not only on request. When a provider hands back a whole batch, Nash can break it into single deliveries and send those to a dispatch strategy you nominate, rather than hunting for one provider willing to take the batch again. Find it under advanced options on the dispatch strategy. See [Failover and reassignment](/reference/dispatch-strategies#failover-and-reassignment). #### Fixes **Orchestration** * A workflow no longer starts from an event that has gone stale. Workflow-backed notification triggers are now checked against the same ten-minute staleness guard the other notification paths use, so a geolocation event that arrives late can't kick off a run — or send a message about a delivery that finished half an hour ago. The scheduler also claims the executions it picks up, rather than repeatedly selecting the same pending batch. * A notification trigger whose type Nash has no payload mapping for is skipped rather than raising, so it can no longer take down the whole batch of notifications queued behind it. * Stuck-status triggers fire when their timing parameters are explicitly null, instead of quietly doing nothing. * Delivery status filters using **is one of** and **is not one of** apply instead of erroring, so switching an existing condition to a multi-value operator no longer drops you on an error page. * Custom events reach the event timeline whatever the casing your system sends them in. `Order Ready At Store` and `order_ready_at_store` are now read as the same event — which also means an agent reading that timeline to decide whether a store has signalled ready gets the right answer. **Dispatch** * An order whose autodispatch runs out of options is reported as a dispatch failure, including when every quote came in over the strategy's maximum delivery fee. Previously the first delivery on a job could stay sitting in `created` with nothing to indicate it had stopped. * A route that dispatched successfully isn't reported as failed afterwards. A second, fallback dispatch path could run after the route had already gone out and report its own inability to find a contract as the route's outcome. * Dispatching a large wave of routes reports each route's real outcome. The lock covering the wave was scoped to the whole batch rather than to a route, so a long batch could exhaust its own lease and report routes as failed that had in fact dispatched — with the customer notifications that follow never sent. **Fleet** * A batch ETA no longer routes the courier back through stops they've already served. A dropoff that failed at the door and moved to return anchored the remaining-stop chain, pulling completed stops back into it, and every later dropoff ETA inherited that detour. * A pickup scan advances the stop it belongs to, and not every other delivery on the job. On a consolidated pickup that could be a hundred-odd deliveries, including orders belonging to other stops on the same route. * Courier check-in, check-out, and pickup-summary forms are chosen by the driver group the route was dispatched to, rather than by every group the assigned driver happens to belong to. Drivers who belong to several groups were seeing another group's form. * A masked phone call connects without asking the caller for a code. The driver app already sends the extension as tones; the prompt asked for something nobody had to do. * Return-parking check-in timestamps are readable — `returnParkingLocationUpdatedAt` was written under a misspelled key and so was always null. * Revoking or expiring a delivery window works on routes containing a break stop, which previously returned an error. * Optimization keeps a vehicle type open-ended when it declares no end depot, even if another type in the same run does declare one. Before, one type's end depot pulled every route in the run back to it. * Driver pay totals tolerate a stop with no delivery — an order canceled seconds into a dispatched route left the rest of the route's stops unable to load in the driver app. * On the driver app: long item names wrap and row actions stay aligned on the routes package list; an order canceled mid-pick reads as failed rather than picked; and the stop sheet shows the action button where the driver can act — a pending dropoff whose pickup is done is workable, and a pending dropoff whose pickup isn't says so. **Portal** * The order details edit form shows delivery windows in your own timezone rather than UTC. * The public tracking page shows the dropoff ETA it has, instead of falling back to "Arriving soon" — the timestamp was arriving without a timezone marker and being read as local. * Duplicating a job keeps the saved pickup and dropoff store locations, instead of submitting them as free-form addresses and producing a package with no store attached. * The Add to Route drawer closes and confirms once the order is actually on the route, and says so plainly when the route has already been dispatched. * The timezone switch offers your browser's timezone again, alongside the resolved organization or personal one. * The Portal home and welcome pages load rather than erroring on a render loop. * Flags with no payload no longer render an empty box on the Execute page. * The API key form won't let you submit with organizations selected but no permissions, and explains why — a key with no permissions can't be issued, and the attempt used to fail with an empty error message. * Organizations billed in Great Britain can add a payment method again; the setup request named a payment type the account can't use directly, and Stripe rejected the whole request, leaving the dialog blank. **Integrations** * Toast order updates no longer overwrite a tip that came from somewhere Toast can't see. A gratuity booked as a service charge or typed into the Portal used to be replaced with \$0 on the next update. * Uber quotes read package weight as the weight of the package, not of each item in it — an order declaring a 50 kg package and eight items was being quoted as 400 kg. * Purolator quotes work for orders whose items carry no dimensions, rather than failing and blocking dispatch. * AlShrouq deliveries track: status updates from polling and from the webhook now apply, and cancellations reach the provider. * Leajlak cancellations are recognized on both the webhook and the polling path, so a canceled delivery stops looking active and doesn't fall through to reassignment. * A DoorDash contract scoped to a business reports a missing store mapping by name at request time, rather than passing the request on and returning the provider's opaque rejection. **Nash Agent** * **Stop** stops the work. Stopping a session in the Portal is now a server-side transition rather than the browser hanging up: pending actions and scheduled ticks are revoked, and late tool results, artifacts, and status writes are refused. A first turn can be canceled too. * A session that failed stays failed, on reload and in the sidebar, rather than reappearing as completed — and progress messages match the outcome they describe, instead of a step that failed keeping its success copy. * Mentioning Nash in an existing Slack thread starts a conversation. Slack delivers a mention twice, and the delivery that arrived first was claiming the message and then discarding it. * Nash replies to external Slack Connect users in shared channels, instead of failing the run before the agent started. * An assessment an agent produces is retried when the write is lost to a transient failure, so the assessment — and any workflow waiting on it — isn't silently dropped. ### Week of August 3, 2026 #### Highlight **Hand a step of a workflow to an agent.** A workflow is good at the decisions you can write down in advance: if the order is over \$200, use the white-glove strategy. The ones you can't write down have always gone to a person — read the note the customer left and decide whether this needs a call, look at what the provider has done for the last forty minutes and judge whether the delivery is still recoverable. This week a workflow node can hand that step to one of your custom agents, and the rest of the graph branches on what it reports back. * **The agent arrives knowing the case.** It receives the entity the workflow fired on, the trigger's metadata, and the output of every node upstream of it. So the task context you write is about the judgment you want, not about re-explaining the delivery. * **Branch on the finding, not on the fact that it ran.** The full written report lands in the node's own variable, and each field of the agent's output format gets a variable of its own, at its own type — so a downstream filter compares a number, tests a boolean, or matches an enum exactly the way it would with any other field. * **What it's allowed to do is settled before it runs.** Only report-only agents can be picked, so a node can't quietly take an action on your behalf. An agent node can't sit on a workflow that runs synchronously while an order is being created, because the order would wait on it. And each node carries its own timeout and its own answer for what the workflow does when the agent fails. * **Or run one against the delivery in front of you.** The delivery details page has an agent picker. **Run** works the agent against that delivery and streams it into the panel beside the page; **Ask** opens a chat session with the delivery already in context, for when you turned up with your own question. Find it as **Run Custom Agent** in the workflow editor, or `invoke_agent` on the API. See [Hand a step to an agent](/reference/workflows#hand-a-step-to-an-agent). #### New & improved **Nash Agent** * Answer an agent's question in your own words. Every closed-choice question on a guided intake card carries a free-text **Other** row, so a question whose options don't fit no longer leaves dismissing the card as the only way out. An agent can still close a set where the options have to resolve to real records, such as a notification group or a trigger. * Tools you bring reach the agent as you declared them. A connector built on your own endpoint publishes its real parameter schema instead of the agent inferring the shape from a description: required parameters are enforced, values are coerced to the declared types, and a call that can't be made comes back with the reason. See [Connectors](/nash-agent/connectors). **Orchestration** * An optimization strategy can route around water. A ferry leg costs the solver no more than ordinary driving, so it will send a truck across a sound whenever that's the fastest line — and the result looks feasible while being undeliverable, because sailings are fixed, need booking, and cap height and weight. `avoidFerries` keeps the solver on the road. Any coastal operation can meet this, not just the one that reported it. * A workflow can turn a condition it detects into durable work on the Execute page: create, update, resolve, assign, or unassign a route flag. Repeat runs land on the same flag rather than stacking duplicates, a flag that comes back reopens and pages the route's assignee the way the first occurrence did, and a flag a dispatcher has already acknowledged is left alone unless you choose otherwise. See [Actions](/reference/workflows#actions). **Platform API** * Look up an order by the reference your own system uses. `GET /v1/orders?referenceId=` matches exactly and ignores the default date window, so an order from six months ago is still findable. It returns every match: reference IDs aren't unique in Nash, and collapsing them to one would hide orders. The field is on the list response now too. * A delivery's `statusHistory` carries where each scan happened. Carriers that move a parcel through a scan network report a facility or suburb per scan; that arrives as `location` on the history entry and in `delivery.*` webhooks, next to the status and its description. It's `null` for point-to-point couriers, which have no scan points. See [Where a scan happened](/reference/webhooks#where-a-scan-happened). * A quote rejected on package requirements says which requirement. Failed quotes carry an `errorCode` and an `errorDetails` object naming the requirements the provider wouldn't take. When every eligible provider rejects the package, the delivery carries a `quoteFailure` summary and a normalized `failureCode`, and dispatching it returns a `422` instead of a generic failure. See [Order requirements](/api-reference/order/order-requirements). * Pick-and-pack orders can ask the shopper to call. `contact` joins `refund` and `substitute` as a substitution preference: rather than Nash deciding for the customer when an item isn't on the shelf, the shopper reaches them and agrees a replacement. Support depends on the pick-and-pack provider. See [Pick and pack](/api-reference/store-catalog/pick-and-pack). * The `dispatched` webhook can be replayed for a route dispatch, not only for delivery-window and optimized-order dispatch. A customer who missed that message can be resent it from the Portal rather than by hand. **Fleet** * Let drivers skip item marking. *(Opt-in.)* For operations where ticking off each item isn't part of the job, the stop sheet hides the package list and shows a read-only item count instead; the driver takes proof of delivery and completes the stop, and Nash resolves the items behind it. A dropoff still can't deliver an item that was never picked up. Turn it on in **My Fleet → Features → Delivery & Returns**, with a per-driver-group override. * Route offers reach the routes driver app. Drivers see the same offer card, countdown, and accept / decline / expire behavior the batch app has, so an operation that assigns work by offer can move onto routes. **Portal** * Arabic, and a portal that reads right to left. Arabic joins the portal languages, selectable as your organization's default or as your own [display preference](/reference/display-preferences), and layout, navigation, controls, charts, and drag-and-drop follow the direction of the language rather than staying pinned left to right. * Name your own columns. Orders and Routes column labels can be overridden per organization from **Portal Customization**, so a team dispatching technicians isn't reading "Pickup Location" and "Order Value". The labels you set show to everyone in the organization, in every language. * Define the custom events your organization uses. *(Opt-in.)* A catalog under **Orchestrate** holds each event's key and name, and the delivery **Add Event** dialog offers the active ones while still accepting a free-form event. Keys can be dotted now — `order.ready`, `store.order.packed` — so the catalog can describe the vocabulary your integrations already send. * The Deliveries table shows package weight beneath value and item count, and in the package tooltip. * The Orders table gains a **Dropoff Zip** column you can sort on, for operations that group a day's work by postal code. * A refund larger than the original order value is flagged where it's decided and in refund history. The warning doesn't block the refund; the insurance cap still does its own job. **Integrations** * A Grubhub contract can carry the sub-brand name Nash sends, instead of it being derived from the seller ID on each order. #### Fixes **Platform API** * A request comfortably inside your rate limit is no longer rejected as though it were over it. Counting requests is now a single atomic operation, so a counter that expired mid-count can't be left in a state that turns away the next caller. * Notifications that depend on a zone geofence are evaluated without backing up everything queued behind them, so alerts land when they're supposed to. **Fleet** * Item actions on the same route are applied one at a time, so two updates that arrive together can't overwrite each other's item states. * An item action that fails on several items reports every failure, not only the first one. * A failed stop gives the reason its own status carries, rather than always reporting the stop as unattempted. * Proof-of-delivery photo checks read unpackaged store goods as delivered items, and treat a house number or a courier in frame as supporting evidence rather than a requirement. A photo that plainly shows the delivery on a doorstep passes instead of being flagged. **Integrations** * Shopify pickup orders carry the tags you configured, the same as the rest of your orders. **Nash Agent** * An artifact Slack refuses is reported instead of vanishing, and a file an agent uploads to Slack from outside a chat session arrives. * A run notification's body is cut on a markdown boundary, so a truncated message doesn't end mid-formatting. * A provider failure is recorded as a failed run and retried, rather than recorded as a run that succeeded and returned nothing. * A chat turn survives a worker being recycled instead of being killed mid-answer. * A watch tick is never scheduled in the past, and a tick that fails is retried and reported. * In Slack, the right task chip resolves when an agent makes several tool calls at once, and a run's message links back to its session. **Portal** * The Execute route panel keeps the data it did receive when part of a response fails, instead of showing a skeleton that never resolves. * The delivery map draws the `dropoff_complete` marker. * Tracking customization saves instead of silently refusing when an earlier preference is stale. * On the tracking map, the store pin stays visible behind other markers, past ETAs stop being shown as if they were still ahead, and the map stops polling while it's in a background tab. * An agent session opened from a link after it ended shows its transcript and its files rather than an empty page. * Switching organizations queries the region that organization actually lives in. ### Week of July 27, 2026 #### Highlight **Your units follow you.** Organization-wide display defaults landed earlier this month: one language, one timezone, one set of formats and units for everybody. This week, each person can override those defaults for themselves. Teammates in different timezones can open the same delivery in the format they use without changing what anyone else sees. * **An Account page of your own.** Every signed-in role now has **Account** in the avatar menu. It holds your profile details, which you can edit yourself, and your own display preferences. It's separate from organization settings, and you don't need permission on those to reach it. * **Eight preferences, each independent.** Language, timezone, date format, time format, currency, and your weight, distance, and dimension units. Override the ones you care about and inherit the rest, so you can keep your organization's currency and still use your own clock. * **Defaults that stay defaults.** Organization settings now read **Organization Defaults**, because that's what they've become. Change one and everyone inheriting it moves with you; anyone who set their own keeps it. * **Nothing quietly misread.** A value Nash can't use, such as an unsupported currency or a timezone the browser doesn't know, falls through to the next layer instead of being applied, so a bad organization default can't propagate to everyone. If you switch your weight or dimension unit while an order form is open, the numbers already typed are converted rather than reinterpreted: 10 kg becomes 22.05 lb, not 10 lb. Find it under your avatar, in **Account**. See [Display preferences](/reference/display-preferences). #### New & improved **Nash Agent** * Reports an agent produces come back as branded PDFs with your logo and colors, consistent typography, and a real chart vocabulary for trends, rankings, comparisons, and shares, rather than whatever the model improvised that run. It applies to reports asked for in chat as well as scheduled runs. * Ask for a revision and you get a version, not a second file. Artifacts built across turns of one session under the same filename are grouped as one deliverable with a `v2/3` chip, and selecting one opens a side panel that previews it inline instead of downloading it. * Notify only when the result is worth reading. A succeeded run's notification can be gated on the agent's own structured output: `is_fraud` came back true, `status` is one of the values you list, `confidence` is at or above `0.8`. Number fields take comparisons, not just membership. See [Notify only when the result is worth reading](/nash-agent/custom-agent-configuration#notify-only-when-the-result-is-worth-reading). * Try an event-triggered agent on part of your live traffic. Run it on a percentage of matching events, stop it after a fixed number of runs, or both. This gives you a setting between off and full production. See [Try an event trigger on part of your traffic](/nash-agent/scheduling-and-execution#try-an-event-trigger-on-part-of-your-traffic). * Long chat sessions have a timeline rail: hover to scrub through turns and preview them, select one to jump. **Orchestration** * A dispatch strategy you choose by hand stays chosen. Nash now records whether an order's strategy came from you or from your automations, so an unrelated edit, such as a time-window change or a save followed by a dispatch, no longer re-runs the rules and replaces your pick. An automation-managed order shows **Via Workflow** in the picker instead of a strategy that isn't actually pinned to it. See [When you pick a strategy yourself](/reference/dispatch-strategies#when-you-pick-a-strategy-yourself). * Dropoff ETA estimates skip days the origin is closed, so a Saturday pickup from a site that's shut for the weekend is measured from Monday instead of landing two days early. Closed weekdays and specific dates such as holidays both count. *(Opt-in.)* * Shift-based route optimization can be anchored to the shift itself: start a set number of minutes before pickup and re-optimize on an interval, rather than re-running on every scheduler tick. *(Opt-in.)* * Orders created from an ingested email resolve their pickup to your saved store location. **Platform API** * Test a saved workflow before you activate it. `POST /v1/workflows/{id}/test` evaluates it against a real job or order and reports per-node status, so you can confirm which filters matched without turning the workflow on. Actions preview by default. See [Test a workflow before you activate it](/reference/workflows#test-a-workflow-before-you-activate-it). * Items you send without an `id` get one. Nash generates a stable identifier for any item or sub-item that arrives without one, so a courier can mark it picked, delivered, or returned. Previously, item-level actions skipped those items entirely. * The per-package weight ceiling is now 50,000 kg, up from 3,000. Freight-weight orders are accepted instead of failing validation and quietly becoming ineligible. * A route stop's `serviceTime`, `distanceFromPrevious`, and `durationFromPrevious` are typed as integers rather than decimals. The values were always whole seconds and meters, and now the schema says so. **Fleet** * The driver pay report moved in-product and gained a fee breakdown: wait fees, tolls, and return fees as their own columns, a column picker, and an itemized mode that lists each route and delivery instead of aggregating per courier. Totals are computed server-side, and the CSV export matches what you see. * Route names appear as a column in the Execute queue, so a renamed route is identifiable without opening it. * Set a route's status by hand from the Execute route drawer, the same way you already can from the delivery drawer. * Edit a vehicle's capabilities and tags in place from the Vehicles list alongside the existing bulk editing. Hover the cell and pick from your organization's vocabulary. A marker shows when a vehicle overrides its type's presets. * Create a driver without an email address. Supplied addresses are still validated. * Provider and customer-provider organizations get merchant and fee columns on their route views. **Portal** * The optimization flow has a new interface across engine selection, settings, dispatch confirmation, order information, and results, including its scheduled and in-progress states. **Integrations** * Roadie deliveries report that no driver is assigned yet from the moment they're created, rather than after the first status poll. * Senpex's scheduled-delivery status maps to "no driver assigned" instead of being misread. * Returns from a third-party delivery channel carry their reason through to Nash. * Just Eat pickup-phase statuses are polled until they settle, so a pickup update that never arrives by webhook still lands. * Australia Post tracking requests are split into batches of the size Australia Post accepts, so a large tracking pull completes instead of being rejected. * Otter and Flipdish orders resolve their pickup to your saved store location. * Medzoomer deliveries carry their batch identifier, and a Medzoomer pickup-arrived update is reflected on the delivery. * Shopify fulfillments are created for orders that move between fulfillment orders and for orders that carry no line items. #### Fixes **Orchestration** * A pinned contract is quoted only by the provider that owns it, so a shift route auto-dispatches to its own driver group rather than to whichever provider happened to quote. * An automatic re-offer leaves a delivery a driver has already accepted or started alone, so the same delivery can't go live on two drivers' apps at once. * Splitting a job recomputes the driving metrics on the batch that remains. **Fleet** * Retrying an item action that already succeeded reports success instead of an error, so a driver app draining its offline queue after a dropped connection doesn't stall on work it already did. This covers picking, delivering, and returning an item. * Failing an ID check on a stop that mixes age-restricted and ordinary items fails the whole stop when partial delivery is turned off, rather than delivering the rest of it. * The already-scanned message in the routes scan flow names the stop the barcode belongs to. * The driver app reads your branding and unit preferences correctly when one of those fields is left unset. **Nash Agent** * A run's notification email arrives even when a generated attachment can't be sent. The attachment is dropped; the message isn't. * A date given without a year is read against the run's own date instead of being guessed. * Long pasted context is accepted in chat. * A report asked for in chat comes back branded, and a report the agent revises while answering produces one file rather than two. * A PDF report sent to Slack arrives instead of timing out. **Portal** * Report Incident rejects a note that's only whitespace instead of accepting an empty one. * The Orders filter panel opens and closes reliably. * PDFs render in the artifact viewer. * The Execute triage queue keeps the rows it did receive when part of a response fails, instead of emptying. ### Week of July 20, 2026 #### Highlight **Decide what happens to an order that arrives after you close.** Every operation has a version of this problem. An order lands at 11pm for a store that opens at 8am, or a provider drops a delivery on a Sunday when nobody is there to catch it. Until now the answer was the same for everyone: dispatch it and sort it out later. This week you get to choose — and the choice for a brand-new order doesn't have to match the choice for a delivery that just lost its driver. * **Say when you're open, once.** Set a default opening time, closing time, and pickup cutoff, then add overrides only for the days that differ. A shop that's open 9–5 with late Thursdays and closed Sundays needs one default and two exceptions — not seven rows to keep in sync. * **Two decisions, not one.** Initial dispatch can **allow** an out-of-hours order through or **reschedule** it to the next open day. Reassignment gets a third option: **prevent** it, so a delivery that needs a new provider at 2am stops and waits for a person instead of quietly going out. Both default to allow, so nothing changes until you change it. * **Keep your on-time numbers honest.** When an order reschedules to the next open day, you decide whether its on-time baseline moves with it or stays pinned to what you originally promised. Either answer is defensible; the point is that it's yours, and it's explicit. Find it in **Organization settings → Operations**. See [Dispatching outside your operating hours](/reference/dispatch-strategies#dispatching-outside-your-operating-hours). #### New & improved **Orchestration** * Run a workflow across the orders you've selected. Pick them on the Orders page, choose **Run Workflow**, and Nash starts one run per order with a result you can open individually. See [Running a workflow by hand](/reference/workflows#running-a-workflow-by-hand). * A workflow that's switched off can still be run by hand. `inactive` now means "doesn't fire on its own" rather than "can't run at all" — useful for testing a change before you turn it back on. **Platform API** * Orders can declare their units. Send `weightUnit` as `lbs` or `lengthUnit` as `in` and Nash converts to the kilograms and centimeters it stores, rather than rejecting the order or taking the number at face value. An unrecognized unit comes back as a clear error instead of a surprise. See [Order validations](/api-reference/order/order-validations). * Strings containing quotes, backslashes, or JSON survive a REST request intact instead of arriving double-escaped. **Fleet** * Give a route a name a dispatcher recognizes. Rename it from the Execute page — including after it's dispatched — or name a route as you split stops into it, and that name leads on printed handoffs and in the Portal. See [Naming a route](/reference/routes#naming-a-route). * Filter Execute routes by the provider assigned to them, or by having no provider assigned yet. * Route orders on the Execute page carry their package reference fields, so a dispatcher can match a physical parcel to a stop without opening it. **Nash Agent** * Ask about products and inventory in chat and the answer comes back as a browsable widget — a product carousel with images rather than a list of SKUs. * Agents can embed proof-of-delivery photos in the files they produce, so a PDF an agent writes up carries the photo evidence inline. New agents have it on; existing agents keep their current setting, and it's a per-agent toggle in the agent's tool settings. **Portal** * Dates, times, durations, numbers, weights, and prices follow your organization's preferences across the Portal, so a kilogram org and a pound org each see their own units in the same table. * Creating a child organization asks for its country up front, which is what routes its billing correctly from the start. * Order and route activity, and webhook replay, are gated on permissions, so read-only roles can look without being able to re-fire anything. #### Fixes **Orchestration** * A delivery whose reassignment fails raises the *Tasks Attempts Exhausted* [notification trigger](/api-reference/notifications/notifications) instead of going quiet, so the delivery that needs a person gets one. * Australian PO Box addresses resolve to the right city rather than failing to parse. * Shifts no longer offer optimization strategies that were deleted. * A workflow's version history is listed in version order. **Fleet** * Routes report their travel metrics — distance and duration — including per-package driving metrics on batch dispatch. **Portal** * The Report Incident drawer opens instead of crashing the page. * Date-range presets stay relative when you switch between saved views, so "last 7 days" still means last 7 days tomorrow. Picking one end of a range no longer briefly reads "Invalid Date". * Automation conditions show your organization's currency rather than always a dollar sign. * Map pin tooltips name the business at the stop, not the customer's account. * Wix stores get their own onboarding steps. **Integrations** * Uber pick manifests accept items with no substitutes listed. * Quotes from ANC and Team Global Express stay valid longer, so a quote picked later in a planning cycle is still good when you dispatch it. ### Week of July 13, 2026 #### Highlight **Tracking that works before there's a courier.** The tracking link is the first thing a customer looks for after they order. Until this week, following one before the order dispatched got them an error page — the link was live, but there was no delivery behind it yet, so there was nothing to show. Now the same URL covers the whole journey, starting the moment you take the order. * **One URL, two phases.** While the order waits for a provider, Nash serves an order-received screen with your branding. As soon as the delivery is created, the same link becomes the live-tracking view — nothing to swap out in your confirmation emails, texts, or order pages. * **Say what you want in the gap.** Add up to 280 characters of your own copy to that screen — what happens next, when to expect an update, who to contact. Leave it blank and customers see the standard order-received message. * **Check both states before you publish.** Tracking customization has a before-dispatch / delivery switcher, so you can see each phase exactly as a customer will. Turn it on in **Customization → Tracking**. It's off until you do, and links behave exactly as they do today until then. See [Tracking before dispatch](/reference/nash-embedding#tracking-before-dispatch). #### New & improved **Nash Agent** * Give an agent's notifications their own title. It becomes the email subject and the opening line in Slack and text, and takes the same `{{variable}}` placeholders as the body — so `Late deliveries — {{status}}` reads usefully in an inbox without anyone opening it. See [Notifications](/nash-agent/custom-agent-configuration#notifications). * Turn a single action off for one agent. Alongside auto and confirm-first, an action can now be **disabled** outright — the agent can't take it and there's no card to approve. See [Guardrails](/nash-agent/guardrails#action-tiers). * Keep an interval schedule inside working hours. An agent that runs every hour can be bounded to a start and end hour instead of running around the clock. See [Set up a schedule](/nash-agent/scheduling-and-execution#set-up-a-schedule). * Put an agent in your own product. A custom agent's new **Embed** tab generates a snippet that drops a chat widget into your web app — a tracking page, an order-status page — so customers can ask about their delivery without contacting anyone. Embedded conversations are read-only no matter how the agent is configured, and each one starts from a token your own backend mints. See [Embedded chat](/nash-agent/embedded-chat). * Agent answers that are really reports now render as report blocks — headline figures, tables, and charts laid out as cards rather than a wall of text. * A chat session shows which agent produced it, so a session you come back to a week later identifies itself. **Platform API** * Webhook consumers can stop retries for one message or disable an endpoint directly from their webhook response. See [Webhooks](/reference/webhooks#stop-retries-or-disable-an-endpoint) for the response header and behavior. * Public tracking URLs accept an order ID, not just a job or package ID — the addressing behind [tracking before dispatch](/reference/nash-embedding#tracking-before-dispatch). * A product can carry different images per provider. Set `imageUrls` inside a product's `providerConfigs` entry and a provider that shows your catalog to shoppers uses the image you picked for it. See [Create or update products](/api-reference/store-catalog/create-or-update-products). **Portal** * Choose which metadata fields show on a delivery's page. An admin picks the keys worth surfacing and labels them, and they render as chips at the top of the job — the same control routes already had. Fields holding JSON are unwrapped and readable rather than printed as an escaped string. * The Automations screen shows the default dispatch and optimization strategy on the main view, so you can see what an unmatched order falls back to without opening a rule. * Merchant refunds can be filtered by date range and by provider. * Delivery and order pages lead with your own references rather than Nash's internal ID tags. **Fleet** * Route and order drawers on the Execute page show a real activity timeline — what happened, when, and who did it. * The Execute routes table has a wider filter set, and custom events you emit now appear in the route Activities view. The Exceptions filter group starts collapsed so the panel opens on the filters most dispatchers reach for first. * In the driver app, stop details show the delivery window; pull-to-refresh works on the stop list, stop details, and items list; and a driver correcting a crate count is no longer forced to pick a reason. * Drivers can adjust item quantities while offline — the changes queue per stop and sync when the device is back on a signal. **Integrations** * Australia Post consignments record every scan on the delivery timeline instead of only the most recent one, so the tracking history matches what the carrier actually reported. Weights are sent at gram precision. * Autolane receives the customer's name and email on quote and delivery-create calls. #### Fixes **Nash Agent** * A scheduled agent runs at the local time you picked. A 5am schedule in your time zone fires at 5am local, holds through daylight-saving changes, and catches up safely after an interruption rather than being skipped. * A dropped connection to a tool server no longer ends a chat run mid-answer. * Chatting with a custom agent holds its execution mode and session for the whole conversation, and an agent whose time zone was never set says so instead of showing a blank. **Orchestration** * Updating an order with an explicit dispatch strategy honors that strategy, and swapping it re-quotes against the new one instead of keeping the earlier quotes. * Recipients on a multi-stop route get the notification for their own delivery, once — not a neighbor's, and not twice. * Toast store prep times are read as minutes, so pickup times are no longer set days out. * GoShare deliveries hold their status instead of flipping back to a pre-pickup state when a poll and a webhook disagree. **Fleet** * A failed or missing dropoff leaves the remaining item quantities alone instead of resetting them. * A completed pickup stays completed when a sibling delivery on the same route fails and starts a return. * Signatures a driver captures at a stop reach the delivery record as proof. * Australia Post consignments no longer log a second "manifested" entry when the carrier echoes a location-only update, and pickups are booked against the local despatch date. **Portal** * Clearing a time window saves as cleared instead of silently keeping the old value. * Selecting a tracking status while configuring a notification trigger no longer freezes the page. * Switching organizations mid-load lands in the organization you picked. * Links between deliveries carry the right delivery's context, so following one from a job opens that job and not the one you came from. * Routes fall back to the customer's name when a delivery has no business name, and a drawer's close button no longer sits on top of the Dispatch action. ### Week of July 6, 2026 #### Highlight **See why an agent run failed — and fix it on the spot.** When you hand work to an automated agent, the worst thing it can report back is nothing. A run that ends in a bare "failed" leaves you guessing whether a provider timed out for a moment or the agent was pointed at the wrong thing. This week, Nash Agent tells you what happened. * **A plain-language reason on every failed run.** A failed run now shows what went wrong, in plain language, along with a suggested fix — in the run history and when you hover a failed run. It applies to past failures too, so a run that failed last week explains itself now. That's usually enough to tell a transient hiccup from a misconfigured trigger at a glance. * **Re-run on demand.** A **Run now** button in the agent's header starts a fresh run immediately. Fix the setup, run it again, and confirm the agent is healthy without waiting for its next scheduled run. Read more in [Scheduling and execution](/nash-agent/scheduling-and-execution). #### New & improved **Nash Agent** * Run history separates an agent's automated runs from your chat sessions with it, so its run count, activity sparkline, and status badges reflect the work it did on its own. * Reorder the fields in a custom agent's structured output by dragging them into the order you want. * Set up a results destination — email or Slack — without leaving the agent editor, see the exact recipients each destination resolves to, and open a finished agent straight from its confirmation card. * Nash Agent chat answers are formatted for easier reading, and entity references render as clickable chips instead of plain text. **Platform API** * Set a zone's geographic boundary through the API: [Create a zone](/api-reference/zones/create-a-zone) (`POST /v1/zones`) and [Update a zone](/api-reference/zones/update-a-zone) (`PATCH /v1/zones/{id}`) now accept a `polygon` field. See [Zones](/reference/zones#set-a-polygon-boundary). * Nash now supports Spain as a delivery country, with euro pricing and local address handling. **Integrations** * Australia Post senders that print their own labels can skip Nash label creation, so merchant-allocated shipments manifest without a duplicate-label error. *(Opt-in.)* **Portal** * The My Fleet settings page is redesigned with search, a category sidebar, and grouped setting cards, so a long list of fleet settings is faster to navigate. * The Activities view recognizes dispatch-and-manifest runs and opens a results drawer with per-order status and the manifest PDFs. **Fleet** * The Execute route table shows each route's current stop status — for example, "drop-off en route" — and new Route Status and Stop Status filters narrow the table to what you're looking for. (The route label "In Transit" is now "In Progress.") * Download the routes you're viewing on the Execute page as a CSV. * Notes a driver adds to a stop now appear in the Portal, in the route's stop list and the driver details card. * Select routes on the Orders → Routes tab to dispatch, archive, optimize, or create them in bulk. * On pickup and return runs, the driver app can show per-item counts — picked against ordered, with failed items kept out of the completion total — and mark an item "package not found" so the stop resolves as missing without creating a return. *(Opt-in.)* #### Fixes **Nash Agent** * Scheduled report agents that do heavy, high-volume work no longer time out before they finish, and a report that used to arrive blank now carries the agent's findings — with the notification showing the agent's complete written report, tables included, rather than a one-line summary. * A webhook-triggered agent runs when its event fires, and the raw webhook payload — including Zapier's format — is passed into the agent's prompt. * Turning an agent off reliably stops it, including agents started by an alert, a webhook, or a file upload. * Creating an agent by describing it now keeps an email recipient you name in the same request instead of dropping it. * Nash Agent chat no longer repeats a mid-run follow-up, and rich text you paste keeps its formatting. * When saving a custom agent fails, the message explains why — for example, that an event-triggered agent needs at least one trigger. * When the Nash Agent voice agent handles a call about an order that was automatically reassigned to a new provider, it works from the live delivery attempt — so the status it reports and the reschedule it makes are for the active delivery, not a superseded one. **Integrations** * Purolator shipments with longer drop-off instructions are no longer rejected. * FULFLLD deliveries show a live, updating drop-off ETA instead of a frozen one. * Addison Lee accepts scheduled orders whose pickup is near-term instead of rejecting them. * Medzoomer proof-of-delivery photos now appear on the delivery. * GoShare deliveries move to en route when the driver sets off, not only once they reach the pickup. **Orchestration** * Deliveries to UK, Canadian, and Dutch postcodes entered without a space are no longer wrongly rejected as out of area. * Stale, unaccepted driver offers expire and re-dispatch instead of leaving a delivery stuck without a driver. * Merchants reliably receive the sent-to-provider and manifested webhooks on large batched dispatches. **Fleet** * The live-tracking map keeps following the driver after a batch stop is completed, instead of freezing at the first drop. * The delivery timeline no longer shows each barcode scan twice. * Editing a driver group — adding or removing drivers — no longer silently reverts your first change. * Completed and canceled routes no longer offer a provider-reassign action that always failed. * A route that ends with every stop failed or returned now shows as complete in the activity feed, and a returned delivery emits its full return sequence — return in progress, arrived, returned — even when no arrival was scanned. ### Week of June 29, 2026 #### Highlight **Talk to your custom agents.** Custom agents have always worked for you in the background — a schedule fires, the agent runs, a report lands. This week, conversation became the way to work with them in both directions: chat with any agent you've built, and build new ones by describing them. Open a live conversation with an agent and it answers as the specialist you briefed — your instructions, your data scope, your rules about what it can touch: * **Two ways in.** Select **New chat** on an agent's page, or type `@` in the message box and pick the agent. Either way the conversation is bound to that agent from the first message and labeled with its name. * **It knows its own setup.** Ask an agent what it watches, when it runs, or what it's allowed to change, and it answers from its live configuration — useful for auditing an agent you set up months ago. * **Its execution mode holds in chat.** A report-only agent's conversation stays advisory — it investigates and tells you what it *would* do, but can't make changes, no matter how the request is phrased. An auto-execute agent can act mid-conversation, with sensitive actions pausing for your approval as always. Read more in [Chat with a custom agent](/nash-agent/chat-with-a-custom-agent). Building an agent works the same way — describe what you want, answer a couple of questions, and Nash assembles it for you: * **Describe it, and Nash builds it.** Tell the agent what you want in plain language — "watch for late pickups each morning and flag anything trending the wrong way" — and it asks a short set of questions, each with a recommended default, then assembles the agent so you don't have to fill out the builder by hand. * **Turn a conversation into a standing report.** When a chat session has done something worth repeating, choose **Schedule as a report** from the session menu. Nash turns it into a scheduled agent, drafting the name, cadence, time zone, and recipients into a card you edit and confirm. * **Author skills by describing them.** Describe a playbook you want your agents to follow and Nash drafts the skill — its name, its when-to-use trigger, and the procedure — then shows a confirmation card before saving, and a field-by-field diff when you're updating one. The builder you fill in by hand got shorter too — see below. #### New & improved **Nash Agent** * The agent builder is now organized into three steps — Basics, Execution, and Output — with less-common settings tucked into an **Advanced** section, so the common path is shorter. * A new **Only notify on certain results** control lets an agent notify you only when a result matches a condition you set — for example, only when a check comes back failing — instead of on every run. * Custom agents now run on a model Nash picks for the job, so there's no model to choose — the model selector is gone from the builder. * Agent descriptions are written for you from the agent's name and instructions, so there's no description to fill in. * Notification templates can reference `{{agent_response}}` to drop the agent's written response into the message. * A quick-nav rail in agent chat lets you jump between your messages in a long conversation. * The Nash Agent voice call can now tell a caller what canceling a delivery would cost, and what was charged on one that's already canceled. **Workflows** * The **Driver Stacking** alert can be set to fire only when the same driver is booked across *different* providers, so intentional single-provider batching no longer triggers it. *(Opt-in.)* **Orchestration** * Nash can hold a delivery whose pickup store is already past closing time, rather than reassigning it to a provider that can't complete the pickup. *(Opt-in.)* **Integrations** * Added **Australia Post** as a delivery provider in Australia for merchant-allocated shipments — Nash creates and manifests the shipment and forwards your article IDs, barcodes, packaging type, and email tracking. *(Opt-in.)* **Portal** * Route activities now show forms submitted at the route level, not only per-job submissions. **Fleet** * The driver app groups the package list by stop on pickup and return runs — items sit under their destination stop, with the stop number, recipient, and a count — so drivers load and hand off stop by stop. * Drivers can complete a custom check-in form your organization has configured, rendered in the app at check-in. *(Opt-in.)* #### Fixes **Nash Agent** * Agent notifications no longer repeat the report body — emails and Slack posts show it once — and markdown tables render as real tables instead of raw text. * Agents reason from the correct current time, so elapsed-time and SLA calculations come out right. * You can approve, reject, or dismiss a pending action straight from the agent's **Needs review** card, so the count clears even when there's no chat thread to open. * A custom agent's detail header shows a single On/Off switch that matches whether the agent is actually paused, instead of an "Off" label next to a Pause button. * A notification trigger attached to a custom agent now turns on automatically, so the agent starts running as soon as you connect it. **Workflows** * The **Job Tags** workflow filter matches the tags you set on your orders, so conditions built on it fire as expected. **Integrations** * JustEat orders in the EU, including Spain, dispatch reliably — Nash no longer sends fields the market rejects and always includes the required order identifier. * Uber pick-pack-and-pay grocery orders record a pickup update when the courier finishes shopping, so that status and its webhook arrive on time. **Portal** * The Invoices card no longer shows a "Billed monthly" label for organizations on other billing cycles. * Text that was unreadable in light mode now renders correctly — provider support details, tooltips, and status and error messages. * Opening an optimization run or route map no longer fails when a stop has invalid coordinates, and meal and break stops are labeled as breaks rather than dropoffs. **Fleet** * Map links from the driver app open directly in driving-directions mode instead of dropping a plain map pin. ### Week of June 22, 2026 #### Highlight **Trigger Nash on your own events.** Your operation has milestones Nash doesn't define on its own — a quality check clears, a customer confirms a delivery window, an item comes back damaged. Until now you could react to Nash's built-in delivery statuses, but not to the events specific to how you run. Now you can. Post a custom event to an order — the event name is yours — and a [workflow](/reference/workflows) that listens for it runs. Build the workflow around your event with an `event` trigger, then let it do the work: branch on the order's attributes, apply a dispatch strategy, modify the order, send a notification, hand off to a Nash Agent to investigate and act, or emit the next event in your own process. The events that used to live only in your systems can now drive Nash automations end to end. #### New & improved **Nash Agent** * **Agent settings as code.** View, edit, and download a custom agent's full configuration as JSON from the agent editor — preview the diff before you save a change, and create or share an agent by pasting a definition back in. * Agent run notifications now carry an attribution footer that links the agent and the exact session that produced the message, across email, Slack, and webhook destinations, with a per-agent toggle to turn it off. *(On by default.)* * Org skills now appear in the chat slash menu, and skill error messages read in plain language instead of raw error text. * Custom agents show their chosen icon in the sidebar, so a workspace with several agents is easier to scan. **Workflows** * The order-modified trigger now lets you choose exactly which order fields fire it — pickup or dropoff time, location, contact, instructions, items, tip, or order value — instead of firing on every change. * Filter workflows on driving distance for an order or package, and reference a delivery's own ID as a workflow variable. **Integrations** * Added **Autolane** as a delivery provider, including live status updates over webhooks. **Portal** * Choose which route metadata fields appear in the route side pane and as columns in the routes table, with suggestions drawn from the open route's own metadata and a live value preview. * The Activities page has a new **Skipped** filter. * Product details now show a product's dropoff service time alongside its handling specs. **Tracking** * A new **Driver Location After Delivery** setting keeps a delivery's last-known driver location visible on the public tracking page after it completes — useful for support teams confirming a driver arrived. *(Opt-in; off by default. The return-to-store leg stays hidden either way.)* **Fleet** * Driver app: in routes mode, a single barcode scan can complete the whole delivery, accepting all of its items at once. *(Opt-in, per driver group.)* #### Fixes **Nash Agent** * When a tool returns an unexpected result, the Agent reports the real cause instead of inventing one — it no longer, for example, falsely blames a permissions problem. * The Agent side panel no longer carries a previous chat session into a new one. **Orchestration** * A delivery that falls back to a backup dispatch strategy now switches on every reassignment, not only the first — so repeated provider cancellations keep escalating to the configured backup. * Optimizing orders whose contract has no eligible vehicle types now returns a clear "no eligible routes" error instead of failing opaquely. **Tracking** * A batch delivery with a single remaining stop and no live driver location now shows an ETA estimated from drive time, instead of perpetually reading "arriving now." **Integrations** * Shopify orders build the dropoff address from Shopify's structured address fields, so apartment and suite numbers are preserved instead of being dropped during geocoding. * Shopify packing slips reflect order discounts in line totals and the subtotal, reconciling to the order total. * The Toast dining-options selector keeps your selection instead of resetting it. * Purolator shipments that haven't been scanned yet refresh cleanly and fetch the shipping label, instead of erroring until the first scan lands. * Fulfilld deliveries that fail are reported as failed, not as completed. * Uber pick-pack-and-pay orders with a \$0 pickup payment now dispatch instead of being rejected. * Provider-reported wait fees are kept when a delivery completes, instead of being overwritten to zero. **Portal** * A slow or unreachable region no longer freezes the org picker or sign-in. * Large CSV uploads that hit a transient database drop on the final write retry and keep their summary and failed-rows download, instead of being marked as a timeout with the counts lost. * The refund requests table shows the correct page count. ### Week of June 15, 2026 #### Highlight **Edit live routes without starting over.** Once a route is out the door, the day keeps changing — a stop has to move, an order jumps the queue, one route is suddenly overloaded. Until now, adjusting a dispatched route meant canceling and rebuilding it. This week you can reshape live routes in place. From the route drawer, move a delivery stop from one active route to another, with a side-by-side comparison of candidate routes — driver, vehicle, capacity, store match, and open exceptions — and a map preview before you confirm. Resequence the stops on a live route by dragging them into the order you want, with keyboard and touch support; if a driver completes a stop while you're mid-edit, the view stays accurate instead of overwriting their progress. The result: dispatchers adjust to the shift as it actually unfolds, without tearing a route down and starting over. #### New & improved **Nash Agent** * Create and update custom scheduled agents directly from chat — describe the report or watch you want, and the Agent sets up the schedule and email delivery, confirming before it saves. * A single custom agent can now run on multiple schedules (for example, a morning and an evening report) without cloning it. * Draft prompts are reviewed before you save an agent, flagging instructions that belong in enforced settings — scope, output schema, guardrails, cost caps — with suggested fixes you can apply or undo. * Analytics queries can export the complete result set as a downloadable CSV or Excel file, delivered in chat or attached to scheduled runs, instead of a capped preview. * Chat now accepts Word (`.docx`) and PowerPoint (`.pptx`) uploads, reading their text so you can ask the Agent about documents and decks. * Administrators can switch the Sessions list between their own chats and all users' chats across the organization, with an owner label on each session. * Custom agents and org defaults pinned to an older model tier now run on the latest model in the same tier automatically. The "Most capable" option tracks the latest model in that tier, and new custom agents default to the Balanced tier. You can still choose any model. * Insight stat changes are colored by whether the change is good or bad for your operation — a falling cost or error rate reads positive — rather than by raw direction. * Insights render inline, full-size charts and support sorting, filtering by impact and category, a "has chart" filter, and load-more pagination. * Pulse charts (runs per day, trend, and outcome donuts) are interactive with hover tooltips. * Follow-ups sent while the Agent is mid-answer are folded into the current run and shown as separate chat bubbles, and continuing a teammate's shared session loads the full conversation history. * Nash Agent reads relative dates like "last week" or "yesterday" in your local time zone instead of UTC, so a Monday request for "last week" returns your week rather than a U.S. one. * Approve or cancel an agent's confirmation-gated actions — like canceling a delivery — directly from Slack, with the same confirm step as the web app. * Structured results from a custom agent run now render inline in the chat session, right under the message they came from, instead of only on the Outputs dashboard. **Orchestration** * When a manual reassign targets a dispatch strategy with a single allowed provider, the order automatically gets fresh quotes and dispatches, instead of waiting for manual quote selection. * Route triage labels internal-fleet assignments and quotes with their driver-group name instead of a generic "Internal Provider." * Reoptimizing a route asks for confirmation to prevent an accidental recalculation. * Manually assigning a delivery to a single driver makes it visible in their Active tab and notifies them immediately, overriding the dispatch-before-pickup timer. * Batch deliveries left without an assigned provider are now swept and re-dispatched automatically. **Integrations** * Several provider integrations now set the provider's `trackingUrl` on `GET /v1/jobs/{id}`, the jobs list, and webhook payloads, alongside the existing Nash tracking link. * Shopify packing slips refresh as soon as finalized tax and shipping totals are available, instead of waiting for the lagging order-updated webhook. * Axis orders now forward the dropoff phone number, shipment weight, and line items. **Platform API** * Query zones in bulk: a new endpoint returns zones in a paginated `{data, meta}` envelope, filterable by IDs and external IDs — replacing per-zone `GET` loops. * New `POST /v2/orders/upsert/bulk` returns a `{results, summary}` envelope with per-row success and failure outcomes and error details. * A new endpoint removes a route-based order from dispatch, freeing its package external identifier for re-dispatch and returning the updated order. * The Driver Geolocation notification trigger now accepts a `deliveryStatusIncludes` array, so one trigger can fire across several delivery statuses. * Bulk delivery updates now accept `packageExternalIdentifier` as a key for matching deliveries. * The public delivery-feedback endpoint now returns `deliveryId`, `score`, and `comment`. **Fleet** * Driver item acceptance now appears across the Portal: per-stop picked, delivered, and returned counts on routes, item events on the route timeline, per-item rows on the delivery timeline, and return condition and notes on returned items. * Search the fleet vehicle list by name, type, make, model, license plate, or external ID. * New Route Card Fields settings under My Fleet let you hide price, status, metrics, addresses, or stops on driver-app route cards. * Route maps number each stop and show richer depot tooltips. * Driver app: pinch-to-zoom while capturing a proof-of-delivery photo. * Driver app: a grouped pickup and return item list, configurable per org. * Driver app: the route name or external ID now appears in the route subtitle. **Portal** * A Shift column (hidden by default) on the Orders table and CSV export shows the shift code for both routed and delivery-window orders. * Item and sub-item details now show each item's Category. * Delivery details show when a driver adjusted an item's quantity at pickup, with the original count struck through next to the new count. * Redesigned Billing settings: a clearer payment-methods view with expiry warnings, an invoices list with open-balance cues and Pay, Receipt, and PDF actions, and a hosted billing-portal fallback for accounts where a payment method can't be added in-app. Organizations billed outside of Stripe now see their custom billing report here. * Find a notification trigger or destination by pasting its ID into the Notifications search. * **Activities** is now a standalone page, available to every org with Orders enabled — an Activities button opens it in place of the inline drawer on the Orders and Routes tables, and existing `?activityId=` links redirect to it. * The **Triage** workspace is now called **Routes**. * Bulk-select and delete delivery-window and shift templates from their list views. * Set a per-contract timeout for how long a job may wait without an assigned driver, configurable in the contract editor. #### Fixes **Nash Agent** * A single agent with an invalid saved configuration no longer takes down the whole agents list — it degrades gracefully while the rest stay available. * Disabling an agent or stopping an entity watch clears its outstanding confirmation prompts so they no longer linger in the Needs Attention queue. * Files the Agent generates in Slack (Excel, CSV, PDF) are reliably saved and delivered as downloads. * Run-count labels on the Pulse and runs-per-day charts are no longer clipped. **Platform API** * Orders created without a description or vehicle size apply package defaults reliably instead of failing pre-dispatch quote requests. * Dispatch-strategy swaps regenerate quotes against the new strategy and clear any stale selection, so orders ship on the correct provider. * Route stops keep stable, unique IDs across copy, split, and upsert, and a route completes automatically when its last open order is removed. * Manually-added job and order events trigger matching event-type workflows. * Address sync no longer overwrites merchant-owned provider store names. * Creating a courier with `workRelationship` or `dateOfBirth` set succeeds instead of returning a 400. * Dispatching an order with missing pickup or dropoff time windows returns a clear `TASK_MISSING_TIME_WINDOW` error. * Re-creating a package for the order that already owns it no longer raises a false duplicate-identifier error; genuine cross-order duplicates still return `RESOURCE_ALREADY_EXISTS`. * Auto reassign-on-failure evaluates provider eligibility against the fallback strategy's allowed providers. * AU PO Box dropoff addresses validate when the geocoder omits the postcode, and request-log search recovers records that were indexed with a null external identifier. * An order pointing at a soft-deleted dispatch strategy is recovered automatically when that is its only validation error. * Provider webhooks and quotes tolerate non-JSON content types and fractional-second timestamps. * Quote and provider type values always serialize as strings (for example, `PICK_AND_PACK`). * Webhook notifications that exceed the size limit are now retried automatically instead of failing. **Integrations** * Shopify subscription delivery orders with unmapped shipping codes are now created instead of skipped. * Gophr pickups that fail at collection now report as a failed delivery. **Fleet** * Individual pickup and dropoff markers reappear on the deliveries history map when zoomed in, with scheduled time and ETA back in the tooltips. * Changing items on a dispatched order reconciles per-item pick states, so added items become trackable and removed items no longer appear as phantom pending items in the driver app. Removing an order from dispatch resets its item pick states. * Drivers are no longer blocked from finishing a route after it's split mid-delivery — stop actions stay available. * The driver app captures proof-of-delivery photos in-app, resolving a camera crash on Android and iOS. * Route map markers render on iOS 26.5 without hanging the app. * A driver who acts on a route after one of its stops was removed now sees a clear "stop removed" dialog instead of a stale action. **Portal** * Workflow filters using "starts with" and "ends with" save and apply reliably. * Drawing zone, coverage, and route-restriction polygons works again. * Route times render in the depot's time zone across the Plan panel and the simulation drawer. ### Multiplayer Agents, Testable Workflows, and Smarter Route Control Multiplayer Nash Agent sessions, mid-run steering, and code execution with file artifacts. Plus testable workflows, a new Customers workspace, post-dispatch route edits, and routing that respects vehicle dimensions. #### New features **Nash Agent** * **Collaborative sessions:** Multiple teammates can join the same Nash Agent session live, see each other's messages, and work through investigations together in a single shared thread. * **Mid-run steering:** Send a follow-up while Nash Agent is still working and it folds the new direction into the current turn, with no cancellation or restart required. * **Code execution with file artifacts:** Nash Agent can run analyses on the fly and stream Excel, CSV, and chart files back into the chat as it works. * **Per-session token safety limits:** Admins can stop each Nash Agent session after a configured maximum number of tokens, set at the org level with tighter per-Custom Agent overrides. This safety control limits oversized runs; it does not set the price. * **Webhook-triggered custom agents:** External systems can kick off custom agent runs via a webhook authenticated by a Nash API key, with no user login required. **Workflows & automation** * **Workflow simulation:** A new Test surface in the workflow builder runs an entire workflow or a single step against mock data, validates channels and variable interpolation, and tags executions so they're easy to distinguish from real events. * **API workflow trigger:** Any workflow can be invoked directly through `POST /v1/workflows/{id}/trigger`, with optional input data and both synchronous and asynchronous execution modes. **Portal** * **Customers workspace:** A new Customers section in the Portal lists every customer in your org, with a detail view showing lifetime stats, recent orders, last-meter preferences, a 7×24 delivery-times heatmap, and recent proofs of delivery. **Optimization** * **Exception status lifecycle:** Route exceptions move through clear open, in-progress, resolved, and dismissed states, with consistent badges across the Portal so dispatch teams can track investigations. #### Improvements **Dispatch & route control** * **Post-dispatch route edits:** Operators can modify routes after they've been dispatched without canceling and recreating them, controlled by a new org preference. * **Auto-dispatch on route creation:** Newly created routes can be auto-dispatched the moment they're created (including via the routes API), and a failed dispatch attempt no longer blocks the route itself from being created. * **Configurable return routing:** Returns routing is configurable per store, so each location can pick the approach that matches its operations. **Driver app navigation** * **Default navigation app per driver:** Drivers can pick their preferred map app from a per-user setting, with Nash's in-app navigation available behind a per-fleet toggle. * **In-app navigation polish:** The map stays active throughout a trip, supports pinch-to-zoom and free panning, and shows a destination pin with which side of the road the dropoff is on. **Portal & API** * **Searchable activities:** The Activities screen has searchable display names, filter counts on every facet, and a new Store Location filter for faster triage. * **New webhook & API surfaces:** Custom form submissions emit a webhook event, order items are exposed in webhook payloads, and route assignments are available via GraphQL. * **Inbound delivery update API:** Internal fleets and Nash-connected partners can drive their delivery state (status, coordinates, proof of delivery, courier identity, and failures) via single or bulk `PATCH /v1/fleet/deliveries` endpoints authenticated by their org API key. **Optimization** * **Vehicle dimensions in route planning:** The routing engine factors vehicle height, weight, length, and width into route plans, so trucks and vans avoid road segments — including low bridges — they can't legally or safely use. ### Smarter Optimization, Multi-Session Agents, and Stronger Driver Tools A new Review & Dispatch surface for optimized routes, a replayable optimization workspace, and parallel Nash Agent sessions. The driver app gains route search, WhatsApp messaging, and route flags. #### New features **Optimization & dispatch** * **Optimization review:** A new Review & Dispatch surface in the Optimize Orders drawer separates routes that need attention from those safe to dispatch, with toggleable Table, Map, and Timeline views. * **Replayable optimization workspace:** A new Portal app lets teams replay past route plans with adjustable parameters to compare outcomes and tune dispatch strategies. **Nash Agent** * **Multi-session Nash Agent:** Manage multiple Nash Agent sessions in parallel, making it easier to track several investigations at once. * **Chat cards for Nash objects:** Nash Agent renders driver, order, and route information as rich, in-conversation cards for faster context. * **User-level Agent permissions:** Admins can scope Nash Agent access to specific users for finer-grained governance over who runs AI workflows. **Driver app** * **Route search and filtering:** Drivers can search routes by customer name or phone and group routes by merchant for faster trip selection. * **WhatsApp messaging:** Drivers can message customers via WhatsApp directly from the driver app, in addition to SMS. * **Route flags:** Dispatchers can flag routes that need attention, making it easier to track and triage problematic deliveries. #### Improvements **API & webhooks** * A new `route.stops_updated` webhook event fires whenever a route's stops change, keeping integrations in sync with mid-route adjustments. * **House photos in create-order API:** Drivers see a photo of the customer's dropoff location at the stop when a house-photo link is included on order creation. * **Report when a status change actually happened:** Status updates can carry the time the change occurred, so late-arriving updates are recorded at the right moment. * **Return condition tracking:** Drivers can mark returned items as good or damaged in the Driver App, and each update fires a webhook for downstream systems. **Portal refinements** * **Redesigned org switcher:** A search-first org picker on sign-in and in the sidebar, plus static sidebar chrome when toggling between main and settings views. * **Type-to-confirm reassignment:** A new confirmation dialog protects post-pickup driver reassignments from accidental clicks. **Driver app polish** * **Redesigned route card:** The driver app route card shows the delivery window more clearly and groups stops by merchant for at-a-glance trip planning. #### Fixes * The optimization results drawer no longer shows the wrong contract when opened from an activity link. * Portal shift links use path segments instead of query parameters, so the right shift opens consistently when shared. ### Smarter Coordination, Stronger Privacy, and More Control Across Workflows New AI Agents for staggered arrivals and face masking, a clearer prompt-to-response activity timeline, and smarter dispatch controls. #### New features * **Staggered Arrivals AI Agent:** Coordinates staggered arrival times to keep delivery scheduling efficient and reduce congestion at pickup and dropoff locations. Fully configurable from the Portal. * **Face Masking AI Agent:** Automatically detects and pixelates faces in proof-of-delivery photos to protect the privacy of people involved in deliveries. * **Structured prompt-to-response timeline:** The activity timeline groups prompts, tool calls, and responses into cohesive conversation cycles, making AI-driven workflows easier to follow and audit. * **Grouped tool calls under phases:** Tool calls are visually nested under phase updates (for example, "Investigating" or "Implementing") for a clearer progression of work. * **Compact passing quality checks:** Multiple passed quality-check rows condense into a single summary, reducing timeline clutter. * **Refund request experience:** The refund submission page displays separate Original, Requested, and Approved amounts, giving providers and customers more transparency at every step of review. * **Route optimization analytics:** Performance tracking for route optimization, including usage data and simulation insights. #### Improvements **Smarter dispatch controls** * **Dynamic Dispatch:** Settings include advanced customization of dispatch strategies and presets like "Balanced" and "Promise-Based." * The **Bird's Eye map view** includes dropoff and pickup time tags and business information for faster operational decisions. **Messaging & notifications** * Custom notification configurations are available in additional sandbox environments for testing real-world use cases. * Improved timing instrumentation across the notification pipeline. * Support for custom SMS sender settings, including Twilio accounts and alphanumeric sender IDs. * Notification pipelines support asynchronous webhook processing, reducing delays and improving throughput. **Consistency** * **Improved timeline icons:** A standardized icon system across the platform for better readability and consistency. #### Fixes * Delivery rescheduling workflows and timeline evaluations run reliably. * Notifications are no longer delayed by cache connection issues. * Dashboards load faster, and report data that was missing now appears. Looking for releases before 2026? Earlier entries are archived on the [Nash website release notes](https://usenash.com/changelog).