Skip to content

Add line items

Open .md

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

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
Terminal window
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.

Pick the kind of line first. It decides product.type, which decides pricing.type.

  1. A blank from a connected supplier, such as SanMarCatalog product
  2. One of the shop's own products, in ProductsYour simple product, or one variant of your variant product
  3. A charge on its own, such as setup or rushFee
  4. Anything elseCustom line item: a name and a price you write
Not sure the shop already has the product? Look it up first: see Look up products and prices.
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”.

{
"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.

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.

YoPrint checks these in order and uses the first that applies:

  1. A fee lineCharges exactly the amount in pricing.product.total
  2. "override": trueCharges exactly the unit price in pricing.product.total, as the web app does
  3. "customer_supplied": trueThe product price is 0
  4. 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
YoPrint uses the first rule that applies.

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.name is the fee’s name. description is optional.
  • pricing.product.total is 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

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.name and customer_supplied are required.
  • pricing.product.total with override: true is the price for each piece, here 12.50.
  • pricing.product_cost.total is optional: your cost for each piece, used for profit and margin.

One of your own products. Find its id with:

GET List products /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 from pricing.product_cost.total when you send one, and applies the order’s pricing group or a matching pricing rule.
  • pricing.product_cost is optional: { "override": true, "total": { "value": 600, "dp": 2 } } sets your cost, and a total of 0 means “use the product’s price”.
  • A product that has been phased out or deleted can’t be added to a new line.

A size or color of one of your own products. List the product’s variants to find the variant’s id:

GET List a product's variants /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 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:

  1. Get the style’s sizes and colors (hydrate_imported_product) to retrieve all sizes, colors, and variant IDs (attr_data).
  2. 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.
  3. Add the line item (order_item store) with that supplier cost in pricing.catalog.
  1. Get the style’s sizes and colors. Send any catalog product id of the style (db_catalog), or the style’s own id (db_catalog_style):

    POST Load a product for a sales order line /v1/api/store/{team_slug}/hydrate_product
    { "type": "db_catalog", "id": "smr-10001" }

    The answer is the whole style. Each row of attr_data is one size and color, with its id, size_label, color_label, provider and dscnt (true when the supplier has discontinued it). The style’s own id is in style_id.

  2. 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, and local_pricing, YoPrint’s stored copy of the price list. Pick the price level you sell from.

  3. 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.catalog is the supplier cost the line starts from. A new catalog line must have it. Without it, a single add answers 422 with “Enter the catalog cost for this line.” on pricing.catalog, and a bulk create answers 422 bulk_validation_payload_failed. All four of its fields are required:
    • provider: the supplier code, from the row’s provider in step 1. It’s the part of the id before the hyphen: smr for smr-10002.
    • selected: the price level, one of piece, dozen, case, account, sales, custom or legacy.
    • 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. Then pricing.catalog and pricing.product are both optional.

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} PUT Update a line item /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}

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
POST Add, change and delete line items in one save /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" }
]
  • quantity.type is always unit. Sizes come from the product or variant, not from the quantity.
  • Send override: true with your price. Without it, a pricing rule or the pricing group can change the price, silently.
  • pricing.type must match product.type. Use the table at the top.
  • Catalog lines need their catalog cost. Send pricing.catalog unless the customer supplies the product.
  • Amounts are pairs. Plain numbers like 12.5 are refused with 422. 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.