---
title: "How to authenticate and keep a session alive"
description: "Mint, refresh, and revoke workspace tokens, and handle expiry without surprises."
---

> Documentation Index
> Fetch the complete documentation index at: https://tee.hypetrade.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# How to authenticate and keep a session alive

Authentication has two tiers. This guide covers moving between them and keeping
a session healthy.

## Tenant credentials

Two headers, on exactly five routes — creating, listing, and deleting
workspaces, reading quota, and minting a token:

```bash
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"
```

Missing or wrong credentials return `401` with code `bad_api_key`.

## Mint a token

```bash
curl -X POST "$API_URL/auth/token" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Api-Secret: $API_SECRET" \
  -H "content-type: application/json" \
  -d '{"workspace":"demo","password":"…","scopes":["read","sign"]}'
```

`scopes` is optional; omitting it grants `["read","write","sign"]`. Request the
narrowest set you need — see [Scopes and permissions](/concepts/scopes/).

A `201` returns the token and its expiry:

```json
{
  "token": "eyJhbGciOi...",
  "expiresAt": "2026-08-18T12:34:56.000Z",
  "workspace": "demo",
  "scopes": ["read", "sign"]
}
```

Use it as a bearer token on every other route:

```bash
-H "Authorization: Bearer $TOKEN"
```

## Refresh before the idle clock runs out

```bash
curl -X POST "$API_URL/auth/token/refresh" \
  -H "Authorization: Bearer $TOKEN"
```

The body must be empty — sending anything else returns `400` with
`invalid_body`. The response adds `sessionExpiresAt`, the absolute ceiling that
refresh cannot move:

```json
{
  "token": "eyJhbGciOi...",
  "expiresAt": "2026-08-18T12:49:56.000Z",
  "sessionExpiresAt": "2026-08-18T20:15:00.000Z",
  "workspace": "demo",
  "scopes": ["read", "sign"]
}
```

Each refresh returns a **new** token for the same lease. Replace the one you
hold — but note the previous JWT is not cancelled by the exchange. It keeps
working until its own `expiresAt`, because refresh extends the lease rather
than rotating it. Treat refresh as buying time, never as containing a leak.

> **Refresh on a timer, not on failure**
>
> Refreshing at roughly half the idle window is cheaper and calmer than
> discovering expiry mid-transaction. Reserve re-minting for when
> `sessionExpiresAt` has passed.

## Handle expiry

An expired session returns `401` with `session_expired`. The recovery is always
the same: mint a new token with the workspace password and retry. A robust
client treats this as routine rather than exceptional.

Note that refresh cannot rescue a session past its absolute ceiling — once
`sessionExpiresAt` is in the past, only a fresh mint works.

## Revoke when finished

```bash
curl -X DELETE "$API_URL/auth/token" \
  -H "Authorization: Bearer $TOKEN"
```

Returns `204`. This revokes the lease of the token you sent, and only that
one. Other tokens minted for the same workspace keep working — the workspace
locks when its last lease goes.

That makes revoke the tool for containing a specific token: send the
compromised one with itself. It is not a way to end everything at once.

## Failures worth handling

| Code | Status | What happened |
|---|---|---|
| `bad_api_key` | 401 | Tenant credentials missing or wrong |
| `bad_password` | 401 | Workspace password wrong |
| `workspace_not_found` | 404 | No such workspace for this tenant |
| `session_expired` | 401 | Idle or absolute clock elapsed |
| `scope_denied` | 403 | Valid token, but the scope was not granted |
| `mint_rate_limited` | 429 | Too many mint attempts for this tenant |
| `session_capacity_exceeded` | 429 | Too many workspaces open at once |
| `export_disabled` | 403 | Asked for `export` without an operator-registered key |

## Related

- [Workspaces and tokens](/concepts/workspaces-and-tokens/) — the model behind this
- [Auth endpoints](/api/auth/) — exact shapes

Source: https://tee.hypetrade.xyz/guides/authenticate/index.mdx
