Orders API
List, create, retrieve, update, and cancel orders — endpoints, request/response shapes, and payment verification.
Create and manage orders for your organization. Orders represent purchase intents and can be created directly via the API or more commonly through a Checkout Session. Use the Orders API to verify payment status after checkout, modify pending orders, or cancel orders that should not proceed.
- Base URL:
https://api.usedecal.com - Current API version:
v0 - Authentication: See Authentication
- Order types: See Order types for the full schemas of every object referenced below.
For a complete end-to-end walkthrough — creating sessions, fulfilling orders via webhook, handling abandoned carts — start with the Web Integration Guide or POS Integration Guide.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v0/orders | List orders |
POST | /v0/orders | Create an order |
GET | /v0/orders/:orderId | Retrieve an order |
PATCH | /v0/orders/:orderId | Update an order |
POST | /v0/orders/:orderId/cancel | Cancel an order |
List orders
GET /v0/ordersReturns a paginated list of orders for your organization, scoped to the API key's mode.
Query parameters
Standard pagination parameters (page, limit) plus:
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | no | Filter by order status: open, completed, or cancelled. |
paymentStatus | string | no | Filter by payment status: unpaid, partially_paid, paid, overpaid, partially_refunded, refunded. |
provider | string | no | Filter by order source: native, square, or clover. |
locationId | string | no | Filter by location ID (loc_live_...). |
Response
Returns an array of Order objects under orders, plus a
Pagination object.
curl "https://api.usedecal.com/v0/orders?status=open&limit=10" \
-H "Authorization: Bearer sk_live_your_api_key_here"const response = await fetch("https://api.usedecal.com/v0/orders?status=open&limit=10", {
headers: {
Authorization: "Bearer sk_live_your_api_key_here",
},
});
const { orders } = await response.json();
for (const order of orders) {
console.log(order.id, order.paymentStatus);
}Example response
{
"orders": [
{
"id": "ord_live_xyz789abc",
"provider": "native",
"currency": "USD",
"status": "open",
"paymentStatus": "unpaid",
"amounts": {
"subtotal": 1798,
"tax": 160,
"discount": 0,
"tip": 0,
"total": 1958,
"paid": 0
},
"items": [
{
"id": "itm_abc1",
"name": "Latte",
"quantity": 2,
"unitPrice": 699,
"totalPrice": 1398,
"itemType": "product"
},
{
"id": "itm_abc2",
"name": "Croissant",
"quantity": 1,
"unitPrice": 400,
"totalPrice": 400,
"itemType": "product"
}
],
"taxes": [
{
"id": "tax_cuid_def",
"name": "Sales Tax",
"type": "additive",
"rate": null,
"amount": 160,
"scope": "order"
}
],
"discounts": [],
"createdAt": "2026-04-15T09:00:00.000Z",
"updatedAt": "2026-04-15T09:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 1,
"hasMore": false
}
}Create an order
POST /v0/ordersCreates a new native order. Use this to programmatically create an order before or without creating a checkout session.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
items | OrderItem[] | yes | Line items for the order (min 1). |
externalId | string | no | Your internal order identifier. Unique per organization and mode. See externalId and idempotency. |
taxes | OrderTax[] | no | Taxes applied to the order. |
discounts | OrderDiscount[] | no | Discounts applied to the order. |
customer | OrderCustomer | no | Attach or create a customer. |
shippingAddress | ShippingAddress | no | Shipping address. |
locationId | LocationId | no | Associate this order with a merchant location. |
paymentDestination | string | no | Override the payment destination for this order. See Payment Routing. |
metadata | Metadata | no | Key/value data attached to the order. |
Response
Returns a single Order object with status 201 Created.
curl -X POST https://api.usedecal.com/v0/orders \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "name": "Cold Brew", "quantity": 1, "unitPrice": 550 },
{ "name": "Blueberry Muffin", "quantity": 2, "unitPrice": 375 }
],
"taxes": [{ "type": "additive", "amount": 120, "name": "Sales Tax" }],
"discounts": [
{ "type": "fixed", "amount": 200, "name": "Loyalty Credit" },
{ "type": "percentage", "amount": 10, "name": "Happy Hour" }
],
"customer": { "email": "alex@example.com" }
}'const response = await fetch("https://api.usedecal.com/v0/orders", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{ name: "Cold Brew", quantity: 1, unitPrice: 550 },
{ name: "Blueberry Muffin", quantity: 2, unitPrice: 375 },
],
taxes: [{ type: "additive", amount: 120, name: "Sales Tax" }],
discounts: [
{ type: "fixed", amount: 200, name: "Loyalty Credit" },
{ type: "percentage", amount: 10, name: "Happy Hour" },
],
customer: { email: "alex@example.com" },
}),
});
const { order } = await response.json();
console.log(order.id);
// => "ord_live_abc123xyz"Example response
{
"order": {
"id": "ord_live_abc123xyz",
"provider": "native",
"currency": "USD",
"status": "open",
"paymentStatus": "unpaid",
"amounts": {
"subtotal": 1300,
"tax": 120,
"discount": 330,
"tip": 0,
"total": 1090,
"paid": 0
},
"customer": {
"id": "cust_live_abc123",
"email": "alex@example.com"
},
"items": [
{
"id": "cuid_1",
"name": "Cold Brew",
"quantity": 1,
"unitPrice": 550,
"totalPrice": 550,
"itemType": "product"
},
{
"id": "cuid_2",
"name": "Blueberry Muffin",
"quantity": 2,
"unitPrice": 375,
"totalPrice": 750,
"itemType": "product"
}
],
"taxes": [
{
"id": "tax_cuid_abc",
"name": "Sales Tax",
"type": "additive",
"rate": null,
"amount": 120,
"scope": "order"
}
],
"discounts": [
{
"id": "disc_cuid_abc",
"name": "Loyalty Credit",
"type": "fixed",
"amount": 200,
"scope": "order"
},
{
"id": "disc_cuid_def",
"name": "Happy Hour",
"type": "percentage",
"amount": 10,
"scope": "order"
}
],
"createdAt": "2026-04-15T10:00:00.000Z",
"updatedAt": "2026-04-15T10:00:00.000Z"
}
}Retrieve an order
GET /v0/orders/:orderIdFetch a single order by its ID. Use this after a checkout session completes to confirm payment.
Path parameters
| Parameter | Description |
|---|---|
orderId | The order ID returned at creation (ord_live_...). |
Response
Returns a single Order object with status 200 OK.
curl https://api.usedecal.com/v0/orders/ord_live_abc123xyz \
-H "Authorization: Bearer sk_live_your_api_key_here"const orderId = "ord_live_abc123xyz";
const response = await fetch(`https://api.usedecal.com/v0/orders/${orderId}`, {
headers: {
Authorization: "Bearer sk_live_your_api_key_here",
},
});
const { order } = await response.json();
if (order.paymentStatus === "paid") {
console.log("Payment confirmed, fulfil the order.");
}Update an order
PATCH /v0/orders/:orderIdModify an open, unpaid order. You can replace items, taxes, or discounts, and update customer
details. Only orders with status: "open" and amounts.paid: 0 can be updated.
Path parameters
| Parameter | Description |
|---|---|
orderId | The order ID to update. |
Request body
All fields are optional. Provide only the fields you want to change.
| Field | Type | Description |
|---|---|---|
items | OrderItem[] | Replaces all existing items. Must contain at least 1 item if provided. |
taxes | OrderTax[] | Replaces all existing taxes. Pass an empty array to clear taxes. |
discounts | OrderDiscount[] | Replaces all existing discounts. Pass an empty array to clear discounts. |
customer | OrderCustomer | Resolves or creates a customer and merges fields into the snapshot. |
shippingAddress | ShippingAddress | Replaces the shipping address. |
metadata | Metadata | Replaces the metadata object. |
Replace vs. merge semantics:
items,taxes,discounts,shippingAddress, andmetadatause replace semantics — providing any of these replaces the entire existing value. Omitting a field leaves it unchanged.customerfields use merge semantics — each provided field updates only the corresponding part of the customer snapshot, leaving other customer fields intact. The customer record is also resolved (find-or-create) whencustomeris provided.
The order's amounts object is automatically recalculated whenever items, taxes, or
discounts are updated.
Response
Returns the updated Order object with status 200 OK.
curl -X PATCH https://api.usedecal.com/v0/orders/ord_live_abc123xyz \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "name": "Cold Brew", "quantity": 2, "unitPrice": 550 },
{ "name": "Blueberry Muffin", "quantity": 1, "unitPrice": 375 }
],
"taxes": [{ "type": "additive", "amount": 145, "name": "Sales Tax" }]
}'const orderId = "ord_live_abc123xyz";
const response = await fetch(`https://api.usedecal.com/v0/orders/${orderId}`, {
method: "PATCH",
headers: {
Authorization: "Bearer sk_live_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{ name: "Cold Brew", quantity: 2, unitPrice: 550 },
{ name: "Blueberry Muffin", quantity: 1, unitPrice: 375 },
],
taxes: [{ type: "additive", amount: 145, name: "Sales Tax" }],
}),
});
const { order } = await response.json();
console.log(order.amounts.total);
// => 1620 (1100 + 375 + 145)Cancel an order
POST /v0/orders/:orderId/cancelCancels an open, unpaid order. The order record is preserved with status: "cancelled". This
endpoint is idempotent — cancelling an already-cancelled order returns 200 with the order.
Only orders with status: "open" and amounts.paid: 0 can be cancelled. Orders that have received
any payment return 400.
Path parameters
| Parameter | Description |
|---|---|
orderId | The order ID to cancel. |
Response
Returns the cancelled Order object with status 200 OK.
curl -X POST https://api.usedecal.com/v0/orders/ord_live_abc123xyz/cancel \
-H "Authorization: Bearer sk_live_your_api_key_here"const orderId = "ord_live_abc123xyz";
const response = await fetch(`https://api.usedecal.com/v0/orders/${orderId}/cancel`, {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_api_key_here",
},
});
const { order } = await response.json();
console.log(order.status);
// => "cancelled"externalId and idempotency
Pass an externalId to link the Decal order to your internal order identifier (cart ID, order
number, POS ticket ID, etc.). The value is stored on the order and returned on every subsequent
response.
externalId values are unique per organization and mode — creating a second order with the same
externalId in the same mode returns 409 Conflict with code: "DUPLICATE_EXTERNAL_ID". This
gives you safe idempotency: retrying a failed create-order call with the same externalId cannot
create two orders.
- Max length: 255 characters. Empty strings are rejected.
- Scope: Unique per
(organization, mode). The sameexternalIdcan exist in both test and live mode simultaneously. - Cannot be changed after creation. The PATCH endpoint does not accept
externalId. - Returned as: the top-level
externalIdfield on the order object.
{
"items": [{ "name": "Ticket", "quantity": 1, "unitPrice": 2500 }],
"externalId": "shopify_order_8812"
}Payment destination override
Pass paymentDestination on order creation to override where this order's payment is routed instead
of the default orchestration flow. See
Payment Routing for the full guide.
Error codes
See Errors for the common error response shape.
| Code | Status | Description |
|---|---|---|
INVALID_ORDER_ID | 400 | Order ID format is invalid. |
ORGANIZATION_NOT_FOUND | 400 | API key is not associated with an organization. |
ORDER_NOT_FOUND | 404 | Order does not exist or belongs to another org or mode. |
ORDER_CREATE_FAILED | 400 | Order could not be created. |
ORDER_UPDATE_FAILED | 400 | Order could not be updated. |
ORDER_NOT_EDITABLE | 400 | Order cannot be modified (not open, or has received payment). |
ORDER_CANCEL_FAILED | 400 | Order cannot be cancelled (not open, or has received payment). |
ORDERS_FETCH_FAILED | 400 | Failed to list orders. |
ORDER_FETCH_FAILED | 400 | Failed to retrieve the order. |
DUPLICATE_EXTERNAL_ID | 409 | An order with this externalId already exists for this org and mode. |
INVALID_LOCATION_ID | 400 | Location ID format is invalid. |
LOCATION_NOT_FOUND | 400 | Merchant location not found. |
INVALID_PAYMENT_DESTINATION | 400 | paymentDestination is not a valid destination ID or address. |
PAYMENT_DESTINATION_NOT_FOUND | 400 | No matching payment destination found for this org and mode. |
PAYMENT_DESTINATION_NOT_ACTIVE | 400 | Destination exists but is inactive, deleted, or not yet verified. |