---
title: "Workspaces, tokens, and session lifetime"
description: "Why minting a token is the unlock, how sessions expire, and what refresh actually extends."
---

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

# Workspaces, tokens, and session lifetime

A workspace is a password-protected collection of accounts. At rest it is
locked, and its contents are unreadable without the password.

## A token is the unlock

There is no separate unlock endpoint. `POST /v1/auth/token` takes the workspace
password and returns a bearer token, and **that token's lifetime is the unlock
session**. While a valid token exists, the workspace is open. Revoke it and the
workspace locks again.

This has three consequences worth internalising:

- The workspace password is presented exactly once per session, at mint time.
  That call is authenticated by tenant credentials, because it is the only
  workspace-tier call that has no token yet.
- `DELETE /v1/auth/token` revokes the lease of the token that sent it, and
  only that one. Other tokens on the same workspace keep the session open; it
  locks when the last lease goes. Minting twice for one workspace gives you two
  independently revocable tokens, not two copies of one.
- No workspace-token route takes the workspace in its path. It comes from the
  token's claim, so a path and a token can never disagree about which workspace
  is in play. The tenant-tier routes do use a path slug — they carry no token
  to disagree with.

## Two clocks

A session is bounded by two independent timers, both configured per tenant.

| Clock | Config | Default | Meaning |
|---|---|---|---|
| **Idle** | `ttl.workspaceIdleSec` | 15 minutes | Time since the last activity on the session. **Any** authenticated request resets it, not only a refresh. |
| **Absolute** | `ttl.workspaceAbsoluteSec` | 8 hours | Total session lifetime from mint. Refresh does **not** extend it. |

Your operator can set both per tenant, so treat the defaults as the shape of
the thing rather than a contract — but they are what applies when nothing is
configured.

`POST /v1/auth/token/refresh` issues a new token and pushes the idle clock out.
Its response reports both, so you can see the ceiling coming:

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

When `sessionExpiresAt` arrives, refreshing stops working. Mint a new token with
the workspace password instead.

> **Plan for expiry, not against it**
>
> An expired session returns `401` with code `session_expired`. Treat it as a
> normal, expected outcome: re-mint and retry, rather than trying to keep one
> session alive indefinitely.

## Accounts have their own lock

Locking is two-tiered. Opening the workspace does not necessarily open
everything inside it.

- An account using the **shared workspace password** unlocks transparently the
  first time you touch it — but only for as long as its custody window lasts.
  When that window closes the account locks like any other, and reopening it
  means calling `POST /v1/accounts/{slug}/unlock` with the workspace password.
- An account created with `hasOwnPassword: true` has its own password and must
  be unlocked explicitly via `POST /v1/accounts/{slug}/unlock`. Until then it
  answers `423` with code `account_locked`.

Account unlocks have their own absolute timer (`ttl.accountAbsoluteSec`,
typically much shorter than the session) and their own rate limit — repeated
failed attempts return `429` with `account_unlock_rate_limited`, backing off
1, 2, 4, then 8 seconds.

**The fifth consecutive failure ends the session.** Not just that unlock — the
whole workspace session is destroyed, and every other token holding it open
starts answering `401` with `session_expired`. Treat a wrong account password
as a stop condition, not something to retry into.

## Capacity

Four limits can refuse a mint even when your credentials are perfect.

Three of them share the code `session_capacity_exceeded`, and `error.details`
names which one you hit:

| `details.scope` | Ceiling | Default |
|---|---|---|
| `workspace` | Live tokens on this one workspace | 64 |
| `tenant` | Workspaces your tenant holds open at once (`limits.maxUnlockedWorkspaces`) | 8 |
| `process` | Workspaces open across the **whole service**, every tenant | 32 |

The `process` leg is worth knowing about because you cannot fix it: it is not
your tenant's activity that filled it, and revoking your own tokens may not
clear it. The `workspace` leg surprises clients that mint per request instead
of reusing a token — sixty-four live leases on one workspace is enough, with no
second workspace open at all.

The fourth is the mint rate limit — `429` with `mint_rate_limited`, per tenant.
The charge applies once the request is well formed and names a known workspace,
so a tight retry loop against a bad password is counted.

> **Account unlock spends the mint allowance**
>
> `POST /v1/accounts/{slug}/unlock` draws on the same tenant-wide budget as token
> minting, because both run the same expensive key derivation. So an unlock can
> fail with `mint_rate_limited` — not only with `account_unlock_rate_limited` —
> and a burst of unlocks can leave you unable to mint.

## Related

- [Authenticate](/guides/authenticate/) — the token lifecycle as a task
- [Scopes and permissions](/concepts/scopes/) — what a token is allowed to do
- [Auth endpoints](/api/auth/) — exact request and response shapes

Source: https://tee.hypetrade.xyz/concepts/workspaces-and-tokens/index.mdx
