Developers
Start building
APIs/App API

App API

Routes, permissions, pagination, idempotency and errors of the App API, and the PolturaClient methods that call them.

On this page

The App API is the HTTP API your server uses to read and change a store's data, under /apps-api/v1. Every request carries an installation access token from the merchant's grant (see Authentication), and can reach only what the merchant granted to that installation. New to it? Start with Make your first API call.

The production origin of the App API is not published yet, so the examples read it from POLTURA_API.

PolturaClient from @poltura/app-sdk wraps every route: new PolturaClient({ origin, accessToken, timeoutMs?, maximumResponseBytes?, fetch? }). origin is an HTTPS origin with no path. Requests time out after 10 seconds unless you set timeoutMs (up to 60 seconds), and answers over 1 MiB are refused unless you raise maximumResponseBytes (up to 8 MiB). The client sends the token as Authorization: Bearer, never sends cookies and never follows redirects.

A write, from the complete example: saving to app storage with the version it replaces and an idempotency key.

export async function saveFeatured(
  client: PolturaClient,
  productIds: readonly string[],
  expectedVersion: number,
  idempotencyKey: string,
): Promise<StorageRecord> {
  const value = FeaturedProducts.parse({ productIds: [...productIds] });
  return client.storage.put(STORAGE_NAMESPACE, FEATURED_KEY, value, {
    expectedVersion,
    idempotencyKey,
  });
}

Routes

Lists take limit (1 to 100, default 25) and cursor, and answer with nextCursor: pass it back to get the next page, until it is null. Each route also has its own page in this section, with parameters and examples.

MethodPathPermissionSDKRequestResponse
GET/apps-api/v1/storesettings.readstore.retrieveStoreContext { id, name, locale, currency, timezone }
GET/apps-api/v1/installationnoneinstallation.retrieveno tenant overrideInstallationContext { installationId, storeId, appId, releaseId, generation, permissions } (permissions max 32)
GET/apps-api/v1/productsproducts.readproducts.listListOptionsProductPage { products, nextCursor } (max 100)
GET/apps-api/v1/products/{id}products.readproducts.retrieveProductIdProductResponse { product }
PATCH/apps-api/v1/products/{id}products.writeproducts.updateProductWriteInput { title?, description?, handle?, status?, metadata? } + Idempotency-KeyWriteResult { ok: true, version }
GET/apps-api/v1/collectionsproducts.readcollections.listListOptionsCollectionPage
GET/apps-api/v1/collections/{id}products.readcollections.retrieveCollectionIdCollectionResponse
PATCH/apps-api/v1/collections/{id}products.writecollections.updateCollectionWriteInput { title?, handle?, metadata? } + Idempotency-KeyWriteResult
GET/apps-api/v1/ordersorders.readorders.listOrderListOptions (status?, updatedAfter?)OrderPage
GET/apps-api/v1/orders/{id}orders.readorders.retrieveOrderIdOrderResponse { order: PublicOrder }
PATCH/apps-api/v1/orders/{id}orders.writeorders.updateOrderWriteInput { metadata?, tags?, note? } + Idempotency-KeyWriteResult
GET/apps-api/v1/customerscustomers.readcustomers.listCustomerListOptions (email?, updatedAfter?)CustomerPage
GET/apps-api/v1/customers/{id}customers.readcustomers.retrieveCustomerIdCustomerResponse
PATCH/apps-api/v1/customers/{id}customers.writecustomers.updateCustomerWriteInput { metadata?, tags? } + Idempotency-KeyWriteResult
GET/apps-api/v1/inventoryproducts.readinventory.listInventoryListOptions (variantId?, sku?)InventoryPage { levels, nextCursor }
GET/apps-api/v1/storage/{namespace}nonestorage.listStorageListQueryStoragePage
GET/apps-api/v1/storage/{namespace}/{key}nonestorage.getStorageRecord
PUT/apps-api/v1/storage/{namespace}/{key}nonestorage.putStoragePutInput { value, expectedVersion } + Idempotency-KeyStorageRecord
DELETE/apps-api/v1/storage/{namespace}/{key}nonestorage.deleteStorageDeleteInput { expectedVersion } + Idempotency-KeyStorageDeleteResult

CatalogueProduct: { id, title 1–500, handle 1–255, description max 20000 or null, image, updatedAt }. PublicProductWriteStatus is draft | published. PublicMetadata max 64 snake_case keys, values max 2000. PublicTags max 32. PublicOrder includes orderNumber (1–64), items max 200 and shippingAddress. Money is { amount: "49.99", currency: "EUR" } in major units; do not multiply by 100.

A write counts as two requests against the installation's quota. Writing a field that the route does not allow fails with invalid_input. Every write takes an Idempotency-Key header, so a retried request is applied once.

Storage limits

RuleValue
Value bytes65536
JSON depth8
JSON nodes1024
Namespaces16
Bytes / records per installation10 MiB / 1000
List page100 / 900 KiB
Live idempotency128
Idempotency TTL24 h
Cursor TTL1 h
First putcreates version 1 with expectedVersion 0

A namespace is 1 to 64 characters, lowercase letters, digits and _, starting with a letter. A key is 1 to 128 characters, letters, digits, ., _ and -, starting with a letter or digit. Sending the same idempotency key with the same body returns the first answer again; with a different body it fails with conflict. A write whose expectedVersion is not the current version fails with stale_version: read the record again and retry.

Backend actions

Actions go the other way: a worker calls useAction(actionId), and Poltura sends a signed POST with { input, context } to your backendOrigin. They are not part of the App API; see Signed actions. Limits per installation: 60 calls a minute from the admin and 300 from the storefront, and 10 a minute per shopper. When a call cannot complete, the worker gets one of not_declared, timeout, upstream, too_large or unavailable.

Errors

A failed call throws PolturaRequestError with code, status, requestId (quote it when you ask for help) and, for rate_limited, retryAfterMs. When the request never got an answer the code is one of response_too_large, invalid_response, request_failed, aborted or unavailable.

The API's error body is { "error": { "code", "message", "requestId", "details"?, "retryAfterMs"? } }, with these codes:

codeHTTP
invalid_input400
unauthorized401
forbidden403
not_found404
conflict409
stale_version412
quota_exceeded409
rate_limited429
payload_too_large413
unsupported_api_version400
unavailable503

The OpenAPI 3.1 specification is at /openapi.json, generated from the same contracts as the SDK.

Next

Last updated