Authentication
Every jtWMS endpoint requires authentication, apart from a few public ones such as sign-up and the plan list. You can authenticate in two ways:
| Method | Header | Use it for |
|---|---|---|
| User access token | Authorization: Bearer <access-token> |
Acting as a person: scripts and apps where someone signs in. |
| API key | X-Api-Key: <key> |
Machine-to-machine integrations, such as an ERP or e-commerce connector. |
Either way, the request is limited to the data of one tenant (your organisation), and to the permissions the token or key carries. See Permissions and scopes.
All examples use https://<your-jtwms-host>. On a local development stack, the API is served at
http://localhost:5050.
User tokens
Section titled “User tokens”jtWMS issues OAuth 2.0 tokens at POST /connect/token. It supports three grant types:
password: exchange a user’s email and password for tokens.refresh_token: exchange a refresh token for new tokens.client_credentials: authenticate an OAuth client rather than a user (see Client credentials).
Other grant types, including authorization code and PKCE, are not supported, and return
400 unsupported_grant_type.
Your jtWMS operator registers the OAuth client you use and gives you its client_id. A public
client, such as the one the jtWMS web app uses, sends no client secret.
Sign in with the password grant
Section titled “Sign in with the password grant”Send the user’s email as username. Request the offline_access scope to receive a refresh token
as well as an access token.
curl -X POST "https://<your-jtwms-host>/connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=password" \ --data-urlencode "client_id=<client-id>" \ --data-urlencode "username=alex@example.com" \ --data-urlencode "password=<password>" \ --data-urlencode "scope=offline_access"{ "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 900, "refresh_token": "eyJhbGciOi..."}A wrong email or password, or a user who is deactivated, gets 400 with the OAuth error
invalid_grant. The /connect/* endpoints use the OAuth error format (error and
error_description), not the problem details the rest of the API uses.
Send the access token in the Authorization header:
curl "https://<your-jtwms-host>/api/v1/facilities" \ -H "Authorization: Bearer <access-token>"The access token carries the user’s tenant, permissions and the features of the tenant’s plan. Treat it as opaque: its contents can change, so don’t build on them.
Token lifetimes
Section titled “Token lifetimes”| Token | Lifetime |
|---|---|
| Access token | 15 minutes |
| Refresh token | 14 days, sliding: each refresh issues a new refresh token valid for another 14 days |
Refresh a session
Section titled “Refresh a session”Before the access token expires, or when a request gets 401, exchange the refresh token for new
tokens:
curl -X POST "https://<your-jtwms-host>/connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "client_id=<client-id>" \ --data-urlencode "refresh_token=<refresh-token>"Refresh tokens rotate. Every refresh redeems the token you send and returns a new one, so store the
new refresh_token from each response and discard the old one. If your client makes requests in
parallel, refresh once and share the result rather than refreshing from each request.
A refresh re-reads the user, so the new access token reflects their current permissions and plan
features. A refresh for a user who has been deactivated fails with invalid_grant.
Switch to your sandbox
Section titled “Switch to your sandbox”If your organisation has a sandbox (POST /api/v1/sandbox), a user holding
identity.sandbox.manage can switch their session into it, and back, without signing in again.
The web app does this from Settings → Sandbox (see Sandbox).
Exchange the current access token (RFC 8693 token exchange):
curl -X POST "https://<your-jtwms-host>/connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ --data-urlencode "client_id=<client-id>" \ --data-urlencode "subject_token=<access-token>" \ --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \ --data-urlencode "scope=offline_access" \ --data-urlencode "jtwms_target=sandbox"The response holds new access and refresh tokens for the sandbox. Requests made with them act in
the sandbox, which sees none of your organisation’s data, and the token’s tenant_kind claim reads
sandbox. To go back, exchange a sandbox access token the same way with jtwms_target=home.
Revoke the refresh token of the session you leave, as when signing out.
The switch fails with invalid_grant when the user lacks the permission, the organisation has no
sandbox, or the session is already where it asked to go. A sandbox session ends when the user is
deactivated, loses the permission or resets their password, and when the sandbox is reset or
deleted.
Sign out
Section titled “Sign out”Revoke the refresh token at POST /connect/revoke (RFC 7009), then discard both tokens:
curl -X POST "https://<your-jtwms-host>/connect/revoke" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "client_id=<client-id>" \ --data-urlencode "token=<refresh-token>" \ --data-urlencode "token_type_hint=refresh_token"401 token_revoked
Section titled “401 token_revoked”jtWMS ends a user’s existing access tokens when their access changes: when their roles or a role’s
permissions change, when they are deactivated, or when their password is reset. A request made with
an access token issued before the change gets 401 with errorCode token_revoked:
{ "type": "https://tools.ietf.org/html/rfc9110#section-15.5.2", "title": "Unauthorized", "status": 401, "detail": "Your session permissions have changed. Please log in again.", "errorCode": "token_revoked", "traceId": "00-6f1c0a9d2b6e4c1f8a3b5d7e9f0a1b2c-4d5e6f7a8b9c0d1e-00", "correlationId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"}Don’t retry the request with the same access token. Refresh the session once: after a role or
permission change, the refresh succeeds and the new access token carries the user’s current
permissions. A password reset also ends the user’s refresh tokens, and a deactivated user can’t
refresh, so if the refresh fails with invalid_grant, sign the user in again with the password
grant.
Client credentials
Section titled “Client credentials”A confidential OAuth client can get a token with grant_type=client_credentials, its client_id
and its client_secret. That token identifies the client only: it carries no user, no tenant and no
permissions, so it gives no access to any tenant’s data.
For machine-to-machine access to your tenant’s data, use an API key.
API keys
Section titled “API keys”An API key authenticates a system as your tenant, with a fixed set of permissions chosen when the key
is created. Create and revoke keys in the app (see API keys) or through
/api/v1/api-keys with a user token. Each tenant starts with one key, “Default API Key”, which can
read but not change data.
Send the key in the X-Api-Key header:
curl "https://<your-jtwms-host>/api/v1/products?pageSize=20" \ -H "X-Api-Key: jtwms_live_<rest-of-key>"Key format
Section titled “Key format”A key is jtwms_live_ followed by 32 URL-safe characters (A-Z, a-z, 0-9, - and _), 43
characters in all. jtWMS shows the full key once, in the response that creates it, and stores only
a hash of it. Afterwards a key is identified by its keyPrefix, its first 16 characters, so store
the key in a secret store as soon as you create it. A lost key cannot be recovered: create a new one
and revoke the old one.
Using a key
Section titled “Using a key”- Send exactly one
X-Api-Keyheader. A request with more than one is refused. - A request that carries both an
Authorizationheader andX-Api-Keyis authenticated by the bearer token, and the key is ignored. - A key’s expiry (
expiresAt, if you set one) and its revocation take effect within about a minute. A revoked or expired key can’t be reactivated. - Requests made with a key have no user, so actions are recorded in the audit trail against the key.
API key errors
Section titled “API key errors”| Status | errorCode |
Meaning | What to do |
|---|---|---|---|
401 |
invalid_api_key |
The key is unknown, revoked or expired, or the tenant is no longer active. The response carries WWW-Authenticate: ApiKey. |
Check the key. Don’t retry with the same key. |
503 |
auth_unavailable |
jtWMS couldn’t check the key just now. The response carries Retry-After: 5. |
Retry after the Retry-After seconds. |
Next steps
Section titled “Next steps”- Permissions and scopes: what a token or key is allowed to do.
- Rate limits and feature gates: limits that apply to every authenticated request.
- Errors: the error format and codes.