Auth API
User registration, authentication, session management, and account security.
Base Path
/authAuthentication
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
export BASE_URL="https://api.chainabit.com/api/v1"
export TOKEN="your-access-token"
export REFRESH_TOKEN="your-refresh-token"Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| POST | /auth/register | Register a new account | Captcha | 10 req / 300s |
| POST | /auth/login | Web UI password login and session creation | Captcha | 8 req / 60s |
| POST | /auth/logout | Invalidate the current session | JWT | -- |
| POST | /auth/refresh | Refresh access and refresh tokens | None | -- |
| POST | /auth/confirm-email | Confirm email address with token | None | 5 req / 300s |
| POST | /auth/resend-confirmation | Resend email confirmation | Captcha | 5 req / 300s |
| POST | /auth/forgot-password | Request password reset email | Captcha | 3 req / 300s |
| POST | /auth/exchange-recovery-code | Exchange recovery code for reset token | None | 15 req / 300s |
| POST | /auth/reset-password | Set a new password using reset token | None | 5 req / 300s |
| GET | /auth/check-username | Check username availability | JWT | 20 req / 60s |
| POST | /auth/claim-username | Claim a username | JWT | 5 req / 60s |
| POST | /auth/change-email | Change account email | JWT | 10 req / 60s |
| POST | /auth/change-password | Change account password | JWT | 10 req / 60s |
| PATCH | /auth/profile | Update user profile | JWT | 120 req / 60s |
| GET | /auth/session | Get current session details | JWT | -- |
| POST | /auth/magic-link | Send magic link email | Captcha | 3 req / 300s |
| POST | /auth/magic-link/verify | Verify magic link token | None | 5 req / 300s |
| GET | /auth/oauth/providers | List OAuth providers | None | 30 req / 60s |
| GET | /auth/oauth/:provider | Get OAuth authorization URL | None | 10 req / 60s |
| GET | /auth/oauth/:provider/callback | Handle OAuth callback | None | 10 req / 60s |
| POST | /auth/oauth/:provider/link | Link OAuth provider | JWT | 5 req / 60s |
| DELETE | /auth/oauth/:provider/unlink | Unlink OAuth provider | JWT | 5 req / 60s |
| GET | /auth/oauth/linked | List linked providers | JWT | 20 req / 60s |
| GET | /oauth/authorize | Begin OAuth provider authorization flow | Browser | 20 req / 60s |
| POST | /oauth/token | Exchange an authorization code or refresh token | Client auth | 20 req / 60s |
| POST | /oauth/revoke | Revoke an OAuth access or refresh token | Client auth | 30 req / 60s |
| GET | /oauth/userinfo | Resolve the authenticated OAuth subject | OAuth bearer | 120 req / 60s |
| POST | /auth/device/authorize | Generate device codes | None | 10 req / 300s |
| POST | /auth/device/approve | Approve a CLI device code from an authenticated browser session | JWT | 20 req / 300s |
| POST | /auth/device/token | Poll device authorization | None | 60 req / 60s |
| POST | /auth/developer-tokens | Create a time-bounded developer token | JWT | 10 req / 300s |
| GET | /auth/developer-tokens | List developer tokens for the current user | JWT | 30 req / 60s |
| DELETE | /auth/developer-tokens/:id | Revoke a developer token | JWT | 20 req / 300s |
| POST | /auth/developer-tokens/exchange | Exchange a developer token for a session payload | None | 20 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-tokenheader.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | Account email address |
password | string | Yes | 8-128 characters | Account password |
fullName | string | No | Max 120 characters | User's full name |
referralAttributionToken | string | No | UUID | Attribution token captured from POST /api/v1/referrals/click |
referralInviteCode | string | No | Max 32 characters | Invite code forwarded during referral-aware signup |
Note:
referralAttributionTokenis optional. Use the value returned byPOST /api/v1/referrals/click, not the placeholder shown below.
Response
Response Example
{
"data": {
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"message": "Confirmation email sent. Please verify your email address."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
userId | string | Unique identifier for the newly created user |
email | string | Email address of the registered account |
message | string | Human-readable status message |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/register \
-H "Content-Type: application/json" \
-H "x-captcha-token: <captcha-token>" \
-d '{
"email": "alice@example.com",
"password": "secureP@ss123",
"fullName": "Alice Johnson",
"referralAttributionToken": "<referral-attribution-token>",
"referralInviteCode": "ABC123"
}'const response = await fetch(`${BASE_URL}/auth/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-captcha-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();import requests
response = requests.post(
f"{BASE_URL}/auth/register",
headers={"x-captcha-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-captcha-tokenheader.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
identifier | string | Yes | Max 256 characters | Email address or username |
password | string | Yes | Max 128 characters | Account password |
rememberMe | boolean | No | -- | Extend token expiry for longer sessions |
Response
Response Example
{
"data": {
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"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
| Field | Type | Description |
|---|---|---|
userId | string | Unique identifier for the authenticated user |
email | string | Email address of the account |
username | string | Username of the account, if claimed |
profileReady | boolean | Whether the user has completed profile setup |
rememberMe | boolean | Whether the extended session was requested |
tokens.accessToken | string | JWT access token for authenticating requests |
tokens.refreshToken | string | Token used to obtain a new access token |
tokens.expiresIn | number | Access token lifetime in seconds |
tokens.tokenType | string | Token scheme (always Bearer) |
tokens.expiresAt | string | ISO 8601 timestamp when the access token expires |
subscription.planCode | string | Active subscription plan identifier |
subscription.status | string | Subscription status (e.g. active, trialing) |
subscription.billingCycle | string | Billing cycle (monthly, yearly, lifetime) |
subscription.currentPeriodEnd | string | ISO 8601 timestamp when the current period ends |
entitlements.features[] | string[] | List of feature flags enabled for this account |
entitlements.limits.workspaces | number | Maximum number of workspaces allowed |
entitlements.limits.membersPerWorkspace | number | Maximum members per workspace |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-H "x-captcha-token: <captcha-token>" \
-d '{
"identifier": "alice@example.com",
"password": "secureP@ss123",
"rememberMe": true
}'const response = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-captcha-token": "<captcha-token>",
},
body: JSON.stringify({
identifier: "alice@example.com",
password: "secureP@ss123",
rememberMe: true,
}),
});
const data = await response.json();import requests
response = requests.post(
f"{BASE_URL}/auth/login",
headers={"x-captcha-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
{
"data": null,
"meta": null,
"error": null
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/logout \
-H "Authorization: Bearer $TOKEN"const response = await fetch(`${BASE_URL}/auth/logout`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
},
});
const data = await response.json();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.refreshTokenreturned by this endpoint. Reusing an older refresh token will return401 Unauthorized.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
refreshToken | string | Yes | -- | Refresh token from a previous login or refresh |
Response
Response Example
{
"data": {
"tokens": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "bmV3IHJlZnJlc2ggdG9r...",
"expiresIn": 3600,
"tokenType": "Bearer",
"expiresAt": "2026-03-17T14:00:00.000Z"
}
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
tokens.accessToken | string | New JWT access token |
tokens.refreshToken | string | New refresh token (rotated on each refresh) |
tokens.expiresIn | number | Access token lifetime in seconds |
tokens.tokenType | string | Token scheme (always Bearer) |
tokens.expiresAt | string | ISO 8601 timestamp when the new access token expires |
Client Warning
- Always persist the latest
tokens.refreshTokenfrom 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
curl -X POST https://api.chainabit.com/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "dGhpcyBpcyBhIHJlZnJl..."
}'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();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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
token | string | Yes | -- | Email confirmation token from the confirmation link |
Response
Response Example
{
"data": {
"message": "Email confirmed successfully."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable confirmation message |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/confirm-email \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbGciOiJIUzI1NiIs..."
}'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();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-tokenheader.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | Email address to resend confirmation to |
Response
Response Example
{
"data": {
"message": "Confirmation email sent."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable status message |
Code Examples
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": "alice@example.com"
}'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: "alice@example.com",
}),
});
const data = await response.json();import requests
response = requests.post(
f"{BASE_URL}/auth/resend-confirmation",
headers={"x-captcha-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-captcha-tokenheader.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | Account email address |
redirectTo | string | No | Valid URL, max 300 characters | URL to redirect to after password reset |
Response
Response Example
{
"data": {
"message": "Password reset email sent."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable status message |
Code Examples
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": "alice@example.com",
"redirectTo": "https://app.chainabit.com/reset-password"
}'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: "alice@example.com",
redirectTo: "https://app.chainabit.com/reset-password",
}),
});
const data = await response.json();import requests
response = requests.post(
f"{BASE_URL}/auth/forgot-password",
headers={"x-captcha-token": "<captcha-token>"},
json={
"email": "alice@example.com",
"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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
code | string | Yes | -- | Recovery code from the password reset email |
Response
Response Example
{
"data": {
"accessToken": "eyJhbGci..."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
accessToken | string | Temporary token to be used in the /auth/reset-password request |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/exchange-recovery-code \
-H "Content-Type: application/json" \
-d '{
"code": "RCVR-ABCD-1234-EFGH"
}'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();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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
accessToken | string | Yes | -- | Token obtained from the recovery code exchange |
newPassword | string | Yes | 8-128 characters | New account password |
Response
Response Example
{
"data": {
"message": "Password reset successfully."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable confirmation message |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"accessToken": "eyJhbGci...",
"newPassword": "newSecureP@ss456"
}'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();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
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
username | string | Yes | 3-24 characters, alphanumeric and underscores only | Username to check availability for |
Response
Response Example
{
"data": {
"username": "alice_j",
"available": true
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
username | string | The username that was checked |
available | boolean | Whether the username is available for claiming |
Code Examples
curl "https://api.chainabit.com/api/v1/auth/check-username?username=alice_j" \
-H "Authorization: Bearer $TOKEN"const response = await fetch(
`${BASE_URL}/auth/check-username?username=alice_j`,
{
headers: {
Authorization: `Bearer ${TOKEN}`,
},
}
);
const data = await response.json();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-urlencodedcontent type, not JSON. Send the username as a form field.
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
username | string | Yes | 3-24 characters, alphanumeric and underscores only | Username to claim |
Response
Response Example
{
"data": {
"username": "alice_j",
"claimedAt": "2026-03-17T12:00:00.000Z"
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
username | string | The username that was claimed |
claimedAt | string | ISO 8601 timestamp when the username was claimed |
Code Examples
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"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();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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | New email address |
Response
Response Example
{
"data": {
"message": "Confirmation email sent to new address."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable status message |
Code Examples
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"
}'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();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.
Authentication: JWT required Rate limit: 10 req / 60s
Request
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
oldPassword | string | Yes | 8-128 characters | Current account password |
newPassword | string | Yes | 8-128 characters | New account password |
Response
Response Example
{
"data": {
"message": "Password changed successfully."
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable confirmation message |
Code Examples
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"
}'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();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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
fullName | string | No | Max 50 characters | Display name |
avatarUrl | string | No | Valid URL | Profile avatar URL |
bio | string | No | Max 160 characters | Profile biography |
tagline | string | No | Max 60 characters | Short tagline |
visibility | string | No | public or private | Profile visibility setting |
Response
Response Example
{
"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
| Field | Type | Description |
|---|---|---|
username | string | The user's claimed username |
fullName | string | Updated display name |
avatarUrl | string | URL of the profile avatar |
bio | string | Updated profile biography |
tagline | string | Updated short tagline |
visibility | string | Updated profile visibility (public or private) |
Code Examples
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"
}'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();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
{
"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
| Field | Type | Description |
|---|---|---|
user.id | string | Unique identifier for the user |
user.email | string | Email address of the account |
profile.username | string | Claimed username |
profile.fullName | string | Display name |
profile.avatarUrl | string | URL of the profile avatar |
profile.bio | string | Profile biography |
profile.tagline | string | Short tagline |
profile.visibility | string | Profile visibility (public or private) |
profile.isReady | boolean | Whether the profile setup is complete |
preferences.theme | string | UI theme preference |
preferences.language | string | Language preference (BCP 47 tag) |
preferences.timezone | string | Timezone preference (IANA tz identifier) |
subscription.planCode | string | Active subscription plan identifier |
subscription.status | string | Subscription status |
subscription.billingCycle | string | Billing cycle (monthly, yearly, lifetime) |
subscription.currentPeriodEnd | string | ISO 8601 timestamp when the current period ends |
entitlements.features[] | string[] | List of feature flags enabled for this account |
entitlements.limits.workspaces | number | Maximum number of workspaces allowed |
entitlements.limits.membersPerWorkspace | number | Maximum members per workspace |
defaultAccount.id | string | Unique identifier for the default account |
defaultAccount.name | string | Display name of the default account |
defaultAccount.role | string | The user's role in the default account |
workspaces[].id | string | Unique identifier for a workspace |
workspaces[].name | string | Display name of the workspace |
workspaces[].role | string | The user's role in the workspace |
Code Examples
curl "https://api.chainabit.com/api/v1/auth/session" \
-H "Authorization: Bearer $TOKEN"const response = await fetch(`${BASE_URL}/auth/session`, {
headers: {
Authorization: `Bearer ${TOKEN}`,
},
});
const data = await response.json();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
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to send the magic link to |
redirectTo | string | No | URL to redirect after verification |
Response
Response Example
{
"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
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": "alice@example.com"
}'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: "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
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Magic link token from the email |
email | string | Yes | Email address used when sending |
Response
Returns the same AuthSessionDto as POST /auth/login.
Code Examples
curl -X POST https://api.chainabit.com/api/v1/auth/magic-link/verify \
-H "Content-Type: application/json" \
-d '{
"token": "a1b2c3d4e5f6...",
"email": "alice@example.com"
}'const response = await fetch(`${BASE_URL}/auth/magic-link/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: "a1b2c3d4e5f6...",
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
{
"data": {
"providers": ["google", "github", "microsoft"]
},
"meta": null,
"error": null
}Code Examples
curl "https://api.chainabit.com/api/v1/auth/oauth/providers"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
| Parameter | Description |
|---|---|
provider | OAuth provider name (google, github, microsoft) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
redirectTo | string | No | URL to redirect after authentication |
Response
Response Example
{
"data": {
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...&code_challenge=..."
},
"meta": null,
"error": null
}Code Examples
curl "https://api.chainabit.com/api/v1/auth/oauth/google"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
| Param | Location | Description |
|---|---|---|
provider | Path | The OAuth provider being completed, e.g. google, github |
code | Query | Authorization code issued by the provider |
state | Query | Flow token generated by GET /auth/oauth/:provider |
error, error_description | Query | Present 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
| Parameter | Type | Description |
|---|---|---|
redirectTo | string | Optional redirect URL after linking |
Response
Response Example
{
"data": { "url": "https://github.com/login/oauth/authorize?..." },
"meta": null,
"error": null
}Code Example
curl -X POST "$BASE_URL/auth/oauth/github/link" \
-H "Authorization: Bearer $TOKEN"DELETE /auth/oauth/:provider/unlink
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
{
"data": { "message": "Provider unlinked successfully" },
"meta": null,
"error": null
}Code Example
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
{
"data": [
{
"provider": "google",
"providerId": "1234567890",
"email": "alice@gmail.com",
"linkedAt": "2026-03-18T10:00:00.000Z"
}
],
"meta": null,
"error": null
}Code Example
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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Device display name such as chainabit-cli |
deviceType | string | No | Client type such as cli |
appVersion | string | No | Client version for audit and support diagnostics |
settings | object | No | Free-form client metadata |
Response
Response Example
{
"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
curl -X POST https://api.chainabit.com/api/v1/auth/device/authorizeconst 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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
userCode | string | Yes | XXXX-XXXX | User-facing device code shown by the CLI |
Response
Response Example
{
"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
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
| Field | Type | Required | Description |
|---|---|---|---|
deviceCode | string | Yes | Device code from the authorize response |
Response
Response Examples
Pending:
{ "data": { "status": "authorization_pending" } }Expired:
{ "data": { "status": "expired" } }Approved:
{
"data": {
"status": "approved",
"session": { /* AuthSessionDto */ }
}
}Code Examples
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
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
label | string | Yes | max 80 characters | Friendly name such as GitHub Actions |
expiresInDays | number | No | 1-365, default 30 | Lifetime of the developer token |
Response
Response Example
{
"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
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
{
"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
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
{
"data": { "revoked": true },
"meta": null,
"error": null
}Code Examples
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
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Opaque developer token created earlier |
Response
Response Example
{
"data": {
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"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
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-urlencodedcontent type, not JSON. Send the username as a form field:bashcurl -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
refreshTokenhas a longer expiry than theaccessToken. Use the/auth/refreshendpoint to obtain new tokens before the access token expires. - The
rememberMeflag 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
intervalrate returned in the authorize response, and complete approval withPOST /auth/device/approvefrom 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.