Orders

Build an order from a cart

The dashboard's order builder makes an order without a checkout: it fills a cart, turns it into a draft order, and then collects payment from the order. The same three routes are available to a secret key.

Create the draft order

POST /api/v1/orders/from-cart takes the id of an open cart in the credential's application and environment. Build the cart first with the cart API (line items, customer, addresses, shipping). Scope orders:write.

Create a draft order from a cart
curl -X POST https://api.usethrottle.dev/api/v1/orders/from-cart \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "cartId": "3f1c9a52-..." }'
201 response
{
  "data": {
    "id": "8b2e41d0-...",
    "orderNumber": "SS-MUB7K2QX",
    "status": "draft",
    "currency": "USD",
    "subtotal": 12900,
    "total": 13798,
    "cartId": "3f1c9a52-...",
    "metadata": { "createdVia": "dashboard" },
    "createdAt": "2026-10-30T14:02:11.000Z"
  }
}

The order is always created in draft and stamped metadata.createdVia: "dashboard". Nothing is charged: a draft is an order waiting for a way to be paid.

A draft from the builder keeps its lines
Its line items are locked to the cart's total, so PATCH /orders/{id}/line-items answers 409 order_edit_in_builder. Cancel the order and build a new one instead. See Order line-item edit codes.

Converting a cart twice does not create a second order. The 409 names the first one:

409 cart_already_converted
{
  "error": {
    "code": "cart_already_converted",
    "message": "This cart has already been turned into an order",
    "details": { "orderId": "8b2e41d0-..." }
  }
}
CodeHTTPMeaning
validation_error400The body. cartId must be a UUID and is the only field.
insufficient_scopes403The API key lacks orders:write. A dashboard member without the role to create orders also gets a 403.
cart_not_found404No such cart in the application and environment of the credential.
cart_already_converted409The cart is already an order. error.details.orderId names it, so a retry can fetch the existing order instead of creating a second one.
tax_calculation_failed409The final tax calculation for the cart failed. No order was created and the cart stays open.

Send a payment link

POST /api/v1/orders/{id}/payment-link opens a hosted checkout session on the order's cart that stays payable for seven days, and emails it to the order's customer. Send "send": false to create the link without emailing it; emailedTo is then null, as it is when the order has no customer email. The body is optional.

The route both changes the order and creates a checkout session, so an API key needs orders:write and checkout_sessions:write.

Create and email a payment link
curl -X POST https://api.usethrottle.dev/api/v1/orders/8b2e41d0-.../payment-link \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "send": true }'
200 response
{
  "data": {
    "sessionId": "c7d0e9f4-...",
    "url": "https://checkout.usethrottle.dev/s/c7d0e9f4-...",
    "sentAt": "2026-10-30T14:05:40.000Z",
    "expiresAt": "2026-11-06T14:05:40.000Z",
    "emailedTo": "[email protected]"
  }
}

Calling it again replaces the link: the previous session is cancelled first, so only the newest link can be paid. When the customer pays, the order leaves draft as any checkout does.

The link pays the order exactly as built. Its page re-quotes tax only, on the cart's lines and shipping, and the buyer cannot change the email, addresses or VAT number: a /calculate that sends values other than the cart's answers 409 payment_link_details_locked. To change them, cancel the draft and build a new order.

DELETE /api/v1/orders/{id}/payment-link cancels the link and answers 204. It answers 204 when the order has no link, too.

Cancel the payment link
curl -X DELETE https://api.usethrottle.dev/api/v1/orders/8b2e41d0-.../payment-link \
  -H "X-API-Key: $THROTTLE_SECRET_KEY"
CodeHTTPMeaning
validation_error400The body. send is the only field.
insufficient_scopes403The API key lacks orders:write or checkout_sessions:write.
not_found404No such order in the application and environment of the credential.
order_not_draft409The order is not a draft built with POST /orders/from-cart.
order_has_no_cart409The order was not built from a cart.
payment_in_progress409The customer is paying the current link right now. Try again in a few minutes.
payment_link_conflict409The link changed while this request ran. Reload the order and retry.
payment_link_cancel_failed502The current link could not be cancelled, so it was left in place and not replaced.

DELETE returns the same 403, 404, payment_in_progress, payment_link_conflict and payment_link_cancel_failed codes.

Invoice on terms

POST /api/v1/orders/{id}/invoice-on-terms issues a Net-N invoice for the draft and moves the order to processing, the way a buyer choosing Invoice Terms at checkout does. The merchant accepts the terms on the buyer's behalf: companyName and billingEmail are recorded on the acceptance, and the payment's metadata records termsAcceptedBy: "merchant". It requires an active Invoice Terms connector. An API key needs payments:write and orders:write.

netDays (0 to 365, 0 is due on issue) sets the terms for this invoice. Omit it and the usual order applies: the customer's terms, then the application default.

Issue a Net-45 invoice
curl -X POST https://api.usethrottle.dev/api/v1/orders/8b2e41d0-.../invoice-on-terms \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "netDays": 45, "companyName": "Acme Wholesale", "billingEmail": "[email protected]" }'

The 201 response is the net30 payment, at authorized, carrying its netN, netNSource and invoice number. A payment link on the order is cancelled first, so the buyer cannot also pay by card. Two requests for the same order do not issue two invoices: the second answers 409 invoice_in_progress.

CodeHTTPMeaning
validation_error400companyName and billingEmail are required; netDays is an integer from 0 to 365.
insufficient_scopes403The API key lacks payments:write or orders:write.
not_found404No such order in the application and environment of the credential.
order_not_draft409The order is not a draft built with POST /orders/from-cart, or it already has a payment.
invoice_terms_not_connected409Invoice Terms is not connected in this application and environment.
invoice_in_progress409Another request is invoicing this order. Reload in a moment.
payment_in_progress409The customer is paying the order’s payment link right now.
payment_link_conflict409The order’s payment link changed while this request ran. Reload and retry.
invoice_issue_failed502The invoice could not be issued, or it was issued but the order was not fully updated. Retrying finishes the job without issuing a second invoice.
payment_link_cancel_failed502The order’s payment link could not be cancelled, so no invoice was issued.

Cancelling a builder order

Cancelling a dashboard-built order with POST /orders/{id}/cancel, or by setting its status to cancelled, cancels its payment link first. If the link cannot be cancelled the order is not cancelled either, and the request answers 409 payment_in_progress (the customer is paying it right now), 409 payment_link_conflict, or 502 payment_link_cancel_failed. Orders without a payment link cancel as before. See Order states.