Add line items
AI agents: read /llms.txt; add .md to any page URL. This page as Markdown: /guides/line-items.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.
Read first: Products and pricing, for what a line can sell and how its price is worked out.
A quote or sales order is made of line items. Each line is one product, item or fee, with a price and a quantity, usually inside a job.
POST Add a line item to a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/order_item
Before you add lines: a job
Section titled “Before you add lines: a job”Lines that are produced together, such as the sizes of one shirt printed with one design, belong in one job. Create the job on the order first:
POST Add a job to a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{hash_id}/job
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/job \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Accept: application/json" \ -H "X-Yoprint-Decimal-Format: exact-v1" \ -H "Content-Type: application/json" \ -d '{ "name": "Front logo tees" }'The answer is the job, with its id in data.id. Use it in each line’s job. See Schedule and track production.
What are you adding?
Section titled “What are you adding?”Pick the kind of line first. It decides product.type, which decides pricing.type.
- A blank from a connected supplier, such as SanMarCatalog product
- One of the shop's own products, in ProductsYour simple product, or one variant of your variant product
- A charge on its own, such as setup or rushFee
- Anything elseCustom line item: a name and a price you write
| You’re adding | Kind | product.type |
pricing.type |
You name it by |
|---|---|---|---|---|
| A blank from a supplier’s catalog | Catalog product | db_catalog |
catalog_product_pricing |
The catalog product id, such as smr-10001 |
| One of your own products, with no sizes or colors | Your simple product | user_simple |
product_cost_service |
The product’s id |
| A size or color of one of your own products | Your variant product | db_variant |
product_cost_service |
The variant’s id |
| A one-off item that isn’t in any catalog | Custom line item | custom |
custom_product |
A name you write |
| A charge, such as a setup, rush or art fee | Fee | fee |
fee |
A name you write |
Send the pricing.type that matches the product.type. YoPrint refuses a mismatch with 422. In the web app, the Add Product dialog’s type filter calls your products “Custom Simple Product” and “Custom Variant Product”.
The shape every line shares
Section titled “The shape every line shares”{ "product": { "type": "...", "properties": { } }, "pricing": { "type": "...", "...": "..." }, "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } }, "job": { "type": "linked", "id": "HuhbGhG57Z7Qyi" }, "position": 100000}| Field | Rule |
|---|---|
product |
What the line is. type from the table above, and properties for that kind. |
pricing |
How it’s priced. type from the table above, and the fields for that kind. |
quantity |
Always "type": "unit", with total as a decimal pair. |
job |
{ "type": "linked", "id": "<job id>" } to put the line in a job, or { "type": "standalone" } for a line outside any job. |
position |
An integer: lines are shown by ascending position. The web app spaces lines 100000 apart, which leaves room to insert between them. A value outside the allowed range is replaced by the next free position at the end. |
For line items, every amount and quantity is a decimal pair (value ÷ 10^dp: { "value": 1250, "dp": 2 } is 12.50), and every price is for one piece. Sending a plain number such as 12.5 is refused with 422. Note that dp can vary across fields (line prices are often dp 6, quantities dp 0, order totals dp 4), and other areas of the API (such as payments) use thousandths (300000 is 300.00). See Money and quantities.
What comes back
Section titled “What comes back”Adding a line answers with the line wrapped in item, and the order’s new lock versions. Shortened:
{ "status": 200, "success": true, "data": { "action_performed": "CREATED", "has_changes": true, "object_type": "order_item", "item": { "id": "uEm77FEbwZEMvu", "order_id": "V4keXwwxMQyXw7", "job": { "type": "linked", "id": "HuhbGhG57Z7Qyi" }, "position": 100000, "unit_price": { "value": 12500000, "dp": 6 }, "subtotal": { "value": 3000000, "dp": 4 } } }}The line’s id is data.item.id, not data.id. Keep it: invoice and shipment lines point at the order line by it, and you change or delete the line with it.
How a line’s price is decided
Section titled “How a line’s price is decided”YoPrint checks these in order and uses the first that applies:
- A fee lineCharges exactly the amount in
pricing.product.total - "override": trueCharges exactly the unit price in
pricing.product.total, as the web app does - "customer_supplied": trueThe product price is 0
- Calculated priceThe cost (
pricing.catalog,pricing.product_cost.total, or the product’s price in Products), marked up by the order’s pricing group or a matching pricing rule
The final unit price adds any decoration price from the linked job (or pricing.service.total when service.override is true). The line subtotal is the unit price multiplied by quantity.
Why send both price and cost? Price (pricing.product) is what you charge the customer. Cost (pricing.product_cost or pricing.catalog) is what you pay the supplier or vendor. Even when you override the price, sending the cost ensures YoPrint can accurately calculate order profit and margin in reports.
The simplest line: a name and an amount.
{ "product": { "type": "fee", "properties": { "name": "Screen setup" } }, "pricing": { "type": "fee", "product": { "total": { "value": 2500, "dp": 2 } } }, "quantity": { "type": "unit", "total": { "value": 1, "dp": 0 } }, "job": { "type": "standalone" }, "position": 500000}product.properties.nameis the fee’s name.descriptionis optional.pricing.product.totalis the fee’s amount, here 25.00.
Your team’s saved fees are fee presets, with their names and amounts:
GET List fee presets/v1/api/store/{team_slug}/setting/fee_preset
A custom line item
Section titled “A custom line item”An item with a name and a price you set:
{ "product": { "type": "custom", "properties": { "name": "Screen printed tee", "customer_supplied": false } }, "pricing": { "type": "custom_product", "product": { "override": true, "total": { "value": 1250, "dp": 2 } }, "product_cost": { "total": { "value": 400, "dp": 2 } } }, "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } }, "job": { "type": "linked", "id": "HuhbGhG57Z7Qyi" }, "position": 100000}product.properties.nameandcustomer_suppliedare required.pricing.product.totalwithoverride: trueis the price for each piece, here 12.50.pricing.product_cost.totalis optional: your cost for each piece, used for profit and margin.
Your simple product
Section titled “Your simple product”One of your own products. Find its id with:
/v1/api/store/{team_slug}/product
{ "product": { "type": "user_simple", "properties": { "id": "5MnM49NNFDgB9A", "customer_supplied": false } }, "pricing": { "type": "product_cost_service", "product": { "override": true, "total": { "value": 1500, "dp": 2 } } }, "quantity": { "type": "unit", "total": { "value": 24, "dp": 0 } }, "job": { "type": "linked", "id": "HuhbGhG57Z7Qyi" }, "position": 200000}- This sets the selling price to 15.00 a piece.
- To let YoPrint price it instead, send
"product": { "override": false }. YoPrint then starts from the product’s price in Products, or frompricing.product_cost.totalwhen you send one, and applies the order’s pricing group or a matching pricing rule. pricing.product_costis optional:{ "override": true, "total": { "value": 600, "dp": 2 } }sets your cost, and a total of0means “use the product’s price”.- A product that has been phased out or deleted can’t be added to a new line.
Your variant product
Section titled “Your variant product”A size or color of one of your own products. List the product’s variants to find the variant’s id:
/v1/api/store/{team_slug}/product/{hash_id}/variant
The body is the same as for your simple product, with "type": "db_variant" and the variant’s id in product.properties.id. The size and color come from the variant. A phased-out or deleted variant can’t be added.
A catalog product
Section titled “A catalog product”A blank from a supplier catalog. Each catalog product id represents one size and color of a style and starts with the supplier’s code and a hyphen, such as smr-10001. For example, “24 tees in three sizes” requires three separate lines in the same job.
Adding a catalog line requires three calls in order:
- Get the style’s sizes and colors (
hydrate_imported_product) to retrieve all sizes, colors, and variant IDs (attr_data). - Get the supplier’s cost (
real_time_data_fetcher_pricing) to look up live supplier pricing for those IDs. Catalog lines require an upfront supplier cost, but supplier prices change frequently and are not stored in the style definition. - Add the line item (
order_itemstore) with that supplier cost inpricing.catalog.
-
Get the style’s sizes and colors. Send any catalog product id of the style (
POST Load a product for a sales order linedb_catalog), or the style’s own id (db_catalog_style):/v1/ api/ store/ {team_slug}/ hydrate_product { "type": "db_catalog", "id": "smr-10001" }The answer is the whole style. Each row of
attr_datais one size and color, with itsid,size_label,color_label,provideranddscnt(true when the supplier has discontinued it). The style’s own id is instyle_id. -
Get the supplier’s cost for the ids you chose, up to 100 at once:
POST Get live supplier pricing for catalog products/v1/ api/ store/ {team_slug}/ real_time_data_fetcher/ pricing { "product": [{ "type": "db_catalog", "id": "smr-10002" }] }The answer has
real_time_pricing, fetched live from the supplier with your team’s connection, andlocal_pricing, YoPrint’s stored copy of the price list. Pick the price level you sell from. -
Add the line, with that cost in
pricing.catalog:{"product": {"type": "db_catalog","properties": { "id": "smr-10002", "customer_supplied": false }},"pricing": {"type": "catalog_product_pricing","catalog": {"provider": "smr","selected": "piece","date": "2026-11-03","total": { "value": 450, "dp": 2 }},"product": { "override": false }},"quantity": { "type": "unit", "total": { "value": 12, "dp": 0 } },"job": { "type": "linked", "id": "HuhbGhG57Z7Qyi" },"position": 300000}
pricing.catalogis the supplier cost the line starts from. A new catalog line must have it. Without it, a single add answers422with “Enter the catalog cost for this line.” onpricing.catalog, and a bulk create answers422bulk_validation_payload_failed. All four of its fields are required:provider: the supplier code, from the row’sproviderin step 1. It’s the part of the id before the hyphen:smrforsmr-10002.selected: the price level, one ofpiece,dozen,case,account,sales,customorlegacy.date: the date of the price.total: the cost for one piece.
- With
"product": { "override": false }, YoPrint marks the cost up with the order’s pricing group or a matching pricing rule. To charge your own price instead, send"product": { "override": true, "total": { "value": 900, "dp": 2 } }. - When the customer supplies the garment, send
"customer_supplied": true. Thenpricing.catalogandpricing.productare both optional.
Read the lines back
Section titled “Read the lines back”The answer to each add is the new line. To see all of an order’s lines, and the new totals:
GET List the line items of a sales order or quote/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/order_item
GET Get a sales order or quote /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}
Change or remove a line
Section titled “Change or remove a line”/v2/api/store/{team_slug}/v2/sales_order/order_item/{order_item_unique_key}
Send only what changes. A line’s kind can’t change: to turn a custom line item into a catalog product, delete it and add a new line.
DELETE Delete a line item/v2/api/store/{team_slug}/v2/sales_order/order_item/{order_item_unique_key}
Many lines at once
Section titled “Many lines at once”The bulk operation takes a JSON array of actions on one order. Each action is one of three shapes:
| Field | create |
update |
delete |
|---|---|---|---|
bid |
Required: your own label for the action, such as line-1 |
Required | Required |
action |
"create" |
"update" |
"delete" |
payload |
Required: the same body as a single add | Required: the same body as a single update (only what changes) | Not sent |
cart_item_id |
Not sent | Required: the line’s id |
Required: the line’s id |
/v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}/order_item/bulk
[ { "bid": "line-1", "action": "create", "payload": { "product": { "type": "fee", "properties": { "name": "Rush fee" } }, "pricing": { "type": "fee", "product": { "total": { "value": 5000, "dp": 2 } } }, "quantity": { "type": "unit", "total": { "value": 1, "dp": 0 } }, "job": { "type": "standalone" }, "position": 600000 } }, { "bid": "line-2", "action": "delete", "cart_item_id": "uEm77FEbwZEMvu" }]Pitfalls
Section titled “Pitfalls”quantity.typeis alwaysunit. Sizes come from the product or variant, not from the quantity.- Send
override: truewith your price. Without it, a pricing rule or the pricing group can change the price, silently. pricing.typemust matchproduct.type. Use the table at the top.- Catalog lines need their catalog cost. Send
pricing.catalogunless the customer supplies the product. - Amounts are pairs. Plain numbers like
12.5are refused with422. Send{ "value": 1250, "dp": 2 }. Remember that other areas, such as payments, use thousandths. See Money and quantities. - Each size is its own line. For a catalog shirt in four sizes, add four lines, one per catalog product id from
attr_data, in the same job. - The kind is fixed. An update can’t change
product.type. - Phased-out products are refused on new lines. Existing lines keep working, but a draft holding one can’t be published until the line is changed.
- Totals change on every add. Read the order back for the new totals rather than adding them up yourself.
