Take an order from quote to payment
AI agents: read /llms.txt; add .md to any page URL. This page as Markdown: /guides/quote-to-payment.md. Index of every page and the rules for every call: /llms.txt. Guides and concepts in one file: /llms-full.txt. OpenAPI: /openapi.json.
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, Add line items, Invoice an order and Take payments and refunds. It uses the words from Quotes and sales orders and Invoices and payments. 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:
- Draft quoteSteps 1 and 2: create it, then add a line
- QuoteStep 4: send it to the customer
- Sales orderSteps 6 to 8: invoice it, record a payment, read the balance
Before you start
Section titled “Before you start”You need your API key, your Team ID and a customer’s id. The Quickstart gets the first two and creates a test customer. Put all three in your shell, with your own values in place of these:
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.
1. Create a draft quote
Section titled “1. Create a draft quote”/v2/api/store/{team_slug}/v2/sales_order/new
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"}EOFThe answer is the new draft. Shortened:
{ "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:
export ORDER_ID="V4keXwwxMQyXw7"object_typeisquoteorsales_order. Without it, YoPrint creates a sales order. To skip the quote, sendsales_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.
2. Add a line
Section titled “2. Add a line”Add one line, here a custom line item with a name and a price:
POST Add a line item to a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/order_item
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.
The answer wraps the new line in item. Shortened:
{ "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:
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, and Add line items for the other kinds of line.
3. Publish the quote
Section titled “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.
/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/publish
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:
{ "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:falseif the draft has no customer (for example, if created with an unknowncustomer_id). Use thechange_customeraction to set one.has_order_items:falseif no lines have been added.has_sellable_items:falseif a line uses a phased-out or deleted product, variant or color.unsellable_itemslists each affected line with its reason (product_phased_out,product_deleted,variant_phased_outorvariant_deleted).removed_pricing_group: names the draft’s pricing group when it was removed or disabled. Choose another with thechange_pricing_groupaction.
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)
Section titled “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:
/v2/api/store/{team_slug}/v2/sales_order/{hash_id}/approval
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.
5. Convert the quote to a sales order
Section titled “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 Run an action on a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/action/{action_name}
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:
{ "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
Section titled “6. Create an invoice and bill the line”/v2/api/store/{team_slug}/v2/sales_order/{unique_key}/invoice
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:
{ "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:
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.
/v2/api/store/{team_slug}/v2/invoice/{unique_key}/item
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}EOFThe answer wraps the invoice line in item, like step 2. Shortened:
{ "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
POST Add, change and remove invoice lines at onceitem_actionswhen you create the invoice:/v2/ api/ store/ {team_slug}/ v2/ invoice/ {unique_key}/ item/ bulk -
On an order with Auto Invoice, YoPrint keeps the invoice for you and refuses invoices you create yourself.
7. Record a payment
Section titled “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:
/v1/api/store/{team_slug}/setting/payment_method
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"{ "status": 200, "success": true, "data": [ { "id": "StmZGqEqgq5tt6", "name": "Bank Transfer", "default": false, "trashed": false } ]}Pick one that isn’t trashed, and keep its id:
export PAYMENT_METHOD_ID="StmZGqEqgq5tt6"Then record the payment on the sales order.
POST Record a payment on a sales order/v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment
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"}EOFThe answer is the payment. Shortened:
{ "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:
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 Create a payment link for a sales order/v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment_link
8. Read back the balance
Section titled “8. Read back the balance”/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}
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:
{ "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_totalis what the order’s lines are worth: 300.00.invoiced_totalis what has been invoiced: 300.00.paid_amountis what’s been paid, in thousandths: 300.00.order_balance_dueis what’s left to pay,invoiced_totalminuspaid_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 and How the balance works.
Clean up
Section titled “Clean up”Delete in this order. Deleting a published order moves its payments to the customer’s credit, so delete the payment first.
-
The payment:
DELETE Delete a payment or refund/v2/ api/ store/ {team_slug}/ v2/ sales_order/ {hash_id}/ payment/ {payment_reference} Terminal window 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" -
The sales order. This also deletes its invoice and fires
DELETE Delete a sales order or quoteorder.deleted. It goes to the trash, where it can be restored./v2/ api/ store/ {team_slug}/ v2/ sales_order/ {order_unique_key} Terminal window 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 for the customer.
Change the order later
Section titled “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 Run an action on a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/action/{action_name} 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
Section titled “Common mistakes”- Reading the line’s id from
data.id. The add-line answer wraps the line initem: its id isdata.item.id.data.iddoesn’t exist there. - Trusting the
200from publish. Publish can “succeed” without publishing. Always checksuccessanddata.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
amountis thousandths (300000is 300.00). - Expecting a copy when you convert. The quote becomes the sales order, with the same id. Use
clone_orderfor a copy. - Reading
calc_total. It’s deprecated and stays 0 until the order is invoiced. Readorder_item_cart_totalandinvoiced_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.
