Skip to content

Authentication

LiberClaw supports four authentication methods. All return the same JWT token pair on success.

Every successful authentication returns:

{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 900
}
FieldTypeDescription
access_tokenstringShort-lived JWT for API requests (15 minutes)
refresh_tokenstringLong-lived token for obtaining new access tokens (30 days)
token_typestringAlways "bearer"
expires_inintegerAccess token lifetime in seconds

Use the access token in the Authorization header for all authenticated requests:

Authorization: Bearer <access_token>

A passwordless flow that sends a login link or 6-digit code to the user’s email.

POST /auth/login/email

Rate 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:

Terminal window
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" }),
});

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-link

Rate limit: 10/minute

Request body (token):

{
"token": "abc123..."
}

Request body (code):

{
"email": "user@example.com",
"code": "123456"
}

Response: Token Pair

curl (with code):

Terminal window
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"}'

A browser-based redirect flow using Google’s consent screen.

GET /auth/oauth/google

Redirects the user to Google’s OAuth consent screen. After the user approves, Google redirects back to the callback URL.

GET /auth/oauth/google/callback

The 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>
POST /auth/exchange

Rate 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 page
const 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();

For native mobile apps that use Google Sign-In SDK directly:

POST /auth/mobile/google

Request body:

{
"id_token": "<google_id_token>"
}

Response: Token Pair


Same redirect flow as Google, using GitHub’s authorization screen.

GET /auth/oauth/github
GET /auth/oauth/github/callback

Redirects to your frontend with a one-time auth code, same as Google.

Same as Google — use POST /auth/exchange with the code from the callback redirect.


Challenge-response authentication using an EVM wallet (MetaMask, WalletConnect, etc.).

POST /auth/wallet/challenge

Rate limit: 10/minute

Request body:

{
"address": "0x1234...abcd"
}

Response:

{
"nonce": "a1b2c3d4...",
"message": "Sign this message to log in to LiberClaw.\n\nNonce: a1b2c3d4..."
}

curl:

Terminal window
curl -X POST https://api.liberclaw.ai/api/v1/auth/wallet/challenge \
-H "Content-Type: application/json" \
-d '{"address": "0x1234...abcd"}'

Have the user sign the message with their wallet, then submit the signature.

POST /auth/wallet/verify

Rate 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 challenge
const 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 verify
const 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();

Create an anonymous guest account tied to a device ID. Guest accounts have limited functionality until upgraded.

POST /auth/guest

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


Access tokens expire after 15 minutes. Use the refresh token to get a new pair without re-authenticating.

POST /auth/refresh

Rate 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:

Terminal window
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, ... }
}

POST /auth/logout

Request body:

{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}

Response: 204 No Content

POST /auth/logout/all

Requires Authorization header. Revokes all active sessions for the current user.

Response: 204 No Content

curl:

Terminal window
curl -X POST https://api.liberclaw.ai/api/v1/auth/logout/all \
-H "Authorization: Bearer <access_token>"