---
title: "Mint, refresh, and revoke workspace tokens"
description: "The three token routes — how a workspace session is opened, extended, and ended, and which credentials each one takes."
---

> 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.

# Mint, refresh, and revoke workspace tokens

Minting is the only workspace-tier operation authenticated by tenant
credentials — it is where the workspace password is presented.

## `POST /v1/auth/token`

Mint a workspace token. **Auth: tenant credentials.** Returns `201`.

### Request

```json
{
  "workspace": "demo",
  "password": "a-strong-workspace-password",
  "scopes": ["read", "sign"]
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `workspace` | string | yes | Workspace slug |
| `password` | string | yes | At least 1 character |
| `scopes` | string[] | no | Defaults to `["read","write","sign"]`. 1–4 entries, each ≤32 chars |

Only `read`, `write`, `sign`, and `export` are grantable. An unrecognised name
returns `400` with `invalid_body` — unknown scopes are never silently dropped.
Requesting `export` without an operator-registered key returns `403` with
`export_disabled`.

### Response

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

### Errors

| Status | Code |
|---|---|
| 400 | `invalid_body`, `invalid_slug` |
| 401 | `bad_api_key`, `bad_password` |
| 403 | `export_disabled` |
| 404 | `workspace_not_found` |
| 429 | `mint_rate_limited`, `session_capacity_exceeded` |

> **Rate limiting is charged early**
>
> The mint rate limit is charged once the request is well formed and names a known
> workspace — before any password comparison. A tight retry loop against a wrong
> password is counted.

## `POST /v1/auth/token/refresh`

Issue a new token and push out the idle clock. **Auth: workspace token**, any
scope. Returns `200`.

### Request

The body must be empty. Sending anything else returns `400` with `invalid_body`.

### Response

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

`sessionExpiresAt` is the absolute ceiling. Refresh moves `expiresAt`; it never
moves `sessionExpiresAt`. Once the ceiling passes, mint a new token instead.

Each refresh returns a new token — replace the one you hold. Responses carry
`cache-control: no-store`.

Refreshing does not invalidate the token you sent. It extends the same lease
and issues a fresh JWT for it; the older one keeps working until its own
`expiresAt` passes. If a token has leaked, refreshing does nothing to contain
it — revoke it.

### Errors

| Status | Code |
|---|---|
| 400 | `invalid_body` |
| 401 | `session_expired` |

## `DELETE /v1/auth/token`

End the session and lock the workspace. **Auth: workspace token**, any scope.
Returns `204` with no body.

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

> **This revokes one lease, not the session**
>
> Revoking ends the lease of the token you sent, immediately. Any other token
> minted for the same workspace keeps working — a workspace can hold many live
> leases at once, and each is revoked on its own.
>
> The workspace locks when its **last** lease goes. So a revoke is what contains
> a leaked token, but only the leaked one: send it with that token, not another.

## Related

- [Authenticate](/guides/authenticate/) — the lifecycle as a task
- [Workspaces and tokens](/concepts/workspaces-and-tokens/) — the model

Source: https://tee.hypetrade.xyz/api/auth/index.mdx
