Authentication & User Management Authentication methods, user CRUD, getting started patterns

Base URL: https://api.fast.io/current/ Request format: application/x-www-form-urlencoded (POST) or query string (GET) Response format: JSON

Authentication Methods

All authenticated endpoints require: Authorization: Bearer {token}

The token can be a JWT (from Basic Auth or OAuth), an API key, or a 2FA-upgraded JWT.

Method 1: Basic Auth to JWT

Send HTTP Basic Auth (email:password) to get a JWT.

GET /current/user/auth/

Authorization: Basic {base64(email:password)}

Returns auth_token (JWT). If the account has 2FA enabled, the returned token has limited scope until 2FA verification is completed.

Optional revocable=true — pass to mint a session-bound JWT that can be invalidated server-side via POST /current/user/auth/sign-out/. Recommended for browser sessions where users expect a logout button to terminate access. JWTs minted without revocable (the default) are stateless and sign-out has no effect on them — but ALL login tokens (with or without revocable) can still be killed via POST /current/user/auth/invalidate-all/, which terminates every login session for the user. See the session-termination endpoints (sign-out / invalidate-all) below.

Optional x-ve-session-cookie request header — browser clients only. Send x-ve-session-cookie: 1 (or true/yes — the same vocabulary as revocable) to ask the server to ALSO deliver the issued token in an HttpOnly, Secure, SameSite=Lax cookie scoped to the site's registrable domain (e.g. fast.io), so it is available across subdomains on that domain and the session never has to be stored anywhere JavaScript can read it between page loads. The token is still returned in the response body as usual. The browser then calls POST /current/user/auth/bootstrap/ on each page load — the only endpoint that reads the cookie — which returns that same token so the app can bring it into memory. The opt-in is only honoured as a request header; it is deliberately not accepted as a URL or body parameter. Omit the header (the default) and no cookie is set. On a 2FA-enabled account, sign-in returns a pre-2FA token and NO cookie; the cookie is issued by POST /current/user/auth/2factor/auth/{token}/ instead.

Method 2: API Keys

Long-lived tokens for service-to-service communication. Created via the API or the web UI. Used with the same Authorization: Bearer {api_key} header format as JWTs. Keys optionally support scoped permissions (scopes), agent names (agent_name), and expiration (expires). Scoped keys are enforced using the same scope system as v2.0 JWT tokens. Legacy keys without scopes retain full access.

Scope formats: OAuth authorization requests accept named scope strings (user, org, workspace, all_orgs, all_workspaces, all_shares, all_sign_envelopes). API responses return scopes as arrays of entity_type:entity_id:access_mode strings (e.g., ["org:12345:rw"]). See the OAuth 2.0 reference for full scope format details.

Method 3: OAuth 2.0 PKCE

For desktop/mobile apps and MCP-connected agents. No password passes through the agent. Access tokens last 1 hour; refresh tokens are long-lived. S256 challenge method only. See the OAuth 2.0 reference for the full flow.

Method 4: 2FA

When 2FA is enabled on an account, Basic Auth returns a limited-scope JWT. Complete authentication via POST /current/user/auth/2factor/auth/{token}/ with the 2FA code. The response contains a full-scope JWT.

Getting Started

Option 1: Use a Human's Existing Account (API Key)

A human creates an API key and gives it to you. You operate as that user with their permissions, org, and billing.

Human instructions: "Go to Settings > Devices & Agents > API Keys and click Create API Key. Optionally enter a memo to label the key (e.g., 'Agent access'), then click Create. Copy the key immediately -- it is only displayed once. Direct link: https://go.fast.io/settings/api-keys"

Once you have the key: Authorization: Bearer {api_key}. No further steps needed.

Option 2: Create Your Own Agent Account (Autonomous)

Create your own account to work independently. Agent accounts are ordinary Fastio accounts tagged account_type=agent — they require an email address and follow the same signup, organization, and paid-plan flow as everyone else.

  1. POST /current/user/ with email_address, password, tos_agree=true, agent=true
  2. GET /current/user/auth/ with Basic Auth to get JWT
  3. Verify email:
    • POST /current/user/email/validate/ with email — sends verification code
    • POST /current/user/email/validate/ with email and email_token — validates the code
  4. POST /current/org/create/ with domain (required, 2-63 chars lowercase alphanumeric + hyphens)
  5. Select a paid plan to activate the org via POST /current/org/{org_id}/billing/ with billing_plan (e.g. solo_monthly) — a new organization must choose a paid plan (Starter, Business, or Growth) before it can be used (see the Organizations reference)
  6. POST /current/org/{org_id}/create/workspace/ with folder_name, name, perm_join, perm_member_manage

New organizations choose a paid plan (Starter, Business, or Growth) to get started; until a paid plan is selected the org is in an upgrade-only state (the same state as an org that has exhausted its credits).

Option 3: Agent Account Invited to a Human's Org

  1. Create an agent account (steps 1-2 from Option 2)
  2. Give the human your agent's email address
  3. Human invites agent to their org or workspace
  4. Accept: POST /current/org/{org_id}/members/join/ or POST /current/workspace/{workspace_id}/members/join/
  5. You now operate within their resources with granted permissions

Option 4: PKCE Browser Login (No Password Sharing)

Most secure option. Works with SSO. No credentials pass through the agent.

  1. Agent initiates PKCE flow via POST /current/oauth/authorize/ with code_challenge, code_challenge_method=S256, client_id, redirect_uri, response_type=code
  2. User opens the returned URL in browser, signs in, approves access
  3. Browser displays authorization code — user copies it to agent
  4. Agent calls POST /current/oauth/token/ with grant_type=authorization_code, code, code_verifier
  5. Access tokens last 1 hour; refresh via POST /current/oauth/token/ with grant_type=refresh_token

Which option to choose

Compact Responses (output=)

Every endpoint that returns user objects — including your own profile (/current/user/details/), other users' profiles, and member listings on workspaces, orgs, and shares — accepts an optional output query parameter that selects the response shape. A single detail-level token may be combined with modifier tokens; specifying two detail levels (e.g. ?output=terse,standard) returns HTTP 406. When output= is omitted, responses are full and byte-for-byte unchanged.

LevelFields returned on each user (cumulative)
terseid, account_type, first_name, last_name, profile_pic
standardterse + email_address, is_anonymous, status, permissions, created, member_added_at (membership responses only), updated, invite, expires, locked, suspended, closed (last three visible to self/managers only)
fullstandard + country_code, phone_country, phone_number, 2factor, notify, sync_profile, tos_agree, valid_email, valid_phone, apps, owner_defined, parents

Use terse for mention pickers, avatar lists, creator cells, and message-author headers — it carries the identifier, display name, account type (human/agent), and profile picture, which is everything the avatar/name cells render. email_address is intentionally excluded from terse to keep PII out of the smallest shape. Use standard for member list views and account-settings summaries — it adds email_address, the caller-relative permissions role, active/pending status, invitation details for pending members, and account created/updated timestamps (now visible at standard for every user the caller can see, not just self/managers). Membership responses — the member-detail endpoint for an org, workspace, or share — also carry member_added_at at standard: the date the membership was created, distinct from created, which is the date the user's own account was created. Fastio returns it to the member themselves and to admins (and owners) of the containing org, workspace, or share; for any other caller the key is absent from the response rather than null, and it is omitted for every caller when the membership has no recorded date. It is formatted like every other response timestamp, e.g. 2026-08-26 14:03:11 UTC. Admin member-list UIs also receive the lock/suspend/close account-status chips at standard; these three fields are gated server-side to the self-view or manager-view of the target user, so non-privileged callers never see them at any tier. Use full (or omit the parameter) for the user profile screen, account settings, admin audits, and any workflow that reads phone, 2FA, TOS, anonymous-guest detection, or account-validity fields. Unknown tokens are silently ignored. Add the markdown modifier (e.g. ?output=standard,markdown) to receive the response as GitHub-flavored Markdown (Content-Type: text/markdown; charset=UTF-8) instead of JSON — see the cross-cutting ?output= reference for the full contract.

User Creation

POST /current/user/

Create a new user account.

Auth: None (IP-throttled)

Request Parameters

ParameterTypeRequiredConstraintsDescription
email_address string Yes Valid email format; domain must accept email; must be unique User's email address. Tags (e.g., +tag) are stripped for storage and uniqueness checks, but the original is preserved.
password string Yes Must pass password validity checks Account password.
tos_agree string Yes Must be "true" Must be "true" to accept Terms of Service.
agent string No "true" or "false" Set "true" for AI agent accounts. Sets account_type to "agent" permanently for identification. It does not grant a different or free plan — agent accounts follow the same signup, organization, and paid-plan flow as everyone else.
first_name string No Must pass name validation User's given/first name.
last_name string No Must pass name validation User's family/last name.
phone_country string No Numeric country calling code Phone country code. Required if phone_number is provided.
phone_number string No Numeric phone number Phone number. Required if phone_country is provided.

Request Example

curl -X POST "https://api.fast.io/current/user/" \
  -d "email_address=jane.doe@example.com" \
  -d "password=$PASSWORD" \
  -d "tos_agree=true" \
  -d "first_name=Jane" \
  -d "last_name=Doe" \
  -d "agent=true"

Success Response (200 OK)

{
  "result": true
}

Response Fields

FieldTypeDescription
resultbooleantrue on success

Error Responses

Error CodeHTTP StatusMessageCause
10025406"An invalid email was supplied."Email format invalid
10025406"The email domain is invalid or cannot receive email."Email domain validation failed
10026406"An invalid password was supplied."Password does not meet requirements
10394406"An invalid tos_agree value was create."TOS value not a valid boolean string
10395406"You declined to accept the terms of service."TOS set to "false"
10027406"An invalid first name was supplied to create."First name fails validation
10027406"An invalid last name was supplied to create."Last name fails validation
10163406"An invalid phone country code was supplied."Invalid phone country code
10029406"An invalid phone number was supplied."Invalid phone number
10165406"An invalid phone number or country code was supplied."Full phone number validation failed
10354401"Your attempt to create an account was not accepted."Risk/fraud check failed
10032500"We were unable to create your user account..."Internal processing failure

Notes

User Management Endpoints

POST /current/user/update/

Update the current authenticated user's profile information.

Auth: Required (JWT)

Request Parameters

All fields are optional. Only provided fields are updated.

ParameterTypeRequiredConstraintsDescription
email_address string No Valid email format; unique; domain must accept email New email address. You MUST also send current_password. An account with no password yet (password_set: false, SSO-only) is refused with 10766 — set a password through the email reset flow first. Does not take effect immediately: a confirmation link is emailed to the new address and the change applies only after it is confirmed via /current/user/email/change/. Your current email stays active and verified until then.
password string No Must pass validity checks; POST-only New password for an account that already has one — you MUST also send current_password. An account with no password yet (password_set: false on GET /current/user/details/, i.e. SSO-only) cannot set its first password here: the request is refused with 10766 and the first password is set through the email reset flow (POST /current/user/email/reset/ then POST /current/user/password/{code}/). Must be sent in the POST body — a copy in the query string is rejected, not ignored.
current_password string Conditional Must match the account's current password; POST-only Required to change the password or email_address of an account that already has a password. POST body only, never the query string.
first_name string No Must pass name validation Updated given/first name.
last_name string No Must pass name validation Updated family/last name.
phone_country string No Numeric country code; 2FA must be disabled first Updated phone country code. Pass "null" or empty to clear.
phone_number string No Numeric phone number; 2FA must be disabled first Updated phone number. Pass "null" or empty to clear.
owner_defined string (JSON) No Must be valid JSON if provided Custom owner-defined properties. Pass null or empty to clear.

Request Example

curl -X POST "https://api.fast.io/current/user/update/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "first_name=Jane" \
  -d "last_name=Smith"

Success Response (200 OK)

{
  "result": true,
  "sessions_invalidated": true,
  "auth_token": "{jwt_token}"
}

Both extra fields are optional and appear only when the password was changed:

FieldWhen presentMeaning
sessions_invalidatedAlways, when the password changedOther sessions on this account have been signed out. Your own credential may be among them — see auth_token.
auth_tokenWhen the password changed and you authenticated with a sign-in session tokenA replacement session token. Your previous one is no longer valid; use this for subsequent requests.
email_send_failedA combined password + email_address change whose confirmation email could not be sentThe password change still succeeded; the email change did not start.

If sessions_invalidated is true but no auth_token is returned, you authenticated with a credential that was not invalidated (an API key or an OAuth access token), so no replacement is needed and you can keep using it. Do not treat a missing auth_token as a failure.

Error Responses

Error CodeHTTP StatusMessageCause
10025406"An invalid email was supplied to update."Invalid email format
10025406"The email domain is invalid or cannot receive email."Invalid email domain
10025409"The email you specified is not available."Email already in use
20544500"We could not send the confirmation email; please try again."The email-change confirmation could not be sent, so the email change was not started; any other fields in the same request were still applied
10164406"You must disable 2-Factor before updating your phone."2FA enabled when trying to change phone
10026406"An invalid password was supplied to update."Invalid password
10026406"The password must be sent in the POST body, not the query string."password was supplied as a query parameter
10175403"The scope of your credentials are not sufficient."password or email_address sent with a credential that carries no account-level authority (see the note below)
10766406"This account has no password yet. Set the first password through the email reset flow: request a code with POST /current/user/email/reset/ and complete it with POST /current/user/password/{code}/."password sent for an account that has no password (SSO-only) — the first password is set through the email reset flow, never through a signed-in session
10766406"This account has no password yet. Set a password through the email reset flow first (POST /current/user/email/reset/, then POST /current/user/password/{code}/), then change the email with current_password."A changed email_address sent for an account that has no password (SSO-only) — set a password first, then change the email
10759403"Your current password is required and must be correct to change your password or email."Changing the password or email of a password-having account without a valid current_password
10027406"An invalid first name was supplied to update."Invalid first name
10027406"An invalid last name was supplied to update."Invalid last name
10731406"Owner-defined properties must be valid JSON."Invalid JSON in owner_defined
10354406"Your request was not accepted."The new email address failed a risk check. The message is deliberately non-specific and there is nothing to correct in the request itself — this is the same code as the signup-time risk rejection, in a different context

Notes


POST /current/user/close/

Close (soft-delete) the current user's account.

Auth: Required (JWT)

Request Parameters

ParameterTypeRequiredDescription
email_address string Yes Must match the user's current email address (confirmation).
dryrun string No If truthy, checks eligibility without closing the account.

Request Example

curl -X POST "https://api.fast.io/current/user/close/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "email_address=jane.doe@example.com"

Success Response (202 Accepted)

{
  "result": true
}

Dry Run Response (Cannot Close, 202 Accepted)

{
  "result": false
}

Error Responses

Error CodeHTTP StatusMessageCause
10024404"User not found to close."User object invalid
10025406"An invalid email was supplied to close account."Invalid email format
10025406"An incorrect email was supplied to close account."Email does not match user's email
159788406"Cannot close user account that owns active organizations..."User owns active organizations

Notes


POST /current/user/email/

Deprecated. This endpoint previously reported whether an email was already registered, which let anyone enumerate Fastio accounts. It no longer performs any account lookup: for any well-formed email it returns a uniform 202 Accepted / result: true. It is retained only so existing callers keep receiving a success response. To handle an already-registered email, just call signup (POST /user/) — it notifies the existing account and returns the same success as a new signup.

Auth: None (IP-throttled)

Request Parameters

ParameterTypeRequiredDescription
email string Yes Email address (format-validated only; not looked up).

Request Example

curl -X POST "https://api.fast.io/current/user/email/" \
  -d "email=jane.doe@example.com"

Response (202 Accepted) — always, regardless of whether the email is registered

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10022406"You provided an invalid email to check."Invalid email format or missing

Notes


POST /current/user/email/reset/

Request a password reset email.

Auth: None (IP-throttled)

Request Parameters

ParameterTypeRequiredDescription
email string Yes Email address of the account.

Request Example

curl -X POST "https://api.fast.io/current/user/email/reset/" \
  -d "email=jane.doe@example.com"

Success Response (202 Accepted)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10022406"You provided an invalid email to check."Invalid email format
20544500"We were unable to send a verification email."Email send failure

Notes


POST /current/user/email/validate/

Send or validate an email verification code. Two-step flow.

Auth: Required (JWT)

Mode 1: Send Verification Code

When email_token is NOT provided, sends a new validation code to the user's email.

ParameterTypeRequiredDescription
email string Yes Must match the authenticated user's email address.

Mode 2: Validate Code

When email_token IS provided, validates the code and marks the email as verified.

ParameterTypeRequiredDescription
email string Yes Must match the authenticated user's email address.
email_token string Yes Verification code received via email.

Request Example (Send Code)

curl -X POST "https://api.fast.io/current/user/email/validate/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "email=jane.doe@example.com"

Request Example (Validate Code)

curl -X POST "https://api.fast.io/current/user/email/validate/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "email=jane.doe@example.com" \
  -d "email_token=123456"

Success Response (202 Accepted)

{
  "result": true
}

Side Effects

Error Responses

Error CodeHTTP StatusMessageCause
10011401"Your credentials were not supplied or invalid."User not authenticated
10037406"Your email address is already verified."Email already verified
10023409"Your credentials do not match the email you provided."Email mismatch with authenticated user
10033406"You provided an invalid or expired token to validate email."Invalid or expired code
10199401"Provided code has expired, get a new code and try again."Code expired

POST /current/user/email/change/

Confirm a pending email change. Consumes the one-time confirmation token from the link that was emailed to the new address when the change was requested via /current/user/update/, and applies the new email.

Auth: Required (JWT). The signed-in user must be the account the change was requested for.

Request Parameters

ParameterTypeRequiredDescription
token string Yes The one-time confirmation token from the confirmation link.

Request Example

curl -X POST "https://api.fast.io/current/user/email/change/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "token={confirmation_token}"

Success Response (202 Accepted)

{
  "result": true
}

Side Effects

Error Responses

Error CodeHTTP StatusMessageCause
10755406"There is no pending email change to confirm."No pending change exists for this account
10033401"The confirmation link is invalid or has expired."Token invalid, expired, or already used
10023401"This confirmation link does not belong to the signed-in account."Token belongs to a different account than the one signed in
10025409"That email address is no longer available."The pending email was claimed by another account before confirmation
10032500"There was an internal error applying your email change."The change could not be applied

Notes


POST /current/user/password/{code}/

Set a new password using a password reset code.

Auth: None (code-based authentication)

Path Parameters

ParameterTypeRequiredDescription
{code} string Yes Password reset code from the reset email.

Request Parameters

ParameterTypeRequiredDescription
password1 string Yes New password.
password2 string Yes New password confirmation. Must match password1.

Request Example

curl -X POST "https://api.fast.io/current/user/password/abc123def456/" \
  -d "password1=NewSecureP@ss" \
  -d "password2=NewSecureP@ss"

Success Response (202 Accepted)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10197401"An invalid code was provided, cannot reset password."Invalid code format
10198401"Provided code was not found or expired, cannot reset password."Code not found or wrong type
10199401"Provided code has expired, get a new code and try again."Code expired
10200409"Provided code belongs to another user account and cannot be used."Code/user mismatch
10201404"The provided code belongs to an invalid user."User not found for code
10202409"The provided passwords don't match."password1 and password2 differ
10204406"Both password fields must be provided and match."Missing password fields
10203500"The provided password could not be processed..."Encryption failure
10204500"The provided password could not be processed..."The new password could not be written
10204500"The provided password could not be processed..."The account could not be reloaded before its sessions were invalidated
10204500"The provided password could not be processed..."The reset code could not be claimed because the datastore did not answer; nothing was consumed, retry

Notes


GET /current/user/password/{code}/details/

Get details of a password reset code (check if valid/expired).

Auth: None (code-based)

Path Parameters

ParameterTypeRequiredDescription
{code} string Yes Password reset code to check.

Request Example

curl -X GET "https://api.fast.io/current/user/password/abc123def456/details/"

Success Response (200 OK)

{
  "result": true,
  "email": "jane.doe@example.com"
}

Response Fields

FieldTypeDescription
response.emailstringThe email address associated with the reset code.

Error Responses

Error CodeHTTP StatusMessageCause
10197401"An invalid code was provided, cannot reset password."Invalid code format
10198401"Provided code was not found or expired, cannot reset password."Code not found
10199401"Provided code has expired, get a new code and try again."Code expired
10200409"Provided code belongs to another user account..."Code/user mismatch
10207423"The account has been restricted and cannot be updated."Account locked/suspended/closed

GET /current/user/phone/{country_code}-{phone_number}/

Validate a phone number and country code combination.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{country_code}-{phone_number} string Yes Country code and phone number separated by a hyphen (e.g., 1-5551234567).

Request Example

curl -X GET "https://api.fast.io/current/user/phone/1-5551234567/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (202 Accepted)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10022406"You provided an invalid phone number to check."Invalid format
10163406"An invalid phone country code was supplied."Invalid country code
10029406"An invalid phone number was supplied."Invalid phone number
10165406"An invalid phone number or country code was supplied."Full number validation failed

GET /current/user/pin/

Get the user's support PIN and identity verification hash.

Auth: Required (JWT)

Request Example

curl -X GET "https://api.fast.io/current/user/pin/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "supportcode": "1234",
  "intercom": "a1b2c3d4e5f6..."
}

Response Fields

FieldTypeDescription
response.supportcodestring4-digit support PIN. Defaults to "0000" if not set.
response.intercomstringHMAC-SHA256 identity-verification hash of your user ID, for authenticating you to the support widget.

Error Responses

Error CodeHTTP StatusMessageCause
10023404"Unable to fetch the user details."User not found
10541500"Internal temporary error, please try again later."Internal error

GET|POST /current/user/sso/signin/{provider}/

SSO (Single Sign-On) authentication flow.

Auth: None (IP-throttled)

Path Parameters

ParameterTypeRequiredDescription
{provider} string Yes SSO provider name: google or microsoft.

GET: Get SSO Redirect URL

Returns the OAuth2 authorization URL for the specified provider.

curl -X GET "https://api.fast.io/current/user/sso/signin/google/"

To request a session-bound JWT after the SSO flow completes:

curl -X GET "https://api.fast.io/current/user/sso/signin/google/?revocable=true"

Optional Query Parameters (GET only)

ParameterTypeDefaultDescription
revocable boolean false When true, the JWT issued at the end of the SSO flow is session-bound and can be invalidated server-side via POST /current/user/auth/sign-out/. The flag rides through the OAuth round-trip inside the HMAC-signed state token. Recommended for browser SSO logins.
return_url string (unset) Optional https:// URL to redirect to after the SSO callback completes. Must be a trusted Fastio domain.

GET Response (200 OK)

{
  "result": true,
  "provider": "google",
  "redirect_url": "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=...",
  "return_url": "https://fast.io/sso/callback/google"
}

Response Fields

FieldTypeDescription
response.providerstringThe provider name.
response.redirect_urlstringURL to redirect the user to for SSO authentication.
response.return_urlstringCallback URL the provider will redirect back to.

POST: Process SSO Callback

Processes the OAuth2 callback with the authorization code from the provider.

ParameterTypeRequiredDescription
code string Yes Authorization code from the SSO provider.
state string Yes State token for CSRF protection.

POST Response (200 OK)

{
  "result": true,
  "provider": "google",
  "email": "user@example.com",
  "token": "{jwt_token}",
  "2factor": false,
  "account_created": true
}

Response Fields

FieldTypeDescription
response.providerstringThe provider that authenticated the user.
response.emailstringThe email address on the authenticated account.
response.tokenstringThe issued JWT. Send it as Authorization: Bearer {token}.
response.2factorbooleanWhether the account has 2FA enabled. When true, complete the 2FA step before the token has full access.
response.account_createdbooleanPresent and true only when this exchange created the account — i.e. a first-time SSO signup. Omitted entirely for a returning sign-in, so treat a missing field as false.
response.redirect_after_loginstringOnly present when a return_url was supplied on the GET step; the URL to send the user to after login completes.

Error Responses

Error CodeHTTP StatusMessageCause
10041406"An invalid provider name was supplied."Invalid provider name format
10226406"An unknown provider name was supplied."Provider not in allowed list
10530406"Cookies must be enabled and passed to this API."Missing state cookie
10260401"Permission was not granted by the provider."OAuth error returned from provider
10230401"Invalid or missing input in a required field was received."Missing code or state

Notes


GET /current/user/assets/

List available user asset metadata types (e.g., profile photo specifications).

Auth: None

Request Example

curl -X GET "https://api.fast.io/current/user/assets/"

Notes


GET /current/user/available_profiles/

Check what profile types (orgs, workspaces, shares) the current user has access to.

Auth: Required (JWT)

Request Example

curl -X GET "https://api.fast.io/current/user/available_profiles/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "has_orgs": true,
  "has_workspaces": true,
  "has_shares": false,
  "has_pending_invitations": false
}

Response Fields

FieldTypeDescription
response.has_orgsbooleanWhether the user has access to any organizations.
response.has_workspacesbooleanWhether the user has access to any workspaces.
response.has_sharesbooleanWhether the user has access to any shares.
response.has_pending_invitationsbooleanWhether the user has any pending invitations awaiting their explicit acceptance. Computed for verified-email accounts only (false otherwise).

Error Responses

Error CodeHTTP StatusMessageCause
10023404"Unable to fetch the user details."User not found

GET /current/user/{user_id}/details/

Get user profile details.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{user_id} string No 19-digit user ID. If omitted, returns the current user's details.

Request Example

curl -X GET "https://api.fast.io/current/user/1234567890123456789/details/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "user": {
    "id": "1234567890123456789",
    "account_type": "human",
    "email_address": "jane.doe@example.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "locked": false,
    "profile_pic": "https://assets.fast.io/..."
  }
}

Response Fields

FieldTypeDescription
response.user.idstring19-digit user ID.
response.user.account_typestring"human" or "agent".
response.user.email_addressstringUser's email address.
response.user.first_namestringGiven name.
response.user.last_namestringFamily name.
response.user.lockedbooleanWhether the account is locked.
response.user.profile_picstringProfile photo URL.

Self-Only Fields (included only when viewing your own profile)

FieldTypeDescription
2factorbooleanWhether 2FA is enabled.
closedbooleanWhether the account is closed.
country_codestringCountry of residence.
createdstringRegistration date.
password_setbooleanWhether the account has a password set (false = SSO-only). Self/manager view only.
phone_countrystringPhone country code.
phone_numberstringPhone number.
suspendedbooleanSuspension status.
tos_agreestringToS agreement date.
updatedstringLast profile update time.
valid_emailbooleanEmail verified status.
valid_phonebooleanPhone verified status.

Error Responses

Error CodeHTTP StatusMessageCause
10023404"Unable to fetch the user details."User not found

GET /current/user/me/autosync/{state}/

Enable or disable profile photo auto-synchronization from SSO providers.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{state} string Yes "enable" or "disable".

Request Example

curl -X GET "https://api.fast.io/current/user/me/autosync/enable/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
135405500"There was an internal error processing your request..."Commit failure

GET /current/user/me/allowed/

Check if the user's country (based on IP geolocation) allows creating shares or organizations.

Auth: None (IP-throttled)

Request Example

curl -X GET "https://api.fast.io/current/user/me/allowed/"

Success Response (200 OK)

{
  "result": true,
  "allowed": true
}

Response Fields

FieldTypeDescription
response.allowedbooleanWhether the user's location allows resource creation.
response.reasonsarrayArray of blocked reason strings. Only present when allowed is false.

GET /current/user/me/limits/orgs/

Check free organization creation eligibility.

Auth: Required (JWT)

Request Example

curl -X GET "https://api.fast.io/current/user/me/limits/orgs/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "can_create_free_org": false,
  "existing_free_orgs": 0,
  "cooldown_remaining": 0,
  "max_free_orgs": 1,
  "reason": "The free plan is no longer available. Please choose a paid plan.",
  "free_trial_eligible": true,
  "trial_days": 14
}

The free plan is retired for new organizations, so can_create_free_org is false and a reason is returned. New organizations select a paid plan (Starter, Business, or Growth) instead. While the free plan is closed, existing_free_orgs and cooldown_remaining are not meaningful (reported as 0/null).

The free_trial_eligible and trial_days fields describe whether a NEW org this user creates can start a free trial of a paid plan (vs. buying immediately). A free trial is only available on a user's first organization, so at this pre-org stage free_trial_eligible is true only when the user owns no organization at all — once the user owns (or has ever owned) any organization, every later org is permanently ineligible regardless of elapsed time. A secondary per-user cooldown (60 days on modern plans, anchored at trial start) also applies on top of this. Use these fields to render the plan-selection cards ("Start N-day free trial" vs. "Buy now") before any org exists. When free_trial_eligible is false, a no_trial_reason string is also returned explaining why; a trial_available_at timestamp is included only when the block is the cooldown (the first-org block is permanent, so it has no future date).

Response Fields

FieldTypeDescription
response.can_create_free_orgbooleanWhether the user can create a free organization. The free plan is retired for new orgs, so this is false.
response.existing_free_orgsintegerNumber of existing free organizations owned by the user.
response.cooldown_remainingintegerSeconds remaining before next creation is allowed.
response.max_free_orgsintegerMaximum number of free organizations allowed.
response.reasonstringReason creation is not allowed. Only present when can_create_free_org is false.
response.free_trial_eligiblebooleanWhether a new org this user creates can start a free trial of a paid plan. true only when the user owns no organization at all (a trial is only ever available on a user's first org); false and permanent thereafter, subject also to the per-user cooldown.
response.trial_daysintegerLength of the free trial in days for the default paid plan.
response.no_trial_reasonstringWhy the user is not trial-eligible. Present only when free_trial_eligible is false and a reason exists.
response.trial_available_atstringCanonical Y-m-d H:i:s UTC timestamp of when the user next becomes trial-eligible. Present only when blocked by the per-user cooldown — absent when the user already owns an organization, since that block is permanent.

Error Responses

Error CodeHTTP StatusMessageCause
141088404"Unable to fetch the user."User not found

GET /current/user/me/list/shares/

List all shares accessible to the current user.

Auth: Required (JWT)

Query Parameters

ParameterTypeRequiredDefaultDescription
archived string No "false" "true" to show archived shares, "false" to show non-archived.
limit integer No 100 Page size (1–500). An out-of-range value is rejected as invalid input.
offset integer No 0 Number of items to skip before the returned page.

Request Example

curl -X GET "https://api.fast.io/current/user/me/list/shares/?limit=50&offset=0" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "shares": [
    {
      "id": "1234567890123456789",
      "name": "Project Files",
      "type": "send",
      "archived": false
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 50,
    "offset": 0,
    "has_more": false
  }
}

Response Fields

FieldTypeDescription
response.sharesarrayArray of share resource objects for the current page. Each includes parent workspace and org info.
response.paginationobjectPagination metadata: total (count of all matching shares before paging), limit, offset, and has_more (boolean — true when more items remain beyond this page).

Notes


GET /current/user/{user_id}/assets/

List set assets (e.g., profile photo) for a user.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{user_id} string Yes 19-digit numeric user ID.

Request Example

curl -X GET "https://api.fast.io/current/user/1234567890123456789/assets/" \
  -H "Authorization: Bearer {jwt_token}"

POST|DELETE /current/user/{user_id}/assets/{asset_name}/

Upload or delete a user asset.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{user_id} string Yes 19-digit numeric user ID.
{asset_name} string Yes Asset type name (e.g., profile_pic).

POST: Upload Asset

Multipart form data with exactly one file upload.

ParameterTypeRequiredDescription
(file) file Yes The asset file. Exactly one file must be included.
metadata array No Optional metadata. Must be a valid array if provided.

DELETE: Delete Asset

No request body required.

Error Responses

Error CodeHTTP StatusMessageCause
10586400"Only user may modify."Non-owner attempting to modify
10418400"Asset upload missing"No file in POST request
156780406"metadata invalid"Invalid metadata parameter

Notes


GET|HEAD /current/user/{user_id}/assets/{asset_name}/read/

Read the binary content of a user asset.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{user_id} string Yes 19-digit numeric user ID.
{asset_name} string Yes Asset type name (e.g., profile_pic).

Notes

Installed Apps

Track the desktop/mobile apps and agents a user has installed on their account. All four endpoints are user-authenticated and scoped to the calling user; installations are keyed by a caller-defined app_id.

An installation object has this shape everywhere it is returned:

FieldTypeDescription
idstring19-digit installation ID.
app_idstringCaller-defined app identifier this installation belongs to.
app_versionstring or nullApp version last reported, or null if never provided.
platformstring or nullPlatform string last reported (e.g. macos, windows, ios), or null.
statusstring"installed" or "uninstalled".
installed_atstringCanonical Y-m-d H:i:s UTC timestamp of first install.
uninstalled_atstring or nullCanonical Y-m-d H:i:s UTC timestamp of last uninstall, or null if currently installed.
last_heartbeatstring or nullCanonical Y-m-d H:i:s UTC timestamp of the last heartbeat/install check-in, or null.
metadataobject or nullArbitrary caller-defined JSON metadata, or null.

GET /current/user/apps/

List all app installations for the authenticated user (both installed and previously uninstalled).

Auth: Required (JWT)

Request Example

curl -X GET "https://api.fast.io/current/user/apps/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "apps": [
    {
      "id": "1234567890123456789",
      "app_id": "com.example.desktop",
      "app_version": "1.4.2",
      "platform": "macos",
      "status": "installed",
      "installed_at": "2026-07-07 16:37:29 UTC",
      "uninstalled_at": null,
      "last_heartbeat": "2026-07-07 18:02:10 UTC",
      "metadata": null
    }
  ]
}

Response Fields

FieldTypeDescription
response.appsarrayArray of installation objects (see shape above).

Error Responses

Error CodeHTTP StatusMessageCause
227759500"Internal error initializing app installations."Backend unavailable
218645404"No app installations found."Installation lookup failed

Notes


POST /current/user/apps/install/

Register an app installation for the authenticated user. Creates a new record, reinstalls a previously uninstalled app, or updates the version/platform (and refreshes the heartbeat) on an existing installation — all keyed by app_id.

Auth: Required (JWT)

Request Parameters

ParameterTypeRequiredDescription
app_idstringYesCaller-defined app identifier. Must not be blank.
app_versionstringNoApp version string.
platformstringNoPlatform string (e.g. macos, windows, ios, android).
metadatastring (JSON)NoArbitrary JSON object of caller-defined metadata.

Request Example

curl -X POST "https://api.fast.io/current/user/apps/install/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "app_id=com.example.desktop" \
  -d "app_version=1.4.2" \
  -d "platform=macos"

Success Response (200 OK)

{
  "result": true,
  "installation": {
    "id": "1234567890123456789",
    "app_id": "com.example.desktop",
    "app_version": "1.4.2",
    "platform": "macos",
    "status": "installed",
    "installed_at": "2026-07-07 16:37:29 UTC",
    "uninstalled_at": null,
    "last_heartbeat": "2026-07-07 16:37:29 UTC",
    "metadata": null
  }
}

Response Fields

FieldTypeDescription
response.installationobjectThe created or updated installation object (see shape above).

Error Responses

Error CodeHTTP StatusMessageCause
296856 / 210782 / 224953 / 296358406Variousapp_id blank/missing/too long → 296856; app_version too long → 210782; platform too long → 224953; metadata not valid JSON → 296358
246127500"Internal error initializing app installations."Backend unavailable
217449404"Failed to save app installation."Persisting the installation failed

Notes


POST /current/user/apps/uninstall/

Mark an app installation as uninstalled.

Auth: Required (JWT)

Request Parameters

ParameterTypeRequiredDescription
app_idstringYesCaller-defined app identifier of the installation to uninstall. Must not be blank.

Request Example

curl -X POST "https://api.fast.io/current/user/apps/uninstall/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "app_id=com.example.desktop"

Success Response (200 OK)

{
  "result": true,
  "installation": {
    "id": "1234567890123456789",
    "app_id": "com.example.desktop",
    "app_version": "1.4.2",
    "platform": "macos",
    "status": "uninstalled",
    "installed_at": "2026-07-07 16:37:29 UTC",
    "uninstalled_at": "2026-07-07 19:15:44 UTC",
    "last_heartbeat": "2026-07-07 18:02:10 UTC",
    "metadata": null
  }
}

Response Fields

FieldTypeDescription
response.installationobjectThe updated installation object; status is now uninstalled.

Error Responses

Error CodeHTTP StatusMessageCause
234003406Variousapp_id blank/missing or too long
226304404"No installation record found for this app."No installation exists for this app_id
233650409"This app is already uninstalled."Installation already in the uninstalled state
237113500"Failed to save uninstall status."Persisting the change failed

POST /current/user/apps/heartbeat/

Record a periodic liveness check-in from an installed app, updating last_heartbeat and optionally the app version.

Auth: Required (JWT)

Request Parameters

ParameterTypeRequiredDescription
app_idstringYesCaller-defined app identifier of the installed app. Must not be blank.
app_versionstringNoUpdated app version string. When provided, it replaces the stored version.

Request Example

curl -X POST "https://api.fast.io/current/user/apps/heartbeat/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "app_id=com.example.desktop" \
  -d "app_version=1.4.3"

Success Response (200 OK)

{
  "result": true,
  "installation": {
    "id": "1234567890123456789",
    "app_id": "com.example.desktop",
    "app_version": "1.4.3",
    "platform": "macos",
    "status": "installed",
    "installed_at": "2026-07-07 16:37:29 UTC",
    "uninstalled_at": null,
    "last_heartbeat": "2026-07-07 20:41:03 UTC",
    "metadata": null
  }
}

Response Fields

FieldTypeDescription
response.installationobjectThe updated installation object with a refreshed last_heartbeat.

Error Responses

Error CodeHTTP StatusMessageCause
276731 / 272675406Variousapp_id blank/missing/too long → 276731; app_version too long → 272675
245439404"No installation record found for this app."No installation exists for this app_id
231483409"Cannot heartbeat an uninstalled app."Installation is in the uninstalled state
237563500"Failed to save heartbeat."Persisting the change failed

Notes

Invitations

GET /current/user/invitation/{invitation_id}/details/

Get details for a specific invitation.

Auth: Required (JWT)

Path Parameters

ParameterTypeRequiredDescription
{invitation_id} string Yes Invitation ID (numeric) or invitation key (alphanumeric).

Request Example

curl -X GET "https://api.fast.io/current/user/invitation/1234567890123456789/details/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "invitation": {
    "id": "1234567890123456789",
    "invitation_key": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6abcd",
    "inviter": "Jane Doe",
    "invitee_email": "newuser@example.com",
    "entity_type": "workspace",
    "workspace": {
      "id": "9876543210987654321",
      "name": "Marketing Team"
    },
    "state": "pending",
    "created": "2025-01-15 10:30:00 UTC",
    "expires": "2025-01-22 10:30:00 UTC"
  },
  "owner": {
    "id": "1111111111111111111",
    "account_type": "human",
    "email_address": "admin@example.com",
    "first_name": "Admin",
    "last_name": "User",
    "profile_pic": "https://assets.fast.io/..."
  },
  "org": {
    "id": "2222222222222222222",
    "name": "Example Org"
  }
}

Response Fields

FieldTypeDescription
response.invitationobjectInvitation resource. Embeds entity_type and the entity object (org/workspace/share). Includes invitation_key because this is the invitee's own authenticated view.
response.ownerobjectUser resource of the profile owner.
response.orgobject or nullOrg resource if the invitation is for an org-owned entity.

Error Responses

Error CodeHTTP StatusMessageCause
10618406"An invalid invitation ID was supplied."Invalid ID format
10630406"Invitation not found."Invitation does not exist
159135500"Failed to load the invitation profile or its owner."Profile or owner load failure

Notes


GET /current/user/invitation/{invitation_id}/public/details/

Get public details for an invitation without authentication.

Auth: None (IP-throttled)

Path Parameters

ParameterTypeRequiredDescription
{invitation_id} string Yes Invitation ID (numeric) or invitation key (alphanumeric).

Request Example

curl -X GET "https://api.fast.io/current/user/invitation/1234567890123456789/public/details/"

Success Response (200 OK)

{
  "result": true,
  "invitation": {
    "id": "1234567890123456789",
    "state": "pending"
  },
  "owner": {
    "id": "1111111111111111111",
    "account_type": "human",
    "first_name": "Admin",
    "last_name": "User"
  },
  "org": {
    "id": "2222222222222222222",
    "name": "Example Org"
  }
}

Notes


POST /current/user/invitation/{invitation_id}/accept/

Accept a single pending invitation by its ID. Self-service: authorized by the authenticated invitee's verified email (or user ID), so the secret invitation key is not required. Use this to act on the invitations returned by GET /current/user/invitations/list/ — for example from a no-org landing page where the user has no email-link token.

Auth: Required (JWT, validated email)

Path Parameters

ParameterTypeRequiredDescription
{invitation_id} string Yes The invitation id from the invitations list/details response.

Request Example

curl -X POST "https://api.fast.io/current/user/invitation/1234567890123456789/accept/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "invitation": {
    "id": "1234567890123456789",
    "inviter": "Jane Doe",
    "invitee_email": "newuser@example.com",
    "entity_type": "workspace",
    "workspace": {
      "id": "9876543210987654321",
      "name": "Marketing Team"
    },
    "state": "accepted",
    "created": "2025-01-15 10:30:00 UTC",
    "expires": "2025-01-22 10:30:00 UTC"
  }
}

Response Fields

FieldTypeDescription
response.invitationobjectThe updated invitation resource; state is now accepted.

Error Responses

Error CodeHTTP StatusMessageCause
10618406"An invalid invitation ID was supplied."Malformed ID
10631406"This invitation can no longer be accepted."Already accepted/declined, or expired
10630404"Invitation not found."No such invitation
127827401"You are not authorized to act on this invitation."The invitation is not addressed to the authenticated user

Notes


POST /current/user/invitation/{invitation_id}/decline/

Decline a single pending invitation by its ID. Self-service counterpart to the accept endpoint; authorized by the authenticated invitee's verified email (or user ID), no invitation key required.

Auth: Required (JWT, validated email)

Path Parameters

ParameterTypeRequiredDescription
{invitation_id} string Yes The invitation id from the invitations list/details response.

Request Example

curl -X POST "https://api.fast.io/current/user/invitation/1234567890123456789/decline/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "invitation": {
    "id": "1234567890123456789",
    "inviter": "Jane Doe",
    "invitee_email": "newuser@example.com",
    "entity_type": "workspace",
    "workspace": {
      "id": "9876543210987654321",
      "name": "Marketing Team"
    },
    "state": "declined",
    "created": "2025-01-15 10:30:00 UTC",
    "expires": "2025-01-22 10:30:00 UTC"
  }
}

Response Fields

FieldTypeDescription
response.invitationobjectThe updated invitation resource; state is now declined.

Error Responses

Error CodeHTTP StatusMessageCause
10618406"An invalid invitation ID was supplied."Malformed ID
10631406"This invitation can no longer be declined."Already accepted/declined
10630404"Invitation not found."No such invitation
127827401"You are not authorized to act on this invitation."The invitation is not addressed to the authenticated user

Notes


POST /current/user/invitations/acceptall/

Accept all pending invitations.

Auth: Required (JWT)

Request Parameters

ParameterTypeRequiredDescription
invitation_key string No Optional invitation key. If the user's email is not validated, this key can identify invitations.

Request Example

curl -X POST "https://api.fast.io/current/user/invitations/acceptall/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Notes


GET /current/user/invitations/list/

List all pending invitations for the current user.

Auth: Required (JWT)

Query Parameters

ParameterTypeRequiredDescription
invitation_key string No Optional invitation key for users without validated email.

Request Example

curl -X GET "https://api.fast.io/current/user/invitations/list/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "invitations": [
    {
      "id": "1234567890123456789",
      "invitation_key": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6abcd",
      "inviter": "Jane Doe",
      "invitee_email": "newuser@example.com",
      "entity_type": "workspace",
      "workspace": {
        "id": "9876543210987654321",
        "name": "Marketing Team"
      },
      "state": "pending",
      "created": "2025-01-15 10:30:00 UTC",
      "expires": "2025-01-22 10:30:00 UTC"
    }
  ]
}

Response Fields

FieldTypeDescription
response.invitationsarrayArray of invitation resource objects.

Notes

User Authentication Endpoints

GET /current/user/auth/

Authenticate via HTTP Basic Auth. Returns JWT token.

Auth: HTTP Basic Auth (email:password)

Query Parameters

ParameterTypeRequiredDefaultDescription
expires integer No Server default Custom JWT expiration time in seconds.
revocable boolean No false When true, the issued JWT is session-bound and can be invalidated server-side via POST /current/user/auth/sign-out/. Recommended for browser sessions. Tokens minted without this flag are stateless and live their full TTL — sign-out has no effect on them.

Request Headers

HeaderTypeRequiredDefaultDescription
x-ve-session-cookie boolean No (absent) Browser clients only. When truthy (1, true or yes — the same vocabulary as revocable), the issued token is ALSO returned in an HttpOnly, Secure, SameSite=Lax cookie scoped to the site's registrable domain (e.g. fast.io), with the same lifetime as the token. auth_token is still returned in the body. On later page loads, bring the cookie's token back into memory with POST /current/user/auth/bootstrap/. The opt-in is only honoured as a request header — there is no URL or body parameter equivalent. On a 2FA-enabled account no cookie is set here — see the notes below.

Request Example

curl -X GET "https://api.fast.io/current/user/auth/" \
  -u "jane.doe@example.com:$PASSWORD"

To request a revocable session-bound JWT (recommended for browser logins):

curl -X GET "https://api.fast.io/current/user/auth/?revocable=true" \
  -u "jane.doe@example.com:$PASSWORD"

To additionally receive the token in an HttpOnly session cookie (browser logins that keep the token out of client storage):

curl -X GET "https://api.fast.io/current/user/auth/" \
  -u "jane.doe@example.com:$PASSWORD" \
  -H "x-ve-session-cookie: 1" \
  --cookie-jar cookies.txt

Success Response (200 OK)

{
  "result": true,
  "expires_in": 86400,
  "auth_token": "{jwt_token}",
  "2factor": false
}

Response Fields

FieldTypeDescription
response.expires_inintegerJWT token expiration time in seconds.
response.auth_tokenstringJWT access token. If 2FA is enabled, has twofactor scope (restricted). Otherwise has user scope (full access).
response.2factorbooleantrue if 2FA is enabled. Token has limited scope until 2FA verification is completed.

Error Responses

Error CodeHTTP StatusMessageCause
10454405"The expires time specified is invalid."Invalid expires parameter
10001401"Your credentials were not supplied or invalid."Missing Basic Auth header
10004401"Username is not valid."Invalid email format
10005401"Password is not valid."Invalid password format
10008401"Your credentials supplied are invalid."Wrong password, unrecognized email, or SSO-only account (no password set) — identical response and matched timing prevent account enumeration. Carries error.params.attempts_remaining + attempts_max when known
10105401"Your account is suspended..."Account suspended (only after a correct password)
10104401"Your account is locked..."Account locked
10106401"Your account is suspended due to abuse."Account flagged for abuse
10103401"Your account is closed by you."Account closed
10103401"This account has not been claimed yet."The address belongs to a placeholder account created by an invitation that has never been claimed. Same code as "account closed" — the message is the only thing that distinguishes them, so branch on the message, not the code. The remedy is different too: this one clears by accepting the invitation, not by contacting support
10760429"Too many failed sign-in attempts. Try again in N minutes."Too many consecutive failed sign-in attempts for this account — a temporary, self-clearing lockout

Notes


POST /current/user/auth/bootstrap/

Return the session token held in the browser's HttpOnly cookie. A browser that signed in with the x-ve-session-cookie header calls this on page load to bring that token into memory, then uses it in the Authorization header for every other request.

This is the ONLY endpoint in the API that authenticates from a cookie — every other endpoint still requires Authorization: Bearer {token}, unchanged. The point of the arrangement is that the durable session credential never has to live anywhere JavaScript can read it, except in memory once bootstrap hands it back.

Auth: The HttpOnly session cookie ONLY, sent automatically by the browser. A request that carries an Authorization header instead is rejected with 401 — a caller that already holds a token does not need to bootstrap.

Rate Limited: Yes (per user)

Request Parameters: None.

Request Example

curl -X POST "https://api.fast.io/current/user/auth/bootstrap/" \
  --cookie cookies.txt

Success Response (200 OK)

{
  "result": true,
  "id": "1234567890123456789",
  "auth_token": "{jwt_token}",
  "expires_in": 2591990
}

Response Fields

FieldTypeDescription
response.idstringThe 19-digit numeric user ID the cookie authenticated as.
response.auth_tokenstringThe session token held in the cookie — the same credential the cookie carries, not a separately issued one.
response.expires_inintegerSeconds of remaining life on the token. Always read this rather than assuming a fixed duration; the lifetime can vary. 0 (or a 401 on a later call) means the session is finished and the user must sign in again.

Error Responses

Error CodeHTTP StatusMessageCause
100966401"This endpoint requires a browser session cookie."The request authenticated with an Authorization header, or carried no session cookie at all

Notes


POST /current/user/auth/sign-out/

Invalidate every revocable JWT issued to the calling user. The user's session counter is incremented; revocable tokens carrying the previous counter value will be rejected on their next request.

Auth: Required (JWT, scope: user or admin; or an unscoped API key for the user)

This is account-wide, not session-scoped. It bumps the user's SHARED session counter, so it signs out every revocable session on the account — not only the caller's. An entity-scoped API key is therefore refused with 10175, the same bar invalidate-all/ applies: a key carries no sv claim, so a sign-out never affects the key itself, which makes this purely an action on other people's sessions when called with one. An unscoped key is unaffected. To end only your own session, discard your own credential — this endpoint cannot do that selectively.

Rate Limited: Yes (per user)

Request Example

curl -X POST "https://api.fast.io/current/user/auth/sign-out/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Notes


POST /current/user/auth/invalidate-all/

Invalidate EVERY login session for the calling user — a strict superset of sign-out. Kills interactive logins (browser, 2FA, password-reset, SSO) regardless of whether they opted into revocable. Any token issued before this call is rejected on its next request.

Use this for "sign me out of all devices" or a suspected account compromise — i.e. account-security actions, as opposed to a per-browser logout button (use sign-out for that). A password change or reset already invalidates other sessions on its own, so calling this afterwards is unnecessary and will additionally invalidate the replacement auth_token that the password change just returned, signing you out of the session you are using. Call it after a password change only when you deliberately want every session gone, including your own.

Auth: Required (JWT, scope: user or admin; or an unscoped API key for the user)

This is a strict superset of sign-out/, not a different class of action — both bump the user's shared session counters and both reach every session on the account; this one additionally kills tokens carrying gsv. The two therefore require the same authority, and both are gated identically. Two credentials are refused here with 10175: the limited twofactor-scope token from a half-completed 2FA sign-in, and an entity-scoped API key. An unscoped key is unaffected.

Request Example

curl -X POST "https://api.fast.io/current/user/auth/invalidate-all/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Notes


GET /current/user/auth/check/

Validate current JWT and get user ID.

Auth: Required (Bearer token)

Request Example

curl -X GET "https://api.fast.io/current/user/auth/check/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "id": "1234567890123456789"
}

Response Fields

FieldTypeDescription
response.idstringThe 19-digit numeric user ID.

Notes


GET /current/auth/scopes/

Token scope introspection. Returns information about the current token's scope, auth type, and agent status.

Auth: Required (Bearer token)

Request Example

curl -X GET "https://api.fast.io/current/auth/scopes/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "auth_type": "jwt_v2",
  "scopes": ["org:12345:rw", "org:67890:rw"],
  "scopes_detail": [],
  "is_agent": true,
  "agent_name": "My MCP Agent",
  "full_access": false
}

Response Fields

FieldTypeDescription
response.auth_typestringToken type: "jwt_v2" (scoped JWT), "jwt_v1" (legacy JWT), "api_key" (unscoped API key), or "api_key_scoped" (API key with scopes).
response.scopesarrayArray of scope strings in entity_type:entity_id:access_mode format. Empty for v1 JWTs and unscoped API keys. Populated for scoped API keys.
response.scopes_detailarrayHydrated scope details with entity information. Empty when scopes are empty.
response.is_agentbooleanWhether the token represents an agent.
response.agent_namestring or nullAgent display name. null if not set or not an agent.
response.full_accessbooleanWhether the token has unrestricted access. true for v1 JWTs and unscoped API keys. false for scoped API keys.

Error Response (401 Unauthorized) — the token could not be verified

{
  "result": false,
  "error": {
    "code": 154843,
    "text": "The supplied token could not be verified.",
    "params": { "reason": "verification_failed" }
  }
}

A bearer token that cannot be decoded returns 401 with error.params.reason set to verification_failed. This is never answered with a 200. Treat it as "this token was not checked", NOT as "this token has no rights" — a successful response always describes a token that was read; it never reports the absence of scopes because verification failed.

reason carries a single value here on purpose. The underlying cause — an expired credential, a forged or malformed one, or key material being temporarily unavailable — is not distinguishable at this layer, and reporting a guessed cause would be worse than reporting that it is unknown. Branch on reason, never on error.code (the numeric code identifies the call site and is not a stable contract).


API Keys

POST /current/user/auth/key/

Create a new API key.

Auth: Required (JWT, scope: user or admin)

Request Parameters

ParameterTypeRequiredDescription
memo string No Label/description for the key.
scopes string No JSON array of scope strings (e.g., ["org:123:rw", "workspace:456:r"]). Omit or null for full access. An explicit empty array ([]) is NOT unconstrained — it grants no authority and the key is refused on every scope-gated endpoint (fails closed).
agent_name string No Agent or application name for tracking. Max 128 characters.
expires string No Expiration datetime. Accepts any strtotime-compatible value; canonical form is Y-m-d H:i:s UTC (e.g. 2026-12-31 23:59:59 UTC). Must be in the future. Omit or null for no expiration.

Request Example

curl -X POST "https://api.fast.io/current/user/auth/key/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "memo=CI/CD Pipeline Key"

Request Example (scoped key with expiration)

curl -X POST "https://api.fast.io/current/user/auth/key/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "memo=Workspace Agent" \
  -d 'scopes=["workspace:1234567890123456789:rw"]' \
  -d "agent_name=my-agent" \
  -d "expires=2026-12-31 23:59:59 UTC"

Success Response (200 OK)

{
  "result": true,
  "api_key": "abcdefghij1234567890abcdefghij12"
}

Response Fields

FieldTypeDescription
response.api_keystringThe newly created API key. Only shown once — store it securely.

Error Responses

Error CodeHTTP StatusMessageCause
10011401"Your credentials were not supplied or invalid."Missing or invalid JWT
10175403"The scope of your credentials are not sufficient."JWT scope not user or admin
10015429"You are at the maximum number of API keys, {max}."Maximum key limit reached
10016406"You provided an invalid Memo."Invalid memo format

Notes


GET /current/user/auth/key/{key_id}/

Get details of an API key (key value is masked).

Auth: Required (JWT, scope: user or admin)

Path Parameters

ParameterTypeRequiredDescription
{key_id} string Yes The API key's unique identifier.

Request Example

curl -X GET "https://api.fast.io/current/user/auth/key/key_12345/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "api_key": {
    "id": "key_12345",
    "api_key": "****************************ab12",
    "memo": "CI/CD Pipeline Key",
    "created": "2024-01-15 10:30:00 UTC",
    "scopes": "[\"workspace:1234567890123456789:rw\"]",
    "agent_name": "my-agent",
    "expires": "2026-12-31 23:59:59 UTC"
  }
}

Response Fields

FieldTypeDescription
response.api_key.idstringUnique key identifier.
response.api_key.api_keystringMasked API key (only last 4 characters visible).
response.api_key.memostringKey description/label.
response.api_key.createdstringKey creation timestamp in UTC.
response.api_key.scopesstring or nullJSON array of scope strings, or null for full access.
response.api_key.agent_namestring or nullAgent/application name, or null if not set.
response.api_key.expiresstring or nullExpiration datetime in canonical Y-m-d H:i:s UTC format, or null for no expiration.

Error Responses

Error CodeHTTP StatusMessageCause
10019406"You provided an invalid Token to get details of."Invalid key ID format
(none)404no error body — result: false onlyKey does not exist at all — no error.code is returned in this case; a key that exists but belongs to a different user instead returns error.code 199646 with the same "The API Key was not found." message

POST /current/user/auth/key/{key_id}/

Update an existing API key's memo, scopes, agent_name, and/or expires.

Auth: Required (JWT, scope: user or admin)

Path Parameters

ParameterTypeRequiredDescription
{key_id} string Yes The API key's unique identifier.

Request Parameters

ParameterTypeRequiredDescription
memo string No Updated label/description for the key.
scopes string No JSON array of scope strings. Send empty string or "null" to clear (restore full access).
agent_name string No Agent/application name. Send empty string or "null" to clear. Max 128 characters.
expires string No Expiration datetime. Accepts any strtotime-compatible value; canonical form is Y-m-d H:i:s UTC (e.g. 2026-12-31 23:59:59 UTC). Must be in the future. Send empty string or "null" to clear (no expiration).

Request Example

curl -X POST "https://api.fast.io/current/user/auth/key/key_12345/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d 'scopes=["org:1234567890123456789:r"]' \
  -d "agent_name=updated-agent"

Success Response (200 OK)

{
  "result": true,
  "api_key": {
    "id": "key_12345",
    "api_key": "****************************ab12",
    "memo": "CI/CD Pipeline Key",
    "created": "2024-01-15 10:30:00 UTC",
    "scopes": "[\"org:1234567890123456789:r\"]",
    "agent_name": "updated-agent",
    "expires": null
  }
}

Error Responses

Error CodeHTTP StatusMessageCause
187851 or 100527404"The API Key was not found."187851 when the key does not exist at all; 100527 when it exists but belongs to another user
121158 / 107184 / 163622406Various121158 invalid scopes JSON; 107184 invalid agent_name; 163622 invalid/past expires

Notes


DELETE /current/user/auth/key/{key_id}/

Delete an API key.

Auth: Required (JWT, scope: user or admin)

Path Parameters

ParameterTypeRequiredDescription
{key_id} string Yes The API key's unique identifier.

Request Example

curl -X DELETE "https://api.fast.io/current/user/auth/key/key_12345/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10019406"You provided an invalid Token to Delete."Invalid key ID format
10020404"You provided a Token that was not found."Key not found or belongs to another user
10021500"There was an error deleting the API Key."Internal deletion failure

Notes


GET /current/user/auth/keys/

List all API keys for the user.

Auth: Required (JWT, scope: user or admin)

An entity-scoped API key (one created with scopes) is refused here with 10175, the same bar POST /current/user/auth/key/ applies: listing the account's keys and mutating them are the same authority, and a key deliberately narrowed to one workspace or org has no claim on the account's credential inventory. An unscoped key is unaffected.

Request Example

curl -X GET "https://api.fast.io/current/user/auth/keys/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "results": 2,
  "api_keys": [
    {
      "id": "key_12345",
      "api_key": "****************************ab12",
      "memo": "CI/CD Pipeline Key",
      "created": "2024-01-15 10:30:00 UTC",
      "scopes": "[\"workspace:1234567890123456789:rw\"]",
      "agent_name": "my-agent",
      "expires": "2026-12-31 23:59:59 UTC"
    },
    {
      "id": "key_67890",
      "api_key": "****************************cd34",
      "memo": "Backup Script",
      "created": "2024-02-20 14:00:00 UTC",
      "scopes": null,
      "agent_name": null,
      "expires": null
    }
  ]
}

No Keys Response (200 OK)

{
  "result": true,
  "results": 0,
  "api_keys": null
}

Response Fields

FieldTypeDescription
response.resultsintegerNumber of API keys.
response.api_keysarray or nullArray of API key objects, or null if none exist.
response.api_keys[].idstringUnique key identifier.
response.api_keys[].api_keystringMasked API key (only last 4 characters visible).
response.api_keys[].memostringKey description/label.
response.api_keys[].createdstringKey creation timestamp in UTC.
response.api_keys[].scopesstring or nullJSON array of scope strings, or null for full access.
response.api_keys[].agent_namestring or nullAgent/application name, or null if not set.
response.api_keys[].expiresstring or nullExpiration datetime in canonical Y-m-d H:i:s UTC format, or null for no expiration.

Two-Factor Authentication (2FA)

GET /current/user/auth/2factor/

Get current 2FA status.

Auth: Required (JWT, scope: user or admin)

Request Example

curl -X GET "https://api.fast.io/current/user/auth/2factor/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "state": "enabled",
  "totp": false
}

Response Fields

FieldTypeDescription
response.statestring2FA status: "enabled" (fully verified), "unverified" (added but not verified), or "disabled" (not configured).
response.totpbooleanWhether the 2FA method is TOTP (Time-based One-Time Password).

POST /current/user/auth/2factor/{channel}/

Enable 2FA on the account.

Auth: Required (JWT, scope: user or admin)

Path Parameters

ParameterTypeRequiredDefaultDescription
{channel} string No sms 2FA delivery channel: sms, call, whatsapp, or totp.

Request Example

curl -X POST "https://api.fast.io/current/user/auth/2factor/sms/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response for SMS/Voice/WhatsApp (202 Accepted)

{
  "result": true
}

Success Response for TOTP (202 Accepted)

{
  "result": true,
  "binding_uri": "otpauth://totp/fast.io:jane@example.com?secret=ABCDEF..."
}

Response Fields (TOTP only)

FieldTypeDescription
response.binding_uristringTOTP provisioning URI for QR code display.

Error Responses

Error CodeHTTP StatusMessageCause
10167409"2Factor already added, please remove first."2FA already enabled
10173406"An invalid channel was supplied."Invalid channel name
10168406"2Factor cannot be added, you need a valid phone_number and phone_country..."No phone number configured

Notes


POST /current/user/auth/2factor/verify/{token}/

Verify a 2FA setup code to confirm enrollment. Transitions 2FA from unverified to enabled state.

Auth: Required (JWT, scope: user or admin)

This is the enrollment step, performed from a fully signed-in session, so the limited twofactor-scope token issued during a 2FA sign-in is refused here with 10175 — that token belongs to POST /current/user/auth/2factor/auth/{token}/, which completes a login rather than confirming enrollment. An entity-scoped API key is likewise refused; an unscoped key is unaffected.

Path Parameters

ParameterTypeRequiredDescription
{token} string Yes 2FA verification code (e.g., 6-digit code).

Request Example

curl -X POST "https://api.fast.io/current/user/auth/2factor/verify/123456/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (202 Accepted)

{
  "result": true
}

Verification Failed (406 Not Accepted)

{
  "result": false
}

Error Responses

Error CodeHTTP StatusMessageCause
10173406"An invalid token was supplied to validate."Invalid token format
10170406"2Factor is not enabled."2FA not configured

Notes


POST /current/user/auth/2factor/auth/{token}/

Authenticate with a 2FA code. Upgrades a limited-scope JWT to a full-scope JWT.

Auth: Required (JWT, scope: user, twofactor, or admin)

Path Parameters

ParameterTypeRequiredDescription
{token} string Yes Valid 2FA verification code (e.g., 6-digit TOTP or SMS code).

Request Headers

HeaderTypeRequiredDefaultDescription
x-ve-session-cookie boolean No (absent) Browser clients only. Same meaning and vocabulary (1, true, yes) as on GET /current/user/auth/: the full-scope token minted here is ALSO delivered in an HttpOnly, Secure, SameSite=Lax cookie scoped to the site's registrable domain (e.g. fast.io). Only honoured as a request header.

Request Example

curl -X POST "https://api.fast.io/current/user/auth/2factor/auth/123456/" \
  -H "Authorization: Bearer {twofactor_jwt_token}"

A browser client that opted into the session cookie at sign-in sends the same header again here:

curl -X POST "https://api.fast.io/current/user/auth/2factor/auth/123456/" \
  -H "Authorization: Bearer {twofactor_jwt_token}" \
  -H "x-ve-session-cookie: 1" \
  --cookie-jar cookies.txt

Success Response (200 OK)

{
  "result": true,
  "expires_in": 86400,
  "auth_token": "{jwt_token}"
}

Response Fields

FieldTypeDescription
response.expires_inintegerJWT expiration time in seconds.
response.auth_tokenstringNew JWT with full user scope.

Error Responses

Error CodeHTTP StatusMessageCause
10173406"An invalid token was supplied to authenticate."Invalid token format
10172406"2Factor is not enabled on this account."2FA not enabled
10174406"The supplied token failed to authenticate."Wrong 2FA code
10009401"Internal Error."JWT creation failure

Notes


DELETE /current/user/auth/2factor/{token}/

Disable (remove) 2FA from the account.

Auth: Required (JWT, scope: user or admin)

Path Parameters

ParameterTypeRequiredDescription
{token} string Yes Valid 2FA verification code. Required only if 2FA is in enabled (verified) state.

Request Example

curl -X DELETE "https://api.fast.io/current/user/auth/2factor/123456/" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true
}

Error Responses

Error CodeHTTP StatusMessageCause
10173406"An invalid token was supplied, valid token required to remove 2Factor."Invalid token format
10174406"The supplied token failed to authenticate."Token verification failed
10169500"2Factor could not be removed, please contact support."Internal removal failure

Notes


2FA Code Delivery Endpoints

Request a 2FA code via different channels. All require auth (accepts user, twofactor, or admin JWT scope).

GET /current/user/auth/2factor/send/sms/

Send code via SMS

GET /current/user/auth/2factor/send/call/

Send code via voice call

GET /current/user/auth/2factor/send/whatsapp/

Send code via WhatsApp

Success Response (202 Accepted)

{
  "result": true
}

Failure Response (406 Not Accepted)

{
  "result": false
}

Error Responses

Error CodeHTTP StatusMessageCause
10011401"Your credentials were not supplied or invalid."Invalid JWT
10175403"The scope of your credentials are not sufficient."Wrong JWT scope
10170406"2Factor is not enabled."2FA not configured on account

Notes


Complete 2FA Flows

Complete 2FA Login Flow

1. GET /current/user/auth/
   - Send email:password via HTTP Basic Auth
   - Response includes "2factor": true and limited-scope auth_token

2. GET /current/user/auth/2factor/send/sms/  (or /call/ or /whatsapp/)
   - Request a fresh 2FA code
   - Uses the limited-scope (twofactor) JWT

3. POST /current/user/auth/2factor/auth/{code}/
   - Submit the 2FA code
   - Receive a new JWT with full "user" scope
   - Use this token for all subsequent requests

Complete 2FA Setup Flow

1. POST /current/user/auth/2factor/{channel}/
   - Choose channel: sms, call, whatsapp, or totp
   - Phone number must be configured on account (for non-TOTP)
   - State becomes "unverified"
   - For TOTP: receive binding_uri for QR code

2. Receive code via selected channel (or scan QR code for TOTP)

3. POST /current/user/auth/2factor/verify/{code}/
   - Submit the verification code
   - State becomes "enabled"
   - 2FA is now active on the account

Complete 2FA Removal Flow

1. DELETE /current/user/auth/2factor/{code}/
   - Must provide valid 2FA code if state is "enabled"
   - Can remove without code if state is "unverified"
   - 2FA is fully removed from the account

Search for people by name or email across your contacts and the people you share access with (members of orgs, workspaces, and shares you belong to).

Auth: Required (JWT)

Query Parameters

ParameterTypeRequiredDescription
search string Yes Search term. Matches against user names and email addresses. Must not be blank.

Request Example

curl -X GET "https://api.fast.io/current/users/search/?search=john" \
  -H "Authorization: Bearer {jwt_token}"

Success Response (200 OK)

{
  "result": true,
  "contacts": {
    "john.doe@example.com": "John Doe",
    "jane.johnson@example.com": "Jane Johnson"
  },
  "users": [
    {
      "id": "1234567890123456789",
      "email": "john.doe@example.com",
      "name": "John Doe"
    },
    {
      "id": null,
      "email": "jane.johnson@example.com",
      "name": "Jane Johnson"
    }
  ]
}

Response Fields

FieldTypeDescription
response.contactsobjectMap of email address (key) to display name (value) for each matched user. Unchanged, backward-compatible.
response.usersarrayList of matched people as {id, email, name} objects, deduplicated by email. Provides the user id the contacts map cannot.
response.users[].idstring | nullThe matched person's user id when they are reachable through one of your shared spaces (an org / workspace / share you have in common); null for a contacts-only match that does not resolve to such an account.
response.users[].emailstringThe matched person's email address.
response.users[].namestringThe matched person's display name.

Error Responses

Error CodeHTTP StatusMessageCause
10011401"Authentication required"Missing or invalid JWT token
205516 / 207092406"This value should not be blank."No custom error code is attached to this field — the code is derived from Symfony's validator: 205516 when search is omitted entirely, 207092 when present but blank
157360 or 120189500"Internal error"157360 when the contacts search client fails to initialize; 120189 when the user-profile search client fails to initialize

Notes

Response Envelope

Success

{"result": true, ...}

Error

{
  "result": false,
  "error": {
    "code": 195654,
    "text": "Human-readable message",
    "documentation_url": "https://api.fast.io/llms.txt",
    "resource": "POST /current/user/"
  }
}

Validation error (HTTP 406) with structured per-parameter detail

{
  "result": false,
  "error": {
    "code": 195654,
    "text": "The email parameter is required. The password must be at least 8 characters.",
    "documentation_url": "https://api.fast.io/llms.txt",
    "resource": "POST /current/user/",
    "params": [
      {"name": "email", "kind": "missing", "message": "The email parameter is required.", "code": 195654},
      {"name": "password", "kind": "invalid", "message": "The password must be at least 8 characters.", "code": 195655, "expected_type": "string"}
    ]
  }
}

error.params is an array of {name, kind, message, code, expected_type?, received_alias?}. It is present on validation errors (HTTP 406) and aggregates every failed parameter so callers see all problems in one round trip. kind is one of missing, invalid, type_mismatch, or unknown_parameter on a validation error, and conflict on a state conflict (HTTP 409). Treat kind as open-ended: handle an unrecognised value as a generic failure rather than rejecting the response. The field is omitted when empty (non-validation errors). The existing text field is retained byte-identically for compatibility and is now advisory — clients should prefer params for programmatic handling.

OPTIONS introspection. Most user/auth endpoints respond to OPTIONS with a JSON description of their accepted parameters (source, required vs optional, expected type, declared constraints). Use this to fetch parameter requirements before issuing a call. Endpoints that don't opt in return 405 Method Not Allowed.

Common Error Codes

CodeDescriptionHTTP Status
1600Internal Error500 Internal Server Error
1605Invalid Input406 Not Acceptable
1658Not Acceptable406 Not Acceptable
1607Duplicate Entry406 Not Acceptable
1669Already Exists409 Conflict
1660Conflict409 Conflict
1609Not Found / Resource Missing404 Not Found
1610General Error500 Internal Server Error
1650Authentication Invalid401 Unauthorized
1651Invalid Request Type405 Method Not Allowed
1653User Not Found404 Not Found
1701Gone410 Gone — endpoint retired by decision; stop calling the path, do not retry or vary the id
1671Rate Limited429 Too Many Requests
1680Access Denied401 Unauthorized
1670Restricted406 Not Acceptable
1677Locked423 Locked
1673SSO Auth Error401 Unauthorized

Rate Limiting

Response headers: x-ve-limit-avail (requests remaining), x-ve-limit-max (window cap), x-ve-limit-expires (Unix-time the window resets).

When exceeded: HTTP 429 with error code 1671 (Rate Limited). Back off until x-ve-limit-expires.

ID Formats

Token Types

TypeFormatLifetimeUse
JWT (Basic Auth)RS256-signed JSON Web TokenConfigurable (default varies)General API access
JWT (OAuth)RS256-signed JSON Web Token1 hourOAuth-based API access
Refresh TokenOpaque stringLong-livedObtaining new access tokens (OAuth only)
API KeyAlphanumeric stringConfigurable (default: no expiry)Service-to-service communication. Optionally scoped with permissions, agent name, and expiration.

Security Best Practices

  1. Always use HTTPS for all API communication.
  2. Store refresh tokens and API keys securely (OS keychain, encrypted storage).
  3. Never log tokens in client-side logs or analytics.
  4. Persist the refresh_token from the response; it is long-lived and returned unchanged on refresh (no rotation needed).
  5. Verify the state parameter in OAuth callbacks to prevent CSRF.
  6. Handle 401 responses by attempting a token refresh; if refresh fails, re-authenticate.
  7. Revoke tokens on logout by calling the revoke endpoint and clearing local storage.
↑ Back to top