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-turnstile-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": "alice@example.com",
    "requiresEmailVerification": true
  }
}
Response Fields ​
FieldTypeDescription
userIdstringUnique identifier for the newly created user
emailstringEmail address of the registered account
requiresEmailVerificationbooleanWhether the account must confirm its email before logging in

Code Examples ​

bash
curl -X POST https://api.chainabit.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -H "x-turnstile-token: <captcha-token>" \
  -d '{
    "email": "alice@example.com",
    "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-turnstile-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "alice@example.com",
    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-turnstile-token": "<captcha-token>"},
    json={
        "email": "alice@example.com",
        "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-turnstile-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": "alice@example.com",
    "profileReady": false,
    "requiresEmailVerification": false,
    "provider": "email",
    "rememberMe": false,
    "tokens": {
      "accessToken": "YOUR_ACCESS_TOKEN",
      "refreshToken": "YOUR_REFRESH_TOKEN",
      "expiresIn": 900,
      "tokenType": "bearer",
      "expiresAt": 1788634555
    },
    "subscription": {
      "planCode": "FREE",
      "planName": "Free",
      "variantId": "00000000-0000-0000-0000-00000000f101",
      "status": "free",
      "provider": null
    },
    "entitlements": [
      { "featureKey": "ai.chat.basic", "limitValue": 1500, "isActive": true, "isPublic": true },
      { "featureKey": "productivity.chainy.create", "limitValue": 3, "isActive": true, "isPublic": true }
    ],
    "profile": {
      "username": null,
      "fullName": "Alice Johnson",
      "avatarUrl": null,
      "isReady": false
    },
    "defaultAccount": { "id": "...", "name": "Default Workspace", "slug": "default" },
    "workspaces": [{ "id": "...", "accountId": "...", "name": "Default Workspace", "slug": "default", "isDefault": true }]
  }
}

Treat userId, email, and tokens as the stable contract. Everything else is real account context the client uses to render itself, but it isn't versioned for external integrations — entitlements in particular is a full list of internal feature-flag keys (dozens of entries on a real account, trimmed above) that changes as the product's plan structure evolves.

Response Fields ​
FieldTypeDescription
userIdstringUnique identifier for the authenticated user
emailstringEmail address of the account
profileReadybooleanWhether the user has completed profile setup
requiresEmailVerificationbooleanWhether the account still needs to confirm its email
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 (900 by default)
tokens.tokenTypestringToken scheme (bearer)
tokens.expiresAtnumberUnix timestamp (seconds) when the access token expires
subscription.planCodestringActive subscription plan code
subscription.statusstringSubscription status
profile.usernamestring | nullUsername, if claimed
entitlements[].featureKeystringInternal feature/limit identifier
entitlements[].limitValuenumber | nullNumeric limit for this feature, or null if unlimited/not applicable
entitlements[].isActivebooleanWhether this entitlement is currently active on the account

Code Examples ​

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

response = requests.post(
    f"{BASE_URL}/auth/login",
    headers={"x-turnstile-token": "<captcha-token>"},
    json={
        "identifier": "alice@example.com",
        "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": { "status": "ok" }
}

The current session is invalidated server-side — reusing the same access token afterward returns 401 Unauthorized (Session not found), even though the JWT itself hasn't expired yet.

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": {
    "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "alice@example.com",
    "tokens": {
      "accessToken": "YOUR_ACCESS_TOKEN",
      "refreshToken": "YOUR_REFRESH_TOKEN",
      "expiresIn": 900,
      "tokenType": "bearer",
      "expiresAt": 1788634652
    }
  }
}

The response carries the same shape as POST /auth/login — tokens plus the same account context (subscription, entitlements, profile, workspaces, etc.), refreshed with any changes since the last token was issued. Only tokens (and userId/email) are the stable contract; see the login response above for the full field notes.

Response Fields ​
FieldTypeDescription
tokens.accessTokenstringNew JWT access token
tokens.refreshTokenstringNew refresh token (rotated on each refresh)
tokens.expiresInnumberAccess token lifetime in seconds (900 by default)
tokens.tokenTypestringToken scheme (bearer)
tokens.expiresAtnumberUnix timestamp (seconds) 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": "YOUR_REFRESH_TOKEN"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/refresh`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    refreshToken: "YOUR_REFRESH_TOKEN",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/refresh",
    json={"refreshToken": "YOUR_REFRESH_TOKEN"},
)
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
emailstringYesValid emailThe account email being confirmed
tokenstringYesMax 128 charsEither the 64-hex token from the confirmation link, or the shorter printed code

Response ​

Confirming successfully logs the account in — the response is the same AuthSessionDto as POST /auth/login (userId, email, tokens, plus account context), not a plain confirmation message.

Code Examples ​

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

response = requests.post(
    f"{BASE_URL}/auth/confirm-email",
    json={"email": "alice@example.com", "token": "YOUR_EMAIL_CONFIRMATION_TOKEN"},
)
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-turnstile-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-turnstile-token: <captcha-token>" \
  -d '{
    "email": "alice@example.com"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/resend-confirmation`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-turnstile-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "alice@example.com",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/resend-confirmation",
    headers={"x-turnstile-token": "<captcha-token>"},
    json={"email": "alice@example.com"},
)
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-turnstile-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-turnstile-token: <captcha-token>" \
  -d '{
    "email": "alice@example.com",
    "redirectTo": "https://chainabit.com/reset-password"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/forgot-password`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-turnstile-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "alice@example.com",
    redirectTo: "https://chainabit.com/reset-password",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/forgot-password",
    headers={"x-turnstile-token": "<captcha-token>"},
    json={
        "email": "alice@example.com",
        "redirectTo": "https://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": "YOUR_ACCESS_TOKEN"
  },
  "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": "YOUR_ACCESS_TOKEN",
    "newPassword": "newSecureP@ss456"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/reset-password`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accessToken: "YOUR_ACCESS_TOKEN",
    newPassword: "newSecureP@ss456",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/reset-password",
    json={
        "accessToken": "YOUR_ACCESS_TOKEN",
        "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 after recent re-authentication.

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

The first valid request without a recent step-up proof returns 403 STEP_UP_REQUIRED. Read error.details.challenge, complete one of its offered methods, then retry the same request. A password proof for this action must be bound to the new email address. The proof is single-use.

Request ​

FieldTypeRequiredConstraintsDescription
emailstringYesValid email formatNew email address

Response ​

Response Example ​
json
{
  "data": {
    "status": "pending_verification",
    "email": "alice.new@example.com"
  }
}
Response Fields ​
FieldTypeDescription
statusstringpending_verification until the new address is confirmed
emailstringNormalized new email address

Without a recent proof, the response is 403 with a challenge such as this one (the IDs vary by request):

json
{
  "error": {
    "code": "STEP_UP_REQUIRED",
    "message": "Changing email requires recent authentication",
    "details": {
      "code": "STEP_UP_REQUIRED",
      "message": "Changing email requires recent authentication",
      "challenge": {
        "challengeId": "<challenge-id>",
        "actionClass": "auth.email.change",
        "minimumAssurance": "any_mfa_or_password",
        "acceptableMethods": ["webauthn", "totp", "password"],
        "maxAgeSeconds": 300
      }
    }
  },
  "meta": { "requestId": "<request-id>" }
}

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": "alice.new@example.com"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/change-email`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "alice.new@example.com",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/auth/change-email",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"email": "alice.new@example.com"},
)
data = response.json()

POST /auth/change-password ​

Change the authenticated user's password after recent re-authentication.

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

The first valid request without a recent step-up proof returns 403 STEP_UP_REQUIRED with error.details.challenge. Complete one of the offered methods and retry the same request. A password proof for this action must be bound to the user's ID. The proof is single-use; a successful change revokes existing sessions.

Request ​

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

Response ​

Response Example ​
json
{
  "data": {
    "status": "ok"
  }
}
Response Fields ​
FieldTypeDescription
statusstringok after the password is changed

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": "alice@example.com"
    },
    "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-turnstile-token: <captcha-token>" \
  -d '{
    "email": "alice@example.com"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/magic-link`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-turnstile-token": "<captcha-token>",
  },
  body: JSON.stringify({
    email: "alice@example.com",
  }),
});
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": "YOUR_MAGIC_LINK_TOKEN",
    "email": "alice@example.com"
  }'
javascript
const response = await fetch(`${BASE_URL}/auth/magic-link/verify`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    token: "YOUR_MAGIC_LINK_TOKEN",
    email: "alice@example.com",
  }),
});
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": "alice@gmail.com",
      "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": "YOUR_DEVICE_CODE",
    "userCode": "ABCD-1234",
    "verificationUri": "https://chainabit.com/auth/device",
    "verificationUriComplete": "https://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": "YOUR_DEVICE_CODE" }'

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": "YOUR_DEVELOPER_TOKEN",
    "tokenPreview": "YOUR_TOKEN_PREVIEW",
    "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": "YOUR_TOKEN_PREVIEW",
      "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": "alice@example.com",
    "rememberMe": false,
    "tokens": {
      "accessToken": "YOUR_ACCESS_TOKEN",
      "refreshToken": "YOUR_REFRESH_TOKEN",
      "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": "YOUR_DEVELOPER_TOKEN" }'

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 $TOKEN" \
      -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.