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.
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-..." }' {
"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.
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:
{
"error": {
"code": "cart_already_converted",
"message": "This cart has already been turned into an order",
"details": { "orderId": "8b2e41d0-..." }
}
} | Code | HTTP | Meaning |
|---|---|---|
validation_error | 400 | The body. cartId must be a UUID and is the only field. |
insufficient_scopes | 403 | The API key lacks orders:write. A dashboard member without the role to create orders also gets a 403. |
cart_not_found | 404 | No such cart in the application and environment of the credential. |
cart_already_converted | 409 | The 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_failed | 409 | The 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.
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 }' {
"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.
curl -X DELETE https://api.usethrottle.dev/api/v1/orders/8b2e41d0-.../payment-link \
-H "X-API-Key: $THROTTLE_SECRET_KEY" | Code | HTTP | Meaning |
|---|---|---|
validation_error | 400 | The body. send is the only field. |
insufficient_scopes | 403 | The API key lacks orders:write or checkout_sessions:write. |
not_found | 404 | No such order in the application and environment of the credential. |
order_not_draft | 409 | The order is not a draft built with POST /orders/from-cart. |
order_has_no_cart | 409 | The order was not built from a cart. |
payment_in_progress | 409 | The customer is paying the current link right now. Try again in a few minutes. |
payment_link_conflict | 409 | The link changed while this request ran. Reload the order and retry. |
payment_link_cancel_failed | 502 | The 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.
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.
| Code | HTTP | Meaning |
|---|---|---|
validation_error | 400 | companyName and billingEmail are required; netDays is an integer from 0 to 365. |
insufficient_scopes | 403 | The API key lacks payments:write or orders:write. |
not_found | 404 | No such order in the application and environment of the credential. |
order_not_draft | 409 | The order is not a draft built with POST /orders/from-cart, or it already has a payment. |
invoice_terms_not_connected | 409 | Invoice Terms is not connected in this application and environment. |
invoice_in_progress | 409 | Another request is invoicing this order. Reload in a moment. |
payment_in_progress | 409 | The customer is paying the order’s payment link right now. |
payment_link_conflict | 409 | The order’s payment link changed while this request ran. Reload and retry. |
invoice_issue_failed | 502 | The 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_failed | 502 | The 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.