# Create and change quotes and sales orders

> Create a draft quote or sales order, find and read them, change their header fields and run named actions, publish, ask the customer to approve, convert or clone, delete and restore, and read the history.

Web page: https://developer.yoprint.com/guides/orders/

**Read first:** [Quotes and sales orders](https://developer.yoprint.com/how-yoprint-works/quotes-and-sales-orders.md), for the draft and published model, statuses, dates, approvals and what else an order carries.

This guide creates and changes the order record itself: its customer, dates, status and the few operations that act on the whole order. Lines belong to the order too, but have their own guide: [Add line items](https://developer.yoprint.com/guides/line-items.md). Invoicing, payments and shipping have their own guides too: [Invoice an order](https://developer.yoprint.com/guides/invoices.md), [Take payments and refunds](https://developer.yoprint.com/guides/payments.md) and [Ship an order](https://developer.yoprint.com/guides/shipping.md).

**Diagram:** Each step has its own section below. Steps 5 and 6 don't apply to every order.

- **1. Create a draft**: Quote or sales order, with defaults from the team and customer
- **2. Read and find orders**: By id, a filtered list, or a quick search
- **3. Change it**: Header fields with update. Everything else with a named action
- **4. Publish**: Check `data.valid`: a failed publish can still answer `200`
- **5. Ask the customer to approve**: Only once published
- **6. Convert or clone**: Convert keeps the id. Clone makes a new draft
- **7. Delete and restore**: Deleting also undoes money and stock
- **8. Read the history**: Who changed what, and when

## 1. Create a draft

`POST /v2/api/store/{team_slug}/v2/sales_order/new` [Create a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-create.md)

```bash
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "client_note": "Prices valid for 30 days.",
    "customer_id": "63KcENAAsKuipY",
    "object_type": "quote"
  }'
```

- `object_type` is `quote` or `sales_order`. Send `quote` (or the older `quote: true`) for a quote. Leave both out for a sales order.
- `customer_id` is optional, but an order needs one before it can be published. An id that isn't one of your customers is ignored, not refused: the draft is created without a customer.
- The draft already has its order number (`scoped_id`, such as `SO1001`): your own, if you send `scoped_id`, or the team's next one.
- Its other defaults come from the team and the customer: today's issue date, the customer's (or team's) pricing group, payment term and warehouse, the customer's primary brand profile, the team's starting status, and due and production dates from the team's settings.

Add jobs and [line items](https://developer.yoprint.com/guides/line-items.md) next, then publish.

## 2. Read and find orders

Open one order or quote, with its line items, customer and message participants:

`GET /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}` [Get a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-show.md)

`{order_unique_key}` takes the order's `id` or its `unique_key` (`ord_…`). The order number people see, such as `SO1001`, is not accepted. Deleted orders come back too, with `trashed: true`.

List sales orders or quotes a page at a time, with filters and sorting. Each lists one kind only:

`POST /v2/api/store/{team_slug}/v2/sales_order/filter` [List sales orders](https://developer.yoprint.com/reference/sales-orders/v2-sales-order-filter.md)

`POST /v2/api/store/{team_slug}/v2/quote/filter` [List quotes](https://developer.yoprint.com/reference/quotes/v2-quote-filter.md)

```json
{
  "filters": [{ "key": "status_type", "operator": "in", "value": ["start", "in_progress"] }],
  "sort": [{ "key": "due_date", "direction": "asc" }]
}
```

- Rows come back without line items, each with its `customer` record, so you don't need a lookup per row.
- See [Filtering and sorting](https://developer.yoprint.com/concepts/filtering-and-sorting.md) for the filter keys, operators and the date-offset rules.

To search by a word or number, for a search box or a picker:

`GET /v2/api/store/{team_slug}/search/orders` [Search sales orders and quotes](https://developer.yoprint.com/reference/search/v2-search-orders-get.md)

```bash
curl "https://secure.yoprint.com/v2/api/store/your-team/search/orders?q=northwind&quote=0" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

- `q` matches anywhere in the order number, order name, customer PO number, customer name, or any part of the addresses. Several words can each match a different field.
- Returns at most 30 matches, highest order number first, with no paging. `quote=1` searches quotes only, `quote=0` sales orders only. For a `POST` body, send `"quote": true` or `false` instead.
- For more than 30 results, or for filters, use the list operations above instead.

## 3. Change an order

Update the header fields directly, sending only what changes:

`PUT /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}` [Update a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-update.md)

```json
{
  "customer_po_number": "PO-1190",
  "is_rush": true,
  "name": "Spring team shirts"
}
```

- This covers `name`, `scoped_id`, `customer_po_number`, `client_note`, `prod_note`, `is_rush`, the billing and shipping address ids, `customer_profile_id` and `customer_contact_id`, `created_by`, and `status_id`.
- Its `object_type` and `quote` fields take only the order's current value on a draft. They don't convert it. Use the `convert_to_quote` or `convert_to_sales_order` action for that.
- `status_id` here changes the status without sending the status webhooks (the change is still recorded in the history). Prefer the `change_status` action below, which sends them.

For everything else, run a named action, with the action's name in the path and its fields in the body:

`PUT /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/action/{action_name}` [Run an action on a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-single-action.md)

```bash
curl -X PUT https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/action/change_status \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{ "value": "52CuV8sWcDZLPm" }'
```

| Purpose                | `action_name`                                      | Body                                                                               | Draft | Published |
| ---------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------- | ----- | --------- |
| **Customer and terms** | `change_customer`                                  | `value`: customer id                                                               | yes   | no        |
|                        | `change_payment_term`                              | `payment_term_id`, `deposit_due`, `full_amount_due`, optional `deposit_total`      | yes   | yes       |
|                        | `change_pricing_group`                             | `pricing_group_id`                                                                 | yes   | yes       |
|                        | `change_pricing_strategy`                          | `pricing_strategy`                                                                 | yes   | yes       |
|                        | `change_warehouse`                                 | `warehouse_id`                                                                     | yes   | yes       |
| **Dates**              | `change_issue_date`                                | `value`: date. Moves the order's other dates, jobs and invoices with it            | yes   | yes       |
|                        | `change_customer_due_date`, `change_ship_due_date` | `value`: date or null                                                              | yes   | yes       |
|                        | `change_deposit_due`, `change_full_amount_due`     | `value`: date or null                                                              | yes   | yes       |
|                        | `change_start_end_date_date`                       | `start_date`, `due_date`: both or neither                                          | yes   | yes       |
| **Status and rush**    | `change_status`                                    | `value`: a sales order or quote status id, from the status list below              | no    | yes       |
|                        | `mark_order_as_rush`, `mark_order_as_not_rush`     | `{}`                                                                               | yes   | yes       |
|                        | `mark_all_job_as_rush`, `mark_all_job_as_not_rush` | `{}`                                                                               | yes   | yes       |
| **Convert and clone**  | `convert_to_quote`, `convert_to_sales_order`       | optional `status_id`, `pricing_group_id`                                           | yes   | yes       |
|                        | `clone_order`                                      | `is_time_shift`, `cloned_anchor_date`, `item_choices`, optional `pricing_group_id` | yes   | yes       |
| **Sending**            | `send_order_detail`                                | `{}`                                                                               | no    | yes       |
|                        | `send_payment_reminder`                            | `{}`                                                                               | no    | yes       |
|                        | `send_approval_reminder`                           | `{}`                                                                               | no    | yes       |

- `change_customer` resets the order's brand profile, addresses, contact, payment term, warehouse and pricing group from the new customer. It's refused if the draft already has a payment, a payment link, a sent message or linked stock.

- The `send_*` actions email the customer and count toward the team's daily email limit. Ask before calling one on someone's behalf.

- Status ids come from the team's status lists, one for sales orders and one for quotes. Statuses such as "In production" exist in the job list too: use the sales order one for an order.

  `GET /v1/api/store/{team_slug}/setting/status/sales_order` [List sales order statuses](https://developer.yoprint.com/reference/statuses/v1-status-list-sales-order.md)

- On an order synced from Shopify, only `change_status`, `change_customer_due_date`, `change_ship_due_date`, `change_start_end_date_date` and `send_order_detail` work. Every other action fails with `422`.

- An action that isn't allowed for the order's state (draft or published) fails with `object_action_error` "Action not found".

Two more operations set just a note, and can clear it:

`PUT /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/customer_note` [Set the Client Note of a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-customer-note-update.md)

`PUT /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/production_note` [Set the production note of a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-production-note-update.md)

```json
{ "customer_note": "Ship in two boxes." }
```

- The Client Note (printed on documents the customer receives) is `customer_note` on these dedicated operations, but `client_note` on create and update above. The production note (for your shop floor only) is `production_note` here, but `prod_note` on update.
- Both work on drafts, published, and even deleted orders. Send `null` to clear a note. Leave the field out to leave it unchanged.

## 4. Publish

`PATCH /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/publish` [Publish a draft sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-publish.md)

```bash
curl -X PATCH https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/publish \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

Publishing is the web app's **Publish** button: the draft becomes a real order, shows in lists and reports, and can take payments, approvals and comments.

- It checks four things, reported in `data.validation`: `has_customer`, `has_order_items`, `has_sellable_items` (no line on a phased-out or deleted product, size or color), and that the pricing group hasn't been removed (`removed_pricing_group`).
- A failed check still answers HTTP `200`, with `data.valid: false`. Always check `success` and `data.valid`, not the status code.
- `data.validation.unsellable_items` lists each unsellable line with its `reason`. Replace or remove those lines and publish again.
- `data.validation.removed_pricing_group` names the removed group, or is `null`. Choose another with the `change_pricing_group` action, then publish again.

## 5. Ask the customer to approve

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/approval` [Ask the customer to approve an order](https://developer.yoprint.com/reference/sales-order-approvals/v2-order-approval-store.md)

```bash
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/approval \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -d '{
    "notes_to_customer": "Please review the sizes and colors.",
    "send_later": false
  }'
```

- Creates an order approval on a **published** order and, by default, emails it. The customer opens it, types their name as a signature, and approves it or asks for changes.
- `send_later: true`, or an order with no customer, creates the approval without emailing it: its `status` stays `pending`. Send it later with the `send_approval_reminder` action or from the web app.

List an order's approvals:

`GET /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/approval` [List the order approvals of an order](https://developer.yoprint.com/reference/sales-order-approvals/v2-order-approval-index.md)

Record the customer's answer yourself, for example when they answered by phone:

`PUT /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/approval/{hash_id2}/complete` [Record the customer's answer to an order approval](https://developer.yoprint.com/reference/sales-order-approvals/v2-order-approval-complete.md)

```json
{ "signature": "Alex Rivera", "status": "completed" }
```

Send `status: "need-changes"` with `notes_from_customer` to record a change request instead. Always send `status`: leaving it out still completes the approval, with no status set.

> **Note: Job artwork approvals are separate**
>
> These order approvals ask the customer to approve the whole quote or order. A job's mockups get their own artwork approvals, one by one. Email either kind to the customer with the same operation:
>
> `PATCH /v1/api/store/{team_slug}/approvals/{hash_id}/send_to_customer` [Email an approval request to the customer](https://developer.yoprint.com/reference/approvals/v1-approvals-send-to-customer.md)

## 6. Convert or clone

Converting turns the record itself into the other kind, keeping its `id`:

`PUT /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/action/{action_name}` [Run an action on a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-single-action.md)

```bash
curl -X PUT https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/action/convert_to_sales_order \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{}'
```

- Run `convert_to_sales_order` on a quote, or `convert_to_quote` on a sales order. Both take an optional `status_id` (a status of the target kind, by default the team's starting status) and an optional `pricing_group_id`.
- Converting a sales order back to a quote releases its stock reservations.
- **Shopify rule:** a Shopify order, or an order whose customer is a Shopify customer, can't be converted, in either direction. Neither can a deleted order. Restore it first.

Cloning makes a copy instead, as a new draft with a new number and the starting status:

```json
{ "is_time_shift": false }
```

- `is_time_shift: true` shifts every date so the copy's issue date is `cloned_anchor_date`. `false` keeps the original dates.
- `item_choices` is required for each line on a phased-out or deleted product, size or color: leave it out, or replace it with another size or color of the same product.
- Shopify orders can't be cloned.

## 7. Delete and restore

`DELETE /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}` [Delete a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-destroy.md)

> **Caution: Deleting a published order undoes its money and stock**
>
> Payments on it move to the customer's store credit (not for Shopify orders), its stock workflows are cancelled and reservations released, and its Shippo labels are voided. Its invoices, jobs, shipments and messages are deleted with it.

- Deleting an order that's already deleted succeeds and changes nothing.

Bring it back, with everything deleted alongside it:

`PATCH /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/restore` [Restore a deleted sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-restore.md)

- Restoring an order that isn't deleted answers HTTP `200` with `success: false`. Check `success`, not the status code, before treating it as done.

## 8. Read the history

`GET /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/history` [List the history of a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-history.md)

Returns who changed what and when, newest first, a page at a time (`pagination.total`, `perPage`, `currentPage`).

```json
{ "action": "status_change", "message_key": "order.status_change" }
```

- Works on deleted orders too.
- Use this, not the retired v1 history.

## Pitfalls

- **`hide_draft` on the order search doesn't hide drafts**, despite its name. Check `internal_status` in the results instead.
- **Order approvals treat some orders specially.** Listing them returns an empty list for a Shopify order, and `404` for an old (version 1) order, the same as for an unknown one. Asking for one on a Shopify order is refused with `422`.
- **Clone and the two conversions keep the order's own pricing group.** If it was removed, they're refused with `clone_needs_pricing_group`. The refusal's `error.suggested_pricing_group` names one to send instead.
- **An unknown `customer_id` doesn't fail the create.** It makes a draft with no customer, and publishing fails later. A customer synced from Shopify is refused with `422`.
- **Amounts are decimal pairs or thousandths depending on the field**, and `deposit_total` in `change_payment_term` is a percentage sent as a pair with `dp` exactly `3`. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md).
- **Creates aren't idempotent.** If a create times out, search before you retry. See [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md).
