Authentication
JAOT supports two authentication methods: API keys for programmatic access and JWT tokens for browser-based sessions. All protected endpoints accept either method via the Authorization header.
API Key Authentication
API keys are long-lived credentials that identify a user and their organization. Keys are prefixed with ok_live_ (production) or ok_test_ (test environments) and are SHA-256 hashed before storage, so a database breach does not expose any keys.
Using an API Key
Include the key in the Authorization header with the Bearer scheme:
import httpx
API_KEY = "ok_live_a1b2c3d4e5f6789012345678901234567890abcdef"
response = httpx.get(
"https://api.jaot.io/api/v2/auth/me",
headers={"Authorization": f"Bearer {API_KEY}"},
)
user = response.json()
print(user["organization_name"])Creating API Keys
Create additional keys via the API or the dashboard under Settings > API Keys.
import httpx
response = httpx.post(
"https://api.jaot.io/api/v2/keys",
headers={"Authorization": "Bearer ok_live_your_existing_key"},
json={
"name": "Production key",
"description": "Used by the production server",
"expires_days": 365,
},
)
data = response.json()
print(data["api_key"]) # Save this -- it will not be shown againThe response includes the plaintext key exactly once:
{
"api_key": "ok_live_a1b2c3d4e5f6789012345678901234567890abcdef",
"id": "key_f3a9b2c1",
"name": "Production key",
"is_active": true,
"created_at": "2026-02-19T10:00:00Z"
}API keys are shown only once at creation. Store the key in a secure location immediately. If you lose a key, revoke it and create a new one.
Key Prefixes
| Prefix | Environment |
|---|---|
ok_live_ | Production keys for live API access |
ok_test_ | Test keys for development and staging |
JWT Authentication
JWT tokens are used for browser-based sessions in the JAOT dashboard. The login flow returns a short-lived access token and a longer-lived refresh token.
Login
Send email and password to obtain tokens:
import httpx
response = httpx.post(
"https://api.jaot.io/api/v2/auth/login",
json={
"email": "alice@example.com",
"password": "your_password",
},
)
data = response.json()
access_token = data["access_token"] # Expires in 15 minutes
refresh_token = data["refresh_token"] # Expires in 7 daysUse the access token in subsequent requests:
curl https://api.jaot.io/api/v2/auth/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Token Refresh
Access tokens expire after 15 minutes. Use the refresh token to obtain a new access token without re-entering credentials:
import httpx
response = httpx.post(
"https://api.jaot.io/api/v2/auth/refresh",
headers={"Authorization": f"Bearer {refresh_token}"},
)
data = response.json()
new_access_token = data["access_token"]Token Lifetimes
| Token | Lifetime | Purpose |
|---|---|---|
| Access token | 15 minutes | Authenticates API requests |
| Refresh token | 7 days | Obtains new access tokens |
Dual-Auth Middleware
All protected endpoints accept both API keys and JWT tokens. The auth middleware inspects the Authorization: Bearer <token> header and determines the auth path based on the token format:
- Tokens starting with
ok_live_orok_test_are treated as API keys and verified via SHA-256 hash lookup. - All other Bearer tokens are treated as JWTs and verified via signature validation.
Most API users should use API keys. JWT authentication is primarily for the JAOT web dashboard and browser-based integrations.
Best Practices
Use environment variables for keys. Never hardcode API keys in source code.
import os
import httpx
API_KEY = os.environ["JAOT_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}"}Rotate keys periodically. Create a new key, update your applications, then revoke the old key.
Use separate keys per environment. Create distinct keys for development, staging, and production to isolate access and simplify rotation.
Restrict key scope. List keys via GET /api/v2/keys to audit which keys exist and when they were last used. Revoke any key that is no longer needed.
Never expose API keys in client-side JavaScript. Browser code is visible to end users. Use server-side code or a backend proxy to make authenticated API calls.
Error Responses
Authentication failures return standard error responses:
401 Unauthorized
Returned when the token is missing, invalid, or expired.
{
"error": "unauthorized",
"message": "Invalid or expired API key"
}Common causes:
| Scenario | Fix |
|---|---|
Missing Authorization header | Add Authorization: Bearer <token> to the request |
Missing Bearer prefix | Use the full format: Authorization: Bearer ok_live_... |
| Expired API key | Create a new key and update your configuration |
| Expired JWT access token | Refresh the token via POST /api/v2/auth/refresh |
| Revoked key | Create a new key in the dashboard |
403 Forbidden
Returned when the authenticated user lacks permission for the requested resource.
{
"error": "forbidden",
"message": "Admin access required"
}Admin endpoints (/api/v2/admin/*) require is_admin=true on the user account. Contact your organization administrator to request elevated access.