Reference

Data access

App tokens, permissions, the Open API, rate limits, error codes and background tasks.

View Markdown

Apps read/write shop data through the existing Open API (Open API docs): same endpoints, response format, pagination and update rules. Code that already works with an API key only needs a different way to get credentials.

TypeScript
// Before: a static, full-access key that leaks easily when it sits in frontend code
fetch(`https://pos.pages.fm/api/v1/shops/${SHOP_ID}/orders?api_key=${STATIC_KEY}`);

// Inside an app: the SDK attaches the app token
await pos.api.get(`/shops/${pos.shop.id}/orders`);

App token

POS issues an app token automatically while an extension runs; apps do not need, and should not have, a static API key.

Shop API Key User API Key App token
Issued by Admin, long-lived User, long-lived POS, automatically, expires after 15 minutes
Identity The shop's connection account A real user A real user + the app
Permissions Full access (can be limited) The user's permissions The user's permissions ∩ the app's access
Shop scope One shop Every shop the user belongs to Only the shop currently open
Used from Servers Servers, scripts Inside the app in POS
  • The token is bound to one shop, one user, one app and the running version.
  • The SDK sends the token in the Authorization: Bearer <token> header. ?api_key=<token> is still accepted for compatibility with existing Open API code, but not recommended because URLs easily end up in logs.
  • The token is revoked immediately when the app is disabled or removed, the admin changes access, or the user logs out or their session changes. Removing the user from the shop or changing their role takes effect on the next request.

Effective permissions

Text
App token permissions = permissions of the current user ∩ the app's access (as approved by the admin)

User permissions apply exactly as when the user works in POS: role, per-warehouse permissions, CRM table/field permissions, department, working hours, allowed IPs, enabled modules and subscription.

The app's access narrows them further: allowed methods (only GET by default), warehouses, order sources, order statuses, order creation date, product categories/brands, hidden order fields. See Manifest › access.

Consequences:

  • The same app shows more data to users with broader permissions than to users with narrower ones.
  • A request that works with a Shop API Key may be rejected with an app token. That is the intended behavior.
  • Apps should handle 403 instead of assuming every user has every permission.

Always-blocked groups

These groups are only available from a logged-in session in the POS UI; every kind of API key and app token is rejected:

  • Staff management: adding/editing staff, assigning roles.
  • Linking Google accounts.
  • API key management.
  • App management and app token issuance.

Allowed endpoints

  • Only use endpoints listed in the Open API docs. Internal endpoints used by the POS UI can change at any time and will be blocked for app tokens.
  • Every path must belong to the current shop: /shops/{shop_id}/....
  • If data or an operation you need is missing, request it in the Open API instead of using internal endpoints.

Main endpoints for the resources that have extension points:

Resource Open API Record id in the path
order GET/POST /shops/{shop_id}/orders, GET/PUT /shops/{shop_id}/orders/{id} record.system_id
product GET /shops/{shop_id}/products/variations, POST /shops/{shop_id}/products, GET/PUT /shops/{shop_id}/products/{id} record.id
purchase GET/POST /shops/{shop_id}/purchases, GET/PUT /shops/{shop_id}/purchases/{id} record.id
export GET/POST /shops/{shop_id}/export, GET/PUT /shops/{shop_id}/export/{id} record.id
warehouse_transfer GET /shops/{shop_id}/transfers, POST /shops/{shop_id}/transfers/multi, GET/PUT /shops/{shop_id}/transfers/{id} record.id

Context data

pos.data holds the minimum data POS is already showing (for example the id and status of the open order or purchase), so extensions do not have to call the API right away. It is already filtered by the app's access. Call the Open API for anything else.

Rate limits

  • Counted per app + user + shop + endpoint; all extensions of the same app share the quota: under 30 reads (GET) and under 100 writes per second.
  • When exceeded: HTTP 429. Retry later with an increasing delay (for example 1 s, 2 s, 4 s), at most 3 times.
  • Do not call the API on every render. Batch requests, paginate, and use AbortSignal to cancel stale requests when the user changes filters.

Audit

Every write made with an app token is recorded with the real user, the app and the version. Automatic fields such as creator and last editor are set to the current user.

Error codes

The code column is the error_code field of the response body, which the SDK puts in PosApiError.code.

HTTP code Meaning SDK behavior
401 111 The token expired or was revoked: the app was disabled, another version was activated, the departments that can see the app changed, or the user's login session changed (see App token) Gets a new token and retries once. If it still fails, throws PosApiError (status: 401, code: 111); if POS refuses to issue a new token (the app is disabled or the user can no longer see it), throws PosRuntimeError with code: 'failed'
403 Not allowed: role, warehouse, working hours, IP, always-blocked groups Throws PosApiError
403 110 The endpoint is not in the Open API Throws PosApiError
404 Not found, or the data is outside the app's access Throws PosApiError
404 The method is not in access.methods (when sent directly, bypassing the SDK) The SDK blocks it before sending
429 106 Rate limit exceeded Throws PosApiError; the app retries
200 depends on the endpoint The body has success: false: a business error, see message Throws PosApiError with status: 200

Calling external systems

  • Declare origins in network.allowed_origins; requests to other origins are blocked.
  • External servers must enable CORS for the app's sandbox origin, which is the Origin header of the requests the app sends while running the active version. In development mode the origin is the address you entered when turning it on (for example http://localhost:5174); enable CORS for it too if you want to test.
  • Never send the app token elsewhere, except to the backend of a self-hosted page. If an external system needs POS data, the app reads it through the Open API and sends only what is needed.
  • Never put secrets in app code. Anyone who can open the app can download the bundle. To call systems that need a secret, put a backend of your own in between and authenticate callers separately.

Background tasks

Apps only run while someone has POS open. Scheduled jobs, syncs or event handling while nobody uses POS need the shop's own backend:

  • Call the Open API with a User API Key (recommended, user-scoped permissions) or a Shop API Key as today.
  • Receive change events through the shop's webhooks (Settings › Webhook - API).

Planned for phase 2: a per-app service key (acting as a designated user, with the app's access applied) and per-app webhook subscriptions with HMAC signatures.

App-owned data

This version has no dedicated storage for apps. Options:

  • The shop's custom CRM tables, read/written through the Open API, with table- and field-level permissions built in.
  • The shop's own backend.
  • localStorage in an iframe page: stored only in the current browser, suitable for UI preferences.