CognitiveX Docs

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

GroupEndpoint
AccountSignup · Signin · Refresh · SignoutPOST /api/auth/{signup,signin,refresh,signout}
PasswordForgot · Reset · ChangePOST /api/auth/{forgot-password,reset-password,change-password}
IdentityCurrent user · InstructionsGET /api/auth/me, GET·PUT /api/auth/instructions
ProfileGet · Update · AvatarGET·PUT /api/profile, POST·DELETE /api/profile/avatar
PreferencesList · UpdateGET /api/preferences, PUT /api/preferences/{key}
API keysCreate · List · RevokePOST·GET /api/auth/api-keys, DELETE /api/auth/api-keys/{id}
OAuthAuthorize · Callback · Connections · Disconnect/api/auth/oauth/*
EmailVerify · Resend · ChangePOST /api/auth/{verify-email,resend-verification,change-verification-email}
MFASetup · Enable · Verify · Disable, codes, status/api/auth/mfa/*
SessionsList · Revoke one · Revoke allGET·DELETE /api/auth/sessions
HandoffMint · ExchangePOST /api/auth/handoff/{mint,exchange}
MCP loginStart · Poll · Authorize/api/auth/mcp/*
AccountDeleteDELETE /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, and handoff/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.io send the httpOnly refresh_token cookie with credentials: '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.

FieldTypeRequiredDescription
emailstringyesValid email, 5–255 chars. Disposable domains are rejected.
passwordstringyes8–128 chars.
confirm_passwordstringyesMust 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.

FieldTypeRequiredDescription
emailstringyesAccount email.
passwordstringyesAccount 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.

FieldTypeRequiredDescription
emailstringyesAccount 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.

FieldTypeRequiredDescription
tokenstringyesToken from the reset email link.
passwordstringyesNew 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.

FieldTypeRequiredDescription
current_passwordstringyesThe existing password.
new_passwordstringyesNew 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.

FieldTypeRequiredDescription
instructionsstringyesInstruction 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.

FieldTypeRequiredDescription
display_namestringyes1–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.

FieldTypeRequiredDescription
filefileyesImage 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 paramTypeRequiredDescription
keystringyesOne of the known preference keys (below).
Body fieldTypeRequiredDescription
valueanyyesNew value; validated against the key's allowed set.

Known keys:

KeyAllowed valuesDefault
auto_fetch_urlsalways, ask, neverask
auto_learn_urlson, offoff
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.

FieldTypeRequiredDescription
namestringyesHuman 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 paramTypeRequiredDescription
key_idstringyesThe 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 paramTypeRequiredDescription
providerstringyesgoogle, github, or twitter.
Query paramTypeRequiredDescription
frontend_originstringnoAllow-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 paramTypeRequiredDescription
codestringyesAuthorization code from the provider.
statestringyesThe 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 the refresh_token cookie.
  • On failure, to <frontend>/auth?error=<reason> where reason is one of invalid_state, unknown_provider, oauth_failed, no_email, or email_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.

FieldTypeRequiredDescription
tokenstringyesToken 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.

FieldTypeRequiredDescription
emailstringyesNew 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.

FieldTypeRequiredDescription
secretstringyesThe secret returned by setup.
codestringyesA 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.

FieldTypeRequiredDescription
partial_tokenstringyesThe partial_token from the MFA-pending signin response.
codestringyesA TOTP code, or a backup code if is_backup is true.
is_backupbooleannoSet 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 — requires password (and code when MFA is active). Returns { "message": "MFA disabled" }. Errors: 400 on wrong password or invalid code.
  • POST /api/auth/mfa/backup-codes — requires a valid code; returns a fresh { "backup_codes": [...] }. Errors: 400 if 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).

FieldTypeRequiredDescription
target_originstringyesAllow-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.

FieldTypeRequiredDescription
tokenstringyesThe 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 paramTypeRequiredDescription
codestringyesThe 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" }