Skip to main content

Overview

All S2 API requests require authentication using Bearer tokens in the Authorization header. Access tokens are managed through the S2 account API and can be scoped to specific resources and operations.

Access Tokens

Access tokens are string credentials that authenticate your API requests. They are:
  • Hierarchical - Root tokens can create scoped child tokens
  • Scoped - Restrict access to specific basins, streams, or operations
  • Expirable - Optional expiration time for security
  • Revocable - Can be revoked at any time

Authentication Header

Include your access token in the Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN

Example Request

curl https://account.s2.dev/v1/basins \
  -H "Authorization: Bearer s2_ak_1234567890abcdef"

Getting Your First Token

For S2.dev (managed service):
  1. Sign up at s2.dev
  2. Your initial root token is provided in the dashboard
  3. Export it as an environment variable:
export S2_ACCESS_TOKEN="s2_ak_your_token_here"
Store your root token securely. It has full access to your account. Consider using a secrets manager in production.
For S2 Lite (self-hosted):
# S2 Lite does not require authentication by default
export S2_ACCESS_TOKEN="ignored"

Creating Scoped Tokens

Create child tokens with restricted permissions using the access tokens API.

Issue a Token

curl -X POST https://account.s2.dev/v1/access-tokens \
  -H "Authorization: Bearer YOUR_ROOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "read-only-token",
    "scope": {
      "basins": {"prefix": "prod-"},
      "streams": {"prefix": ""},
      "op_groups": {
        "stream": {"read": true, "write": false}
      }
    }
  }'
Response:
{
  "access_token": "s2_ak_scoped_token_here"
}
The full token value is only returned when created. Store it securely as it cannot be retrieved later.

Token Scope

Control what a token can access using scope configuration.

Resource Scoping

Restrict access to specific resources:

Exact Match

Allow access to a single resource:
{
  "basins": {"exact": "production-basin"},
  "streams": {"exact": "critical-stream"}
}

Prefix Match

Allow access to resources starting with a prefix:
{
  "basins": {"prefix": "prod-"},
  "streams": {"prefix": "logs-"}
}

All Resources

Use empty prefix to allow all:
{
  "basins": {"prefix": ""},
  "streams": {"prefix": ""}
}

No Access

Use empty exact match to deny all:
{
  "basins": {"exact": ""},
  "access_tokens": {"exact": ""}
}

Operation Scoping

Control what operations a token can perform.

Operation Groups

Grant read and/or write permissions at different levels:
{
  "op_groups": {
    "account": {"read": true, "write": false},
    "basin": {"read": true, "write": true},
    "stream": {"read": true, "write": false}
  }
}
Permission levels:
  • Account: List basins, access tokens, account metrics
  • Basin: Create/delete basins, configure basin settings, basin metrics
  • Stream: Create/delete streams, append/read records, stream metrics

Specific Operations

Grant access to individual operations:
{
  "ops": [
    "list-basins",
    "list-streams",
    "read",
    "append"
  ]
}
Available operations:
  • Account: list-basins, create-basin, delete-basin, reconfigure-basin, get-basin-config, issue-access-token, revoke-access-token, list-access-tokens, account-metrics
  • Basin: basin-metrics
  • Stream: list-streams, create-stream, delete-stream, get-stream-config, reconfigure-stream, check-tail, append, read, trim, fence, stream-metrics
Operation permissions are the union of op_groups and ops. A token is granted access if either allows the operation.

Auto-Prefixing Streams

Automatic stream name prefixing provides namespace isolation:
{
  "id": "service-a-token",
  "auto_prefix_streams": true,
  "scope": {
    "streams": {"prefix": "service-a/"}
  }
}
With auto-prefixing enabled:
  • Token can only access streams starting with service-a/
  • Stream names in requests are automatically prefixed with service-a/
  • Stream names in responses have the prefix stripped
This allows different services to use the same stream names without conflicts.

Token Expiration

Set an expiration time when creating tokens:
{
  "id": "temporary-token",
  "expires_at": "2026-12-31T23:59:59Z",
  "scope": {...}
}
Expired tokens return 401 Unauthorized. Create new tokens before expiration or use tokens without expiration for long-lived services.
If expires_at is not specified, the token inherits the expiration of the parent token used to create it.

Managing Tokens

List Tokens

Retrieve metadata about existing tokens (not the secret values):
curl https://account.s2.dev/v1/access-tokens \
  -H "Authorization: Bearer YOUR_TOKEN"
Response:
{
  "access_tokens": [
    {
      "id": "read-only-token",
      "expires_at": "2027-01-01T00:00:00Z",
      "auto_prefix_streams": false,
      "scope": {...}
    }
  ],
  "has_more": false
}

Revoke a Token

Revoke a token to immediately invalidate it:
curl -X DELETE https://account.s2.dev/v1/access-tokens/read-only-token \
  -H "Authorization: Bearer YOUR_ROOT_TOKEN"
Revoking a token also revokes all child tokens created from it.

Security Best Practices

1. Use Scoped Tokens

Create tokens with minimal required permissions:
{
  "id": "analytics-reader",
  "scope": {
    "basins": {"exact": "analytics"},
    "streams": {"prefix": ""},
    "op_groups": {
      "stream": {"read": true, "write": false}
    }
  }
}

2. Rotate Tokens Regularly

Periodically create new tokens and revoke old ones:
# Create new token with expiration
curl -X POST https://account.s2.dev/v1/access-tokens \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "id": "service-token-2026-03",
    "expires_at": "2026-04-01T00:00:00Z",
    "scope": {...}
  }'

# Revoke old token after transitioning
curl -X DELETE https://account.s2.dev/v1/access-tokens/service-token-2026-02 \
  -H "Authorization: Bearer YOUR_TOKEN"

3. Use Environment Variables

Never hardcode tokens in source code:
# Good
export S2_ACCESS_TOKEN="s2_ak_..."

# Bad - DO NOT DO THIS
const token = "s2_ak_..."; // hardcoded token

4. Limit Token Scope by Service

Create separate tokens for each service or environment:
{
  "id": "production-api-server",
  "scope": {
    "basins": {"exact": "production"},
    "op_groups": {
      "stream": {"read": true, "write": true}
    }
  }
}

Error Responses

401 Unauthorized

Missing or invalid token:
{
  "error": "unauthorized",
  "message": "Invalid or expired access token"
}

403 Forbidden

Token lacks required permissions:
{
  "error": "forbidden",  
  "message": "Token does not have permission for this operation"
}

SDK Support

S2 SDKs handle authentication automatically:

Rust SDK

use s2::Client;

let client = Client::from_env()?;
// Token read from S2_ACCESS_TOKEN environment variable

CLI

export S2_ACCESS_TOKEN="your_token"
s2 list-basins

Next Steps

API Overview

Learn about API structure and endpoints

Quickstart

Get started with S2 in minutes

Build docs developers (and LLMs) love