> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metastreams.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Session tokens

> Creates a short-lived credential that a browser sends instead of an API key.

Creates a session token, a short-lived credential that a browser sends instead of an API key. This endpoint requires an API key, and each successful call costs one credit. [Session tokens](https://docs.metastreams.io/get-started/authentication.md#session-tokens) describes how a backend and a browser use it.

## Request body

* `origins` lists 1 to 10 origins allowed to use the token, each in the format of the browser's `Origin` header, such as `https://app.example.com`.
* `subject` is an optional label for the user, such as a user ID, of 1 to 128 characters without control characters.
* `ttlSeconds` is the token's lifetime, from 60 to 3,600 seconds. The default is 900.

## Response

`token` is an opaque string of a few hundred characters that starts with `metasession_`. Treat it as a secret, and do not parse it. `expiresAt` is the expiry time in Unix milliseconds. Tokens cannot be refreshed, so create a new token before `expiresAt`.

## Errors

| Code | When |
| - | - |
| `VALIDATION_ERROR` | No origin or more than 10, a value that is not a valid origin, a `subject` or `ttlSeconds` out of range, or an unknown field |
| `FORBIDDEN` | The request used a session token instead of an API key |
| `ORIGIN_NOT_ALLOWED` | The request used a session token from an origin that the token does not allow |

```bash cURL theme={null}
curl -X POST "https://api.metastreams.io/v1/session-tokens" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"origins": ["https://app.example.com"], "subject": "user-42", "ttlSeconds": 900}'
```

```typescript TypeScript theme={null}
const response = await fetch("https://api.metastreams.io/v1/session-tokens", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ origins: ["https://app.example.com"], subject: "user-42", ttlSeconds: 900 }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { token, expiresAt } = await response.json();
```

```rust Rust theme={null}
use serde_json::{Value, json};

let response: Value = reqwest::Client::new()
    .post("https://api.metastreams.io/v1/session-tokens")
    .bearer_auth(std::env::var("API_KEY")?)
    .json(&json!({ "origins": ["https://app.example.com"], "subject": "user-42", "ttlSeconds": 900 }))
    .send()
    .await?
    .error_for_status()?
    .json()
    .await?;
let token = &response["token"];
```

```json Created theme={null}
{
  "token": "metasession_AbC...",
  "expiresAt": 1789632915120
}
```


## OpenAPI

````yaml specs/openapi.json POST /v1/session-tokens
openapi: 3.1.0
info:
  title: Metastreams API
  description: >-
    The Metastreams market-data API. Amounts are decimal strings, timestamps are
    Unix milliseconds, and every field name is camelCase.
  license:
    name: Proprietary
    identifier: Proprietary
  version: 1.0.0
servers:
  - url: https://{host}
    variables:
      host:
        default: api.metastreams.io
        description: API host.
security:
  - bearerKey: []
paths:
  /v1/session-tokens:
    post:
      tags:
        - sessionTokens
      summary: Mint a session token for a browser or app user.
      description: >-
        A backend calls this with its API key, once per signed-in user, and
        hands the token to

        the page. The page then calls the API directly, from the named origins,
        until the token

        expires. Nothing refreshes a token, so mint the next one before the
        current one expires.

        A token spends its key's credits within its key's limits. Revoking the
        key ends every

        token it minted.
      operationId: session_token_create
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionTokenRequest'
        required: true
      responses:
        '201':
          description: >-
            The token, and when it expires. Present it as `Authorization: Bearer
            <token>`, or on the stream handshake in `Sec-WebSocket-Protocol`
            beside `metastreams.v1`, from one of its origins.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UiSessionToken'
        '400':
          description: >-
            An origin count, `subject` or `ttlSeconds` outside the bounds the
            request schema gives, or an origin a browser never sends as
            `Origin`.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: >-
            Missing or invalid credential: an API key, or a session token a key
            minted.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '402':
          description: >-
            The organization's credits this billing period reach its plan's
            limit.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: >-
            Only a key mints: a session token answers `FORBIDDEN`, or
            `ORIGIN_NOT_ALLOWED` from an origin it does not name. Also a key
            whose plan excludes this endpoint.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: Rate limit exceeded.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait before retrying.
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: Unexpected server failure.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: Dependency down or maintenance.
          headers:
            X-Credits-Cost:
              schema:
                type: integer
              description: >-
                Credits charged for this request. Zero on any non-2xx. Sent on a
                metered tier only.
            X-Credits-Remaining:
              schema:
                type: integer
              description: Credit balance after this request. Sent on a metered tier only.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: >-
                Requests left in the bucket this request spent from. Not sent
                with a 401.
            X-Request-ID:
              schema:
                type: string
              description: >-
                This request's trace ID. Echoed from the request, or generated
                when it sent none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
components:
  schemas:
    CreateSessionTokenRequest:
      type: object
      description: The body of `POST /v1/session-tokens`.
      required:
        - origins
      properties:
        origins:
          type: array
          items:
            type: string
          description: >-
            The origins the token may be presented from, 1 to 10, each as a
            browser sends `Origin`.
          example:
            - https://app.example.com
          maxItems: 10
          minItems: 1
        subject:
          type:
            - string
            - 'null'
          description: >-
            Who the token is for, in the caller's own terms: at most 128
            characters, no control characters.
          example: user-42
          maxLength: 128
        ttlSeconds:
          type:
            - integer
            - 'null'
          format: int32
          description: How long the token lives, 60 to 3600 seconds; 900 when absent.
          example: 900
          maximum: 3600
          minimum: 60
      additionalProperties: false
    UiSessionToken:
      type: object
      description: A session token as the mint answers it.
      required:
        - token
        - expiresAt
      properties:
        expiresAt:
          $ref: '#/components/schemas/UnixMilliseconds'
          description: When it stops authenticating.
        token:
          type: string
          description: >-
            The token, presented as `Authorization: Bearer <token>`, or on the
            stream handshake in `Sec-WebSocket-Protocol`.
    ErrorEnvelope:
      type: object
      description: The complete error response body — every error is exactly this.
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorDetail'
          description: The error.
    UnixMilliseconds:
      type: integer
      format: int64
      description: An instant, as milliseconds since the Unix epoch.
    ErrorDetail:
      type: object
      description: The `error` object inside the envelope.
      required:
        - code
        - message
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
          description: >-
            Stable, machine-readable. Clients switch on this, never on
            `message`.
        details:
          description: Optional; shape is fixed per code.
        message:
          type: string
          description: |-
            Human-readable, written for the integrating developer:
            `[What happened]. [What to do next — if actionable].`
    ErrorCode:
      type: string
      description: |-
        Every wire error code the API can return.

        Append-only: a new code is non-breaking, since clients fall back on the
        HTTP status for one they do not recognize, and a published code is never
        renamed or removed.
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - VALIDATION_ERROR
        - UNSUPPORTED_CHAIN
        - NOT_FOUND
        - RATE_LIMITED
        - CREDITS_EXHAUSTED
        - INTERNAL_ERROR
        - SERVICE_UNAVAILABLE
        - RANGE_TOO_LARGE
        - INVALID_CREDENTIALS
        - ORIGIN_NOT_ALLOWED
        - NOT_ORG_MEMBER
        - MEMBERSHIP_INACTIVE
        - EMAIL_NOT_VERIFIED
        - EMAIL_TAKEN
        - ALREADY_MEMBER
        - INVITATION_ALREADY_SENT
        - INVITATION_ALREADY_ACCEPTED
        - LAST_ADMIN
        - CANNOT_REMOVE_SELF
        - KEY_LIMIT_REACHED
        - TOKEN_EXPIRED
        - PAYLOAD_TOO_LARGE
        - UNSUPPORTED_MEDIA_TYPE
        - IMAGE_TOO_LARGE
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      description: >-
        Send an API key, or a session token a key minted, as `Authorization:
        Bearer <credential>`. A browser opening `/v1/stream` offers its session
        token in `Sec-WebSocket-Protocol` instead.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.