Skip to main content

Overview

API tokens provide long-lived authentication for CLI tools, CI/CD pipelines, and automated systems. Unlike JWT tokens:
  • No expiration by default (or set custom expiration)
  • Scoped permissions (read, write, admin)
  • Bypass MFA (design choice for automation)
  • Revocable without affecting other sessions
  • Prefix-based identification (rexec_...)
API tokens are powerful and bypass MFA. Treat them like passwords:
  • Never commit tokens to version control
  • Rotate regularly
  • Use minimal required scopes
  • Revoke unused tokens immediately

Create API Token

Generate a new API token with optional expiration and scopes.
POST /api/tokens
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "name": "CI/CD Pipeline",
  "scopes": ["read", "write"],
  "expires_in": 90
}
name
string
required
Descriptive name for the token (e.g., “GitHub Actions”, “Production CLI”)
scopes
string[]
Array of permission scopes. Defaults to ["read", "write"]Available scopes:
  • read - Read-only access (view containers, logs, etc.)
  • write - Create, update, delete resources
  • admin - Administrative operations
expires_in
number
Days until token expires. Omit for no expiration.
token
string
Full API token starting with rexec_. Only shown once!
token_prefix
string
First 12 characters for identification in lists
expires_at
string
ISO 8601 expiration timestamp (null if no expiration)
The full token is only returned on creation. Save it immediately - you cannot retrieve it later!

Example: Create Non-Expiring Token

curl -X POST https://api.rexec.io/api/tokens \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Development CLI",
    "scopes": ["read", "write"]
  }'

Example: Create 30-Day Read-Only Token

curl -X POST https://api.rexec.io/api/tokens \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monitoring Tool",
    "scopes": ["read"],
    "expires_in": 30
  }'

List API Tokens

Retrieve all API tokens for the authenticated user.
GET /api/tokens
Authorization: Bearer YOUR_JWT_TOKEN
tokens
object[]
Array of API tokens (full token value never returned)
tokens[].id
string
Token UUID for revocation/deletion
tokens[].token_prefix
string
First 12 characters for identification
tokens[].last_used_at
string
ISO 8601 timestamp of last use (null if never used)
tokens[].revoked_at
string
ISO 8601 timestamp when revoked (null if active)

Revoke API Token

Revoke a token to prevent further use. Revoked tokens remain in the list but cannot authenticate.
POST /api/tokens/{token_id}/revoke
Authorization: Bearer YOUR_JWT_TOKEN
token_id
string
required
Token UUID from the list endpoint

Example

curl -X POST https://api.rexec.io/api/tokens/token-uuid-1/revoke \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Delete API Token

Permanently delete a token. Unlike revocation, this removes the token from the database.
DELETE /api/tokens/{token_id}
Authorization: Bearer YOUR_JWT_TOKEN
token_id
string
required
Token UUID from the list endpoint
Revoke vs Delete: Use revoke to temporarily disable a token while keeping audit history. Use delete to permanently remove unused tokens.

Validate API Token

Validate a token and retrieve associated user information (useful for CLI tools).
GET /api/tokens/validate
Authorization: Bearer rexec_YOUR_API_TOKEN
valid
boolean
Whether the token is valid
scopes
string[]
Permissions granted to this token

Example

curl https://api.rexec.io/api/tokens/validate \
  -H "Authorization: Bearer rexec_1a2b3c4d5e6f7g8h9i0j..."

Using API Tokens

Authentication Header

API tokens use the same Authorization: Bearer header format as JWT tokens:
curl -H "Authorization: Bearer rexec_YOUR_API_TOKEN" \
  https://api.rexec.io/api/containers
The middleware automatically detects API tokens (by rexec_ prefix) and validates them differently from JWT tokens.

CLI Configuration

The Rexec CLI stores API tokens in ~/.rexec/config.json:
{
  "api_url": "https://api.rexec.io",
  "api_token": "rexec_1a2b3c4d5e6f7g8h9i0j...",
  "user": {
    "id": "uuid",
    "username": "johndoe",
    "email": "[email protected]"
  }
}

Environment Variables

export REXEC_API_TOKEN=rexec_1a2b3c4d5e6f7g8h9i0j...
export REXEC_API_URL=https://api.rexec.io

rexec container list

Token Security

How Tokens are Stored

  1. Hashed: Only SHA-256 hash is stored in the database
  2. Prefix: First 12 characters stored for identification
  3. Plaintext: Full token shown only once on creation

Token Format

rexec_<32-byte-random-hex>
Example: rexec_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7

Validation Process

  1. Check rexec_ prefix
  2. Hash the token with SHA-256
  3. Look up hash in database
  4. Verify:
    • Token exists
    • Not revoked (revoked_at is null)
    • Not expired (expires_at > now)
  5. Update last_used_at timestamp
  6. Set user context (bypass MFA/screen lock)

Scope-Based Authorization

Available Scopes

read
scope
Read-only access
  • View containers, images, logs
  • List resources
  • Get user profile
  • View audit logs (own actions)
write
scope
Create, update, delete
  • All read permissions
  • Create/delete containers
  • Execute commands
  • Upload files
  • Manage SSH keys
admin
scope
Administrative operations
  • All read and write permissions
  • View all users (if admin)
  • Manage system settings
  • Access admin endpoints

Checking Scopes in Middleware

The auth middleware sets api_token_scopes in the Gin context:
scopes := c.GetStringSlice("api_token_scopes")
hasWrite := contains(scopes, "write")

Error Responses

400 Bad Request

{
  "error": "name is required"
}
Reason: Missing required field

401 Unauthorized

{
  "error": "Invalid or expired API token"
}
Reasons:
  • Token not found
  • Token revoked
  • Token expired
  • Invalid format

404 Not Found

{
  "error": "token not found"
}
Reason: Token ID doesn’t exist or doesn’t belong to user

500 Internal Server Error

{
  "error": "failed to create token"
}
Reason: Database or cryptographic operation failed

Best Practices

Token Naming: Use descriptive names indicating the token’s purpose and environment:
  • ✅ “GitHub Actions - Production”
  • ✅ “Dev CLI - John’s Laptop”
  • ❌ “Token 1”
  • ❌ “My Token”
Scope Minimization: Grant only required permissions:
  • Monitoring/CI status checks: read only
  • Deployment pipelines: read, write
  • Admin automation: read, write, admin
Rotation Schedule:
  • Rotate tokens every 90 days
  • Use expires_in to enforce automatic expiration
  • Monitor last_used_at to identify stale tokens
Token Storage:
  • ✅ Environment variables (CI/CD)
  • ✅ Secret managers (AWS Secrets Manager, HashiCorp Vault)
  • ✅ Encrypted config files
  • ❌ Plain text files in repositories
  • ❌ Client-side code
  • ❌ Logs or error messages
Incident Response: If a token is compromised:
  1. Revoke immediately via API or UI
  2. Review audit logs for unauthorized access
  3. Rotate affected credentials
  4. Generate new token with updated scopes

Token Lifecycle

  1. Created: Token generated and returned (plaintext shown once)
  2. Active: Token validates successfully, updates last_used_at
  3. Revoked: Token blocked from authentication, remains in database
  4. Expired: Token past expires_at, treated as revoked
  5. Deleted: Token removed from database permanently

Build docs developers (and LLMs) love