Authentication & keys
The /api/auth, /api/profile, and /api/preferences endpoints for signup, login, OAuth, sessions, handoff, instructions, and API key lifecycle.
This page is the contract reference for the authentication surface: account
lifecycle, sessions, OAuth, MFA, profile, preferences, and API key management.
Every endpoint is mounted under /api/auth, /api/profile, or
/api/preferences on the base URL https://api.cognitivx.io. For a conceptual
overview of the two auth mechanisms, see
Authentication.
Endpoints
| Group | Endpoint | |
|---|---|---|
| Account | Signup · Signin · Refresh · Signout | POST /api/auth/{signup,signin,refresh,signout} |
| Password | Forgot · Reset · Change | POST /api/auth/{forgot-password,reset-password,change-password} |
| Identity | Current user · Instructions | GET /api/auth/me, GET·PUT /api/auth/instructions |
| Profile | Get · Update · Avatar | GET·PUT /api/profile, POST·DELETE /api/profile/avatar |
| Preferences | List · Update | GET /api/preferences, PUT /api/preferences/{key} |
| API keys | Create · List · Revoke | POST·GET /api/auth/api-keys, DELETE /api/auth/api-keys/{id} |
| OAuth | Authorize · Callback · Connections · Disconnect | /api/auth/oauth/* |
| Verify · Resend · Change | POST /api/auth/{verify-email,resend-verification,change-verification-email} | |
| MFA | Setup · Enable · Verify · Disable, codes, status | /api/auth/mfa/* |
| Sessions | List · Revoke one · Revoke all | GET·DELETE /api/auth/sessions |
| Handoff | Mint · Exchange | POST /api/auth/handoff/{mint,exchange} |
| MCP login | Start · Poll · Authorize | /api/auth/mcp/* |
| Account | Delete | DELETE /api/auth/delete-account |
Auth model
Endpoints fall into three groups:
- Public (no auth):
signup,signin,refresh,signout,forgot-password,reset-password,verify-email, the OAuth and MFA-verify steps, andhandoff/exchange. These either establish a session or run before one exists. - Bearer-authenticated: pass
Authorization: Bearer <token>where the token is either an API key (icog_…, created at developers.cognitivx.io/keys) or a short-lived JWT access token.X-API-Key: <key>is accepted as an alternative. - Cookie-authenticated: browser apps on
*.cognitivx.iosend the httpOnlyrefresh_tokencookie withcredentials: 'include'. The session and handoff endpoints rely on this cookie.
Access tokens expire after 60 minutes. The refresh_token cookie is valid for
one year and is scoped to the /api/auth path. Call POST /api/auth/refresh to
mint a fresh access token without re-entering credentials.
Error responses are always { "detail": "<message>" } with a 4xx/5xx status.
Signup
Create a new email/password account. Returns an access token plus the new user,
and sets the refresh_token cookie.
POST /api/auth/signup · No auth · Rate limited.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Valid email, 5–255 chars. Disposable domains are rejected. |
password | string | yes | 8–128 chars. |
confirm_password | string | yes | Must equal password. |
curl -X POST https://api.cognitivx.io/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"hunter2hunter2","confirm_password":"hunter2hunter2"}'{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12",
"email": "[email protected]",
"created_at": "2026-06-16T12:00:00Z",
"email_verified": false,
"display_name": null,
"avatar_url": null
}
}Returns 201 Created on success.
Errors: 400 if passwords differ, the email is malformed, or the domain is
disposable. 409 if the email is already registered.
Signin
Authenticate with email and password. On success, returns the same
TokenResponse shape as signup and sets the refresh_token cookie. If the
account has MFA enabled, returns a partial token instead (see
MFA verify).
POST /api/auth/signin · No auth · Rate limited.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Account email. |
password | string | yes | Account password. |
curl -X POST https://api.cognitivx.io/api/auth/signin \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"hunter2hunter2"}'import { configureApiClient, auth } from '@cognitivx/sdk';
configureApiClient({ baseUrl: 'https://api.cognitivx.io' });
const result = await auth.signin({ email: '[email protected]', password: 'hunter2hunter2' });Standard (no MFA) response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12",
"email": "[email protected]",
"created_at": "2026-06-16T12:00:00Z",
"email_verified": true,
"display_name": "Ada",
"avatar_url": null
}
}MFA-pending response (status 200):
{ "mfa_required": true, "partial_token": "eyJ0eXAiOiJKV1Qi..." }Errors: 401 on unknown email or wrong password. 400 if the account was
created via social login and has no password. 429 after 10 failed attempts
(account locks for 15 minutes; the message states the remaining minutes).
Refresh
Mint a new 60-minute access token from the refresh_token cookie. Send the
cookie (browser does this automatically with credentials: 'include').
POST /api/auth/refresh · Cookie auth.
curl -X POST https://api.cognitivx.io/api/auth/refresh \
--cookie "refresh_token=<token>"{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer" }Errors: 401 if the cookie is missing, expired, the wrong token type, or
the user no longer exists.
Signout
Revoke the current session and clear the refresh_token cookie.
POST /api/auth/signout · Cookie auth (optional).
curl -X POST https://api.cognitivx.io/api/auth/signout \
--cookie "refresh_token=<token>"{ "message": "Signed out" }To sign out every device at once, use DELETE /api/auth/sessions.
Password reset
A two-step, email-driven flow. forgot-password always returns 200 to avoid
leaking which emails exist; the reset link is only mailed if the account is real.
Request a reset
POST /api/auth/forgot-password · No auth · Rate limited.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Account email. |
curl -X POST https://api.cognitivx.io/api/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'{ "message": "If that email exists, a reset link has been sent." }Errors: 400 if email is empty.
Complete a reset
POST /api/auth/reset-password · No auth · Rate limited.
| Field | Type | Required | Description |
|---|---|---|---|
token | string | yes | Token from the reset email link. |
password | string | yes | New password, minimum 8 chars. |
curl -X POST https://api.cognitivx.io/api/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token":"<reset-token>","password":"newpassword123"}'{ "message": "Password reset successfully" }Errors: 400 if token or password is missing, the password is under 8
chars, or the token is invalid/expired.
Change password
For an already-authenticated user. Verifies the current password before updating.
POST /api/auth/change-password · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
current_password | string | yes | The existing password. |
new_password | string | yes | New password, minimum 8 chars. |
curl -X POST https://api.cognitivx.io/api/auth/change-password \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"current_password":"hunter2hunter2","new_password":"evenbetterpw99"}'{ "message": "Password changed" }Errors: 400 if either field is missing, the new password is under 8 chars,
or the current password is incorrect. 401 if the bearer token is invalid.
Current user
Return the signed-in user with onboarding and admin flags.
GET /api/auth/me · Bearer auth.
curl https://api.cognitivx.io/api/auth/me \
-H "Authorization: Bearer icog_xxx"{
"id": "8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12",
"email": "[email protected]",
"onboarding_completed": true,
"email_verified": true,
"display_name": "Ada",
"avatar_url": "/static/avatars/8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12.webp",
"is_admin": false
}Errors: 404 if the user record is gone. 401 if the token is invalid.
Custom instructions
Per-user free-text instructions that steer iCog's responses.
Get instructions
GET /api/auth/instructions · Bearer auth.
curl https://api.cognitivx.io/api/auth/instructions \
-H "Authorization: Bearer icog_xxx"{ "instructions": "Always answer in metric units. Keep replies under 3 sentences." }Returns { "instructions": "" } when none are set.
Update instructions
PUT /api/auth/instructions · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
instructions | string | yes | Instruction text. Trimmed; must be under 2000 characters. |
curl -X PUT https://api.cognitivx.io/api/auth/instructions \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"instructions":"Always answer in metric units."}'{ "message": "Instructions saved" }Errors: 400 if the text exceeds 2000 characters.
Profile
Display name, avatar, email, and username live under /api/profile. (Custom
instructions are a separate endpoint, above.)
Get profile
GET /api/profile · Bearer auth.
curl https://api.cognitivx.io/api/profile \
-H "Authorization: Bearer icog_xxx"{
"display_name": "Ada",
"avatar_url": "/static/avatars/8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12.webp",
"email": "[email protected]",
"username": "ada"
}Errors: 404 if the user is not found.
Update profile
Updates the display name only.
PUT /api/profile · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | yes | 1–50 chars after trimming whitespace. |
curl -X PUT https://api.cognitivx.io/api/profile \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"display_name":"Ada Lovelace"}'{
"display_name": "Ada Lovelace",
"avatar_url": "/static/avatars/8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12.webp",
"email": "[email protected]"
}Errors: 422 if display_name is empty or over 50 chars.
Upload avatar
Accepts a multipart image (JPEG, PNG, or WebP, up to 5MB). The server center-crops to a square and stores a 200x200 WebP.
POST /api/profile/avatar · Bearer auth · multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
file | file | yes | Image file (image/jpeg, image/png, or image/webp). |
curl -X POST https://api.cognitivx.io/api/profile/avatar \
-H "Authorization: Bearer icog_xxx" \
-F "[email protected]"{ "avatar_url": "/static/avatars/8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12.webp" }Errors: 400 if the content type is not allowed, the file is over 5MB, or
the image cannot be decoded.
Delete avatar
DELETE /api/profile/avatar · Bearer auth.
curl -X DELETE https://api.cognitivx.io/api/profile/avatar \
-H "Authorization: Bearer icog_xxx"{ "message": "Avatar removed" }Preferences
A small per-user key/value store. GET returns the full map with defaults
filled in; PUT upserts one key, validated against the server-side schema.
List preferences
GET /api/preferences · Bearer auth.
curl https://api.cognitivx.io/api/preferences \
-H "Authorization: Bearer icog_xxx"{ "auto_fetch_urls": "ask", "auto_learn_urls": "off" }Update a preference
PUT /api/preferences/{key} · Bearer auth.
| Path param | Type | Required | Description |
|---|---|---|---|
key | string | yes | One of the known preference keys (below). |
| Body field | Type | Required | Description |
|---|---|---|---|
value | any | yes | New value; validated against the key's allowed set. |
Known keys:
| Key | Allowed values | Default |
|---|---|---|
auto_fetch_urls | always, ask, never | ask |
auto_learn_urls | on, off | off |
curl -X PUT https://api.cognitivx.io/api/preferences/auto_fetch_urls \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"value":"always"}'{ "key": "auto_fetch_urls", "value": "always" }Errors: 404 if key is not a known preference. 422 if value fails
validation for that key.
API keys
Personal API keys for programmatic and MCP access. The full key is shown once on creation; afterward only its prefix is returned. Keys are also creatable from the developer console.
Create a key
POST /api/auth/api-keys · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human label for the key. |
curl -X POST https://api.cognitivx.io/api/auth/api-keys \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"name":"CI pipeline"}'import { configureApiClient, keys } from '@cognitivx/sdk';
configureApiClient({ baseUrl: 'https://api.cognitivx.io', apiKey: 'icog_xxx' });
const created = await keys.create({ name: 'CI pipeline' });
console.log(created.key); // shown only once{
"id": "1b9d6e2c-3a4f-4d8e-9c1a-7e5b2f0d8a31",
"name": "CI pipeline",
"key_prefix": "icog_aB1",
"created_at": "2026-06-16T12:00:00Z",
"last_used_at": null,
"key": "icog_aB1cD2eF3gH4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3"
}Store key immediately. It is never returned again. List and create-from-list
responses only include key_prefix.
List keys
GET /api/auth/api-keys · Bearer auth.
curl https://api.cognitivx.io/api/auth/api-keys \
-H "Authorization: Bearer icog_xxx"[
{
"id": "1b9d6e2c-3a4f-4d8e-9c1a-7e5b2f0d8a31",
"name": "CI pipeline",
"key_prefix": "icog_aB1",
"created_at": "2026-06-16T12:00:00Z",
"last_used_at": "2026-06-16T13:22:01Z"
}
]Revoke a key
DELETE /api/auth/api-keys/{key_id} · Bearer auth.
| Path param | Type | Required | Description |
|---|---|---|---|
key_id | string | yes | The key's id (not its prefix). |
curl -X DELETE https://api.cognitivx.io/api/auth/api-keys/1b9d6e2c-3a4f-4d8e-9c1a-7e5b2f0d8a31 \
-H "Authorization: Bearer icog_xxx"Returns 204 No Content. Revocation is immediate.
Errors: 404 if the key does not exist. 403 if the key belongs to another
user.
OAuth (Google, GitHub, X)
A two-step provider flow. The frontend first asks the API for the provider
consent URL, redirects the user there, and the provider calls back to the API,
which finishes by redirecting to your frontend with an access token in the query
string. X (Twitter) uses PKCE (S256); the verifier is carried server-side in the
signed state parameter, so clients never handle it.
Start authorize
GET /api/auth/oauth/{provider}/authorize · No auth.
| Path param | Type | Required | Description |
|---|---|---|---|
provider | string | yes | google, github, or twitter. |
| Query param | Type | Required | Description |
|---|---|---|---|
frontend_origin | string | no | Allow-listed origin to return to after callback. |
curl "https://api.cognitivx.io/api/auth/oauth/google/authorize?frontend_origin=https://icog.app"{
"authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...",
"redirect_uri": "https://api.cognitivx.io/api/auth/oauth/google/callback"
}Redirect the user's browser to authorize_url.
Errors: 400 if the provider is unknown or not configured.
Callback
GET /api/auth/oauth/{provider}/callback · No auth (called by the provider).
| Query param | Type | Required | Description |
|---|---|---|---|
code | string | yes | Authorization code from the provider. |
state | string | yes | The signed state issued by authorize. |
This endpoint always responds with a 302 redirect:
- On success, to
<frontend>/auth/callback?token=<access_token>&new=<0|1>, and sets therefresh_tokencookie. - On failure, to
<frontend>/auth?error=<reason>wherereasonis one ofinvalid_state,unknown_provider,oauth_failed,no_email, oremail_taken.
You normally do not call this directly; the provider does.
List linked connections
GET /api/auth/oauth/connections · Bearer auth.
curl https://api.cognitivx.io/api/auth/oauth/connections \
-H "Authorization: Bearer icog_xxx"[
{ "provider": "google", "display_name": "Ada Lovelace", "avatar_url": "https://lh3.googleusercontent.com/..." }
]Disconnect
DELETE /api/auth/oauth/disconnect · Bearer auth.
curl -X DELETE https://api.cognitivx.io/api/auth/oauth/disconnect \
-H "Authorization: Bearer icog_xxx"{ "message": "OAuth provider disconnected" }Errors: 400 if no provider is linked, or if disconnecting would leave the
account with no way to sign in (set a password first).
Email verification
Verify email
POST /api/auth/verify-email · No auth.
| Field | Type | Required | Description |
|---|---|---|---|
token | string | yes | Token from the verification email link. |
curl -X POST https://api.cognitivx.io/api/auth/verify-email \
-H "Content-Type: application/json" \
-d '{"token":"<verification-token>"}'{ "message": "Email verified" }Errors: 400 if the token is missing, invalid, or expired.
Resend verification
POST /api/auth/resend-verification · Bearer auth · Rate limited (60s).
curl -X POST https://api.cognitivx.io/api/auth/resend-verification \
-H "Authorization: Bearer icog_xxx"{ "message": "Verification email sent" }Errors: 400 if the email is already verified. 404 if the user is gone.
429 if a verification email was sent in the last 60 seconds.
Change email
Updates the address and sends a verification link to the new one.
POST /api/auth/change-verification-email · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | New email address. |
curl -X POST https://api.cognitivx.io/api/auth/change-verification-email \
-H "Authorization: Bearer icog_xxx" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'{ "message": "Verification email sent to new address" }Errors: 400 if the email is missing or malformed. 409 if the new email
is already in use by another account.
MFA
TOTP-based two-factor. Setup generates a secret; enable verifies a code and returns one-time backup codes; verify exchanges the partial token from signin for full tokens.
Setup
POST /api/auth/mfa/setup · Bearer auth. Generates a secret and QR code. Does
not enable MFA.
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_data_uri": "data:image/png;base64,iVBORw0KGgo...",
"uri": "otpauth://totp/CognitiveX:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=CognitiveX"
}Enable
POST /api/auth/mfa/enable · Bearer auth.
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | yes | The secret returned by setup. |
code | string | yes | A current 6-digit TOTP code. |
{ "message": "MFA enabled", "backup_codes": ["A1B2C3D4-E5F6G7H8", "..."] }Errors: 400 if secret/code are missing or the code is invalid.
Verify
POST /api/auth/mfa/verify · No auth (uses the partial token from signin).
Issues full tokens and sets the refresh_token cookie.
| Field | Type | Required | Description |
|---|---|---|---|
partial_token | string | yes | The partial_token from the MFA-pending signin response. |
code | string | yes | A TOTP code, or a backup code if is_backup is true. |
is_backup | boolean | no | Set true to redeem a one-time backup code. |
Returns the same TokenResponse as signin.
Errors: 400 if fields are missing, the code is invalid, or MFA is not
enabled. 401 if the partial token is invalid or expired (5-minute lifetime).
Disable, backup codes, status
POST /api/auth/mfa/disable— requirespassword(andcodewhen MFA is active). Returns{ "message": "MFA disabled" }. Errors:400on wrong password or invalid code.POST /api/auth/mfa/backup-codes— requires a validcode; returns a fresh{ "backup_codes": [...] }. Errors:400if MFA is off or the code is wrong.GET /api/auth/mfa/status— returns{ "mfa_enabled": bool, "backup_codes_count": int }.
Sessions
Each signin or MFA verify creates a tracked session keyed by the refresh-token hash. These endpoints let a user audit and revoke them.
List sessions
GET /api/auth/sessions · Bearer auth.
curl https://api.cognitivx.io/api/auth/sessions \
-H "Authorization: Bearer icog_xxx"[
{
"id": "c4a1...",
"device_name": "Desktop · macOS",
"browser": "Chrome",
"os": "macOS",
"ip_address": "203.0.113.10",
"created_at": "2026-06-16T12:00:00Z",
"last_seen_at": "2026-06-16T13:00:00Z",
"is_current": true
}
]is_current reflects whether the row matches the caller's refresh_token
cookie.
Revoke one session
DELETE /api/auth/sessions/{session_id} · Bearer auth. Returns 204.
Errors: 404 if the session does not exist or belongs to another user.
Revoke all sessions
DELETE /api/auth/sessions · Bearer auth. Signs out every device and clears the
caller's refresh_token cookie. Returns 204.
Cross-origin handoff
*.cognitivx.io cookies cannot reach icog.app (different registrable domain).
To carry a session across that boundary, mint a one-shot 60-second token on the
signed-in origin, then exchange it on the target origin, which sets a cookie
there. Tokens are single-use and bound to a specific target via the JWT aud
claim.
Mint
POST /api/auth/handoff/mint · Cookie auth (active session required).
| Field | Type | Required | Description |
|---|---|---|---|
target_origin | string | yes | Allow-listed destination origin (e.g. https://icog.app). |
curl -X POST https://api.cognitivx.io/api/auth/handoff/mint \
--cookie "refresh_token=<token>" \
-H "Content-Type: application/json" \
-d '{"target_origin":"https://icog.app"}'{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }Errors: 400 if target_origin is not allow-listed. 401 if no active
session.
Exchange
POST /api/auth/handoff/exchange · No auth. Called from the target app; the
request's Origin header must equal the token's aud. Sets the
refresh_token cookie on the target origin and returns a fresh access token.
| Field | Type | Required | Description |
|---|---|---|---|
token | string | yes | The one-shot token from mint. |
curl -X POST https://api.cognitivx.io/api/auth/handoff/exchange \
-H "Origin: https://icog.app" \
-H "Content-Type: application/json" \
-d '{"token":"<handoff-token>"}'{
"user_id": "8f3c2e1a-9b7d-4c6e-8a2f-1d4b6c8e0a12",
"email": "[email protected]",
"access_token": "eyJhbGciOiJIUzI1NiIs..."
}Errors: 400 if the token is missing, expired, already used, not a handoff
token, the target is not allow-listed, the Origin does not match aud, or the
user no longer exists.
MCP device login
A device-grant-style flow for CLI/MCP clients that cannot open a browser
callback. The client starts a flow, shows the user a verification URL, and polls
until the user approves it in a browser. On approval the client receives a fresh
icog_… API key.
Start
POST /api/auth/mcp/device · No auth.
{
"device_code": "Xy7...",
"verification_url": "https://icog.app/auth/mcp?code=Xy7...",
"expires_in": 300,
"interval": 3
}Poll
GET /api/auth/mcp/poll?code=<device_code> · No auth. Poll every interval
seconds.
| Query param | Type | Required | Description |
|---|---|---|---|
code | string | yes | The device_code from start. |
Possible responses:
{ "status": "pending" }{ "status": "expired" }{ "status": "authorized", "api_key": "icog_...", "email": "[email protected]" }The authorized response is returned once, then the code is consumed.
Authorize (browser step)
POST /api/auth/mcp/authorize?code=<device_code> · Bearer auth. The signed-in
user calls this from the verification page to approve the device. It mints an
API key named "Claude Code MCP (device login)" and marks the code authorized.
{ "status": "ok", "email": "[email protected]" }Errors: 404 if the code is unknown. 410 if the code has expired (the
5-minute window elapsed; restart the flow in the terminal).
Account deletion
DELETE /api/auth/delete-account · Bearer auth. Permanently deletes the user and
all associated data. Irreversible.
{ "message": "Account deleted" }