Skip to content

Create an invoice for a sales order

Open .md

POST/v2/api/store/{team_slug}/v2/sales_order/{unique_key}/invoice

v2Operation ID v2_public_sales_order_invoice_create

Creates an invoice for an order, optionally with its lines in the same call.

The invoice starts with the order's billing and shipping address and issue date unless you send billing_address_id, shipping_address_id or issue_date. It gets the team's next invoice number.

Send {} for an empty invoice. Lines go in item_actions, in the same shape as the invoice line bulk operation: each entry has your bid, an action (create, update or delete), cart_item_id for updates and deletes, and a payload. Without item_actions the invoice is created empty. tax_selection (with both expected_tax and desired_tax) sets the invoice's tax as Set the tax of an invoice does; resort_items: true re-sorts the lines.

Send the header X-Yoprint-Decimal-Format: exact-v1. Without it, a save that would return an amount the old number format can't carry is refused with 422 amount_out_of_range.

The answer is the invoice (with its lines when you sent item_actions, tax_selection or resort_items). {unique_key} is the order's id (for example V4keXwwxMQyXw7) or its unique_key (ord_…).

Permission

Any member of the team.

Business rules

  • Refused for Shopify orders: "Cannot create invoice for a Shopify-synced order."
  • Refused when the order keeps its invoice up to date automatically: "You cannot create an invoice for an auto managed order." Turn that off with the configure_order_auto_invoice_managed action first.
  • When tax_selection is sent it must contain both expected_tax and desired_tax (either may be null).

Side effects

  • Creates the invoice and its lines and refreshes the order for people who have it open in the web app; on a published sales order, Customer Portal pages the customer has open refresh too. Nothing is emailed.
  • With item_actions, recalculates the order's invoiced totals and spreads its payments across the invoices again (money of a payment frozen by an open dispute or review stays where it is).

Path parameters

  • team_slugstringrequired

    Your team slug, shown as Team ID next to your API key in Profile.

    • Example your-team
  • unique_keystringrequired

    The sales order's or quote's id (a hash id), or its unique_key (ord_…). The order number (scoped_id) does not work.

    • Example V4keXwwxMQyXw7

Request body

application/json, required.

  • billing_address_idstring | null

    The billing address id. Default: the order's.

  • issue_datestring (date)

    The invoice date (ISO 8601). Default: the order's issue date.

  • item_actionsarray of objects

    Invoice line operations, each with your own bid: create with payload; update with cart_item_id and payload; delete with cart_item_id.

    Fields of each item (4)
    • actionstringrequired
      • One of: "update", "create", "delete"
    • bidstringrequired
    • For update and delete: the invoice line's id.

    • payloadobject

      Required for create and update: one invoice line, the same body as Add a line to an invoice. Three common shapes. A sales order line: { "product": { "type": "order_item", "properties": { "id": "<order line id>" } }, "quantity": { "type": "unit", "total": { "value": 10, "dp": 0 } }, "tax": { "type": "taxable" }, "position": 100 }. A shipping fee: { "product": { "type": "shipment_fee", "properties": { "name": "Shipping" } }, "pricing": { "type": "fee", "product": { "total": { "value": 1500, "dp": 2 } } }, "position": 200 }. A percent discount: { "product": { "type": "invoice_discount", "properties": { "type": "percent", "rate": { "value": 10, "dp": 0 }, "applies_to_shipping": false } }, "position": 300 }. Tax lines go in tax_selection, not here.

  • true to re-sort the invoice's lines.

  • shipping_address_idstring | null

    The shipping address id. Default: the order's.

  • The invoice's tax: send both expected_tax (the tax line you expect now, or null) and desired_tax (the tax you want, or null).

    Child fields (2)
    • desired_taxobject | null
      Child fields (2)
      • The tax's settings: for user_tax, { "id": tax preset id }.

      • typestring
        • One of: "auto_tax", "user_tax", "override_tax"
    • expected_taxobject | null
      Child fields (2)

Response

200Successful response.

  • dataobjectrequired

    An invoice.

    Child fields (22)
    • idstringrequired
    • order_idstringrequired
    • totaldecimal pairrequired

      Invoice total. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

    • billing_address_idstring | null
    • calc_deposit_totalinteger | number | string | null

      The order's deposit amount. Thousandths, an older encoding: 12500 is 12.50. Usually a whole number; an amount finer than a thousandth comes back as a number with a fraction (1234.5 is 1.2345), or as a string ("1234.5") when you send X-Yoprint-Decimal-Format: exact-v1. See Money and quantities.

    • cart_item_subtotaldecimal pair

      Lines subtotal. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

    • created_atstring (date-time) | null

      Created. ISO 8601 with offset, or null.

    • customerobject | null

      The customer record, with the same fields as Get a customer (GET /v1/api/store/{team_slug}/customer/{hash_id}); the main ones are listed here.

      Child fields (6)
    • discount_subtotaldecimal pair

      Discount. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

    • is_paidboolean
    • is_validboolean
    • issue_datestring (date-time) | null

      Issue date. ISO 8601 with offset, or null.

    • itemsarray of objects

      The invoice lines.

    • paid_amountinteger | number | string | null

      Paid on this invoice. Thousandths, an older encoding: 12500 is 12.50. Usually a whole number; an amount finer than a thousandth comes back as a number with a fraction (1234.5 is 1.2345), or as a string ("1234.5") when you send X-Yoprint-Decimal-Format: exact-v1. See Money and quantities.

    • pdf_urlarray of objects

      The invoice PDF links, despite the name: a list, first the standard invoice, then one per invoice PDF template the team has turned on.

      Fields of each item (4)
    • scoped_idstring | integer

      The invoice number, from the team's invoice counter. A new invoice created without lines answers it as a number (6); with lines, and in every later read, it is a string ("6"). Compare it as text.

    • shipment_subtotaldecimal pair

      Shipping. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

    • Tax on shipping. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

    • shipping_address_idstring | null
    • tax_subtotaldecimal pair

      Tax. A decimal pair: the amount is value ÷ 10^dp ({ "value": 125000, "dp": 4 } is 12.50). Kept with 4 decimal places; an amount never set can come as { "value": 0, "dp": 0 }. Read dp every time: it can differ between fields and records.

  • statusintegerrequired
  • successbooleanrequired

Errors

  • 401The API key is missing or wrong (unauthenticated).

  • 403You are not allowed to do this. api_access_not_in_plan: your team's plan doesn't include API access (Pro and above, with an active subscription or trial), so every request answers this until the team upgrades. When your role lacks the permission the operation needs, the body is only {"message": "Insufficient Permission"}. Other refusals use the error envelope, with unauthorized or user_is_not_authorized.

  • 404Not found. page_not_found: the team slug is unknown, you are not a member of the team, or an id in the path is not one of this team's. code: null, with a message that starts "No query results for": there is no record with that id.

  • 422The request was refused and changed nothing. validation_failed lists what to fix in error.fields; other codes name a business rule, explained in error.message. A few checks answer with only {"message": "..."}. An operation that calls another service (a payment provider, a carrier, an accounting app) can answer connect_timeout ("Please retry again") when that service does not answer in time: the change may or may not have happened there, so read the record back before you retry.

    When
    A Shopify or auto-invoiced order, or an invalid line or tax selection.
    Fix
    Read error.message; fix the named line by its bid.