---
title: "create sales order"
method: POST
path: "/api/v1/sales/orders"
tags: ["orders"]
---

# create sales order

`POST /api/v1/sales/orders`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/sales/orders`
- Auth: HTTP bearer

Creates one order Tightly owns from the moment it exists: its lines, the channel or the account it belongs to, where it ships and what it is worth. Send an `Idempotency-Key` header; the same key with the same body answers the same order, the same key with a different body is refused, and a key sent while the first attempt is still running is refused with `Retry-After`. Tightly keeps a key 30 days.

Orders that come from a connected channel are never created here: they arrive on the sync and changing them is done in the channel. A file of orders goes through `POST /sales/orders/import`, and a buyer's 850 arrives as a draft through EDI.

Scope: `orders:write`, which includes `orders:read`.

## Headers

- `Idempotency-Key` string, required
- `Tightly-Version` string

## Request body

- CreateSalesOrderRequest
  - `cancel_date` string, nullable
  - `currency` string, nullable
  - `customer` union
    - CustomerArg
      - `display_name` string, nullable
      - `email` string, nullable
    - object, nullable
  - `external_reference` string, nullable
  - `is_wholesale` boolean
  - `lifecycle` 'open' | 'draft'
  - `lines` SalesOrderLineArg[], required
    - `quantity` integer, required
    - `sku` string, nullable
    - `unit_price_cents` integer, nullable
    - `variant_id` string, nullable
  - `location_id` string, nullable
  - `origin` 'manual' | 'api'
  - `requested_delivery_end` string, nullable
  - `requested_delivery_start` string, nullable
  - `requested_ship_date` string, nullable
  - `sales_channel_id` string, nullable
  - `ship_to` union
    - ShipToArg
      - `address1` string, nullable
      - `address2` string, nullable
      - `city` string, nullable
      - `company` string, nullable
      - `country_code` string, nullable
      - `name` string, nullable
      - `phone` string, nullable
      - `postcode` string, nullable
      - `region` string, nullable
    - object, nullable
  - `trading_partner_id` string, nullable

## Response `201`

The order as recorded.

- object
  - `data` object: The order, in the shape GET /sales/orders/{order_id} serves.
  - `message` object: severity, service and desc.

## Other responses

- `400`: A line has no product or no positive quantity (`quantity_not_positive`, `variant_unknown`); the body could not be read; no Idempotency-Key was sent (`idempotency_key_required`), or the one sent is longer than 255 characters (`idempotency_key_too_long`); no account, channel or warehouse is on file for the one named (`account_unknown`, `channel_unknown`, `location_unknown`); a line has no price on file, or its price on file is in another currency than the order's (`price_unknown`); a date is not written YYYY-MM-DD (`date_unreadable`).
- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Orders; `organization_mismatch` when the request names an organisation that is not the key's; `ip_not_allowed` when the caller's address is outside the key's allowlist; `account_mismatch` when a key issued to one account names another, in a sentence naming both.
- `409`: `idempotency_key_reused` when the key was already used for a different body; `idempotency_key_in_flight` when the first attempt on this key is still running, and the response carries `Retry-After`.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
