Skip to content

Latest commit

 

History

History
1103 lines (800 loc) · 39.1 KB

File metadata and controls

1103 lines (800 loc) · 39.1 KB
title Authentication API
description Authentication API endpoints for user management and identity verification

Authentication API

Manage user authentication, password resets, passkey (WebAuthn) login, wallet sign-in, Farcaster identity, and token gating.

Auth middleware

The backend uses two authentication patterns depending on the endpoint.

API key authentication (backend core endpoints)

Endpoints such as /api/deployments, /api/openclaw/instances, and per-agent lifecycle routes require a shared API key passed as a bearer token. The key is compared against the configured INTERNAL_API_KEY using a timing-safe comparison.

curl -X GET https://backend.example.com/api/openclaw/instances \
  -H "Authorization: Bearer YOUR_INTERNAL_API_KEY"
HTTP status Error Description
401 Unauthorized Missing or invalid API key
403 Forbidden API key does not match

Standalone auth middleware (requireAuth)

Routes that are not mounted through the main API key middleware can use the standalone requireAuth middleware. This performs the same timing-safe Bearer token verification against INTERNAL_API_KEY and can be applied to individual route handlers. See Security — Auth middleware for an overview of both middleware functions.

HTTP status Error Description
401 Unauthorized Missing Authorization header or missing Bearer prefix
403 Forbidden Token does not match INTERNAL_API_KEY
500 Server misconfigured INTERNAL_API_KEY is not set

Header-based authentication (backend user context)

Backend endpoints that accept user context from the frontend proxy read the following headers. When HMAC_SECRET (or INTERNAL_API_KEY as fallback) is configured, the backend requires a valid HMAC-SHA256 signature to trust these headers.

Header Type Required Description
x-user-email string No User email address
x-user-id string No User ID (defaults to anonymous if missing)
x-user-role string No User role (defaults to user if missing)
x-user-signature string Conditional HMAC-SHA256 signature of the user context. Required when HMAC_SECRET or INTERNAL_API_KEY is set.

The signature is computed over the string {userId}:{userEmail}:{userRole} using the HMAC_SECRET environment variable (falls back to INTERNAL_API_KEY). The frontend proxy signs these headers before forwarding requests to the backend.

HTTP status Error code Description
401 SIGNATURE_REQUIRED HMAC_SECRET is configured but the x-user-signature header is missing
401 INVALID_SIGNATURE The provided signature does not match the expected HMAC-SHA256 digest

The AI route middleware (/api/ai/chat) reads user context from headers separately from this middleware. Access control on AI routes is enforced by the plan middleware, which validates x-user-plan and x-stripe-subscription-id against the user's database record. When the database is available, the subscription ID header is cross-referenced against the stored subscription to prevent forgery — a mismatch returns 402 with code SUBSCRIPTION_MISMATCH. If the database is unavailable, the middleware falls back to header-based validation.

Admin middleware

Endpoints that require admin access check the x-user-email header against the ADMIN_EMAILS environment variable. The comparison is case-insensitive — both the configured emails and the request email are normalized to lowercase before matching.

HTTP status Error code Description
403 ADMIN_REQUIRED Endpoint requires admin privileges

Session authentication (web API)

Most web API endpoints use cookie-based session authentication. The platform issues an agentbot-session cookie upon sign-in that persists for 30 days. After successful authentication, the middleware sets the database-level user context for RLS. All subsequent queries in that request are automatically scoped to the authenticated user's data. See Security for details.

You can retrieve the current session at any time using the Get session endpoint and end it using the Sign out endpoint.

Admin session fallback

The POST /api/provision endpoint supports an admin fallback when the session user ID is missing. The endpoint checks the session email against ADMIN_EMAILS — if it matches, a synthetic session is created and the request proceeds. The body email field is not used for admin detection. This fallback only applies to admin users on the provisioning endpoint — all other session-authenticated endpoints still require a valid session.

Dual authentication

Some endpoints support both session cookies and Bearer API keys, allowing both browser users and programmatic agents to call the same route. The server resolves the caller's identity in order:

  1. Cookie session / NextAuth JWT — used by browser and dashboard users.
  2. Bearer API key — used by programmatic agent access. The key is hashed with SHA-256 and looked up in the database.

If neither method produces a valid session, the endpoint returns 401 Unauthorized.

API keys are created via the keys API and use the ab_ prefix. Include the key in the Authorization header:

curl -X POST "https://agentbot.sh/api/jobs/job_abc123/claim" \
  -H "Authorization: Bearer ab_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"claimerAgentId": "agent-worker"}'

Endpoints that support dual authentication are marked with a note in their documentation. Currently supported:

Endpoint Method Description
/api/jobs/{jobId}/claim POST Claim an M2M job
/api/social/posts POST Create a social post

Sign up

POST /api/register

Protected by bot detection. Automated or non-browser requests may be rejected.

Registration does not create a session. After a successful sign-up, the client must call POST /api/auth/login to authenticate.

New accounts automatically receive a 7-day free trial. You can check trial status using the trial API.

Request body

Field Type Required Description
email string Yes User email address
password string Yes Password (minimum 8 characters)
name string No Display name (defaults to email if omitted)
referralCode string No Alphanumeric referral code that may include hyphens (max 20 characters). Case-insensitive. Both the new user and the referrer receive credit when a valid code is provided.

Response

{
  "id": "user_123",
  "email": "user@example.com",
  "name": "John Doe"
}

The response does not include trial information. Use GET /api/trial to retrieve the trial status after registration.

Errors

Code Description
400 Email and password required, invalid email format, password too short, or invalid referral code
403 Request blocked by bot detection
409 User already exists
429 Too many requests

Sign in

POST /api/auth/login

Authenticates a user with email and password. On success, creates a database-backed session and sets the agentbot-session cookie.

This endpoint requires a valid CSRF token in the x-csrf-token or x-xsrf-token request header. Requests without a valid CSRF token are rejected with a 403 error.

Rate-limited to 5 attempts per 15 minutes per IP address. After 5 failed attempts, subsequent requests are rejected with a 429 error until the 15-minute window resets.

Request headers

Header Type Required Description
x-csrf-token string Yes CSRF token in the format {token}:{signed}. You can also use x-xsrf-token.

Request body

Field Type Required Description
email string Yes User email address (case-insensitive)
password string Yes User password
{
  "email": "user@example.com",
  "password": "securepassword"
}

Response

{
  "ok": true,
  "user": {
    "id": "user_123",
    "name": "John Doe"
  }
}

A Set-Cookie header is included with the agentbot-session token. The cookie is HttpOnly, SameSite=Lax, scoped to /, and expires after 30 days.

Errors

Code Description
400 Missing email or password
401 Invalid email or password
403 Invalid or missing CSRF token
429 Too many login attempts. Rate limit is 5 attempts per 15 minutes per IP address.
500 Login failed

This endpoint replaced the previous NextAuth credentials callback (/api/auth/callback/credentials). If you are migrating from an older integration, update your sign-in requests to use /api/auth/login.

Passkey authentication

Passkey (WebAuthn) endpoints let users register hardware or platform authenticators and sign in without a password. Registration requires an active session; authentication does not.

Challenges are hex-encoded strings (prefixed with 0x) and expire after 5 minutes by default. The TTL is configurable via the PASSKEY_CHALLENGE_TTL_MS environment variable.

Register a passkey — get options

POST /api/passkey/register/options

Requires session authentication. Returns WebAuthn registration options that the client passes to navigator.credentials.create().

Response

Field Type Description
options object Serialized WebAuthn PublicKeyCredentialCreationOptions to pass to the browser credentials API
challenge string Hex-encoded server-generated challenge (0x-prefixed). Send this back in the verify request.
{
  "options": {
    "rp": { "id": "agentbot.sh", "name": "Agentbot" },
    "user": { "id": "...", "name": "user@example.com", "displayName": "John Doe" },
    "challenge": "...",
    "excludeCredentials": [],
    "attestation": "none",
    "authenticatorSelection": { "userVerification": "required" },
    "timeout": 60000
  },
  "challenge": "0x1a2b3c...hex-encoded-challenge"
}

Errors

Code Description
401 Unauthorized — no active session

Register a passkey — verify

POST /api/passkey/register/verify

Requires session authentication. Verifies the WebAuthn attestation response and stores the new passkey credential.

Request body

Field Type Required Description
credential object Yes The serialized credential object returned by navigator.credentials.create()
challenge string Yes The hex-encoded challenge returned by the registration options endpoint
label string No A human-readable name for the passkey (defaults to "Passkey")

Response

{
  "ok": true
}

Errors

Code Description
400 Missing credential or challenge, challenge not found or expired, passkey already registered, or verification failed
401 Unauthorized — no active session

Authenticate with a passkey — get options

POST /api/passkey/auth/options

Returns WebAuthn authentication options for an existing user. No session is required — the user is identified by email.

Request body

Field Type Required Description
identifier string Yes User email address (case-insensitive)

Response

Field Type Description
options object Serialized WebAuthn PublicKeyCredentialRequestOptions to pass to navigator.credentials.get()
challenge string Hex-encoded server-generated challenge (0x-prefixed). Send this back in the verify request.
{
  "options": {
    "allowCredentials": [{ "id": "...", "type": "public-key" }],
    "rpId": "agentbot.sh",
    "userVerification": "required",
    "timeout": 60000
  },
  "challenge": "0x1a2b3c...hex-encoded-challenge"
}

Errors

Code Description
400 Missing identifier
404 Account not found, or no passkeys registered for the account

Authenticate with a passkey — verify

POST /api/passkey/auth/verify

Verifies a WebAuthn assertion and creates a session. On success, sets the agentbot-session cookie.

Request body

Field Type Required Description
response object Yes The serialized assertion response from navigator.credentials.get(). Must include id (credential ID) and signCount.
challenge string Yes The hex-encoded challenge returned by the authentication options endpoint

Response

{
  "ok": true
}

A Set-Cookie header is included with the agentbot-session token. The cookie is HttpOnly, SameSite=Lax, scoped to /, and expires after 30 days.

Errors

Code Description
400 Missing response or challenge, or invalid/expired challenge
401 Passkey verification failed
404 Passkey not recognized

Get session

GET /api/auth/session

Returns the current authenticated user based on the agentbot-session cookie. No request body is required — the session token is read from the cookie automatically.

Response (authenticated)

Field Type Description
user.id string User ID
user.name string Display name
user.email string Email address
user.isAdmin boolean Whether the user has admin privileges. Defaults to false when not set.
{
  "user": {
    "id": "user_123",
    "name": "John Doe",
    "email": "user@example.com",
    "isAdmin": false
  }
}

Response (unauthenticated or expired)

{
  "user": null
}

This endpoint always returns 200. Check whether user is null to determine authentication status.

Sign out

POST /api/auth/signout

Ends the current session by deleting the session record from the database and clearing the agentbot-session cookie. No request body is required.

Response

{
  "ok": true
}

This endpoint always returns 200 even if no active session exists.

Get CSRF token

GET /api/auth/csrf

Returns a fresh CSRF token for use in requests that require CSRF protection, such as POST /api/auth/login. No authentication required.

Response

{
  "token": "random-token-value",
  "signed": "hmac-signature",
  "header": "random-token-value:hmac-signature"
}
Field Type Description
token string The CSRF token value
signed string HMAC signature of the token
header string Pre-formatted value to use in the x-csrf-token request header ({token}:{signed})

Use the header value directly in the x-csrf-token or x-xsrf-token request header when calling endpoints that require CSRF protection.

OAuth sign in

OAuth providers support automatic account linking. If a user with the same email address already exists, the OAuth account is linked to the existing user on first sign-in. This lets users who originally signed up with email and password add an OAuth login without creating a duplicate account.

Google

GET /api/auth/google

Redirects to the Google account selector. Requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to be configured.

The flow requests the openid, email, and profile scopes with offline access and uses the select_account prompt, which lets the user pick a Google account without forcing re-consent. After the user selects an account, Google redirects back to the callback endpoint below.

If GOOGLE_CLIENT_ID is not set, the endpoint redirects to /login?error=GoogleNotConfigured.

Callback

GET /api/auth/google/callback

Handles the OAuth authorization code exchange. This endpoint is called by Google after the user selects an account — you do not call it directly.

If Google returns an error query parameter (for example, when the user cancels the sign-in), the callback redirects to /login with an appropriate error code without attempting a token exchange.

On success the endpoint:

  1. Exchanges the authorization code for an access token.
  2. Fetches the user's email and name from the Google userinfo API.
  3. Creates a new user if no account with that email exists (automatic account linking applies when a matching email is found).
  4. Links a Google Account record to the user for dual-auth compatibility. If the user already has a linked Google account, this step is skipped. The Account record stores the OAuth access token, refresh token, and token metadata so the user can sign in with either email/password or Google.
  5. Creates a session and sets the agentbot-session cookie.
  6. Redirects to /dashboard.

Errors

The callback redirects to /login with an error query parameter instead of returning JSON:

Error value Description
AccessDenied The user cancelled the Google sign-in or denied access
GoogleAuthFailed No authorization code received from Google, or Google returned an unrecognized error
GoogleNotConfigured GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET is not set
GoogleTokenFailed Code-to-token exchange failed
GoogleNoEmail Google account has no email address
GoogleAuthError Unexpected server error during authentication

Cross-Account Protection receiver

POST /api/security/risc

Receives security event tokens from Google via the Cross-Account Protection (RISC) protocol. This is the primary receiver for Google security events. It validates the SET JWT, deduplicates events, and takes targeted action depending on the event type.

This endpoint is intended to be called by Google's RISC infrastructure, not by application clients. You do not need to call it directly.

Request body

The request body is a raw SET (Security Event Token) JWT string. The JWT payload contains:

Field Type Description
iss string Issuer — must be https://accounts.google.com/
aud string Audience — must match a configured GOOGLE_CLIENT_ID
jti string Unique event identifier used for deduplication
events object Map of event URIs to event data. Each event may include subject.sub (Google subject ID), subject.email, and reason.

Token validation

The endpoint validates the incoming JWT before processing:

  1. Checks the issuer is https://accounts.google.com/
  2. Checks the audience matches one of the configured Google client IDs
  3. Fetches Google's signing keys from the JWKS endpoint discovered via https://accounts.google.com/.well-known/risc-configuration (keys are cached for 24 hours)
  4. Matches the signing key by the kid header claim
  5. Verifies the RS256 signature using the Web Crypto API (crypto.subtle) with the matched RSA public key

If validation fails, the endpoint returns 400.

Event deduplication

Events are deduplicated using the jti claim. Each processed event is stored in the risc_events table. If an event with the same jti has already been processed, it is acknowledged but not acted on again.

Supported event types

Event URI Action taken
https://schemas.openid.net/secevent/risc/event-type/account-disabled When reason is hijacking: disables Google Sign-in for the user and invalidates all sessions. Otherwise: invalidates all sessions.
https://schemas.openid.net/secevent/risc/event-type/account-enabled Re-enables Google Sign-in for the user
https://schemas.openid.net/secevent/risc/event-type/sessions-revoked Invalidates all active sessions for the user
https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked Revokes stored OAuth refresh tokens and invalidates all sessions
https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required Logged for security monitoring (no automated action)
https://schemas.openid.net/secevent/risc/event-type/verification Acknowledged — used during RISC setup to verify the endpoint

Users are matched by Google subject ID (sub) or email address.

Response

Returns 202 Accepted with an empty body on success. Event processing continues asynchronously after the response is sent.

Errors

Code Description
400 Empty request body or invalid/unverifiable JWT (bad format, wrong issuer, wrong audience, or unknown signing key)
500 Internal error

Health check

GET /api/security/risc

Returns the endpoint status and list of supported event types.

{
  "status": "ok",
  "endpoint": "/api/security/risc",
  "description": "Google RISC (Cross-Account Protection) receiver",
  "events_supported": [
    "account-disabled",
    "account-enabled",
    "sessions-revoked",
    "tokens-revoked",
    "account-credential-change-required",
    "verification"
  ]
}

For details on how RISC fits into the platform security model and how to configure it in Google Cloud Console, see Security — Google RISC Protocol.

Google RISC webhook (deprecated)

POST /api/auth/google/risc

This endpoint is deprecated and returns 410 Gone. Use POST /api/security/risc instead.

This legacy endpoint previously received security event notifications from Google via the RISC protocol. It has been replaced by the POST /api/security/risc endpoint, which adds JWT signature validation, event deduplication, and more granular event handling.

Response

{
  "error": "Deprecated endpoint",
  "detail": "Use /api/security/risc for verified Google RISC events."
}
Code Description
410 Endpoint is deprecated. Migrate to /api/security/risc.

Wallet sign in

POST /api/wallet-auth

Sign in using an Ethereum-compatible wallet. This endpoint supports two wallet types:

  • Base (Coinbase Smart Wallet) — uses Sign-In with Ethereum (SIWE) on Base Mainnet (chain ID 8453). Supports ERC-6492 signature verification for pre-deployed smart wallets.
  • Tempo — uses personal_sign on the Tempo network (chain ID 4217). Users can connect via an injected provider or through wallet.tempo.xyz.

This endpoint replaced the previous NextAuth wallet callback (/api/auth/callback/wallet). If you are migrating from an older integration, update your wallet sign-in requests to use /api/wallet-auth.

How it works (Base)

  1. The client requests a nonce from GET /api/auth/nonce.
  2. The client opens the Base Account SDK popup and requests a SIWE signature on Base Mainnet.
  3. The wallet address, SIWE message, and signature are sent to POST /api/wallet-auth.
  4. The server verifies the signature using viem (which handles ERC-6492 for smart wallets).
  5. If no account exists for the wallet address, a new user is created automatically.
  6. If an account with the same wallet-derived email already exists, the wallet is linked to the existing account.

How it works (Tempo)

  1. The client requests a nonce from GET /api/auth/nonce.
  2. The client connects to the Tempo network (chain ID 0x1079 / 4217) via an injected Ethereum provider or wallet.tempo.xyz. If the chain is not present in the wallet, it is added automatically with RPC URL https://rpc.tempo.xyz.
  3. The client constructs a plaintext message containing the nonce, chain identifier, and timestamp, then signs it with personal_sign.
  4. The wallet address, signed message, signature, and chain: "tempo" are sent to POST /api/wallet-auth.
  5. The server verifies the signature and nonce, then creates or links the user account.

Request body

Field Type Required Description
address string Yes Ethereum wallet address (0x-prefixed)
message string Yes The signed message string (SIWE format for Base, plaintext for Tempo)
signature string Yes The wallet signature (0x-prefixed)
chain string No Target chain for authentication. Accepted values: "tempo". When omitted, defaults to Base (SIWE).

Tempo message format

When using Tempo wallet sign-in, the signed message follows this format:

Sign in to Agentbot

Nonce: <server-issued-nonce>
Chain: Tempo (4217)
Timestamp: <unix-milliseconds>

Tempo chain parameters

If the user's wallet does not have the Tempo network configured, the client adds it using:

Parameter Value
Chain ID 0x1079 (4217)
Chain name Tempo
Native currency pathUSD (18 decimals)
RPC URL https://rpc.tempo.xyz
Block explorer https://explorer.tempo.xyz

Response

{
  "ok": true,
  "user": {
    "id": "user_123",
    "name": "Wallet:0xaBcD...eF12"
  }
}

A Set-Cookie header is included with the agentbot-session token. The cookie is HttpOnly, SameSite=Lax, scoped to /, and expires after 30 days.

Account linking

When a wallet signs in, the system checks for an existing user by the wallet-derived email address (<address>@wallet.agentbot). If a matching user is found, the wallet is linked to that existing account. This prevents duplicate accounts and lets users access the same data regardless of which sign-in method they use. This applies to both Base and Tempo wallet sign-ins.

Errors

Code Description
400 Missing address, message, or signature
400 Missing nonce in signed message — the message must include a nonce obtained from GET /api/auth/nonce
401 Invalid nonce — the nonce in the signed message does not match the server-issued nonce
401 Address mismatch — the wallet address in the signed message does not match the address field
401 Invalid signature
500 Auth failed

Get nonce

GET /api/auth/nonce
POST /api/auth/nonce

Generates a random nonce for use in wallet sign-in message construction (SIWE for Base, plaintext for Tempo). Both GET and POST methods return the same response.

Response

{
  "nonce": "a1b2c3d4e5f6..."
}

Get current user

GET /api/settings

Requires session authentication. Returns the current user profile.

Response

{
  "id": "user_123",
  "email": "user@example.com",
  "name": "John Doe",
  "plan": "solo",
  "credits": 0,
  "twoFactorEnabled": false,
  "xHandle": "yourhandle",
  "openclaw": {
    "managed": true,
    "instanceId": "inst_abc123",
    "url": "https://openclaw.example.com"
  }
}
Field Type Description
id string User identifier
email string User email address
name string Display name
plan string Current subscription plan (solo, team, etc.)
credits number Remaining referral credits balance
twoFactorEnabled boolean Whether two-factor authentication is enabled
xHandle string | null X (Twitter) handle without the @ prefix, or null if not set. Manage via the X handle API.
openclaw object OpenClaw instance details
openclaw.managed boolean Always true for platform-managed instances
openclaw.instanceId string | null OpenClaw instance identifier, or null if not provisioned
openclaw.url string | null OpenClaw instance URL, or null if not provisioned

Errors

Code Description
401 Unauthorized
404 User not found

Update profile

You can update your profile using either POST or PATCH.

POST /api/settings

Request body (POST)

Field Type Required Description
name string No New display name
email string No New email address (must be unique)

Errors (POST)

Code Description
400 Invalid email format
401 Unauthorized
409 Email address already in use
PATCH /api/settings

Request body (PATCH)

Field Type Required Description
name string No New display name
notifications object No Notification preferences (accepted but not yet persisted)

Response (POST and PATCH)

{
  "id": "user-a1b2c3d4",
  "email": "user@example.com",
  "name": "Updated Name",
  "plan": "solo",
  "credits": 100,
  "twoFactorEnabled": false,
  "openclaw": {
    "managed": true,
    "instanceId": "inst_abc123",
    "url": "https://openclaw.example.com"
  }
}
Field Type Description
id string User identifier
email string User email address
name string Display name
plan string Current subscription plan (solo, team, etc.)
credits number Remaining referral credits balance
twoFactorEnabled boolean Whether two-factor authentication is enabled
openclaw object OpenClaw instance details
openclaw.managed boolean Always true for platform-managed instances
openclaw.instanceId string | null OpenClaw instance identifier, or null if not provisioned
openclaw.url string | null OpenClaw instance URL, or null if not provisioned

Change password

POST /api/settings/password

Request body

Field Type Required Description
currentPassword string Yes Current password
newPassword string Yes New password (minimum 8 characters)

Response

{
  "success": true
}

Errors

Code Description
400 Current and new password required, or password must be at least 8 characters
401 Unauthorized or current password incorrect
404 User not found

Forgot password

POST /api/auth/forgot-password

Protected by bot detection. Rate-limited per IP address. Always returns the same response regardless of whether the email exists, to prevent user enumeration.

Request body

Field Type Required Description
email string Yes Account email address

Response

{
  "message": "If an account exists, a reset link has been sent"
}

Errors

Code Description
400 Email is required, invalid email format, or email service validation error
403 Request blocked by bot detection
429 Too many requests
500 Internal server error

Reset password

POST /api/auth/reset-password

Rate-limited per IP address.

Request body

Field Type Required Description
token string Yes Reset token from the email link
password string Yes New password (minimum 6 characters). Note: sign-up and password change endpoints enforce a minimum of 8 characters.

Response

{
  "message": "Password reset successfully"
}

Errors

Code Description
400 Token and password are required, password too short (minimum 6 characters), or invalid/expired token
404 User not found
429 Too many requests

Farcaster authentication

Verify Farcaster identity

POST /api/auth/farcaster/verify
GET /api/auth/farcaster/verify

The GET method returns endpoint metadata. The POST method verifies a Farcaster ID token and optionally checks $RAVE token gating on Base.

Request body

Field Type Required Description
fidToken string Yes Farcaster ID token
address string No Ethereum address for token gating check

Response

{
  "success": true,
  "sessionToken": "eyJmaWRUb2tlbiI6Ii4uLiIsImFkZHJlc3MiOiIweC4uLiIsInZlcmlmaWVkIjp0cnVlLCJpYXQiOjE3MTA4MDY0MDAsImV4cCI6MTcxMDg5MjgwMH0.HMAC_SIGNATURE",
  "address": "0x...",
  "message": "Farcaster verification successful",
  "tokenGated": true,
  "accessLevel": "premium"
}

The sessionToken is an HMAC-SHA256 signed token in the format {base64url-data}.{base64url-signature}. The data payload contains the following claims:

Claim Type Description
fidToken string Farcaster ID token (truncated to 64 characters)
address string Ethereum address from the request
verified boolean Always true on success
iat number Issued-at timestamp (Unix seconds)
exp number Expiry timestamp (24 hours after issuance)

The token is signed using the FARCASTER_SESSION_SECRET environment variable. If not set, the signing key falls back to NEXTAUTH_SECRET. In production, one of these environment variables must be configured — the endpoint returns a 500 error if neither is set. In non-production environments, a build-time placeholder is used when both are missing.

Session tokens are no longer plain base64-encoded JSON. Tokens issued before this change are not compatible with the new HMAC verification and must be refreshed.

In production, the Farcaster verification endpoint now requires FARCASTER_SESSION_SECRET or NEXTAUTH_SECRET to be configured. Requests fail with a 500 error if neither secret is available. This is a breaking change from the previous behavior which used a hardcoded fallback secret in all environments.

Errors

Code Description
401 Missing Farcaster ID token
403 Token gating failed (insufficient $RAVE balance). Response includes required, minBalance fields
500 Verification failed

Refresh Farcaster token

POST /api/auth/farcaster/refresh
GET /api/auth/farcaster/refresh

The GET method returns endpoint metadata.

Request body

Field Type Required Description
refreshToken string Yes Base64-encoded refresh token

Response

{
  "success": true,
  "sessionToken": "base64-new-session",
  "expiresIn": 86400,
  "message": "Token refreshed successfully"
}

Errors

Code Description
400 Missing refresh token
401 Invalid refresh token
500 Token refresh failed

Token gating

The token gating endpoints check whether a wallet holds sufficient $RAVE tokens on Base mainnet. The following parameters are configurable via environment variables:

Environment variable Default Description
TOKEN_GATING_ADDRESS 0x6EE72eEDEfBa8937Ec8c36dEd9B8c1ef9ca7A3db ERC-20 contract address to check balance against
TOKEN_GATING_MIN_BALANCE 1000000000000000000 (1 RAVE, 18 decimals) Minimum token balance required for access
TOKEN_GATING_RPC https://mainnet.base.org Base network RPC endpoint used for balance queries

These environment variables allow you to change the token address, minimum balance threshold, and RPC endpoint without redeploying. The minBalance, contractAddress, and rpcEndpoint fields in the API responses reflect the currently configured values.

Verify token access (POST)

POST /api/auth/token-gating/verify

Checks whether a wallet holds sufficient $RAVE tokens on Base mainnet.

Request body

Field Type Required Description
fid string Yes Farcaster ID
address string Yes Ethereum address (0x-prefixed, 42 characters)

Response

{
  "fid": "12345",
  "address": "0x...",
  "hasAccess": true,
  "tokenGated": true,
  "minBalance": "1000000000000000000",
  "token": "RAVE",
  "chain": "base",
  "message": "User has sufficient $RAVE balance",
  "timestamp": "2026-03-19T00:00:00Z"
}

Errors

Code Description
400 Missing fid or address, or invalid Ethereum address
500 Verification failed

Verify token access (GET)

GET /api/auth/token-gating/verify?address=0x...

Query parameters

Parameter Type Required Description
address string Yes Ethereum address (0x-prefixed, 42 characters)

Response

{
  "address": "0x...",
  "hasAccess": true,
  "tokenGated": true,
  "minBalance": "1000000000000000000",
  "token": "RAVE",
  "chain": "base",
  "contractAddress": "0x6EE72eEDEfBa8937Ec8c36dEd9B8c1ef9ca7A3db",
  "rpcEndpoint": "https://mainnet.base.org"
}

Webhook events

Event Description
user.created New user registered
user.updated Profile updated
user.deleted Account deleted

Automatic welcome email on signup

When a new user signs up through an OAuth provider (Google, wallet, or Farcaster), a welcome email is automatically sent to their registered email address. This is triggered by the signIn event when isNewUser is true.

The welcome email uses a branded template with example use cases. See transactional email templates for details on the email content and configuration.

Google RISC events (Cross-Account Protection)

The following inbound events are processed by the POST /api/security/risc endpoint when received from Google:

Event Description
account-disabled The Google account was disabled. If the reason is hijacking, Google Sign-in is disabled for the user and all sessions are invalidated. Otherwise, sessions are invalidated.
account-enabled The Google account was re-enabled. Google Sign-in is re-enabled for the user.
sessions-revoked Google revoked the user's sessions. All local sessions are invalidated.
tokens-revoked Google revoked the user's OAuth tokens. Stored refresh tokens are deleted and all sessions are invalidated.
account-credential-change-required Google flagged the account for a credential change. Logged for monitoring.
verification Sent by Google during RISC setup to verify the endpoint is reachable.

Google RISC events (legacy — deprecated)

The legacy POST /api/auth/google/risc endpoint is deprecated and returns 410 Gone. Migrate to POST /api/security/risc for event handling.