Skip to content

Orders API

List Orders

http
GET /api/v1/admin/orders

Query Parameters:

ParameterTypeDescription
pageintPage number (default: 1)
limitintItems per page (default: 20, max: 200)
sortstringSort field: created_at, total, status, order_number
orderstringasc or desc (default: desc)
statusstringFilter by status
customer_idUUIDFilter by customer
searchstringSearch by order number (ILIKE)

Response:

json
{
  "data": [
    {
      "id": "uuid",
      "order_number": "ORD-20260315-A1B2C",
      "customer_id": "uuid",
      "guest_token": "uuid",
      "status": "pending",
      "currency": "EUR",
      "subtotal_net": 1680,
      "subtotal_gross": 1999,
      "shipping_cost": 499,
      "tax_total": 319,
      "total": 2498,
      "created_at": "2026-03-15T10:00:00Z",
      "updated_at": "2026-03-15T10:00:00Z"
    }
  ],
  "meta": { "total": 42, "page": 1, "limit": 20, "pages": 3 }
}

INFO

guest_token is only present for guest orders (where customer_id is null). It is omitted for registered customer orders.

Get Order

http
GET /api/v1/admin/orders/:id

Returns the full order including items, status_history, addresses, and guest_token.

Update Order Status

http
PATCH /api/v1/admin/orders/:id/status

Request Body:

json
{
  "status": "shipped",
  "comment": "Tracking: DHL 12345"
}

Only valid transitions are accepted (see Order Lifecycle). Invalid transitions return 422.

List Payment Transactions

http
GET /api/v1/admin/orders/:orderID/transactions

Returns all payment transactions for a given order.

Response:

json
{
  "data": [
    {
      "id": "uuid",
      "order_id": "uuid",
      "payment_method_id": "uuid",
      "status": "completed",
      "currency": "EUR",
      "amount": 2498,
      "provider_reference": "pi_abc123",
      "created_at": "2026-03-15T10:05:00Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "limit": 1, "pages": 1 }
}
StatusMeaning
pendingPayment initiated
completed / succeededPayment successful
failedPayment failed
refundedPayment refunded
cancelledPayment cancelled

TIP

Transactions are created by payment plugins (e.g. Stripe) via webhooks — they cannot be created manually through this API.

Store API

List Customer Orders

http
GET /api/v1/store/account/orders

Returns all orders for the authenticated customer. Requires JWT authentication.

Get Order

http
GET /api/v1/store/account/orders/:id

Returns a single order with ownership verification.

Authentication:

  • Authenticated customers — the order's customer_id must match the JWT user ID.
  • Guest orders — the stoa_guest_token HTTP-only cookie (set during checkout) is sent automatically by the browser.
bash
# Authenticated customer
curl http://localhost:8080/api/v1/store/account/orders/<id> \
  -H 'Authorization: Bearer <token>'

# Guest order (cookie is sent automatically by the browser)
curl http://localhost:8080/api/v1/store/account/orders/<id> \
  --cookie "stoa_guest_token=<token>"
StatusCondition
200Ownership verified — order returned
403Caller does not own this order
404Order not found

Security

The guest token is delivered as an HTTP-only cookie and never included in the API response body. This prevents XSS attacks from stealing the token. See Guest Token Security for details.

List Payment Transactions (Store)

http
GET /api/v1/store/orders/:orderID/transactions

Returns payment transactions for a given order after verifying ownership. Uses the same ownership model as Get Order.

Authentication:

  • Authenticated customers — the order's customer_id must match the JWT user ID.
  • Guest orders — the stoa_guest_token HTTP-only cookie (set during checkout) is sent automatically by the browser.
bash
# Authenticated customer
curl http://localhost:8080/api/v1/store/orders/<orderID>/transactions \
  -H 'Authorization: Bearer <token>'

# Guest order (cookie is sent automatically by the browser)
curl http://localhost:8080/api/v1/store/orders/<orderID>/transactions \
  --cookie "stoa_guest_token=<token>"

Response:

json
{
  "data": [
    {
      "id": "uuid",
      "order_id": "uuid",
      "payment_method_id": "uuid",
      "status": "completed",
      "currency": "EUR",
      "amount": 2498,
      "provider_reference": "pi_abc123",
      "created_at": "2026-03-15T10:05:00Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "limit": 1, "pages": 1 }
}
StatusCondition
200Ownership verified — transactions returned
403Caller does not own this order
404Order not found

Checkout

http
POST /api/v1/store/checkout

Creates a new order. For guest checkouts, the server sets an HTTP-only stoa_guest_token cookie that the browser uses automatically for payment completion and order lookup. The response includes "is_guest_order": true instead of the raw token.

Request Body:

json
{
  "currency": "EUR",
  "billing_address": { "city": "Berlin", "street": "..." },
  "shipping_address": { "city": "Berlin", "street": "..." },
  "payment_method_id": "uuid",
  "shipping_method_id": "uuid",
  "notes": "Ring the doorbell twice",
  "payment_reference": "pi_3ABC...",
  "items": [
    {
      "product_id": "uuid",
      "variant_id": "uuid",
      "quantity": 2
    }
  ]
}

Checkout Items:

FieldTypeRequiredDescription
product_idUUIDYesProduct to purchase
variant_idUUIDNoSpecific variant (size, color, etc.)
quantityintYesNumber of units (min: 1)

Server-Side Price Enforcement

All prices, product names, and SKUs are resolved server-side from the database. Any client-supplied price fields (unit_price_net, unit_price_gross, tax_rate, name, sku) are ignored. This prevents price manipulation attacks.

If a variant is specified, variant-specific prices and SKU are used. Otherwise, the base product prices apply.

Payment Validation:

If active payment methods are configured in the shop, payment_method_id is required and must reference an active method. If no payment methods are configured (invoice-only shops), the field is optional.

Payment Reference (provider-based methods):

When the selected payment method has a provider (e.g. "stripe"), the payment_reference field is required. This is the provider's payment identifier (e.g. a Stripe PaymentIntent ID like pi_3ABC...) that proves payment has been completed before order creation.

For manual payment methods (where provider is empty), payment_reference is optional and can be omitted.

Error CodeStatusDescription
missing_product_id422A line item is missing the required product_id
invalid_product422The referenced product or variant does not exist
payment_method_required422Active payment methods exist but none was selected
invalid_payment_method422The selected payment method is inactive or does not exist
payment_reference_required422Provider-based payment method selected but no payment_reference provided
checkout_rejected422A plugin hook rejected the checkout (e.g. Stripe PaymentIntent not succeeded)

Checkout Hooks:

Plugins can register checkout.before hooks to validate or reject checkouts. The hook receives metadata including provider and payment_reference, allowing provider plugins to verify payment completion. After-hooks (checkout.after) fire after order creation for post-order actions.

HookFires whenFatal?
checkout.beforeBefore service.Create() — can abort checkoutYes (returns error → 422)
checkout.afterAfter successful order creationNo (errors are logged)
checkout.after_failedAfter service.Create() fails with insufficient_stockNo (errors are logged)

The checkout.after_failed hook enables payment plugins to automatically refund a captured payment when stock runs out after the payment was already confirmed. See the Stripe plugin documentation for an example implementation.

Released under the APACHE 2.0 License.