Orders

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

MethodPathDescription
GET/v0/ordersList orders
POST/v0/ordersCreate an order
GET/v0/orders/:orderIdRetrieve an order
PATCH/v0/orders/:orderIdUpdate an order
POST/v0/orders/:orderId/cancelCancel an order

List orders

GET /v0/orders

Returns a paginated list of orders for your organization, scoped to the API key's mode.

Query parameters

Standard pagination parameters (page, limit) plus:

ParameterTypeRequiredDescription
statusstringnoFilter by order status: open, completed, or cancelled.
paymentStatusstringnoFilter by payment status: unpaid, partially_paid, paid, overpaid, partially_refunded, refunded.
providerstringnoFilter by order source: native, square, or clover.
locationIdstringnoFilter 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/orders

Creates a new native order. Use this to programmatically create an order before or without creating a checkout session.

Request body

FieldTypeRequiredDescription
itemsOrderItem[]yesLine items for the order (min 1).
externalIdstringnoYour internal order identifier. Unique per organization and mode. See externalId and idempotency.
taxesOrderTax[]noTaxes applied to the order.
discountsOrderDiscount[]noDiscounts applied to the order.
customerOrderCustomernoAttach or create a customer.
shippingAddressShippingAddressnoShipping address.
locationIdLocationIdnoAssociate this order with a merchant location.
paymentDestinationstringnoOverride the payment destination for this order. See Payment Routing.
metadataMetadatanoKey/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/:orderId

Fetch a single order by its ID. Use this after a checkout session completes to confirm payment.

Path parameters

ParameterDescription
orderIdThe 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/:orderId

Modify 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

ParameterDescription
orderIdThe order ID to update.

Request body

All fields are optional. Provide only the fields you want to change.

FieldTypeDescription
itemsOrderItem[]Replaces all existing items. Must contain at least 1 item if provided.
taxesOrderTax[]Replaces all existing taxes. Pass an empty array to clear taxes.
discountsOrderDiscount[]Replaces all existing discounts. Pass an empty array to clear discounts.
customerOrderCustomerResolves or creates a customer and merges fields into the snapshot.
shippingAddressShippingAddressReplaces the shipping address.
metadataMetadataReplaces the metadata object.

Replace vs. merge semantics:

  • items, taxes, discounts, shippingAddress, and metadata use replace semantics — providing any of these replaces the entire existing value. Omitting a field leaves it unchanged.
  • customer fields 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) when customer is 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/cancel

Cancels 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

ParameterDescription
orderIdThe 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 same externalId can 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 externalId field 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.

CodeStatusDescription
INVALID_ORDER_ID400Order ID format is invalid.
ORGANIZATION_NOT_FOUND400API key is not associated with an organization.
ORDER_NOT_FOUND404Order does not exist or belongs to another org or mode.
ORDER_CREATE_FAILED400Order could not be created.
ORDER_UPDATE_FAILED400Order could not be updated.
ORDER_NOT_EDITABLE400Order cannot be modified (not open, or has received payment).
ORDER_CANCEL_FAILED400Order cannot be cancelled (not open, or has received payment).
ORDERS_FETCH_FAILED400Failed to list orders.
ORDER_FETCH_FAILED400Failed to retrieve the order.
DUPLICATE_EXTERNAL_ID409An order with this externalId already exists for this org and mode.
INVALID_LOCATION_ID400Location ID format is invalid.
LOCATION_NOT_FOUND400Merchant location not found.
INVALID_PAYMENT_DESTINATION400paymentDestination is not a valid destination ID or address.
PAYMENT_DESTINATION_NOT_FOUND400No matching payment destination found for this org and mode.
PAYMENT_DESTINATION_NOT_ACTIVE400Destination exists but is inactive, deleted, or not yet verified.