Calling it
https://api.tassyir.io/apps/2026-10/<resource>, with an access token:
GET /business needs no scope; every other route needs the scope its guide
names. With @tassyir/app, paths are relative to /apps/2026-10, and paths,
parameters, bodies and answers are typed:
Versions
The version is in the path:2026-10 is the first and, today, the only one.
Within a version, fields may be added, never renamed, retyped or removed, so
ignore fields you do not know. A version that does not exist answers 404.
Versions are released quarterly and supported for 12 months. Tassyir emails
you 90 days before a version you use is retired; once it is, its requests are
answered by the oldest supported version, with the headers
Tassyir-Api-Version-Served and Deprecation.
Values
- JSON fields are camelCase. Ids are strings: never parse them as
numbers. Times are ISO 8601 in UTC (
2026-10-11T09:12:44.120Z). - Money is a decimal string with two decimals (
"2500.00") next to acurrencyfield (DZDtoday), never a number. Keep it a string; for arithmetic, use a decimal library or integer cents, neverparseFloat. An amount Tassyir does not know isnull, never"0.00". When you send money (a product’s price), send a decimal string too. - Open vocabularies (an order’s
status, a carrier’s status) are strings, not enums: a merchant or a carrier can add values. - Contact fields (a buyer’s name, phones, email, address, an order’s
notes) are present only with
read_customer_contact. Without it the keys are absent, notnull(Customers and contact data).
One resource, and lists
A single resource is{ "data": { … } }. A list is
{ "data": [ … ], "nextCursor": "…" | null }:
- Lists are newest first and keyset-paged: a row added while you walk a list never shifts the pages to come, and no row is repeated or skipped. New rows appear on a new walk from the first page.
nextCursoris opaque: never build or edit one. It belongs to its list and its filters; with another list or other filters it is 400INVALID_CURSOR.nextCursor: nullis the last page.- A page can hold fewer than
limititems whilenextCursoris set (app data drops values whose owner you can no longer read). Stop onnull, not on a short page. - Unknown parameters are refused (400
VALIDATION_FAILED, naming them): a misspelt filter never silently returns everything.
admin.paginate walks every page for you:
Fields
?fields=status,total,buyer trims each resource to the named top-level
fields; id is always kept. An unknown name is 400 INVALID_FIELDS, with the
allowedFields. Trimming happens after masking: naming a field your
installation may not read returns nothing for it. With the SDK, fields is a
typed list: fields: ['status', 'total'].
Idempotent writes
Every write (POST, PUT, PATCH, DELETE) accepts an Idempotency-Key
header: 1 to 255 visible ASCII characters, usually a UUID. Send one, and a
retry of the same write never runs it twice. Keys belong to your
installation and are kept 24 hours.
@tassyir/app sends a new key with every write and reuses it for its own
retries. Pass your own when a retry may come from elsewhere (a job that can
run twice), or false to send none:
Errors
Every error under/apps/2026-10/ is application/problem+json
(RFC 9457):
- Act on
code, never ondetailortitle:codeis stable. fixis one sentence saying what to do, when there is one.- Other members name what was refused:
permission,storeId,limit,errors,allowedFields, … requestId(also theX-Request-Idheader) names the request: quote it when you ask Tassyir for help.- A 5xx never carries an internal message: retry with backoff.
@tassyir/app turns a problem into a TassyirApiError, with code, fix,
status, requestId, retryAfterSeconds and the whole problem. The codes
each call can answer are typed:
Stores: 460, and 404
An installation reaches only the stores its merchant gave it. A store your request names (astoreId filter or field) outside them is HTTP 460
STORE_SCOPE_FORBIDDEN. A resource of another store, named by its id,
reads as 404 NOT_FOUND: the API never says it exists.
Rate limits
Every answer carries your installation’s bucket:- Your installation’s bucket: by default 40 requests at once, refilled at
4 a second. Past it: 429
RATE_LIMITED, withRetry-After(seconds until one more request fits). Online and offline tokens of an installation share one bucket. - The business’s ceiling: all the apps of one business share, by
default, 20 requests at once and 20 a second. Past it: 429
BUSINESS_RATE_LIMITED, withRetry-After; then other apps of the business are using the room, so spread your calls out. - A busy API: when the API is already running its maximum of app
requests, it answers 503
API_BUSYwithRetry-After: 1, having done nothing: any call, a write included, can be retried as it was. - Image uploads have their own daily quota (Products and collections).
@tassyir/app; with
retries: 3 it waits Retry-After and retries 429 and 503 API_BUSY (writes
included), any other 503, network errors, 502 and 504 for calls safe to
repeat (reads, and writes with a key), and 500 for reads only.
Plans
No plan is needed to install an app or call the API. A plan’s limits still apply to what your app does, as they apply to the merchant: the service refuses with the code the dashboard would show (PLAN_FEATURE_REQUIRED,
ORDER_LIMIT_REACHED, SUBSCRIPTION_REQUIRED).
Masking
- Without
read_customer_contact, contact fields are absent. - When the merchant’s plan hides phones (no active subscription, or a billing
grace that hides them), phones read
**********. - A waiting order (past the plan’s order allowance,
waiting: true) is masked whatever your scopes: phones**********and notesnull, until it unlocks (orders/updatedwithwaiting).
Caching
Every answer isCache-Control: no-store: it is a merchant’s private data.
Cache only what your app needs, and drop it on app/uninstalled.