# Take an order from quote to payment

> Create a quote for a customer, add a line, publish it, convert it to a sales order, invoice it and record a payment, then read back the balance. Every step shows its request and the answer it gets.

Web page: https://developer.yoprint.com/guides/quote-to-payment/

This guide takes one order from a customer's request to a paid sales order, one operation per step. Every step shows the request and a shortened answer, and says which value to keep for the next step. Each step links to the guide with the full details: [Create and change orders](https://developer.yoprint.com/guides/orders.md), [Add line items](https://developer.yoprint.com/guides/line-items.md), [Invoice an order](https://developer.yoprint.com/guides/invoices.md) and [Take payments and refunds](https://developer.yoprint.com/guides/payments.md). It uses the words from [Quotes and sales orders](https://developer.yoprint.com/how-yoprint-works/quotes-and-sales-orders.md) and [Invoices and payments](https://developer.yoprint.com/how-yoprint-works/invoices-and-payments.md). Read those first if draft, publish or convert are new.

The order is one record the whole way. Each step changes what it is, never its id:

**Diagram:** Keep the id from step 1: every later step uses it.

1. **Draft quote**: Steps 1 and 2: create it, then add a line → 3. publish
2. **Quote**: Step 4: send it to the customer → 5. convert
3. **Sales order**: Steps 6 to 8: invoice it, record a payment, read the balance

> **Caution:** This runs on your real team's data. Use a test customer whose only contact is you, because step 4 emails the customer. [Clean up](#clean-up) at the end deletes the order, its invoice and its payment.

## Before you start

You need your API key, your Team ID and a customer's `id`. The [Quickstart](https://developer.yoprint.com/start/quickstart.md) gets the first two and creates a test customer. Put all three in your shell, with your own values in place of these:

```bash
export YOPRINT_API_KEY="YOUR_API_KEY"
export YOPRINT_TEAM="your-team"
export CUSTOMER_ID="VwYKuTP9vd8Dfo"
```

Each request below uses these variables. Requests with a body use `-d @- <<EOF ... EOF`, so the shell fills the variables in the body too. In `sales_order/…` paths, `{hash_id}`, `{order_unique_key}` and `{unique_key}` are all the order's `id`. In `invoice/…` paths, they're the invoice's `id` from step 6. See [IDs](https://developer.yoprint.com/concepts/ids.md).

## 1. Create a draft quote

`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/$YOPRINT_TEAM/v2/sales_order/new" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "object_type": "quote",
  "customer_id": "$CUSTOMER_ID",
  "customer_po_number": "PO-1001"
}
EOF
```

The answer is the new draft. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "V4keXwwxMQyXw7",
    "object_type": "quote",
    "internal_status": "draft",
    "customer_id": "VwYKuTP9vd8Dfo",
    "customer": { "id": "VwYKuTP9vd8Dfo", "name": "TEST Northwind Apparel" },
    "customer_po_number": "PO-1001",
    "order_item_cart_total": { "value": 0, "dp": 0 }
  }
}
```

Keep `data.id`. Every later step uses it:

```bash
export ORDER_ID="V4keXwwxMQyXw7"
```

- `object_type` is `quote` or `sales_order`. Without it, YoPrint creates a sales order. To skip the quote, send `sales_order`, then skip steps 4 and 5.
- The new record is a **draft**. Its dates come from the team's settings, its pricing group and payment terms from the customer's defaults or the team's, and its currency is the team's.

> **Caution:** Check `data.customer` in the answer. If `customer_id` doesn't match a customer on the team, YoPrint still creates the draft, without a customer. Publishing then fails with `data.valid: false` and `validation.has_customer: false`. Set the customer with the `change_customer` action before you publish (see [Change the order later](#change-the-order-later)).

## 2. Add a line

Add one line, here a custom line item with a name and a price:

`POST /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/order_item` [Add a line item to a sales order or quote](https://developer.yoprint.com/reference/sales-order-line-items/v2-order-item-store.md)

```bash
curl "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/order_item" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "product": {
      "type": "custom",
      "properties": { "name": "Screen printed tee", "customer_supplied": false }
    },
    "pricing": {
      "type": "custom_product",
      "product": { "override": true, "total": { "value": 1250, "dp": 2 } }
    },
    "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } },
    "job": { "type": "standalone" },
    "position": 100000
  }'
```

That's 24 pieces at 12.50 each. Amounts and quantities are decimal pairs (`value` ÷ 10^`dp`: `1250` with `dp` 2 is 12.50, and `24` with `dp` 0 is 24). See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md).

The answer wraps the new line in `item`. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "action_performed": "CREATED",
    "has_changes": true,
    "object_type": "order_item",
    "item": {
      "id": "uEm77FEbwZEMvu",
      "order_id": "V4keXwwxMQyXw7",
      "type": "custom",
      "position": 100000,
      "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } },
      "unit_price": { "value": 12500000, "dp": 6 },
      "subtotal": { "value": 3000000, "dp": 4 }
    }
  }
}
```

Keep `data.item.id`, the line's id. Step 6 bills this line by it:

```bash
export LINE_ID="uEm77FEbwZEMvu"
```

The answer keeps prices at 6 decimal places and the subtotal at 4, so `unit_price` is 12.500000 and `subtotal` is 300.0000. Always read `dp`.

`"override": true` charges exactly the price you send. Without it, YoPrint works the price out from the cost, with the order's pricing group markup or a matching pricing rule, so also send the cost (`pricing.product_cost`) to keep profit and margin right. See [How a line's price is worked out](https://developer.yoprint.com/how-yoprint-works/products-and-pricing.md#how-a-lines-price-is-worked-out), and [Add line items](https://developer.yoprint.com/guides/line-items.md) for the other kinds of line.

## 3. Publish the quote

Publishing makes the draft a real quote. From now on its changes are recorded in its history, and it can be sent and approved. It fires `quote.created`.

`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/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/publish" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

When it worked, `data.valid` is `true`:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "V4keXwwxMQyXw7",
    "valid": true,
    "validation": {
      "has_customer": true,
      "has_order_items": true,
      "has_sellable_items": true,
      "unsellable_items": []
    }
  }
}
```

Publishing answers `200` even when it can't publish. Check `data.valid`. When a check fails, it's `false`, the draft stays a draft, and `validation` says which check failed:

- `has_customer`: `false` if the draft has no customer (for example, if created with an unknown `customer_id`). Use the `change_customer` action to set one.
- `has_order_items`: `false` if no lines have been added.
- `has_sellable_items`: `false` if a line uses a phased-out or deleted product, variant or color. `unsellable_items` lists each affected line with its reason (`product_phased_out`, `product_deleted`, `variant_phased_out` or `variant_deleted`).
- `removed_pricing_group`: names the draft's pricing group when it was removed or disabled. Choose another with the `change_pricing_group` action.

Publishing a quote that's already published answers `200` with `"success": false` and `error.code` `action_not_allowed`, and changes nothing.

## 4. Send it to the customer (optional)

To email the customer the quote, use the `send_order_detail` action. To ask for their approval, create an approval:

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

Both email the customer at once, and count toward your team's email limits. Sending more than 60 customer emails in a minute blocks your whole team's email until YoPrint support lifts it. See [Limits](https://developer.yoprint.com/concepts/limits.md#email-sending).

## 5. Convert the quote to a sales order

When the customer accepts, convert it. The record keeps its id and becomes a sales order:

`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/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/action/convert_to_sales_order" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The answer is the order, now a sales order with the same `id`. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "V4keXwwxMQyXw7",
    "object_type": "sales_order",
    "internal_status": "published",
    "order_item_cart_total": { "value": 3000000, "dp": 4 }
  }
}
```

- It starts in the team's first sales order status. To choose another, send `"status_id"` with a sales order status id.
- It fires `quote.converted_to_sales_order`.
- Quotes synced from Shopify, or made for a customer synced from Shopify, can't be converted.

## 6. Create an invoice and bill the line

`POST /v2/api/store/{team_slug}/v2/sales_order/{unique_key}/invoice` [Create an invoice for a sales order](https://developer.yoprint.com/reference/invoices/v2-sales-order-invoice-create.md)

```bash
curl "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/invoice" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{ "issue_date": "2026-11-03T00:00:00Z" }'
```

The answer is the new, empty invoice. Its addresses come from the order. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "QRbvVd2ZKcvSTB",
    "order_id": "V4keXwwxMQyXw7",
    "issue_date": "2026-11-03T00:00:00+00:00",
    "is_paid": false
  }
}
```

Keep `data.id`, the invoice's id:

```bash
export INVOICE_ID="QRbvVd2ZKcvSTB"
```

Then add the order's line to it. An invoice line points at an order line by that line's `id`: the `data.item.id` you kept in step 2.

`POST /v2/api/store/{team_slug}/v2/invoice/{unique_key}/item` [Add a line to an invoice](https://developer.yoprint.com/reference/invoice-line-items/v2-invoice-item-create.md)

```bash
curl "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/invoice/$INVOICE_ID/item" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "product": { "type": "order_item", "properties": { "id": "$LINE_ID" } },
  "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } },
  "position": 100000
}
EOF
```

The answer wraps the invoice line in `item`, like step 2. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "action_performed": "CREATED",
    "has_changes": true,
    "object_type": "invoice_item",
    "item": {
      "id": "nhsGsn84PyJBvK",
      "invoice_id": "QRbvVd2ZKcvSTB",
      "type": "order_item",
      "product": { "type": "order_item", "properties": { "id": "uEm77FEbwZEMvu" } },
      "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } }
    }
  }
}
```

- One invoice can't bill the same order line twice: a second time answers `422` "Invoice already has the same Order Item".

- An order can have several invoices, for example a deposit invoice and a final one.

- To add many lines at once, use the bulk operation, or send them in `item_actions` when you create the invoice:

  `POST /v2/api/store/{team_slug}/v2/invoice/{unique_key}/item/bulk` [Add, change and remove invoice lines at once](https://developer.yoprint.com/reference/invoice-line-items/v2-invoice-item-bulk.md)

- On an order with Auto Invoice, YoPrint keeps the invoice for you and refuses invoices you create yourself.

## 7. Record a payment

Get the id of the payment method you were paid with, such as cash or bank transfer. `payment_method_id` is optional, but without it the payment has no method:

`GET /v1/api/store/{team_slug}/setting/payment_method` [List payment methods](https://developer.yoprint.com/reference/payment-methods/v1-payment-method-index.md)

```bash
curl "https://secure.yoprint.com/v1/api/store/$YOPRINT_TEAM/setting/payment_method" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

```json
{
  "status": 200,
  "success": true,
  "data": [
    { "id": "StmZGqEqgq5tt6", "name": "Bank Transfer", "default": false, "trashed": false }
  ]
}
```

Pick one that isn't `trashed`, and keep its `id`:

```bash
export PAYMENT_METHOD_ID="StmZGqEqgq5tt6"
```

Then record the payment on the sales order.

> **Caution: Payment amounts are thousandths**
>
> A payment `amount` is a whole number of **thousandths** of the currency, not a decimal pair: `300000` records 300.00. A number with decimals, such as `300000.5`, is refused with `422`. It's the only money field in this guide that isn't a `{ value, dp }` pair. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md#sending-amounts).

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment` [Record a payment on a sales order](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-store.md)

```bash
curl "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/payment" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "payment_method_id": "$PAYMENT_METHOD_ID",
  "amount": 300000,
  "date": "2026-11-03T15:00:00Z",
  "reference_id": "Bank ref 1001"
}
EOF
```

The answer is the payment. Shortened:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "hAwWd7vPrqjuHD",
    "order_id": "V4keXwwxMQyXw7",
    "type": "MANUAL",
    "subtype": "PAYMENT",
    "amount": 300000,
    "payment_method_id": "StmZGqEqgq5tt6",
    "reference_id": "Bank ref 1001",
    "date": "2026-11-03T15:00:00+00:00"
  }
}
```

Keep `data.id` if you'll clean up afterwards:

```bash
export PAYMENT_ID="hAwWd7vPrqjuHD"
```

Recording a payment doesn't charge anyone. It records money you already received, and the order must be published. It updates the order's balance and invoices, and YoPrint doesn't email the customer a receipt for it.

It fires these webhooks:

- `payment_received.any_amount`, for every payment.
- `payment_received.full_amount`, when nothing is left to pay.
- `payment_received.deposit_amount`, only when the total paid is **exactly** the deposit. A payment that goes past the deposit doesn't send it.

To act once the deposit is paid, listen to `payment_received.any_amount`, then read the order and check that `deposit_amount_due` is 0 or less.

To split a payment across the order's invoices, create a payment link. Despite the name, it isn't a page your customer opens, and it moves no money:

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment_link` [Create a payment link for a sales order](https://developer.yoprint.com/reference/payment-links/v2-sales-order-payment-link-create.md)

## 8. Read back the balance

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

```bash
curl "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID" \
  -H "Authorization: Bearer $YOPRINT_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

Shortened, for this guide's order with no tax:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "V4keXwwxMQyXw7",
    "object_type": "sales_order",
    "order_item_cart_total": { "value": 3000000, "dp": 4 },
    "invoiced_total": { "value": 3000000, "dp": 4 },
    "paid_amount": 300000,
    "order_balance_due": { "value": 0, "dp": 4 }
  }
}
```

- `order_item_cart_total` is what the order's lines are worth: 300.00.
- `invoiced_total` is what has been invoiced: 300.00.
- `paid_amount` is what's been paid, in **thousandths**: 300.00.
- `order_balance_due` is what's left to pay, `invoiced_total` minus `paid_amount`: 0.

The totals are decimal pairs: read `dp` every time. If your team adds tax to invoices, your totals are higher and some balance is left. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md) and [How the balance works](https://developer.yoprint.com/how-yoprint-works/invoices-and-payments.md#how-the-balance-works).

## Clean up

Delete in this order. Deleting a published order moves its payments to the customer's credit, so delete the payment first.

1. The payment:

   `DELETE /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{payment_reference}` [Delete a payment or refund](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-destroy.md)

   ```bash
   curl -X DELETE "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID/payment/$PAYMENT_ID" \
     -H "Authorization: Bearer $YOPRINT_API_KEY" \
     -H "Accept: application/json" \
     -H "X-Yoprint-Decimal-Format: exact-v1"
   ```

2. The sales order. This also deletes its invoice and fires `order.deleted`. It goes to the trash, where it can be restored.

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

   ```bash
   curl -X DELETE "https://secure.yoprint.com/v2/api/store/$YOPRINT_TEAM/v2/sales_order/$ORDER_ID" \
     -H "Authorization: Bearer $YOPRINT_API_KEY" \
     -H "Accept: application/json" \
     -H "X-Yoprint-Decimal-Format: exact-v1"
   ```

Both answer `{ "status": 200, "success": true, "data": null }`. See [Test your integration](https://developer.yoprint.com/start/testing.md#clean-up) for the customer.

## Change the order later

**Dates, status, payment terms and other changes**

Most other changes to a quote or order go through one operation, with the action's name in the path:

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

Actions include changing dates (`change_issue_date`, `change_customer_due_date`, `change_start_end_date_date`, `change_ship_due_date`, `change_deposit_due`, `change_full_amount_due`), `change_status`, `change_payment_term`, `change_pricing_group`, `mark_order_as_rush`, `clone_order`, and the two conversions. `change_customer` works only on drafts. `change_status` and the sending actions work only after publishing.

## Common mistakes

- **Reading the line's id from `data.id`.** The add-line answer wraps the line in `item`: its id is `data.item.id`. `data.id` doesn't exist there.
- **Trusting the `200` from publish.** Publish can "succeed" without publishing. Always check `success` and `data.valid`.
- **A wrong `customer_id`.** It doesn't fail the create. It makes a draft without a customer, and publishing fails later.
- **Sending a payment as a decimal pair.** A payment `amount` is thousandths (`300000` is 300.00).
- **Expecting a copy when you convert.** The quote becomes the sales order, with the same id. Use `clone_order` for a copy.
- **Reading `calc_total`.** It's deprecated and stays 0 until the order is invoiced. Read `order_item_cart_total` and `invoiced_total`.
- **Creating an invoice on an Auto Invoice order.** YoPrint refuses it.
- **Retrying a create that timed out.** Creates aren't idempotent: a retry makes a second order, line or payment. Read back first. See [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md).
