Skip to content

Auth API

User registration, authentication, session management, and account security.

Base Path

/auth

Authentication

Authentication requirements vary by endpoint. Some require JWT Bearer tokens, some require captcha verification, and some require no authentication at all. See the table below for specifics.

Chainabit also exposes a separate OAuth 2.0 provider surface for partner applications. That provider flow is documented in OAuth 2.0 Provider Guide.

Environment Variables

bash
export BASE_URL="https://api.chainabit.com/api/v1"
export TOKEN="your-access-token"
export REFRESH_TOKEN="your-refresh-token"

Endpoints

MethodPathDescriptionAuthRate Limit
POST/auth/registerRegister a new accountCaptcha10 req / 300s
POST/auth/loginWeb UI password login and session creationCaptcha8 req / 60s
POST/auth/logoutInvalidate the current sessionJWT--
POST/auth/refreshRefresh access and refresh tokensNone--
POST/auth/confirm-emailConfirm email address with tokenNone5 req / 300s
POST/auth/resend-confirmationResend email confirmationCaptcha5 req / 300s
POST/auth/forgot-passwordRequest password reset emailCaptcha3 req / 300s
POST/auth/exchange-recovery-codeExchange recovery code for reset tokenNone15 req / 300s
POST/auth/reset-passwordSet a new password using reset tokenNone5 req / 300s
GET/auth/check-usernameCheck username availabilityJWT20 req / 60s
POST/auth/claim-usernameClaim a usernameJWT5 req / 60s
POST/auth/change-emailChange account emailJWT10 req / 60s
POST/auth/change-passwordChange account passwordJWT10 req / 60s
PATCH/auth/profileUpdate user profileJWT120 req / 60s
GET/auth/sessionGet current session detailsJWT--
POST/auth/magic-linkSend magic link emailCaptcha3 req / 300s
POST/auth/magic-link/verifyVerify magic link tokenNone5 req / 300s
GET/auth/oauth/providersList OAuth providersNone30 req / 60s
GET/auth/oauth/:providerGet OAuth authorization URLNone10 req / 60s
GET/auth/oauth/:provider/callbackHandle OAuth callbackNone10 req / 60s
POST/auth/oauth/:provider/linkLink OAuth providerJWT5 req / 60s
DELETE/auth/oauth/:provider/unlinkUnlink OAuth providerJWT5 req / 60s
GET/auth/oauth/linkedList linked providersJWT20 req / 60s
GET/oauth/authorizeBegin OAuth provider authorization flowBrowser20 req / 60s
POST/oauth/tokenExchange an authorization code or refresh tokenClient auth20 req / 60s
POST/oauth/revokeRevoke an OAuth access or refresh tokenClient auth30 req / 60s
GET/oauth/userinfoResolve the authenticated OAuth subjectOAuth bearer120 req / 60s
POST/auth/device/authorizeGenerate device codesNone10 req / 300s
POST/auth/device/approveApprove a CLI device code from an authenticated browser sessionJWT20 req / 300s
POST/auth/device/tokenPoll device authorizationNone60 req / 60s
POST/auth/developer-tokensCreate a time-bounded developer tokenJWT10 req / 300s
GET/auth/developer-tokensList developer tokens for the current userJWT30 req / 60s
DELETE/auth/developer-tokens/:idRevoke a developer tokenJWT20 req / 300s
POST/auth/developer-tokens/exchangeExchange a developer token for a session payloadNone20 req / 60s

POST /auth/register

Register a new user account and send a confirmation email.

Authentication: None (Captcha required) Rate limit: 10 req / 300s

Note: This endpoint requires captcha verification. Include a valid captcha token in the x-captcha-token header.

Request

FieldTypeRequiredConstraintsDescription
emailstringYesValid email formatAccount email address
passwordstringYes8-128 charactersAccount password
fullNamestringNoMax 120 charactersUser's full name
referralAttributionTokenstringNoUUIDAttribution token captured from POST /api/v1/referrals/click
referralInviteCodestringNoMax 32 charactersInvite code forwarded during referral-aware signup

Note: referralAttributionToken is optional. Use the value returned by POST /api/v1/referrals/click, not the placeholder shown below.

Response

Response Example
json
{
  "data": {
    "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "[email protected]",
    "message": "Confirmation email sent. Please verify your email address."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
userIdstringUnique identifier for the newly created user
emailstringEmail address of the registered account
messagestringHuman-readable status message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: <captcha-token>" \
  -d '{
    "email": "[email protected]",
    "password": "secureP@ss123",
    "fullName": "Alice Johnson",
    "referralAttributionToken": "<referral-attribution-token>",
    "referralInviteCode": "ABC123"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/register`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-captcha-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "[email protected]",
    password: "secureP@ss123",
    fullName: "Alice Johnson",
    referralAttributionToken: "<referral-attribution-token>",
    referralInviteCode: "ABC123",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/register",
    headers={"x-captcha-token": "<captcha-token>"},
    json={
        "email": "[email protected]",
        "password": "secureP@ss123",
        "fullName": "Alice Johnson",
        "referralAttributionToken": "<referral-attribution-token>",
        "referralInviteCode": "ABC123",
    },
)
data = response.json()

POST /auth/login

Authenticate a user and return access and refresh tokens along with session data.

Authentication: None (Captcha required) Rate limit: 8 req / 60s

Note: This endpoint is intended for browser and first-party web UI flows. CLI and automation clients should prefer the device flow or developer-token exchange endpoints below. This endpoint also requires captcha verification. Include a valid captcha token in the x-captcha-token header.

Request

FieldTypeRequiredConstraintsDescription
identifierstringYesMax 256 charactersEmail address or username
passwordstringYesMax 128 charactersAccount password
rememberMebooleanNo--Extend token expiry for longer sessions

Response

Response Example
json
{
  "data": {
    "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "[email protected]",
    "username": "alice_j",
    "profileReady": true,
    "rememberMe": true,
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
      "expiresIn": 3600,
      "tokenType": "Bearer",
      "expiresAt": "2026-03-17T13:00:00.000Z"
    },
    "subscription": {
      "planCode": "pro",
      "status": "active",
      "billingCycle": "monthly",
      "currentPeriodEnd": "2026-04-17T00:00:00.000Z"
    },
    "entitlements": {
      "features": ["ai_chat", "agents", "advanced_analytics"],
      "limits": {
        "workspaces": 10,
        "membersPerWorkspace": 25
      }
    }
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
userIdstringUnique identifier for the authenticated user
emailstringEmail address of the account
usernamestringUsername of the account, if claimed
profileReadybooleanWhether the user has completed profile setup
rememberMebooleanWhether the extended session was requested
tokens.accessTokenstringJWT access token for authenticating requests
tokens.refreshTokenstringToken used to obtain a new access token
tokens.expiresInnumberAccess token lifetime in seconds
tokens.tokenTypestringToken scheme (always Bearer)
tokens.expiresAtstringISO 8601 timestamp when the access token expires
subscription.planCodestringActive subscription plan identifier
subscription.statusstringSubscription status (e.g. active, trialing)
subscription.billingCyclestringBilling cycle (monthly, yearly, lifetime)
subscription.currentPeriodEndstringISO 8601 timestamp when the current period ends
entitlements.features[]string[]List of feature flags enabled for this account
entitlements.limits.workspacesnumberMaximum number of workspaces allowed
entitlements.limits.membersPerWorkspacenumberMaximum members per workspace

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: <captcha-token>" \
  -d '{
    "identifier": "[email protected]",
    "password": "secureP@ss123",
    "rememberMe": true
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/login`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-captcha-token": "<captcha-token>",
  },
  body: JSON.stringify({
    identifier: "[email protected]",
    password: "secureP@ss123",
    rememberMe: true,
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/login",
    headers={"x-captcha-token": "<captcha-token>"},
    json={
        "identifier": "[email protected]",
        "password": "secureP@ss123",
        "rememberMe": True,
    },
)
data = response.json()

POST /auth/logout

Invalidate the current session and revoke tokens.

Authentication: JWT required

Request

No path, query, or body parameters. Requires Authorization: Bearer $TOKEN.

Response

Response Example
json
{
  "data": null,
  "meta": null,
  "error": null
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/logout \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/auth/logout`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/logout",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

POST /auth/refresh

Exchange a refresh token for a new pair of access and refresh tokens.

Authentication: None

Important: Refresh tokens are rotated on every successful refresh. Clients must replace any stored refresh token with the new tokens.refreshToken returned by this endpoint. Reusing an older refresh token will return 401 Unauthorized.

Request

FieldTypeRequiredConstraintsDescription
refreshTokenstringYes--Refresh token from a previous login or refresh

Response

Response Example
json
{
  "data": {
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "bmV3IHJlZnJlc2ggdG9r...",
      "expiresIn": 3600,
      "tokenType": "Bearer",
      "expiresAt": "2026-03-17T14:00:00.000Z"
    }
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
tokens.accessTokenstringNew JWT access token
tokens.refreshTokenstringNew refresh token (rotated on each refresh)
tokens.expiresInnumberAccess token lifetime in seconds
tokens.tokenTypestringToken scheme (always Bearer)
tokens.expiresAtstringISO 8601 timestamp when the new access token expires
Client Warning
  • Always persist the latest tokens.refreshToken from the response.
  • Never assume the previous refresh token remains valid after a successful refresh.
  • If refresh fails with Invalid refresh token, verify the client is not replaying an older token.

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "dGhpcyBpcyBhIHJlZnJl..."
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/refresh`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    refreshToken: "dGhpcyBpcyBhIHJlZnJl...",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/refresh",
    json={"refreshToken": "dGhpcyBpcyBhIHJlZnJl..."},
)
data = response.json()

POST /auth/confirm-email

Confirm a user's email address using the token sent in the confirmation email.

Authentication: None Rate limit: 5 req / 300s

Request

FieldTypeRequiredConstraintsDescription
tokenstringYes--Email confirmation token from the confirmation link

Response

Response Example
json
{
  "data": {
    "message": "Email confirmed successfully."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable confirmation message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/confirm-email \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJIUzI1NiIs..."
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/confirm-email`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    token: "eyJhbGciOiJIUzI1NiIs...",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/confirm-email",
    json={"token": "eyJhbGciOiJIUzI1NiIs..."},
)
data = response.json()

POST /auth/resend-confirmation

Resend the email confirmation link to a registered address.

Authentication: None (Captcha required) Rate limit: 5 req / 300s

Note: This endpoint requires captcha verification. Include a valid captcha token in the x-captcha-token header.

Request

FieldTypeRequiredConstraintsDescription
emailstringYesValid email formatEmail address to resend confirmation to

Response

Response Example
json
{
  "data": {
    "message": "Confirmation email sent."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable status message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/resend-confirmation \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: <captcha-token>" \
  -d '{
    "email": "[email protected]"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/resend-confirmation`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-captcha-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "[email protected]",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/resend-confirmation",
    headers={"x-captcha-token": "<captcha-token>"},
    json={"email": "[email protected]"},
)
data = response.json()

POST /auth/forgot-password

Send a password reset email to the specified address.

Authentication: None (Captcha required) Rate limit: 3 req / 300s

Note: This endpoint requires captcha verification. Include a valid captcha token in the x-captcha-token header.

Request

FieldTypeRequiredConstraintsDescription
emailstringYesValid email formatAccount email address
redirectTostringNoValid URL, max 300 charactersURL to redirect to after password reset

Response

Response Example
json
{
  "data": {
    "message": "Password reset email sent."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable status message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: <captcha-token>" \
  -d '{
    "email": "[email protected]",
    "redirectTo": "https://app.chainabit.com/reset-password"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/forgot-password`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-captcha-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "[email protected]",
    redirectTo: "https://app.chainabit.com/reset-password",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/forgot-password",
    headers={"x-captcha-token": "<captcha-token>"},
    json={
        "email": "[email protected]",
        "redirectTo": "https://app.chainabit.com/reset-password",
    },
)
data = response.json()

POST /auth/exchange-recovery-code

Exchange a recovery code from the password reset email for a temporary access token.

Authentication: None Rate limit: 15 req / 300s

Request

FieldTypeRequiredConstraintsDescription
codestringYes--Recovery code from the password reset email

Response

Response Example
json
{
  "data": {
    "accessToken": "eyJhbGci..."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
accessTokenstringTemporary token to be used in the /auth/reset-password request

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/exchange-recovery-code \
  -H "Content-Type: application/json" \
  -d '{
    "code": "RCVR-ABCD-1234-EFGH"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/exchange-recovery-code`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    code: "RCVR-ABCD-1234-EFGH",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/exchange-recovery-code",
    json={"code": "RCVR-ABCD-1234-EFGH"},
)
data = response.json()

POST /auth/reset-password

Set a new password using the temporary access token obtained from the recovery code exchange.

Authentication: None Rate limit: 5 req / 300s

Request

FieldTypeRequiredConstraintsDescription
accessTokenstringYes--Token obtained from the recovery code exchange
newPasswordstringYes8-128 charactersNew account password

Response

Response Example
json
{
  "data": {
    "message": "Password reset successfully."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable confirmation message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "accessToken": "eyJhbGci...",
    "newPassword": "newSecureP@ss456"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/reset-password`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accessToken: "eyJhbGci...",
    newPassword: "newSecureP@ss456",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/reset-password",
    json={
        "accessToken": "eyJhbGci...",
        "newPassword": "newSecureP@ss456",
    },
)
data = response.json()

GET /auth/check-username

Check whether a username is available for claiming.

Authentication: JWT required Rate limit: 20 req / 60s

Request

ParameterTypeRequiredConstraintsDescription
usernamestringYes3-24 characters, alphanumeric and underscores onlyUsername to check availability for

Response

Response Example
json
{
  "data": {
    "username": "alice_j",
    "available": true
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
usernamestringThe username that was checked
availablebooleanWhether the username is available for claiming

Code Examples

bash
curl "https://api.chainabit.com/api/v1/auth/check-username?username=alice_j" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(
  `${BASE_URL}/auth/check-username?username=alice_j`,
  {
    headers: {
      Authorization: `Bearer ${TOKEN}`,
    },
  }
);
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/auth/check-username",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={"username": "alice_j"},
)
data = response.json()

POST /auth/claim-username

Claim a username for the authenticated account.

Authentication: JWT required Rate limit: 5 req / 60s

Note: This endpoint uses application/x-www-form-urlencoded content type, not JSON. Send the username as a form field.

Request

FieldTypeRequiredConstraintsDescription
usernamestringYes3-24 characters, alphanumeric and underscores onlyUsername to claim

Response

Response Example
json
{
  "data": {
    "username": "alice_j",
    "claimedAt": "2026-03-17T12:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
usernamestringThe username that was claimed
claimedAtstringISO 8601 timestamp when the username was claimed

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/claim-username \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=alice_j"
javascript
const body = new URLSearchParams({ username: "alice_j" });

const response = await fetch(`${BASE_URL}/auth/claim-username`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: body.toString(),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/claim-username",
    headers={"Authorization": f"Bearer {TOKEN}"},
    data={"username": "alice_j"},
)
data = response.json()

POST /auth/change-email

Request an email address change. A confirmation link is sent to the new address.

Authentication: JWT required Rate limit: 10 req / 60s

Request

FieldTypeRequiredConstraintsDescription
emailstringYesValid email formatNew email address

Response

Response Example
json
{
  "data": {
    "message": "Confirmation email sent to new address."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable status message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/change-email \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/change-email`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "[email protected]",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/change-email",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"email": "[email protected]"},
)
data = response.json()

POST /auth/change-password

Change the authenticated user's password.

Authentication: JWT required Rate limit: 10 req / 60s

Request

FieldTypeRequiredConstraintsDescription
oldPasswordstringYes8-128 charactersCurrent account password
newPasswordstringYes8-128 charactersNew account password

Response

Response Example
json
{
  "data": {
    "message": "Password changed successfully."
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
messagestringHuman-readable confirmation message

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/change-password \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "oldPassword": "secureP@ss123",
    "newPassword": "newSecureP@ss456"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/change-password`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    oldPassword: "secureP@ss123",
    newPassword: "newSecureP@ss456",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/change-password",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "oldPassword": "secureP@ss123",
        "newPassword": "newSecureP@ss456",
    },
)
data = response.json()

PATCH /auth/profile

Update the authenticated user's profile information.

Authentication: JWT required Rate limit: 120 req / 60s

Request

FieldTypeRequiredConstraintsDescription
fullNamestringNoMax 50 charactersDisplay name
avatarUrlstringNoValid URLProfile avatar URL
biostringNoMax 160 charactersProfile biography
taglinestringNoMax 60 charactersShort tagline
visibilitystringNopublic or privateProfile visibility setting

Response

Response Example
json
{
  "data": {
    "username": "alice_j",
    "fullName": "Alice J.",
    "avatarUrl": "https://cdn.chainabit.com/avatars/alice.jpg",
    "bio": "Productivity enthusiast and AI builder.",
    "tagline": "Chainer since 2025",
    "visibility": "public"
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
usernamestringThe user's claimed username
fullNamestringUpdated display name
avatarUrlstringURL of the profile avatar
biostringUpdated profile biography
taglinestringUpdated short tagline
visibilitystringUpdated profile visibility (public or private)

Code Examples

bash
curl -X PATCH https://api.chainabit.com/api/v1/auth/profile \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Alice J.",
    "bio": "Productivity enthusiast and AI builder.",
    "visibility": "public"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/profile`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    fullName: "Alice J.",
    bio: "Productivity enthusiast and AI builder.",
    visibility: "public",
  }),
});
const data = await response.json();
python
import requests

response = requests.patch(
    f"{BASE_URL}/auth/profile",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "fullName": "Alice J.",
        "bio": "Productivity enthusiast and AI builder.",
        "visibility": "public",
    },
)
data = response.json()

GET /auth/session

Retrieve the full session context for the currently authenticated user.

Authentication: JWT required

Request

No path, query, or body parameters. Requires Authorization: Bearer $TOKEN.

Response

Response Example
json
{
  "data": {
    "user": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "email": "[email protected]"
    },
    "profile": {
      "username": "alice_j",
      "fullName": "Alice Johnson",
      "avatarUrl": "https://cdn.chainabit.com/avatars/alice.jpg",
      "bio": "Building the future of productivity.",
      "tagline": "Chainer since 2025",
      "visibility": "public",
      "isReady": true
    },
    "preferences": {
      "theme": "dark",
      "language": "en",
      "timezone": "Europe/Istanbul"
    },
    "subscription": {
      "planCode": "pro",
      "status": "active",
      "billingCycle": "monthly",
      "currentPeriodEnd": "2026-04-17T00:00:00.000Z"
    },
    "entitlements": {
      "features": ["ai_chat", "agents", "advanced_analytics"],
      "limits": {
        "workspaces": 10,
        "membersPerWorkspace": 25
      }
    },
    "defaultAccount": {
      "id": "acc-1234-5678",
      "name": "Alice's Team",
      "role": "owner"
    },
    "workspaces": [
      {
        "id": "ws-abcd-efgh",
        "name": "Product Development",
        "role": "owner"
      }
    ]
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
user.idstringUnique identifier for the user
user.emailstringEmail address of the account
profile.usernamestringClaimed username
profile.fullNamestringDisplay name
profile.avatarUrlstringURL of the profile avatar
profile.biostringProfile biography
profile.taglinestringShort tagline
profile.visibilitystringProfile visibility (public or private)
profile.isReadybooleanWhether the profile setup is complete
preferences.themestringUI theme preference
preferences.languagestringLanguage preference (BCP 47 tag)
preferences.timezonestringTimezone preference (IANA tz identifier)
subscription.planCodestringActive subscription plan identifier
subscription.statusstringSubscription status
subscription.billingCyclestringBilling cycle (monthly, yearly, lifetime)
subscription.currentPeriodEndstringISO 8601 timestamp when the current period ends
entitlements.features[]string[]List of feature flags enabled for this account
entitlements.limits.workspacesnumberMaximum number of workspaces allowed
entitlements.limits.membersPerWorkspacenumberMaximum members per workspace
defaultAccount.idstringUnique identifier for the default account
defaultAccount.namestringDisplay name of the default account
defaultAccount.rolestringThe user's role in the default account
workspaces[].idstringUnique identifier for a workspace
workspaces[].namestringDisplay name of the workspace
workspaces[].rolestringThe user's role in the workspace

Code Examples

bash
curl "https://api.chainabit.com/api/v1/auth/session" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/auth/session`, {
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/auth/session",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

POST /auth/magic-link

Send a magic link to the specified email address for passwordless sign-in.

Authentication: None (Captcha required) Rate limit: 3 req / 300s

Request

FieldTypeRequiredDescription
emailstringYesEmail address to send the magic link to
redirectTostringNoURL to redirect after verification

Response

Response Example
json
{
  "data": {
    "status": "ok"
  },
  "meta": null,
  "error": null
}

Note: This endpoint always returns { "status": "ok" } regardless of whether the email exists to prevent account enumeration.

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/magic-link \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: <captcha-token>" \
  -d '{
    "email": "[email protected]"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/magic-link`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-captcha-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "[email protected]",
  }),
});
const data = await response.json();

POST /auth/magic-link/verify

Verify a magic link token and create a new session.

Authentication: None Rate limit: 5 req / 300s

Request

FieldTypeRequiredDescription
tokenstringYesMagic link token from the email
emailstringYesEmail address used when sending

Response

Returns the same AuthSessionDto as POST /auth/login.

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/magic-link/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "a1b2c3d4e5f6...",
    "email": "[email protected]"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/magic-link/verify`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    token: "a1b2c3d4e5f6...",
    email: "[email protected]",
  }),
});
const data = await response.json();

GET /auth/oauth/providers

List available OAuth providers.

Authentication: None Rate limit: 30 req / 60s

Request

No path, query, or body parameters.

Response

Response Example
json
{
  "data": {
    "providers": ["google", "github", "microsoft"]
  },
  "meta": null,
  "error": null
}

Code Examples

bash
curl "https://api.chainabit.com/api/v1/auth/oauth/providers"
javascript
const response = await fetch(`${BASE_URL}/auth/oauth/providers`);
const data = await response.json();

GET /auth/oauth/:provider

Get the OAuth authorization URL for a provider. Redirect the user to this URL to start the OAuth flow.

Authentication: None Rate limit: 10 req / 60s

Request

Path Parameters
ParameterDescription
providerOAuth provider name (google, github, microsoft)
Query Parameters
ParameterTypeRequiredDescription
redirectTostringNoURL to redirect after authentication

Response

Response Example
json
{
  "data": {
    "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...&code_challenge=..."
  },
  "meta": null,
  "error": null
}

Code Examples

bash
curl "https://api.chainabit.com/api/v1/auth/oauth/google"
javascript
const response = await fetch(`${BASE_URL}/auth/oauth/google`);
const { data } = await response.json();
window.location.href = data.url;

GET /auth/oauth/:provider/callback

OAuth callback endpoint. Called automatically by the provider after user authorization. Redirects to the frontend with tokens in the query string.

Authentication: None Rate limit: 10 req / 60s

This endpoint is called by the OAuth provider, not directly by your application.

Request

ParamLocationDescription
providerPathThe OAuth provider being completed, e.g. google, github
codeQueryAuthorization code issued by the provider
stateQueryFlow token generated by GET /auth/oauth/:provider
error, error_descriptionQueryPresent instead of code/state if the user denied access

Provider-specific extra callback query parameters may be present and are ignored. The API only processes code, state, error, and error_description.

Response

After successful authorization, redirects (302) to: /auth/callback?access_token=...&refresh_token=...&expires_in=3600

Code Examples

Not applicable — this endpoint is invoked by the OAuth provider's redirect, not called directly by API clients.


POST /auth/oauth/:provider/link

Link an OAuth provider to an existing authenticated account.

Authentication: JWT required Rate limit: 5 req / 60s

Request

ParameterTypeDescription
redirectTostringOptional redirect URL after linking

Response

Response Example
json
{
  "data": { "url": "https://github.com/login/oauth/authorize?..." },
  "meta": null,
  "error": null
}

Code Example

bash
curl -X POST "$BASE_URL/auth/oauth/github/link" \
  -H "Authorization: Bearer $TOKEN"

Remove a linked OAuth provider from the account.

Authentication: JWT required Rate limit: 5 req / 60s

Safety: Cannot unlink the last authentication method. If the provider is the only identity and the account has no password set, this returns 400 Bad Request.

Request

No path, query, or body parameters. Requires Authorization: Bearer $TOKEN.

Response

Response Example
json
{
  "data": { "message": "Provider unlinked successfully" },
  "meta": null,
  "error": null
}

Code Example

bash
curl -X DELETE "$BASE_URL/auth/oauth/github/unlink" \
  -H "Authorization: Bearer $TOKEN"

GET /auth/oauth/linked

List all OAuth providers linked to the authenticated account.

Authentication: JWT required Rate limit: 20 req / 60s

Request

No path, query, or body parameters. Requires Authorization: Bearer $TOKEN.

Response

Response Example
json
{
  "data": [
    {
      "provider": "google",
      "providerId": "1234567890",
      "email": "[email protected]",
      "linkedAt": "2026-03-18T10:00:00.000Z"
    }
  ],
  "meta": null,
  "error": null
}

Code Example

bash
curl "$BASE_URL/auth/oauth/linked" \
  -H "Authorization: Bearer $TOKEN"

POST /auth/device/authorize

Generate device and user codes for CLI authentication.

Authentication: None Rate limit: 10 req / 300s

Request

RegisterDeviceDto fields are optional, but CLI clients should send descriptive metadata when available:

FieldTypeRequiredDescription
namestringNoDevice display name such as chainabit-cli
deviceTypestringNoClient type such as cli
appVersionstringNoClient version for audit and support diagnostics
settingsobjectNoFree-form client metadata

Response

Response Example
json
{
  "data": {
    "deviceCode": "a1b2c3d4e5f6...",
    "userCode": "ABCD-1234",
    "verificationUri": "https://app.chainabit.com/auth/device",
    "verificationUriComplete": "https://app.chainabit.com/auth/device?code=ABCD-1234",
    "expiresIn": 300,
    "interval": 5
  },
  "meta": null,
  "error": null
}

Open verificationUriComplete directly in the browser — it includes the code pre-filled so the user does not have to type it manually.

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/device/authorize
javascript
const response = await fetch(`${BASE_URL}/auth/device/authorize`, {
  method: "POST",
});
const data = await response.json();

POST /auth/device/approve

Approve a pending device code from an already authenticated browser session.

Authentication: JWT required Rate limit: 20 req / 300s

Request

FieldTypeRequiredConstraintsDescription
userCodestringYesXXXX-XXXXUser-facing device code shown by the CLI

Response

Response Example
json
{
  "data": {
    "approved": true,
    "userCode": "ABCD-EFGH"
  },
  "meta": null,
  "error": null
}

The authenticated browser session remains in control of approval. The CLI never receives the browser token directly; it keeps polling POST /auth/device/token until approval is complete.

Code Example

bash
curl -X POST https://api.chainabit.com/api/v1/auth/device/approve \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "userCode": "ABCD-EFGH"
  }'

POST /auth/device/token

Poll for device authorization result. Call this endpoint at the interval rate until the status changes.

Authentication: None Rate limit: 60 req / 60s

Request

FieldTypeRequiredDescription
deviceCodestringYesDevice code from the authorize response

Response

Response Examples

Pending:

json
{ "data": { "status": "authorization_pending" } }

Expired:

json
{ "data": { "status": "expired" } }

Approved:

json
{
  "data": {
    "status": "approved",
    "session": { /* AuthSessionDto */ }
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/device/token \
  -H "Content-Type: application/json" \
  -d '{ "deviceCode": "a1b2c3d4e5f6..." }'

POST /auth/developer-tokens

Create a time-bounded developer token for CLI or automation sign-in. The opaque token value is returned only once.

Authentication: JWT required Rate limit: 10 req / 300s

Request

FieldTypeRequiredConstraintsDescription
labelstringYesmax 80 charactersFriendly name such as GitHub Actions
expiresInDaysnumberNo1-365, default 30Lifetime of the developer token

Response

Response Example
json
{
  "data": {
    "id": "7efb71f0-0b76-40c1-b567-2e1b8df11c5c",
    "label": "GitHub Actions",
    "token": "cbt_live_r6q2...",
    "tokenPreview": "cbt_live_r6q...",
    "expiresAt": "2026-04-27T10:00:00.000Z",
    "createdAt": "2026-03-28T10:00:00.000Z"
  },
  "meta": null,
  "error": null
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/developer-tokens \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "label": "GitHub Actions", "expiresInDays": 30 }'

GET /auth/developer-tokens

List developer tokens owned by the current user. The raw token is never returned again; only a safe preview and metadata are exposed.

Authentication: JWT required Rate limit: 30 req / 60s

Request

No path, query, or body parameters. Requires Authorization: Bearer $TOKEN.

Response

json
{
  "data": [
    {
      "id": "7efb71f0-0b76-40c1-b567-2e1b8df11c5c",
      "label": "GitHub Actions",
      "tokenPreview": "cbt_live_r6q...",
      "expiresAt": "2026-04-27T10:00:00.000Z",
      "createdAt": "2026-03-28T10:00:00.000Z"
    }
  ],
  "meta": null,
  "error": null
}

Code Examples

bash
curl https://api.chainabit.com/api/v1/auth/developer-tokens \
  -H "Authorization: Bearer $TOKEN"

DELETE /auth/developer-tokens/:id

Revoke a developer token.

Authentication: JWT required Rate limit: 20 req / 300s

Request

Path params: id — from a POST /auth/developer-tokens or GET /auth/developer-tokens response's data.id / data[].id, exposed as $DEVELOPER_TOKEN_ID below.

Response

json
{
  "data": { "revoked": true },
  "meta": null,
  "error": null
}

Code Examples

bash
curl -X DELETE https://api.chainabit.com/api/v1/auth/developer-tokens/$DEVELOPER_TOKEN_ID \
  -H "Authorization: Bearer $TOKEN"

POST /auth/developer-tokens/exchange

Exchange a developer token for the normal AuthSessionDto response used by authenticated clients.

Authentication: None Rate limit: 20 req / 60s

Request

FieldTypeRequiredDescription
tokenstringYesOpaque developer token created earlier

Response

Response Example
json
{
  "data": {
    "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "[email protected]",
    "rememberMe": false,
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
      "expiresIn": 900,
      "tokenType": "bearer",
      "expiresAt": 1760000000
    },
    "subscription": {
      "planCode": "FREE",
      "planName": "Free",
      "variantId": "variant-free",
      "status": "free"
    },
    "entitlements": []
  },
  "meta": null,
  "error": null
}

Use this exchange endpoint for CLI and non-interactive automation instead of sending a password to POST /auth/login.

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/auth/developer-tokens/exchange \
  -H "Content-Type: application/json" \
  -d '{ "token": "cbt_live_r6q2..." }'

Notes

  • Captcha-protected endpoints require a valid captcha token in the request headers. The specific header name is provided during client SDK initialization.
  • claim-username uses application/x-www-form-urlencoded content type, not JSON. Send the username as a form field:
    bash
    curl -X POST https://api.chainabit.com/api/v1/auth/claim-username \
      -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "username=alice_j"
  • After registration, the user must confirm their email before logging in. The confirmation token is sent to the registered email address.
  • The refreshToken has a longer expiry than the accessToken. Use the /auth/refresh endpoint to obtain new tokens before the access token expires.
  • The rememberMe flag on login extends both access and refresh token expiry durations.
  • Password requirements: minimum 8 characters, maximum 128 characters.
  • Magic link tokens expire after 10 minutes and are single-use.
  • OAuth uses PKCE (S256) for all providers. The callback redirects to your frontend with tokens as query parameters.
  • Device flow codes expire after 300 seconds (configurable). Poll at the interval rate returned in the authorize response, and complete approval with POST /auth/device/approve from an authenticated browser session.
  • Developer tokens are opaque, server-side hashed, and time-bounded. Treat the raw token like a password: show it once, store it in a secret manager, and exchange it for a regular session when needed.

Built with purpose.