Authentication
LiberClaw supports four authentication methods. All return the same JWT token pair on success.
Token Pair Response
Section titled “Token Pair Response”Every successful authentication returns:
{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 900}| Field | Type | Description |
|---|---|---|
access_token | string | Short-lived JWT for API requests (15 minutes) |
refresh_token | string | Long-lived token for obtaining new access tokens (30 days) |
token_type | string | Always "bearer" |
expires_in | integer | Access token lifetime in seconds |
Use the access token in the Authorization header for all authenticated requests:
Authorization: Bearer <access_token>Email Magic Link
Section titled “Email Magic Link”A passwordless flow that sends a login link or 6-digit code to the user’s email.
Step 1: Request Magic Link
Section titled “Step 1: Request Magic Link”POST /auth/login/emailRate limit: 5/minute
Request body:
{ "email": "user@example.com"}Response:
{ "message": "If this email is valid, a magic link has been sent."}The response is always the same regardless of whether the email exists, to prevent enumeration.
curl:
curl -X POST https://api.liberclaw.ai/api/v1/auth/login/email \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com"}'JavaScript:
const res = await fetch("https://api.liberclaw.ai/api/v1/auth/login/email", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "user@example.com" }),});Step 2: Verify Token or Code
Section titled “Step 2: Verify Token or Code”The user receives an email with either a link containing a token or a 6-digit code. Use one of these to obtain tokens.
POST /auth/verify-magic-linkRate limit: 10/minute
Request body (token):
{ "token": "abc123..."}Request body (code):
{ "email": "user@example.com", "code": "123456"}Response: Token Pair
curl (with code):
curl -X POST https://api.liberclaw.ai/api/v1/auth/verify-magic-link \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "code": "123456"}'Google OAuth
Section titled “Google OAuth”A browser-based redirect flow using Google’s consent screen.
Step 1: Redirect to Google
Section titled “Step 1: Redirect to Google”GET /auth/oauth/googleRedirects the user to Google’s OAuth consent screen. After the user approves, Google redirects back to the callback URL.
Step 2: Callback (automatic)
Section titled “Step 2: Callback (automatic)”GET /auth/oauth/google/callbackThe API exchanges the authorization code for tokens, creates or links the user account, and redirects to your frontend with a one-time auth code:
https://app.liberclaw.ai/auth/callback?code=<auth_code>Step 3: Exchange Auth Code for Tokens
Section titled “Step 3: Exchange Auth Code for Tokens”POST /auth/exchangeRate limit: 10/minute
Request body:
{ "code": "<auth_code>"}Response: Token Pair
The auth code is single-use and expires after 60 seconds.
JavaScript:
// After OAuth redirect lands on your callback pageconst params = new URLSearchParams(window.location.search);const code = params.get("code");
const res = await fetch("https://api.liberclaw.ai/api/v1/auth/exchange", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code }),});const tokens = await res.json();Mobile: Google ID Token
Section titled “Mobile: Google ID Token”For native mobile apps that use Google Sign-In SDK directly:
POST /auth/mobile/googleRequest body:
{ "id_token": "<google_id_token>"}Response: Token Pair
GitHub OAuth
Section titled “GitHub OAuth”Same redirect flow as Google, using GitHub’s authorization screen.
Step 1: Redirect to GitHub
Section titled “Step 1: Redirect to GitHub”GET /auth/oauth/githubStep 2: Callback (automatic)
Section titled “Step 2: Callback (automatic)”GET /auth/oauth/github/callbackRedirects to your frontend with a one-time auth code, same as Google.
Step 3: Exchange Auth Code
Section titled “Step 3: Exchange Auth Code”Same as Google — use POST /auth/exchange with the code from the callback redirect.
Wallet Sign-In
Section titled “Wallet Sign-In”Challenge-response authentication using an EVM wallet (MetaMask, WalletConnect, etc.).
Step 1: Request Challenge
Section titled “Step 1: Request Challenge”POST /auth/wallet/challengeRate limit: 10/minute
Request body:
{ "address": "0x1234...abcd"}Response:
{ "nonce": "a1b2c3d4...", "message": "Sign this message to log in to LiberClaw.\n\nNonce: a1b2c3d4..."}curl:
curl -X POST https://api.liberclaw.ai/api/v1/auth/wallet/challenge \ -H "Content-Type: application/json" \ -d '{"address": "0x1234...abcd"}'Step 2: Verify Signature
Section titled “Step 2: Verify Signature”Have the user sign the message with their wallet, then submit the signature.
POST /auth/wallet/verifyRate limit: 10/minute
Request body:
{ "address": "0x1234...abcd", "signature": "0xabc123...", "nonce": "a1b2c3d4..."}Response: Token Pair
JavaScript (with ethers.js):
import { BrowserProvider } from "ethers";
const provider = new BrowserProvider(window.ethereum);const signer = await provider.getSigner();const address = await signer.getAddress();
// Step 1: Get challengeconst challengeRes = await fetch( "https://api.liberclaw.ai/api/v1/auth/wallet/challenge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address }), });const { nonce, message } = await challengeRes.json();
// Step 2: Sign and verifyconst signature = await signer.signMessage(message);const verifyRes = await fetch( "https://api.liberclaw.ai/api/v1/auth/wallet/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, signature, nonce }), });const tokens = await verifyRes.json();Guest Auth
Section titled “Guest Auth”Create an anonymous guest account tied to a device ID. Guest accounts have limited functionality until upgraded.
POST /auth/guestRate limit: 10/minute
Request body:
{ "device_id": "unique-device-identifier"}Response: Token Pair
Guest users can upgrade to a full account by authenticating with email, OAuth, or wallet while passing their existing token.
Token Refresh
Section titled “Token Refresh”Access tokens expire after 15 minutes. Use the refresh token to get a new pair without re-authenticating.
POST /auth/refreshRate limit: 20/minute
Request body:
{ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."}Response: Token Pair
The old refresh token is revoked after use (rotation). Always store the new refresh token from the response.
curl:
curl -X POST https://api.liberclaw.ai/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token": "eyJhbGciOiJIUzI1NiIs..."}'JavaScript:
async function refreshTokens(refreshToken) { const res = await fetch("https://api.liberclaw.ai/api/v1/auth/refresh", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: refreshToken }), }); if (!res.ok) { // Refresh token expired or revoked — re-authenticate throw new Error("Session expired"); } return res.json(); // { access_token, refresh_token, ... }}Logout
Section titled “Logout”Revoke Single Session
Section titled “Revoke Single Session”POST /auth/logoutRequest body:
{ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."}Response: 204 No Content
Revoke All Sessions
Section titled “Revoke All Sessions”POST /auth/logout/allRequires Authorization header. Revokes all active sessions for the current user.
Response: 204 No Content
curl:
curl -X POST https://api.liberclaw.ai/api/v1/auth/logout/all \ -H "Authorization: Bearer <access_token>"