REST API
All routes are mounted under the /api prefix. The full interactive spec is available at GET /api/reference (Swagger UI) and GET /api/doc (OpenAPI 3.0 JSON).
Authentication: API key via x-api-key header, or a Better Auth session cookie for browser clients.
Successful responses: { data: ... }. List endpoints include meta.pagination. Errors return { error: { code, message } }.
X-Request-Id: Every response includes this header with a unique request identifier.
OpenAPI and Swagger
Section titled “OpenAPI and Swagger”| Method | Path | Description |
|--------|------|-------------|
| GET | /api/doc | OpenAPI 3.0 JSON spec |
| GET | /api/reference | Swagger UI (interactive explorer) |
All core routes and plugin routes registered via router() appear in the spec automatically.
Health
Section titled “Health”| Method | Path | Description |
|--------|------|-------------|
| GET | /api/health | Server status, version, database connectivity |
Probes the database connection. Returns 503 if the database is unreachable.
// healthy{ "status": "ok", "version": "0.1.0" }
// unhealthy{ "status": "error", "version": "0.1.0", "database": "unreachable" }Request limits
Section titled “Request limits”Body size: 1 MB maximum. Requests exceeding this receive 413 Payload Too Large.
Rate limiting: Returns 429 Too Many Requests when exceeded.
Rate limit headers on every response:
| Header | Description |
|--------|-------------|
| X-RateLimit-Limit | Max requests in the current window |
| X-RateLimit-Remaining | Remaining requests in this window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
| Retry-After | Seconds until retry (only on 429) |
CSRF: Mutation methods (POST, PUT, PATCH, DELETE) validate the Origin header for browser requests. API-key requests are exempt.
Catalog
Section titled “Catalog”Base path: /api/catalog
Entities
Section titled “Entities”| Method | Path | Description |
|--------|------|-------------|
| GET | /catalog/entities | List catalog entities |
| GET | /catalog/entities/:idOrSlug | Get entity by UUID or slug |
| POST | /catalog/entities | Create a catalog entity |
| PATCH | /catalog/entities/:id | Update an entity |
| DELETE | /catalog/entities/:id | Delete an entity |
| POST | /catalog/entities/:id/publish | Transition to active status |
| POST | /catalog/entities/:id/archive | Transition to archived status |
| POST | /catalog/entities/:id/discontinue | Transition to discontinued status |
GET /catalog/entities query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| type | string | Filter by entity type (product, digitalDownload, etc.) |
| status | string | Filter by status (draft, active, archived, discontinued) |
| category | string | Filter by category ID |
| brand | string | Filter by brand ID |
| page | number | Page number (default 1) |
| limit | number | Items per page (default 20, max 100) |
| sort | string | Sort field and direction (e.g. createdAt:desc) |
| include | string | Comma-separated: attributes, variants, optionTypes, categories, brands, media, inventory, pricing, customFields |
include=customFields returns the entity’s approved custom-field rows (with provenance columns) under data.customFields.
POST /catalog/entities body:
{ "type": "product", "slug": "blue-widget", "status": "draft", "metadata": {}, "customFields": { "material": "linen" }}customFields is validated against the entity type’s field definitions. On PATCH, it is a patch: an explicit null clears a field, omitted keys are untouched. See Catalog Custom Fields.
Custom-field review
Section titled “Custom-field review”| Method | Path | Description |
|--------|------|-------------|
| POST | /catalog/entities/:id/custom-fields/:fieldName/approve | Approve the newest proposal — displaces the live value atomically (catalog:update) |
| POST | /catalog/entities/:id/custom-fields/:fieldName/reject | Reject the newest proposal (catalog:update) |
Both accept a locale query parameter (default en). The org-scoped proposal queue lives at GET /api/admin/custom-field-proposals.
Field ownership
Section titled “Field ownership”| Method | Path | Description |
|--------|------|-------------|
| GET | /catalog/entities/:id/field-ownership | List ownership rows, optional ?storeId (catalog:read) |
| PUT | /catalog/entities/:id/field-ownership | Set an owner: { fieldPath, owner: "platform" \| "store" \| "shared", storeId?, variantId? } (catalog:update) |
See Catalog Field Ownership for path grammar and precedence.
Localized attributes
Section titled “Localized attributes”| Method | Path | Description |
|--------|------|-------------|
| PUT | /catalog/entities/:id/attributes/:locale | Set localized attributes |
| GET | /catalog/entities/:id/attributes/:locale | Get localized attributes |
PUT body:
{ "title": "Blue Widget", "description": "A fine blue widget.", "seoTitle": "Buy Blue Widget", "seoDescription": "Best blue widget online."}Categories
Section titled “Categories”| Method | Path | Description |
|--------|------|-------------|
| GET | /catalog/categories | List all categories |
| POST | /catalog/categories | Create a category |
| PATCH | /catalog/categories/:categoryId | Update a category |
| DELETE | /catalog/categories/:categoryId | Delete a category |
| POST | /catalog/entities/:id/categories/:categoryId | Add entity to a category |
| DELETE | /catalog/entities/:id/categories/:categoryId | Remove entity from a category |
Brands
Section titled “Brands”| Method | Path | Description |
|--------|------|-------------|
| GET | /catalog/brands | List all brands |
| POST | /catalog/brands | Create a brand |
| PATCH | /catalog/brands/:brandId | Update a brand |
| DELETE | /catalog/brands/:brandId | Delete a brand |
| POST | /catalog/entities/:id/brands/:brandId | Add entity to a brand |
| DELETE | /catalog/entities/:id/brands/:brandId | Remove entity from a brand |
Options and variants
Section titled “Options and variants”| Method | Path | Description |
|--------|------|-------------|
| POST | /catalog/entities/:id/options | Create an option type |
| POST | /catalog/options/:optionTypeId/values | Create an option value |
| POST | /catalog/entities/:id/variants | Create a single variant |
| POST | /catalog/entities/:id/variants/generate | Generate variants from an option matrix |
| POST | /catalog/entities/:id/variants/quick | One-call variant: upsert option axes inline + seed zero-stock (201 created / 200 existing) |
| POST | /catalog/entities/:id/variants/bulk | Create the full cartesian matrix from axes, skipping existing combos |
Base path: /api/carts
| Method | Path | Description |
|--------|------|-------------|
| POST | /carts | Create a new cart |
| GET | /carts/:id | Get cart with line items |
| POST | /carts/:id/items | Add an item to the cart |
| PATCH | /carts/:id/items/:itemId | Update item quantity |
| DELETE | /carts/:id/items/:itemId | Remove an item from the cart |
POST /carts body:
{ "currency": "USD", "customerId": "optional-uuid"}POST /carts/:id/items body:
{ "entityId": "uuid", "variantId": "uuid-or-omit", "quantity": 2, "unitPriceSnapshot": 1999}PATCH /carts/:id/items/:itemId body:
{ "quantity": 3 }Checkout
Section titled “Checkout”| Method | Path | Description |
|--------|------|-------------|
| POST | /api/checkout | Create an order from a cart |
Body:
{ "cartId": "uuid", "paymentMethodId": "pm_...", "currency": "USD", "customerId": "optional", "customerGroupIds": ["optional"], "promotionCodes": ["SAVE10"], "shippingAddress": { "line1": "123 Main St", "city": "Springfield", "state": "IL", "postalCode": "62701", "country": "US" }}Returns the created order. If after-hooks produce non-fatal errors, the response includes meta.hookErrors.
Failure: 422 with { error: { code: "CHECKOUT_FAILED", message: "..." } }.
Orders
Section titled “Orders”Base path: /api/orders
| Method | Path | Description |
|--------|------|-------------|
| GET | /orders | List orders |
| GET | /orders/:idOrNumber | Get order by UUID or order number (ORD-YYYY-NNNNNN) |
| PATCH | /orders/:id/status | Change order status |
| GET | /orders/:id/fulfillments | List fulfillments for an order |
| POST | /orders/:id/refunds | Refund specific line quantities (orders:update) |
| POST | /orders/:id/refunds/:refundId/undo | Undo a refund within the window (orders:update) |
| GET | /orders/:id/refunds | List an order’s refunds (orders:read) |
| GET | /orders/refunds/cap | Acting operator’s daily refund-cap status (orders:read) |
| POST | /orders/:id/notes | Add a note (orders:update) |
| GET | /orders/:id/notes | List notes, pinned-first (orders:read) |
| DELETE | /orders/:id/notes/:noteId | Delete a note (orders:update) |
| GET | /orders/:id/timeline | Merged activity timeline (orders:read) |
| GET | /orders/:id/invoice.pdf | Fiscal PDF invoice (orders:read) |
| GET | /orders/:id/invoice.html | HTML invoice (orders:read) |
| GET | /orders/:id/receipt.html | HTML receipt (orders:read) |
| POST | /orders/:id/invoice/email | Email the invoice (orders:read) |
GET /orders query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| status | string | Filter by status |
| page | number | Page number |
| limit | number | Items per page |
PATCH /orders/:id/status body:
{ "status": "confirmed", "reason": "optional reason string"}Valid transitions: pending → confirmed → processing → partially_fulfilled | fulfilled → refunded. Cancel is allowed from pending, confirmed, processing, partially_fulfilled.
POST /orders/:id/refunds body:
{ "lines": [{ "lineItemId": "uuid", "quantity": 1 }], "reason": "optional, max 500 chars"}Rejects refunding more than quantity - refundedQuantity on a line (422). A per-operator daily cap (policies.refundDailyCap, minor units) returns 403 when exceeded. Undo is blocked past policies.refundUndoWindowMinutes (default 15). See Refunds & Exchanges.
POST /orders/:id/notes body:
{ "body": "note text (1–2000 chars)", "pinned": false }GET /orders/:id/timeline returns newest-first entries merging status changes, notes, and refunds (with undo events):
{ "data": [{ "type": "status | note | refund", "at": "ISO", "actor": "...", "summary": "...", "data": {} }] }Inventory
Section titled “Inventory”Base path: /api/inventory
| Method | Path | Description |
|--------|------|-------------|
| GET | /inventory/check | Check stock for one or more entities |
| POST | /inventory/adjust | Adjust an inventory level |
| POST | /inventory/reserve | Reserve inventory |
| POST | /inventory/release | Release a reservation |
| POST | /inventory/warehouses | Create a warehouse |
| GET | /inventory/warehouses | List warehouses |
GET /inventory/check query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| entityIds | string | Comma-separated entity UUIDs |
POST /inventory/adjust body:
{ "entityId": "uuid", "variantId": "optional-uuid", "warehouseId": "uuid", "type": "restock", "quantity": 50}Pricing
Section titled “Pricing”Base path: /api/pricing
| Method | Path | Description |
|--------|------|-------------|
| POST | /pricing/prices | Create a price record |
| GET | /pricing/prices | List prices |
| POST | /pricing/modifiers | Create a price modifier |
GET /pricing/prices query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| entityId | string | Filter by entity ID |
| variantId | string | Filter by variant ID |
| currency | string | Filter by currency code |
| customerGroupId | string | Filter by customer group |
POST /pricing/prices body:
{ "entityId": "uuid", "variantId": "optional-uuid", "currency": "USD", "amount": 2999}Promotions
Section titled “Promotions”Base path: /api/promotions
| Method | Path | Description |
|--------|------|-------------|
| POST | /promotions | Create a promotion |
| GET | /promotions | List active promotions |
| POST | /promotions/validate | Validate a promotion code against a cart |
| POST | /promotions/:id/deactivate | Deactivate a promotion |
POST /promotions body:
{ "code": "SAVE10", "name": "Save 10%", "type": "percentage_off_order", "value": 10, "validFrom": "2025-01-01T00:00:00Z", "validUntil": "2025-12-31T23:59:59Z"}validFrom and validUntil are ISO 8601 strings coerced to Date objects on the server.
POST /promotions/validate body:
{ "code": "SAVE10", "currency": "USD", "subtotal": 5000, "lineItems": [ { "entityId": "uuid", "entityType": "product", "quantity": 1, "unitPrice": 5000, "totalPrice": 5000 } ], "customerId": "optional", "customerGroupIds": ["optional"]}Search
Section titled “Search”Base path: /api/search
| Method | Path | Description |
|--------|------|-------------|
| GET | /search | Full-text search across catalog entities |
| GET | /search/suggest | Autocomplete prefix suggestions |
GET /search query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| q | string | Search query |
| type | string | Filter by entity type |
| category | string | Filter by category |
| brand | string | Filter by brand |
| status | string | Filter by status |
| attr.<name> | string, repeatable | Filter by a custom-field attribute (equality only) |
| page | number | Page number (default 1) |
| limit | number | Results per page (default 20, max 100) |
| facets | string | Comma-separated facet fields — built-ins (type, category, brand, status) and attribute names |
Attribute filters use repeatable attr.<name>=<value> parameters: repeated values for one name are ORed, distinct names are ANDed, matching is equality only. Attribute names must match ^[A-Za-z0-9_-]+$ — anything else returns 422. Only fields whose definition sets filterable: true are indexed, using approved en-locale values; facet counts are keyed by attribute name.
Response includes data (hits array) and meta with total, page, limit, facets.
Without a configured search adapter, the in-memory fallback requires at least a query or one filter — a facets-only request returns an empty result rather than loading the whole catalog. Both shipped adapters (@porulle/adapter-pg-search, @porulle/adapter-meilisearch) implement attribute filtering and facets.
GET /search/suggest query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| prefix | string | Prefix to match |
| type | string | Filter by entity type |
| limit | number | Max suggestions (default 10) |
Base path: /api/media
| Method | Path | Description |
|--------|------|-------------|
| POST | /media/upload | Upload a file (multipart/form-data) |
| GET | /media/:id | Get media URL (redirects 302) |
| DELETE | /media/:id | Delete a media asset |
| POST | /media/attach | Attach a media asset to an entity |
POST /media/upload fields (multipart/form-data):
| Field | Type | Required |
|-------|------|----------|
| file | File | Yes |
| alt | string | No |
Every asset carries an origin (merchant | generated | imported, default merchant), an optional confidence, and an optional derivedFromAssetId link to its source asset. REST uploads are always merchant; the service-level media.upload() accepts an origin (channel imports stamp imported).
DELETE /media/:id — merchant-origin assets are write-guarded: deleting one requires media:write.
GET /media/:id query parameters:
| Parameter | Value | Description |
|-----------|-------|-------------|
| signed | "true" | Return a signed URL with expiry instead of a public URL |
POST /media/attach body:
{ "entityId": "uuid", "mediaAssetId": "uuid", "role": "thumbnail", "variantId": "optional-uuid", "sortOrder": 0}Attachments are unique per level: the same asset may attach once at entity level and once per variant, but a duplicate at the same level returns 409.
Payments
Section titled “Payments”| Method | Path | Description |
|--------|------|-------------|
| POST | /api/payments/webhook/:provider | Payment provider webhook receiver |
Accepts raw webhook payloads from the payment adapter named by :provider, which must match that adapter’s providerId — an unregistered provider returns 404 and records nothing. The delivery is verified by that adapter and recorded against it, so two gateways can be registered at once without either seeing the other’s events.
For the bundled Stripe adapter (/api/payments/webhook/stripe), a payment_intent.succeeded event carrying metadata.orderId transitions that order to confirmed automatically. That handling is Stripe’s own; another gateway’s events are verified and recorded but do not transition an order, because their event vocabulary is not core’s to interpret.
Webhooks
Section titled “Webhooks”Base path: /api/webhooks
Requires webhooks:manage permission.
| Method | Path | Description |
|--------|------|-------------|
| POST | /webhooks | Register a webhook endpoint |
| GET | /webhooks | List registered endpoints |
| DELETE | /webhooks/:id | Delete a webhook endpoint |
Customer portal
Section titled “Customer portal”Base path: /api/me
Requires an authenticated Better Auth session cookie. All routes return 401 without a session.
| Method | Path | Description |
|--------|------|-------------|
| GET | /me/profile | Get current customer profile |
| PATCH | /me/profile | Update current customer profile |
| GET | /me/addresses | List customer addresses |
| POST | /me/addresses | Add a new address |
| DELETE | /me/addresses/:id | Delete an address |
| GET | /me/orders | List orders for the current customer |
| GET | /me/orders/:idOrNumber | Get a specific order by UUID or order number |
| GET | /me/orders/:idOrNumber/tracking | Get fulfillment tracking for an order |
| GET | /me/orders/:orderId/downloads | Get download links for digital items |
| POST | /me/orders/:orderId/reorder | Create a new cart pre-filled from a previous order |
GET /me/orders query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| status | string | Filter by order status |
| page | number | Page number |
| limit | number | Items per page |
Analytics
Section titled “Analytics”Base path: /api/analytics
| Method | Path | Description |
|--------|------|-------------|
| GET | /analytics/meta | List available models, measures, and dimensions |
| POST | /analytics/query | Execute an analytics query |
| GET | /analytics/reports | List canned retail reports (analytics:read) |
| GET | /analytics/reports/:name | Run a retail report (analytics:read) |
POST /analytics/query body: See Analytics reference for the full query format.
Retail reports (:name): daily-journal, tax-summary, inventory-aging, sell-through, reorder-needed, staff-sales. Query params (all YYYY-MM-DD, optional): date for single-day reports; from + to for ranges. Calendar math uses the store’s settings.general.timezone. See Retail reports.
Settings
Section titled “Settings”Base path: /api/settings. All routes require settings:manage. See Store Settings.
| Method | Path | Description |
|--------|------|-------------|
| GET | /settings | All groups |
| GET | /settings/:group | One group’s value ({} when unset) |
| PATCH | /settings/:group | Shallow-merge a group; a null value deletes that key |
Known groups are validated (422 on bad input): general (currency, timezone, locale), branding (store identity for receipts), policies (e.g. refundDailyCap, refundUndoWindowMinutes). Unknown groups accept any object.
Base path: /api/tax. Class routes require tax:manage. See Tax Classes.
| Method | Path | Description |
|--------|------|-------------|
| GET | /tax/classes | List tax classes |
| POST | /tax/classes | Create a class (name slug, rateBps, isDefault?, isActive?) |
| PATCH | /tax/classes/:id | Update a class |
| DELETE | /tax/classes/:id | Delete a class |
Per-line tax resolves variant taxClass → entity taxClass → the org’s default class → 0. When an org defines active classes they take precedence over runtime region rates.
Base path: /api/audit
Requires audit:read permission.
| Method | Path | Description |
|--------|------|-------------|
| GET | /audit | List audit entries |
| GET | /audit/:entityType/:entityId | Audit history for a specific entity |
GET /audit query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| entityType | string | Filter by entity type (e.g. order, catalog_entity, inventory) |
| entityId | string | Filter by entity UUID |
| event | string | Filter by event name (e.g. orders.statusChange) |
| actorId | string | Filter by actor |
| from | string | ISO 8601 start date |
| to | string | ISO 8601 end date |
| limit | number | Max entries (default 50, max 100) |
Base path: /api/auth
Authentication is handled by Better Auth. These are the core sign-up/sign-in endpoints. Better Auth exposes additional endpoints for password reset, email verification, sessions, and OAuth.
| Method | Path | Description |
|--------|------|-------------|
| POST | /auth/sign-up/email | Register a new user |
| POST | /auth/sign-in/email | Sign in with email and password |
| GET | /auth/get-session | Get the current session |
POST /auth/sign-up/email body:
{ "password": "secret", "name": "Jane Doe"}POST /auth/sign-in/email body:
{ "password": "secret"}Job management
Section titled “Job management”Base path: /api/admin/jobs
Requires jobs:admin permission.
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/jobs/failed | List failed background jobs |
| POST | /admin/jobs/:id/retry | Re-enqueue a failed job |
GET /admin/jobs/failed query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| page | number | Page number |
| limit | number | Items per page |
POST /admin/jobs/:id/retry returns the updated job record.
Admin catalog
Section titled “Admin catalog”Base path: /api/admin. All routes require catalog:update.
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/custom-field-proposals | Org-scoped queue of proposed custom-field values, optional ?entityType, paginated |
| GET | /admin/entity-field-definitions | List runtime field definitions, optional ?entityType |
| POST | /admin/entity-field-definitions | Create a runtime field definition |
| PATCH | /admin/entity-field-definitions/:id | Update a definition |
| POST | /admin/entity-field-definitions/:id/archive | Archive a definition (archive-only lifecycle — no delete) |
Proposal items carry the full custom-field row plus entitySlug and entityType. See Catalog Custom Fields.
The cron job runner endpoint:
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/jobs/run | Process pending jobs (cron target) |
Query parameters: queue (default "default"), limit (default 10). Requires *:* permission via x-api-key.
Marketplace plugin
Section titled “Marketplace plugin”Added by @porulle/plugin-marketplace. All paths prefixed with /api/marketplace/.
Vendor management (admin)
Section titled “Vendor management (admin)”| Method | Path | Description |
|--------|------|-------------|
| GET | /vendors | List vendors |
| POST | /vendors | Register a vendor |
| GET | /vendors/:id | Vendor detail |
| PATCH | /vendors/:id | Update vendor |
| POST | /vendors/:id/approve | Approve vendor |
| POST | /vendors/:id/reject | Reject with reason |
| POST | /vendors/:id/suspend | Suspend vendor |
| POST | /vendors/:id/reinstate | Reinstate vendor |
| GET | /vendors/:id/documents | List documents |
| POST | /vendors/:id/documents | Upload document |
| POST | /vendors/:id/documents/:docId/approve | Approve document |
| POST | /vendors/:id/documents/:docId/reject | Reject document |
| GET | /vendors/:id/balance | Balance ledger |
| GET | /vendors/:id/performance | Performance metrics and rating |
Vendor portal (self-service)
Section titled “Vendor portal (self-service)”Scoped automatically to actor.vendorId.
| Method | Path | Description |
|--------|------|-------------|
| GET | /vendor/me | Own vendor profile |
| PATCH | /vendor/me | Update own profile |
| GET | /vendor/me/orders | Own sub-orders |
| POST | /vendor/me/orders/:id/confirm | Confirm sub-order |
| POST | /vendor/me/orders/:id/ship | Ship with tracking number |
| POST | /vendor/me/orders/:id/deliver | Mark delivered |
| POST | /vendor/me/orders/:id/cancel | Cancel sub-order |
| GET | /vendor/me/payouts | Payout history |
| GET | /vendor/me/balance | Balance ledger |
| GET | /vendor/me/reviews | Reviews received |
Other marketplace endpoints
Section titled “Other marketplace endpoints”| Group | Paths |
|-------|-------|
| Commission rules | GET /commission-rules, POST /commission-rules, PATCH /commission-rules/:id, DELETE /commission-rules/:id |
| Payouts | GET /payouts, POST /payouts/run, POST /payouts/:id/retry |
| Sub-orders (admin) | GET /sub-orders, GET /sub-orders/:id, PATCH /sub-orders/:id/status |
| Disputes | POST /disputes, GET /disputes, POST /disputes/:id/respond, POST /disputes/:id/escalate, POST /disputes/:id/resolve |
| Returns | POST /returns, POST /returns/:id/ship-back, POST /returns/:id/receive |
| Reviews | POST /vendors/:id/reviews, GET /vendors/:id/reviews, PATCH /vendors/:id/reviews/:reviewId |
POS plugin (auth & exchanges)
Section titled “POS plugin (auth & exchanges)”Added by @porulle/plugin-pos. See Point of Sale and Refunds & Exchanges. Service-level failures on these routes surface as 500 with the message.
| Method | Path | Description |
|--------|------|-------------|
| PUT | /api/pos/auth/pin | Set/rotate an operator PIN (pos:admin) |
| POST | /api/pos/auth/pin-login | PIN login → mints a short-lived per-shift API key (pos:operate) |
| POST | /api/pos/auth/override | Manager override by PIN (pos:operate) |
| POST | /api/pos/exchanges | Return + replacement order in one transaction (pos:manage + orders:update + orders:create) |
Layaway plugin
Section titled “Layaway plugin”Added by @porulle/plugin-layaway. All paths under /api/layaways. See Layaway.
| Method | Path | Description |
|--------|------|-------------|
| POST | /layaways | Create a plan; reserves stock (layaway:operate) |
| GET | /layaways | List plans, optional ?status (layaway:operate) |
| GET | /layaways/:id | Plan with its payment ledger (layaway:operate) |
| POST | /layaways/:id/payments | Record an installment; auto-completes at full payment (layaway:operate + orders:create) |
| POST | /layaways/:id/forfeit | Forfeit a plan; releases stock (layaway:manage) |
Permission requirements
Section titled “Permission requirements”| Endpoint group | Required permission |
|----------------|---------------------|
| POST /webhooks, GET /webhooks, DELETE /webhooks/:id | webhooks:manage |
| GET /audit, GET /audit/:entityType/:entityId | audit:read |
| POST /pricing/prices, POST /pricing/modifiers | pricing:create |
| GET /pricing/prices | pricing:update |
| POST /promotions | promotions:create |
| PATCH /promotions/:id, POST /promotions/:id/deactivate | promotions:update |
| POST /marketplace/vendors/:id/approve, .../suspend | marketplace:admin |
| GET /admin/jobs/failed, POST /admin/jobs/:id/retry | jobs:admin |
| GET /admin/custom-field-proposals, /admin/entity-field-definitions group | catalog:update |
| Custom-field approve/reject | catalog:update (+ catalog:sync for store-sourced entities) |
| GET /catalog/entities/:id/field-ownership | catalog:read |
| PUT /catalog/entities/:id/field-ownership | catalog:update (+ catalog:sync for store-sourced entities) |
| DELETE /media/:id (merchant-origin assets) | media:write |
| GET /settings, PATCH /settings/:group | settings:manage |
| GET /analytics/reports, GET /analytics/reports/:name | analytics:read |
| GET/POST/PATCH/DELETE /tax/classes | tax:manage |
| POST /orders/:id/refunds, .../undo, notes write | orders:update |
| POST /pos/auth/pin | pos:admin |
| POST /pos/auth/pin-login, .../override | pos:operate |
| POST /pos/exchanges | pos:manage (+ orders:update, orders:create) |
| POST /layaways, .../payments | layaway:operate |
| POST /layaways/:id/forfeit | layaway:manage |
Webhook event types
Section titled “Webhook event types”Events that trigger delivery when a matching endpoint is registered:
| Event | Trigger |
|-------|---------|
| orders.create | New order created via checkout |
| orders.statusChange | Order transitions to a new status |
| catalog.create | New catalog entity created |
| catalog.update | Catalog entity updated |
| catalog.delete | Catalog entity deleted |
| inventory.afterAdjust | Inventory level adjusted |
| customers.create | New customer profile created |
| customers.update | Customer profile updated |
| pricing.create | New price record created |
| pricing.update | Price record updated |
| promotions.create | New promotion created |
| promotions.update | Promotion updated or deactivated |
| fulfillment.create | New fulfillment record created |
| cart.afterAddItem | Item added to a cart |
Error codes
Section titled “Error codes”{ "error": { "code": "NOT_FOUND", "message": "Entity not found." }}| Code | HTTP status | Description |
|------|-------------|-------------|
| NOT_FOUND | 404 | Resource does not exist |
| VALIDATION_FAILED | 422 | Input validation failed |
| CHECKOUT_FAILED | 422 | Checkout pipeline error |
| FORBIDDEN | 403 | Insufficient permissions |
| CONFLICT | 409 | Conflicting state (e.g., duplicate slug) |
| TOO_MANY_REQUESTS | 429 | Rate limit exceeded |
| PAYLOAD_TOO_LARGE | 413 | Request body exceeds 1 MB |
Pagination
Section titled “Pagination”All list endpoints accept page and limit query parameters. limit is capped at 100.
{ "data": [], "meta": { "pagination": { "page": 1, "limit": 20, "total": 142, "totalPages": 8 } }}