---
title: Introduction
slug: api-documentation/fAo4-introduction
docTags: 
createdAt: 2026-09-01T12:15:41.388Z
---

Issues and verifies the JWT access tokens used across the UpStart Commerce
platform — for shoppers browsing a storefront, for staff using management
tools, and for the services that trust those tokens.

Which half you are building for changes what you do with a token:

- **Consumer traffic** — storefronts, apps. Verify the signature locally and
  read the claims. No call back to this service, and no permission lookup.
- **Management traffic** — admin and back-office tools. Verify the same way,
  then resolve authorization against `profile-permissions-svc`. Because the
  token carries role *references* rather than a permission list, granting a
  role takes effect immediately, without reissuing anyone's token.

## Verifying a token

Fetch the signing keys from `/.well-known/jwks.json`. It needs no credentials,
sends CORS headers, and is cacheable for 24 hours — fetch once, cache, and
refetch only when you meet a `kid` you do not recognise.

Verify `RS256`, then check that `exp` is in the future and that `iss` matches the
issuer configured for the environment you are calling — `auth-svc` by default,
but it is a configurable value, so read it from your own config rather than
hard-coding the literal. The claims you get back:

| Claim         | Meaning                                                                                                   |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| `sub`         | User id                                                                                                   |
| `type`        | `consumer` or `management` — which model above applies. Lower-case on the wire; compare case-sensitively. |
| `tenant`      | The tenant this session is active for                                                                     |
| `roles`       | Global and tenant-level role names                                                                        |
| `siteRoles`   | Site id → role names, for site-scoped access                                                              |
| `iat` / `exp` | Issued-at and expiry, as Unix timestamps                                                                  |
| `iss`         | The issuer configured for the environment. `auth-svc` unless overridden.                                  |

Tokens are deliberately thin (\~500 bytes). Everything above is in the token;
anything not in the token needs a lookup.

## Getting a token

Anonymous browsing needs no credentials:

```javascript
curl -X POST https://api.upstartcommerce.com/api/v1/auth/guest \
  -H 'x-upstart-tenant: your-tenant'
```

Signing a user in returns an access and refresh token:

```javascript
curl -X POST https://api.upstartcommerce.com/api/v1/auth/login \
  -H 'x-upstart-tenant: your-tenant' \
  -H 'Content-Type: application/json' \
  -d '{"username":"user@example.com","password":"…"}'
```

Then send it on every call:

```javascript
Authorization: Bearer <accessToken>
```

**Login does not always return tokens.** If the account requires a second
factor you get `mfaRequired: true`, an `mfaChallengeId`, the delivery method, a
masked destination and the challenge's own expiry — and no tokens. Finish with
`/auth/mfa/verify`, or get a fresh code from `/auth/mfa/resend`. Treat it as a
branch in your login flow, not an error.

## Keeping a session alive

Access tokens last one hour and refresh tokens seven days by default, both
environment-configurable — read `expiresIn` from the response rather than
hard-coding either.

Refresh **rotates**. `/auth/refresh` returns a new pair and invalidates the one
you presented, so persist the new refresh token every time. `/auth/logout`
revokes it.

There is also a ceiling on total session age — 30 days by default, and likewise
configurable — that refreshing cannot extend. When you reach it the error is
`session_expired` and the only way forward is a full login, so handle it
distinctly from an ordinary expired access token.

## Tenant and site context

The active tenant travels **in the token**, as the `tenant` claim, so a verified
token already tells a service which tenant it is acting for.

The `x-upstart-tenant` header is how a caller *selects* that tenant when the
user belongs to more than one. `/auth/tenants` lists what the session can reach
— use it to drive a tenant switcher — and `/auth/refresh` moves an existing
session to a different tenant without asking for credentials again.
`/auth/sites` does the same for sites within the active tenant.

## When calls fail

Errors carry a machine-readable `error`, a human `message` and a `code`. The
ones worth branching on:

| `error`                                     | What to do                                                    |
| ------------------------------------------- | ------------------------------------------------------------- |
| `unauthorized`                              | Bad credentials or a bad token. Re-authenticate.              |
| `session_expired`                           | Session ceiling reached. Full login; refresh will not help.   |
| `tenant_required`                           | Multi-tenant user, no tenant chosen. Send `x-upstart-tenant`. |
| `unauthorized_tenant` / `unauthorized_site` | Authenticated, but not for that scope.                        |
| `site_required` / `no_authorized_sites`     | Site context missing, or none available to this user.         |
| `account_not_verified`                      | Real account, unverified email or phone.                      |
| `account_locked`                            | Too many failed attempts. Temporary.                          |
| `rate_limited`                              | Back off and retry.                                           |
| `invalid_request`                           | Malformed body or a missing field.                            |

