Skip to main content
Your server needs two answers before it calls the Admin API: who is looking (which person, business and installation) and a token to call with. The ID token answers the first; offline and online access tokens the second. @tassyir/app handles both, and this guide explains what it does so you can rely on it. Every one of them but the client id is a secret: keep it on your server, and never log it or put it in a page, a URL or a repository.

Set up the SDK

Create one TassyirApp per process. It needs your client id and secret, your app’s numeric id (to verify webhooks), and a token store: where each installation’s offline tokens live. A store must be durable (a set that resolved survives a crash) and must return the latest set.
MemoryTokenStore exists for tests only: it forgets everything on restart. The @tassyir/app README has a complete server with a store and a refresh lock.

Who is looking: the ID token

Your page in the dashboard asks App Bridge for an ID token (Embedded pages) and sends it to your server, which verifies it. The ID token is the only proof of who is looking. The address your page is loaded at, its query string and App Bridge’s context are not: anyone can open your page’s URL with any query string. Read the user, the business and the installation from a verified ID token, nowhere else. An ID token is a JWT signed with Ed25519 (alg: "EdDSA"): tassyir.verifyIdToken(token) checks it the way Tassyir does: alg: EdDSA only, the key chosen by kid from GET https://api.tassyir.io/apps/.well-known/jwks.json (cached an hour, fetched again for an unknown kid), the signature before any claim, iss, aud equal to your client id, and exp, nbf and iat with 30 seconds of clock leeway. It returns userId, businessId, installationId, sessionId and locale.
  • Ask for a new ID token for every request to your server. It lives 60 seconds, and the token endpoint accepts each one once.
  • Accept ID tokens over https only: one is a bearer credential for a minute.
  • A page may ask for 30 ID tokens a minute. A page that calls your server more often should batch its requests, or your server can open its own short session once it has verified a token.

Embedded apps: one offline token per installation

tassyir.authenticate(idToken) verifies the ID token, then reuses the installation’s stored offline token, or exchanges the ID token for one and stores it. Your server can then call the Admin API as your app for that installation, from a page request, a webhook or a background job.
Keep the offline token you have. Each exchange for an offline token starts a new refresh family, and an installation keeps at most 25: past that, the oldest families are revoked. An app that exchanged on every page view would revoke its own tokens.

Online tokens: acting for a person

An online token acts for the dashboard user who opened your page. What it can do is your app’s scopes and that person’s own permissions, in your installation’s stores and that person’s stores. An online token whose person may only see the orders assigned to them reads only those orders. Use one when your app must respect what the person looking may see.
Changes made with an online token are still recorded under your app’s name. Each online exchange spends an ID token: get a new one from the page when the online token expires.

Headless apps: the install code

An app with no page (no admin.page) and an auth.callback_url gets its first tokens from an install code. Right after the install, the dashboard sends the merchant’s browser to your callback with ?code=tsy_ic_…. The code is valid 5 minutes, once, and only your client secret can redeem it.
Tassyir never calls your callback itself: the merchant’s browser does. Never log the URL with its code. When the merchant later accepts a version that changes your scopes, your callback receives a new code the same way.

Refresh safely

An access token lives one hour. tassyir.admin(installationId) refreshes it when needed, and every refresh rotates the refresh token: the old one is spent.
  • A spent refresh token presented again within 60 seconds of its rotation is accepted (an answer lost on the way).
  • Presented later, it revokes the whole family: every refresh and access token issued from it. The merchant then has to reopen your page (an embedded app) or reinstall your app (a headless app).
@tassyir/app’s token manager is built so this does not happen:
  • At most one refresh runs per installation in a process; concurrent callers share its result.
  • The rotated token set is written to your store before the new access token is used. If the write fails, the call fails with TOKEN_STORE_FAILED, and the set is written again on the next call.
  • A refresh whose answer was lost is retried with the same token for at most 40 seconds, inside the 60-second grace.
  • A refresh token the process already rotated is never presented again (REFRESH_TOKEN_ALREADY_USED), even if a stale store hands it back.
More than one process? Then two processes can refresh the same installation at once. Pass a refreshLock (an object with withLock(key, task), built on a lock your processes share) to createTassyirApp, and keep the tokens in a store every process reads. The token set is read again inside the lock, so a process that waited uses the fresh token instead of the spent one.

When a token stops working

The Admin API answers 401 APP_TOKEN_INVALID for every token it will not take: expired, revoked, or of an installation that is no longer active. The answer never says which. Forget an installation’s tokens with tassyir.tokens.forget(installationId) when you receive app/uninstalled or app/scopes_updated.

Client secrets

Create secrets in the console’s Credentials tab. A secret is shown once; Tassyir keeps only a hash and its first characters (tsy_cs_Ab3xY9Qz), so a lost secret means a new one.
  • Rotate by creating a new secret: every active secret keeps working for 24 more hours, so you can deploy the new one first (a secret already in its 24 hours keeps its end).
  • An app has at most 3 live secrets, and a developer may create 10 an hour.
  • Revoke a leaked secret in the console: the token endpoint refuses it at once. Access tokens already issued live out their hour; refreshing needs a live secret.

The token endpoint, without the SDK

POST https://api.tassyir.io/apps/oauth/token, with your client id and secret as HTTP Basic authentication (never a client_secret parameter) and form-encoded parameters (a flat JSON object works too):
  • refresh_token and refresh_token_expires_in (90 days) come with offline tokens only; user_id with online tokens only; issued_token_type with an ID-token exchange only.
  • scope is the installation’s granted scopes, implied ones included. The scope parameter of the request is ignored.
  • Errors are OAuth’s { "error", "error_description" }, not problem+json: invalid_client (401: the client id or secret, or a client_secret in the body), invalid_grant (an ID token, code or refresh token that is invalid, expired, used, revoked or another app’s), invalid_request, unauthorized_client (the app is suspended), unsupported_grant_type, slow_down (429, with Retry-After) and temporarily_unavailable (503, with Retry-After).
  • Each app may make 300 token requests a minute.
The full list is in Errors.