Clerk API Token & Passcode System
Authentication mechanisms for external applications (Chrome extension, OpenClaw, mobile app) to access Syntropy Journals API endpoints.Two Mechanisms
| Mechanism | Lifetime | Use Case | How Issued |
|---|---|---|---|
| API Token | Persistent (optional expiry) | Ongoing API access (Chrome extension, CLI) | Settings → API Tokens, or make admin-token |
| Passcode | Short-lived (10 min default) | Device pairing, one-time verification | Triggered by authenticated user in-app |
API Tokens
Architecture
Token Format
Database Table
clerk_api_tokens — stores token metadata + hashes (never plaintext):
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
user_id | VARCHAR(255) | Clerk user ID, indexed |
short_token | VARCHAR(50) | Unique, indexed — used for O(1) lookup |
long_token_hash | VARCHAR(64) | SHA-256 of the secret portion |
name | VARCHAR(255) | User-facing label |
is_active | BOOL | False when revoked |
expires_at | TIMESTAMPTZ | Null = never expires |
last_used_at | TIMESTAMPTZ | Updated on each verification |
API Endpoints
Local Dev: Issue First Token
scripts/issue_admin_token.py
FastAPI Integration
6-Digit Passcode
Architecture
Security Properties
- No plaintext storage — only SHA-256 hash in DB
- Constant-time comparison —
secrets.compare_digestprevents timing attacks - Single-use — verified passcode immediately marked
is_used=True - Auto-invalidation — issuing a new passcode invalidates prior unused ones (same user+channel)
- Configurable — 4-10 digits, 30s+ TTL (default: 6 digits, 10 minutes)
- Channel-scoped —
"openclaw"passcodes don’t interfere with"email"verification
Database Table
clerk_passcodes:
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
user_id | VARCHAR(255) | Clerk user ID who requested the passcode |
code_hash | VARCHAR(64) | SHA-256 of the plaintext code |
user_identifier | VARCHAR(255) | Email/phone used for matching, indexed |
channel | VARCHAR(50) | Scoping tag (e.g., “email”, “openclaw”) |
expires_at | TIMESTAMPTZ | Default: 10 minutes from issuance |
is_used | BOOL | True after successful verification |
API Endpoint
Issuing Passcodes (Server-Side)
External App Integration Guide (OpenClaw Example)
Option A: Direct API Token (simplest)
- User creates an API token in Syntropy Settings → API Tokens
- User pastes token into OpenClaw’s settings
- OpenClaw stores token and uses it for all API calls
Option B: Passcode Pairing → Token Exchange
For a smoother UX where the user doesn’t copy-paste tokens:Option C: Passcode-Only (ephemeral sessions)
For apps that don’t need persistent access:Configuration
Token system is configured viaClerkState.set_token_config():
scripts/issue_admin_token.py for CLI token issuance.
Files
| File | Purpose |
|---|---|
libs/reflex-clerk-api/.../clerk_provider.py | issue_token_for_user, verify_token, issue_passcode, verify_passcode |
libs/reflex-clerk-api/.../token_models.py | ApiToken and Passcode SQLModel tables |
libs/reflex-clerk-api/.../token_config.py | TokenConfig, result types, exceptions |
libs/reflex-clerk-api/.../fastapi_helpers.py | validate_api_token, validate_passcode, create_token_router |
syntropy_journals/app/api/routes/tokens.py | CRUD endpoints for token management |
scripts/issue_admin_token.py | CLI tool for issuing dev tokens |