# Mint Ephemeral User Token

**POST** `/users/{user_id}/tokens`

Base URL: `https://live.savewithsail.com/api/v1`

Mints a short-lived token for PII access. Mint, use, discard. Never store it. Requires the `token_admin` key scope, which on live keys is exclusive: the minting key carries no data scopes and cannot itself use the tokens it mints. Concurrent tokens are allowed. Expiry is the cleanup. Every mint is audit-logged and mint velocity is rate-limited per user. Narrow `scopes` below the key's grants per workload, e.g. `["numbers"]` for a service that renders masked numbers but must never reveal.

## Authorization

- PartnerKey (http, bearer)

## Path parameters

- `user_id` (string, required)
  The Sail user id.

## Body

Content type: `application/json`

- `ttl_seconds` (integer)
  How long the minted token remains valid, in seconds.
- `scopes` ("identity" | "numbers" | "numbers:reveal"[])
  Defaults to all token scopes the minting key's grants allow. Narrow per workload — omit `numbers:reveal` for services that only display masked numbers.

Example:

```json
{
  "ttl_seconds": 900,
  "scopes": [
    "identity"
  ]
}
```

## Responses

### 201

Token minted.

- `user_token` (string)
  The minted ephemeral token. Pass it in the `x-sail-user-token` header.
- `expires_at` (string<date-time>)
  When the token expires.
- `scopes` (string[])
  The token scopes actually granted.

Example:

```json
{
  "user_token": "ut_test_p2m88xnq1",
  "expires_at": "1970-01-01T00:00:00.000Z",
  "scopes": [
    "string"
  ]
}
```

### 403

`insufficient_key_scope`: key lacks `token_admin`.

### 429

`rate_limited`: mint velocity exceeded for this user.

### default

Standard error envelope covering 400, 401, 403, 404, 429, and 500.

- `error` (object)
  The error detail.
  - `code` (string)
    Machine-readable code, e.g. `not_found`, `token_scope_mismatch`, `product_not_enabled`, `insufficient_key_scope`, `insufficient_token_scope`, `user_token_required`, `user_token_expired`, `invalid_user_token`, `invalid_key_configuration`, `connection_not_reconnectable`, `rate_limited`.
  - `message` (string)
    Human-readable error message. May change, so match on `error.code` instead.
  - `param` (string)
    The request field that caused the error, when applicable. Null otherwise.

Example:

```json
{
  "error": {
    "code": "string",
    "message": "string",
    "param": "string"
  }
}
```