API Reference

ezAuth v0.1 — Multi-tenant authentication service

Authentication

ezAuth uses two types of API keys per application:

Key typeHeaderUsage
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>.

TokenLifetimeNotes
Access JWT15 minutesContains sub, sid, aud, email, email_verified
Refresh token30 daysRotated 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 error
401 Invalid credentials or missing authentication
403 Forbidden (e.g. cross-tenant operation)
404 Resource not found
409 Conflict (duplicate resource)
422 Unprocessable (e.g. missing proof-of-work)
429 Rate limited

Every 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.

EndpointWindowLimitScope
POST /v1/signups60s10IP
POST /v1/signups300s1Email
POST /v1/signins60s10IP

Request Challenge

POST /v1/challenges

Request a proof-of-work challenge. Required before signup if hashcash is enabled.

Headers

X-Publishable-KeyApplication publishable keyrequired

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

POST /v1/signups

Register a new user. Sends a verification email (link or code, depending on app config).

Headers

X-Publishable-KeyApplication publishable keyrequired

Request body

FieldType
emailstringrequired
passwordstring | nulloptional
redirect_urlstring | nulloptional
hashcashobjectif enabled
  hashcash.challengestringrequired
  hashcash.noncestringrequired

Response

{
  "status": "verification_sent",
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}
200 Verification email sent
409 User already exists
410 Hashcash challenge expired
422 Proof-of-work required but not provided
429 Rate limited

Sign In

POST /v1/signins

Sign in with password or request a magic link.

Headers

X-Publishable-KeyApplication publishable keyrequired

Request body

FieldType
emailstringrequired
passwordstring | nulloptional
redirect_urlstring | nulloptional
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 Success
401 Invalid credentials or user not found
429 Rate limited

Verify Email (Link)

GET /v1/email/verify

Verify email address or consume a magic link sign-in token. Redirects the user after verification.

Query parameters

ParamType
tokenstringrequired

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 verification
400 Invalid or expired token

Verify Email (Code)

POST /v1/verify-code

Verify email or sign in using a 6-digit code sent by email.

Headers

X-Publishable-KeyApplication publishable keyrequired

Request body

FieldType
emailstringrequired
codestring (6 digits)required

Response

{
  "access_token": "eyJhbGci...",
  "refresh_token": "dGhpcyBpcyBh...",
  "user_id": "550e8400-...",
  "session_id": "660e8400-..."
}
200 Success, session created
400 Invalid or expired code

Get Current User

GET /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 Success
401 Not authenticated

Logout

POST /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

POST /v1/tokens/session

Exchange a refresh token for a new access token. The refresh token is rotated on each use.

Headers

X-Publishable-KeyApplication publishable keyrequired

Request body

FieldType
refresh_tokenstringrequired

Response

{
  "access_token": "eyJhbGci...",
  "refresh_token": "new-rotated-token...",
  "user_id": "550e8400-...",
  "session_id": "660e8400-..."
}
200 Success
401 Invalid or expired refresh token

SSO Bridge

GET /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

ParamType
return_tostring (URL)required

Response

HTTP 302 redirect to {return_to}?__sso_token={token}. Token expires after 60 seconds.

SSO Exchange

POST /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-KeyApplication publishable keyrequired

Request body

FieldType
tokenstringrequired

Response

{
  "access_token": "eyJhbGci...",
  "refresh_token": "dGhpcyBpcyBh...",
  "user_id": "550e8400-...",
  "session_id": "660e8400-..."
}
200 Success
400 Missing token
401 Invalid or expired token
403 Cross-tenant SSO not allowed

Bot Signup

POST /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-KeyApplication publishable keyrequired

Request body

FieldType
challenge_idstringrequired
public_keystring (base64, 32 bytes)required
To get a challenge_id, call 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 registered
400 Invalid public key or challenge not confirmed
404 Challenge not found
409 Challenge already used or key already registered
429 Rate limited
502 Confirmations API unreachable

Bot Auth

POST /v1/bot/auth

Authenticate a bot by verifying an Ed25519 signature. Returns a standard session with JWT and refresh token.

Headers

X-Publishable-KeyApplication publishable keyrequired

Request body

FieldType
bot_idUUID stringrequired
timestampinteger (unix seconds)required
signaturestring (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 created
401 Invalid signature or expired timestamp
404 Bot not found
429 Rate limited

List Users

GET /v1/users

List all users for an application. Supports pagination and email filtering.

Authentication

Authorization: Bearer sk_...

Query parameters

ParamTypeDefault
limitinteger50
offsetinteger0
emailstringoptional 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

POST /v1/users

Create a user server-side. Skips verification and rate limits.

Authentication

Authorization: Bearer sk_...

Request body

FieldType
emailstringrequired
passwordstring | nulloptional

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 created
409 User already exists

Get User

GET /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 Success
404 User not found

Revoke Session

POST /v1/sessions/revoke

Revoke a session by ID. The user's JWT will be invalid on next verification.

Authentication

Authorization: Bearer sk_...

Query parameters

ParamType
session_idUUIDrequired

Response

{
  "status": "revoked"
}
200 Session revoked
404 Session not found or already revoked

Create Sign-In Token

POST /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

FieldType
user_idUUID stringrequired
expires_in_secondsintegerdefault: 300

Response

{
  "token": "eyJhbGci...",
  "refresh_token": "dGhpcyBpcyBh...",
  "user_id": "550e8400-...",
  "session_id": "660e8400-...",
  "expires_at": "2025-01-15T09:35:00Z"
}
200 Token created
404 User not found

JWKS

GET /.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"
    }
  ]
}