API Reference
ezAuth v0.1 — Multi-tenant authentication service
Authentication
ezAuth uses two types of API keys per application:
| Key type | Header | Usage |
|---|---|---|
pk_... |
X-Publishable-Key |
Frontend / browser requests. Safe to expose in client code. |
sk_... |
Authorization: Bearer sk_... |
Backend / server-to-server requests. Keep secret. |
Alternatively, frontend routes resolve the application from the Host header if a verified domain is configured.
Session tokens
After sign-in, the user receives a JWT access token (set as an httponly cookie named __session) and a refresh token. The JWT can also be sent via Authorization: Bearer <jwt>.
| Token | Lifetime | Notes |
|---|---|---|
| Access JWT | 15 minutes | Contains sub, sid, aud, email, email_verified |
| Refresh token | 30 days | Rotated on each use. Stored as hash. |
Errors
All errors return a JSON body with a detail field:
{
"detail": "Invalid or expired token"
}
400 Bad request or validation error401 Invalid credentials or missing authentication403 Forbidden (e.g. cross-tenant operation)404 Resource not found409 Conflict (duplicate resource)422 Unprocessable (e.g. missing proof-of-work)429 Rate limitedEvery response includes an X-Request-ID header for tracing.
Rate Limiting
Rate limits use a Redis-backed sliding window. Limits apply per IP address or per email, depending on the endpoint. When rate limited, the API returns 429.
| Endpoint | Window | Limit | Scope |
|---|---|---|---|
| POST /v1/signups | 60s | 10 | IP |
| POST /v1/signups | 300s | 1 | |
| POST /v1/signins | 60s | 10 | IP |
Request Challenge
/v1/challenges
Request a proof-of-work challenge. Required before signup if hashcash is enabled.
Headers
X-Publishable-Key | Application publishable key | required |
Response
{
"challenge": "base64-encoded-challenge",
"difficulty": 5,
"algorithm": "argon2id",
"params": {
"time_cost": 2,
"memory_cost": 19456,
"parallelism": 1,
"hash_len": 32
},
"expires_in": 300
}
Sign Up
/v1/signups
Register a new user. Sends a verification email (link or code, depending on app config).
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
email | string | required |
password | string | null | optional |
redirect_url | string | null | optional |
hashcash | object | if enabled |
hashcash.challenge | string | required |
hashcash.nonce | string | required |
Response
{
"status": "verification_sent",
"user_id": "550e8400-e29b-41d4-a716-446655440000"
}
200 Verification email sent409 User already exists410 Hashcash challenge expired422 Proof-of-work required but not provided429 Rate limitedSign In
/v1/signins
Sign in with password or request a magic link.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
email | string | required |
password | string | null | optional |
redirect_url | string | null | optional |
strategy | "password" | "magic_link" | default: "magic_link" |
Response (password strategy)
{
"access_token": "eyJhbGci...",
"refresh_token": "dGhpcyBpcyBh...",
"user_id": "550e8400-...",
"session_id": "660e8400-..."
}
Also sets the __session cookie with the access JWT.
Response (magic_link strategy)
{
"status": "magic_link_sent",
"user_id": null
}
200 Success401 Invalid credentials or user not found429 Rate limitedVerify Email (Link)
/v1/email/verify
Verify email address or consume a magic link sign-in token. Redirects the user after verification.
Query parameters
| Param | Type | |
|---|---|---|
token | string | required |
Response
HTTP 302 redirect to the redirect_url stored in the token, or the app's primary domain, or /. Sets the __session cookie.
302 Redirect after successful verification400 Invalid or expired tokenVerify Email (Code)
/v1/verify-code
Verify email or sign in using a 6-digit code sent by email.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
email | string | required |
code | string (6 digits) | required |
Response
{
"access_token": "eyJhbGci...",
"refresh_token": "dGhpcyBpcyBh...",
"user_id": "550e8400-...",
"session_id": "660e8400-..."
}
200 Success, session created400 Invalid or expired codeGet Current User
/v1/me
Get the authenticated user's info. Requires a valid session.
Authentication
Cookie __session or Authorization: Bearer <jwt>
Response
{
"user_id": "550e8400-...",
"email": "[email protected]",
"email_verified": true
}
200 Success401 Not authenticatedLogout
/v1/sessions/logout
Revoke the current session and clear the session cookie.
Authentication
Cookie __session or Authorization: Bearer <jwt>
Response
{
"status": "logged_out"
}
Refresh Session
/v1/tokens/session
Exchange a refresh token for a new access token. The refresh token is rotated on each use.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
refresh_token | string | required |
Response
{
"access_token": "eyJhbGci...",
"refresh_token": "new-rotated-token...",
"user_id": "550e8400-...",
"session_id": "660e8400-..."
}
200 Success401 Invalid or expired refresh tokenSSO Bridge
/v1/sso/bridge
Generate a one-time SSO token for cross-domain authentication. Redirects the user to the target URL with the token appended as a query parameter.
Authentication
Cookie __session or Authorization: Bearer <jwt>
Query parameters
| Param | Type | |
|---|---|---|
return_to | string (URL) | required |
Response
HTTP 302 redirect to {return_to}?__sso_token={token}. Token expires after 60 seconds.
SSO Exchange
/v1/sso/exchange
Exchange a one-time SSO token for a session. The source and target apps must belong to the same tenant.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
token | string | required |
Response
{
"access_token": "eyJhbGci...",
"refresh_token": "dGhpcyBpcyBh...",
"user_id": "550e8400-...",
"session_id": "660e8400-..."
}
200 Success400 Missing token401 Invalid or expired token403 Cross-tenant SSO not allowedBot Signup
/v1/bot/signup
Register a bot using a confirmed confirmations.info donation challenge and an Ed25519 public key. Each challenge can only be used once.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
challenge_id | string | required |
public_key | string (base64, 32 bytes) | required |
POST https://api.confirmations.info/challenges with {"chain": "ethereum", "usd_amount": 1}, then send the donation. Poll GET /challenges/{id} until status is CONFIRMED.
Response
{
"bot_id": "550e8400-e29b-41d4-a716-446655440000",
"public_key": "base64-encoded-ed25519-public-key"
}
200 Bot registered400 Invalid public key or challenge not confirmed404 Challenge not found409 Challenge already used or key already registered429 Rate limited502 Confirmations API unreachableBot Auth
/v1/bot/auth
Authenticate a bot by verifying an Ed25519 signature. Returns a standard session with JWT and refresh token.
Headers
X-Publishable-Key | Application publishable key | required |
Request body
| Field | Type | |
|---|---|---|
bot_id | UUID string | required |
timestamp | integer (unix seconds) | required |
signature | string (base64) | required |
Signature
Sign the following message with the bot's Ed25519 private key:
ezauth:bot_auth:{app_id}:{bot_id}:{timestamp}
The timestamp must be within 5 minutes of server time.
Response
{
"access_token": "eyJhbGci...",
"refresh_token": "dGhpcyBpcyBh...",
"user_id": "550e8400-...",
"session_id": "660e8400-..."
}
200 Success, session created401 Invalid signature or expired timestamp404 Bot not found429 Rate limitedList Users
/v1/users
List all users for an application. Supports pagination and email filtering.
Authentication
Authorization: Bearer sk_...
Query parameters
| Param | Type | Default |
|---|---|---|
limit | integer | 50 |
offset | integer | 0 |
email | string | optional filter |
Response
{
"users": [
{
"id": "550e8400-...",
"email": "[email protected]",
"email_verified": true,
"created_at": "2025-01-15T09:30:00Z",
"updated_at": "2025-01-15T09:30:00Z"
}
],
"total": 42
}
Create User
/v1/users
Create a user server-side. Skips verification and rate limits.
Authentication
Authorization: Bearer sk_...
Request body
| Field | Type | |
|---|---|---|
email | string | required |
password | string | null | optional |
Response 201
{
"id": "550e8400-...",
"email": "[email protected]",
"email_verified": false,
"created_at": "2025-01-15T09:30:00Z",
"updated_at": "2025-01-15T09:30:00Z"
}
201 User created409 User already existsGet User
/v1/users/{user_id}
Retrieve a specific user by ID.
Authentication
Authorization: Bearer sk_...
Response
{
"id": "550e8400-...",
"email": "[email protected]",
"email_verified": true,
"created_at": "2025-01-15T09:30:00Z",
"updated_at": "2025-01-15T09:30:00Z"
}
200 Success404 User not foundRevoke Session
/v1/sessions/revoke
Revoke a session by ID. The user's JWT will be invalid on next verification.
Authentication
Authorization: Bearer sk_...
Query parameters
| Param | Type | |
|---|---|---|
session_id | UUID | required |
Response
{
"status": "revoked"
}
200 Session revoked404 Session not found or already revokedCreate Sign-In Token
/v1/sign_in_tokens
Create a short-lived session for a user server-side. Useful for impersonation or server-initiated sign-in flows.
Authentication
Authorization: Bearer sk_...
Request body
| Field | Type | |
|---|---|---|
user_id | UUID string | required |
expires_in_seconds | integer | default: 300 |
Response
{
"token": "eyJhbGci...",
"refresh_token": "dGhpcyBpcyBh...",
"user_id": "550e8400-...",
"session_id": "660e8400-...",
"expires_at": "2025-01-15T09:35:00Z"
}
200 Token created404 User not foundJWKS
/.well-known/jwks.json
Retrieve the JSON Web Key Set for verifying JWTs. No authentication required.
App resolution
Pass ?app_id=... as a query parameter, or the app is resolved from the Host header via a verified domain.
Response
{
"keys": [
{
"kty": "RSA",
"kid": "app-uuid",
"use": "sig",
"alg": "RS256",
"n": "base64url-modulus...",
"e": "AQAB"
}
]
}