---
title: "Starts fulfilment of a sales order (the app's Begin Fulfillment button): creates the pick transfer."
method: POST
path: "/api/v1/sales-orders/{id}/fulfill"
tags: ["SalesOrders"]
---

# Starts fulfilment of a sales order (the app's Begin Fulfillment button): creates the pick transfer.

`POST /api/v1/sales-orders/{id}/fulfill`

## Request

- Base URL: `https://app.flexonthejob.com`
- URL: `https://app.flexonthejob.com/api/v1/sales-orders/{id}/fulfill`
- Auth: HTTP bearer

Requires `ManageSalesOrders` and an `Idempotency-Key` header; no body. Same code
path and rules as the Begin Fulfillment button: the order must not be `Complete` or
`Cancelled`; a pick transfer is created from the order's source locations (else the
organization's warehouse locations) to its delivery destination for every line not yet covered, using
available stock; the history records "Fulfillment started (via API key '…')"; the order's
`status` is then recalculated — `Picking` when the transfers cover every line,
otherwise it stays `Pending` (call again once stock arrives). The response always
carries the real status; never assume `Picking`. Replaying the same
`Idempotency-Key` returns the first response. A call with a new key creates another
transfer only for the quantity not yet covered by a live (not Cancelled/Failed) transfer —
a partly covered line is topped up, never claimed twice — and is refused with 409
`Every line is already covered by a transfer` when nothing is left to cover, the same
rule that shows the Begin Fulfillment button in the app. Next steps: `/pick`,
`/complete-pick`, `/ship`, `/deliver`, `/complete`.

## Path parameters

- `id` integer, required

## Response `200`

Fulfilment started; the resulting status and the pick transfer id.

- SalesOrderFulfillmentResponse: Response of `POST /api/v1/sales-orders/{id}/fulfill`.
  - `salesOrderId` integer: The sales order's id. Example: `318`.
  - `status` string: Order status after fulfilment started, recalculated from its transfers: `Picking` when the transfers cover every line, otherwise still `Pending` (some lines had no available stock; fulfil again once stock arrives). Example: `Picking`.
  - `transferId` integer, nullable: Id of the pick transfer created (see `/api/v1/transfers/{id}`). Example: `904`.
  - `message` string, nullable: Human-readable confirmation, as the app shows it. Example: `Order status updated to Picking`.

## Other responses

- `400`: Missing `Idempotency-Key`.
- `401`: Unauthorized
- `403`: Forbidden
- `404`: No such sales order in this organization.
- `409`: The order is `Complete` or `Cancelled` (detail: `Cannot transition from Complete to Picking`), every line is already covered by a transfer (detail: `Every line is already covered by a transfer`) or the order has no lines (`The order has no lines to fulfil`), the pick transfer could not be created (detail: `Error creating transfer: No source locations found for sales order`, or no available stock for any line), or the key's creator is unknown on this server. Nothing changed.
- `429`: Too Many Requests

---

[API](https://skmtc.dev/flexonthejob/apis/flex-api.md) · [All operations](https://skmtc.dev/flexonthejob/apis/flex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/flexonthejob/apis/flex-api/revisions/19c8f4d27acf?raw)
