Docs › API reference

API reference: endpoints

Every endpoint of the Promogator On Demand API v1, with scopes, fields and examples.

Base URL: https://www.promogator.com/api/on-demand/v1 · JSON over HTTPS · Authorization: Bearer <access token> on every call except /oauth/token.

Method Path Scope What it does
POST /oauth/token key (HTTP Basic) Get an access token
GET /account any Your account, limits and this key
GET /catalog/products catalog:read List your products
GET /catalog/products/{id} catalog:read One product with its variants
GET /catalog/variants/{od_sku} catalog:read One variant by our SKU
POST /quotes catalog:read Price an order without placing it
GET /quotes/{id} catalog:read A quote
POST /orders orders:write Place an order
GET /orders orders:read List your orders
GET /orders/{id} orders:read One order
GET /orders/by-external-id/{external_id} orders:read One order by your order number
GET /orders/{id}/shipments orders:read An order's packages and tracking
GET /orders/{id}/invoice orders:read An order's invoice (live orders)
POST /orders/{id}/cancel orders:write Cancel an order
GET, POST /webhook-endpoints webhooks:manage List or add webhook endpoints
PATCH, DELETE /webhook-endpoints/{id} webhooks:manage Change or remove an endpoint
POST /webhook-endpoints/{id}/test webhooks:manage Send a test event
POST /webhook-endpoints/{id}/rotate-secret webhooks:manage New signing secret
GET /events, /events/{id} webhooks:manage Your recent events

Conventions

  • Ids are opaque strings with a prefix: oda_ account, odp_ product, odq_ quote, odo_ order, shp_ shipment, inv_ invoice, we_ webhook endpoint, evt_ event. Test-mode quotes and orders start with test_.
  • Money is a string with two decimals in US dollars, for example "16.95".
  • Times are ISO 8601 in UTC, for example "2026-10-18T09:30:00Z". Dates are YYYY-MM-DD.
  • Lists return {"object": "list", "data": [...], "has_more": true|false}. Pass limit (1–100, default 20) and, for the next page, starting_after with the last id you received.
  • Request ids: every response has an X-Request-ID header (req_…). Send your own X-Request-ID and we echo it back as X-Client-Request-ID. Quote the request id when you contact us.
  • Errors are application/problem+json with a stable code; see Errors, limits and idempotency.
  • Unknown fields in a request body are refused (422 validation_failed), so typos never pass silently.

Authentication

POST /oauth/token

Exchange an API key for an access token. Send the Access Key ID and Secret Key with HTTP Basic; the body is grant_type=client_credentials (form-encoded or JSON) and optionally scope (space-separated, a subset of the key's scopes).

bash
curl -X POST https://www.promogator.com/api/on-demand/v1/oauth/token \
  -u "$ACCESS_KEY_ID:$SECRET_KEY" -d grant_type=client_credentials
json
{ "access_token": "pgod_at_…", "token_type": "Bearer", "expires_in": 3600, "scope": "catalog:read orders:read orders:write" }

Errors use the OAuth format {"error": "...", "error_description": "..."}: invalid_request, unsupported_grant_type, invalid_client (401), invalid_scope, slow_down (429, more than 20 tokens a minute). See Authentication and keys.

GET /account

json
{
  "object": "account",
  "id": "oda_01JB…",
  "name": "Summit Team Stores",
  "status": "active",
  "livemode": false,
  "billing": { "method": "card" },
  "limits": { "max_order_value": "2500.00", "orders_per_day": 50 },
  "api_key": { "id": "pgod_test_ak_…", "name": "Shopify sync", "env": "test", "scopes": ["catalog:read", "orders:read", "orders:write"], "expires_at": null, "probation": null, "last_used_at": "2026-10-18T09:12:44Z" }
}

billing.method is card or terms. probation is set while a new live key is on probation (see Going live).

Catalog

GET /catalog/products

Your products, sorted by product code. Query: limit, starting_after.

Each item is a product with net_price_range (min, max, currency) and variant_count instead of the full variant list.

GET /catalog/products/{id}

One product with variants: one variant per colour and size, each with od_sku, partner_sku, net_price, suggested_retail_price and orderable.

GET /catalog/variants/{od_sku}

One variant by our SKU (for example OD-000125-D1001-BLK-L). Returns 404 not_found if the SKU is not on your product list.

Quotes

POST /quotes

Prices an order without placing it. Body:

Field Type
recipient object Required. See Recipient
items array Required, 1–30 lines. See Items
notes string Optional

Returns 201 with a quote: items at your NET price, shipping, tax_details and totals. A quote is valid for 72 hours (expires_at). It does not reserve anything: the order is priced again when you place it.

If the order cannot be priced (a SKU that is not yours, an address outside the US, a quantity under the minimum…), you get 422 validation_failed with each problem in errors[].detail.

GET /quotes/{id}

A quote you created, with status open or expired.

Orders

POST /orders

Places an order. Headers:

Header
Idempotency-Key Required. Unique per order, up to 255 characters (for example your store's order id). See Idempotency
Content-Type application/json

Body:

Field Type
external_id string Required. Your order number, 1–100 characters, unique in your account
recipient object Required. See Recipient
items array Required. 1–30 lines. See Items
metadata object Optional. Up to 20 keys of your own; returned on the order
requested_in_hands_date date Optional YYYY-MM-DD. A note for our team, not a promise
notes string Optional note for our team

Recipient

Field
name Required
company Optional
address1 Required
address2 Optional (apartment, suite)
city Required
state Required, 2-letter code
postal_code Required
country Optional, US only for now
phone Optional, for the carrier only. We never contact your customer

Items

Field
od_sku or partner_sku Required: our SKU or your own SKU for your designs
quantity Required, 1 or more
retail_price Optional: your selling price for one piece. Used for California sales tax; if left out we use NET + 10%
external_line_id Optional: your line id, returned on the line

Put each SKU on one line only (add up the quantity).

Responses

  • 201 Created: the order. Live orders are charged to your card (card billing) or invoiced (terms). Test orders are accepted and never made.
  • 200 OK with header Idempotent-Replayed: true: you sent the same request with the same Idempotency-Key again; this is the order you already placed.
  • 409 idempotency_conflict: the Idempotency-Key was used with a different body.
  • 409 duplicate_external_id: an order with this external_id exists; fetch it with /orders/by-external-id/{external_id}.
  • 402: payment failed (card billing): card_declined (with decline_code), authentication_required, payment_method_required or payment_failed. No order was created.
  • 422 validation_failed: the order cannot be priced as sent; see errors[].detail.
  • 422 limit_exceeded: a new live key's probation limit (3 orders or $500.00 in its first 24 hours).

GET /orders

Your orders, newest first. Query: limit, starting_after. A test key lists test orders; a live key lists live orders.

GET /orders/{id}

One order.

GET /orders/by-external-id/{external_id}

One order by your order number. URL-encode it (%23 for #).

GET /orders/{id}/shipments

The order's packages: {"object": "list", "data": [shipment, …], "has_more": false}. See Shipment. Test orders have no shipments.

GET /orders/{id}/invoice

The order's invoice: number, status, total, amount paid, credits and balance. Live orders only (404 for test orders).

POST /orders/{id}/cancel

Cancels an order. Body (optional): {"reason": "Customer changed their mind"}.

An order can be cancelled within 24 hours of being placed, as long as production has not started. Returns the cancelled order (and sends order.cancelled), or 409 not_cancellable with the reason in detail.

Webhook endpoints

See Webhooks for events and signatures. Endpoints belong to the mode of the key that creates them (test or live).

POST /webhook-endpoints

Body: url (required, https:// on a public host, up to 500 characters) and events (optional, default all: order.created, order.shipped, order.cancelled). Returns the endpoint with its secret (whsec_…), shown only in this response. Up to 5 endpoints per mode.

GET /webhook-endpoints

Your endpoints in this mode, each with status (enabled or disabled), disabled_reason and last_delivery (status, response_code, at).

PATCH /webhook-endpoints/{id}

Change url, events or status (enabled / disabled).

DELETE /webhook-endpoints/{id}

Removes the endpoint.

POST /webhook-endpoints/{id}/test

Sends a ping event right away and returns {"object": "webhook_test", "event_id", "delivered", "response_code", "error", "duration_ms"}.

POST /webhook-endpoints/{id}/rotate-secret

Returns the endpoint with a new secret.

Events

GET /events

Your recent events in this mode, newest first, in the same shape as webhook bodies. Query: limit.

GET /events/{id}

One event by its evt_ id (the webhook-id header of a delivery).

Need a hand? Partners: your account manager's details are on the Overview tab of your portal. Everyone else: call (877) 737-8191 or email support@promogator.com.
Compare /

Compare Products

Highlighted rows = products differ on this attribute