Authentication & User Management Authentication methods, user CRUD, getting started patterns
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.
/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 valid until their TTL expires.
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.
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.
POST /current/user/withemail_address,password,tos_agree=true,agent=trueGET /current/user/auth/with Basic Auth to get JWT- Verify email:
POST /current/user/email/validate/withemail— sends verification codePOST /current/user/email/validate/withemailandemail_token— validates the code
POST /current/org/create/withdomain(required, 2-80 chars lowercase alphanumeric + hyphens)- Select a paid plan (Starter, Business, or Growth) to activate the org — a new organization must choose a paid plan before it can be used (see the Organizations reference)
POST /current/org/{org_id}/create/workspace/withfolder_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
- Create an agent account (steps 1-2 from Option 2)
- Give the human your agent's email address
- Human invites agent to their org or workspace
- Accept:
POST /current/org/{org_id}/members/join/orPOST /current/workspace/{workspace_id}/members/join/ - 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.
- Agent initiates PKCE flow via
POST /current/oauth/authorize/withcode_challenge,code_challenge_method=S256,client_id,redirect_uri,response_type=code - User opens the returned URL in browser, signs in, approves access
- Browser displays authorization code — user copies it to agent
- Agent calls
POST /current/oauth/token/withgrant_type=authorization_code,code,code_verifier - Access tokens last 1 hour; refresh via
POST /current/oauth/token/withgrant_type=refresh_token
Which option to choose
- Human wants you to manage their account → Option 1 (API key)
- You're building something independently → Option 2 (agent account + own org)
- You need to work within a human's existing org → Option 3 (agent account + invitation)
- Human wants to authorize agent without sharing credentials → Option 4 (PKCE)
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 400. When output= is omitted, responses are full and byte-for-byte unchanged.
| Level | Fields returned on each user (cumulative) |
|---|---|
terse | id, account_type, first_name, last_name, profile_pic |
standard | terse + email_address, is_anonymous, status, permissions, created, updated, invite, expires, locked, suspended, closed (last three visible to self/managers only) |
full | standard + 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). 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
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| 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=SecureP@ss123" \
-d "tos_agree=true" \
-d "first_name=Jane" \
-d "last_name=Doe" \
-d "agent=true"
Success Response (200 OK)
{
"result": true
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid email was supplied." | Email format invalid |
1605 (Invalid Input) | 400 | "The email domain is invalid or cannot receive email." | Email domain validation failed |
1605 (Invalid Input) | 400 | "An invalid password was supplied." | Password does not meet requirements |
1605 (Invalid Input) | 400 | "An invalid tos_agree value was create." | TOS value not a valid boolean string |
1605 (Invalid Input) | 400 | "You declined to accept the terms of service." | TOS set to "false" |
1605 (Invalid Input) | 400 | "An invalid first name was supplied to create." | First name fails validation |
1605 (Invalid Input) | 400 | "An invalid last name was supplied to create." | Last name fails validation |
1605 (Invalid Input) | 400 | "An invalid phone country code was supplied." | Invalid phone country code |
1605 (Invalid Input) | 400 | "An invalid phone number was supplied." | Invalid phone number |
1605 (Invalid Input) | 400 | "An invalid phone number or country code was supplied." | Full phone number validation failed |
1680 (Access Denied) | 403 | "Your attempt to create an account was not accepted." | Risk/fraud check failed |
1654 (Internal Error) | 500 | "We were unable to create your user account..." | Account creation failed |
Notes
- Account enumeration is intentionally not possible. Signing up with an email that already has an account does NOT return an "already in use" error — it returns the SAME success response as a brand-new signup and emails the existing account so the owner can sign in / reset their password. A caller cannot use signup to tell whether an email is registered, and no duplicate account is created.
- Email addresses are normalized by stripping tag extensions (e.g.,
user+tag@example.combecomesuser@example.com) for storage and uniqueness lookup; the original email is preserved separately. - Country code is detected from the client IP and stored automatically.
agent=trueis permanent and cannot be changed after account creation.- An email address is required for every account, agent or human. The
agent=truetag is for identification only and does not grant a different or free plan.
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.
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| email_address | string | No | Valid email format; unique; domain must accept email | New email address. On an account that already has a password you MUST also send current_password. 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 | New password. If the account already has a password you MUST also send current_password; an SSO-only account (no password) may set an initial password without it. |
| 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. Omit when setting an initial password on an SSO-only account. 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
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid email was supplied to update." | Invalid email format |
1605 (Invalid Input) | 400 | "The email domain is invalid or cannot receive email." | Invalid email domain |
1660 (Conflict) | 409 | "There email you specified is not available." | Email already in use |
1663 (Update Failed) | 400 | "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 |
1670 (Restricted) | 403 | "You must disable 2-Factor before updating your phone." | 2FA enabled when trying to change phone |
1605 (Invalid Input) | 400 | "An invalid password was supplied to update." | Invalid password |
1700 (Forbidden) | 403 | "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 (error.code 10759) |
1605 (Invalid Input) | 400 | "An invalid first name was supplied to update." | Invalid first name |
1605 (Invalid Input) | 400 | "An invalid last name was supplied to update." | Invalid last name |
1605 (Invalid Input) | 400 | "Owner-defined properties must be valid JSON." | Invalid JSON in owner_defined |
Notes
- Changing an existing password OR email requires the current password. If the account already has a password, a
passwordchange or anemail_addresschange must include a validcurrent_password(POST-only) or it is rejected with1700/403/10759— this prevents a session-only attacker (e.g. a stolen JWT) from overwriting the password or hijacking the email. An SSO-only account (no password) may set an initial password without it; use thepassword_setfield onGET /current/user/details/to tell which case applies. - Changing the email address starts a confirmation flow rather than changing it immediately: a confirmation link is sent to the new address (and a notification to the current address), and the change applies only after the link is confirmed via
/current/user/email/change/. The current email remains active and verified until then. When a change is pending, the response includes"email_change_pending": true. - Phone number changes require 2FA to be disabled first.
- If no fields have changed, the endpoint returns success and no update is performed.
POST /current/user/close/
Close (soft-delete) the current user's account.
Auth: Required (JWT)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1653 (User Not Found) | 404 | "User not found to close." | User object invalid |
1605 (Invalid Input) | 400 | "An invalid email was supplied to close account." | Invalid email format |
1605 (Invalid Input) | 400 | "An incorrect email was supplied to close account." | Email does not match user's email |
1605 (Invalid Input) | 400 | "Cannot close user account that owns active organizations..." | User owns active organizations |
Notes
- 2FA verification is required if 2FA is enabled on the account.
- Users who own active organizations must close or transfer ownership first.
- The
dryrunparameter checks closure eligibility without actually closing the account. - On closure: subscriptions are cancelled, SSO connections are removed, the account is flagged as closed.
POST /current/user/email/
Deprecated. This endpoint previously reported whether an email was already
registered, which let anyone enumerate Fast.io 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid email to check." | Invalid email format or missing |
Notes
- The response does not reveal whether the email is registered (account enumeration is intentionally not possible).
POST /current/user/email/reset/
Request a password reset email.
Auth: None (IP-throttled)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid email to check." | Invalid email format |
1654 (Internal Error) | 500 | "We were unable to send a verification email." | Email send failure |
Notes
- For security, this endpoint always returns success regardless of whether the email exists in the system.
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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
- On successful validation, any pending invitations addressed to this email remain pending. The user accepts them explicitly via the invitation accept endpoints (list pending invitations with
GET /current/user/invitations/list/, then accept withPOST /current/user/invitation/{invitation_id}/accept/).
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1650 (Authentication Invalid) | 401 | "Your credentials were not supplied or invalid." | User not authenticated |
1658 (Not Acceptable) | 406 | "Your email address is already verified." | Email already verified |
1660 (Conflict) | 409 | "Your credentials do not match the email you provided." | Email mismatch with authenticated user |
1658 (Not Acceptable) | 406 | "You provided an invalid or expired token to validate email." | Invalid or expired code |
1680 (Access Denied) | 403 | "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
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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
- On success the new email becomes the account email and is marked verified. Any pending invitations addressed to the new email remain pending; the user accepts them explicitly via the invitation accept endpoints (list with
GET /current/user/invitations/list/, then accept withPOST /current/user/invitation/{invitation_id}/accept/).
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "There is no pending email change to confirm." | No pending change exists for this account |
1680 (Access Denied) | 403 | "The confirmation link is invalid or has expired." | Token invalid, expired, or already used |
1680 (Access Denied) | 403 | "This confirmation link does not belong to the signed-in account." | Token belongs to a different account than the one signed in |
1660 (Conflict) | 409 | "That email address is no longer available." | The pending email was claimed by another account before confirmation |
1663 (Update Failed) | 400 | "There was an internal error applying your email change." | The change could not be applied |
Notes
- The confirmation token is single-use and time-limited; once used or expired, request the change again via
/current/user/update/.
POST /current/user/password/{code}/
Set a new password using a password reset code.
Auth: None (code-based authentication)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {code} | string | Yes | Password reset code from the reset email. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 403 | "An invalid code was provided, cannot reset password." | Invalid code format |
1680 (Access Denied) | 403 | "Provided code was not found or expired, cannot reset password." | Code not found or wrong type |
1680 (Access Denied) | 403 | "Provided code has expired, get a new code and try again." | Code expired |
1660 (Conflict) | 409 | "Provided code belongs to another user account and cannot be used." | Code/user mismatch |
1683 (Resource Missing) | 404 | "The provided code belongs to an invalid user." | User not found for code |
1660 (Conflict) | 409 | "The provided passwords don't match." | password1 and password2 differ |
1658 (Not Acceptable) | 406 | "Both password fields must be provided and match." | Missing password fields |
1654 (Internal Error) | 500 | "The provided password could not be processed..." | Internal processing error |
Notes
- The reset code is consumed (deleted) after successful password change.
- A new JWT is created after the password is set.
GET /current/user/password/{code}/details/
Get details of a password reset code (check if valid/expired).
Auth: None (code-based)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.email | string | The email address associated with the reset code. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 403 | "An invalid code was provided, cannot reset password." | Invalid code format |
1680 (Access Denied) | 403 | "Provided code was not found or expired, cannot reset password." | Code not found |
1680 (Access Denied) | 403 | "Provided code has expired, get a new code and try again." | Code expired |
1660 (Conflict) | 409 | "Provided code belongs to another user account..." | Code/user mismatch |
1677 (Locked) | 423 | "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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid phone number to check." | Invalid format |
1605 (Invalid Input) | 400 | "An invalid phone country code was supplied." | Invalid country code |
1605 (Invalid Input) | 400 | "An invalid phone number was supplied." | Invalid phone number |
1605 (Invalid Input) | 400 | "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",
"support_hash": "a1b2c3d4e5f6..."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.supportcode | string | 4-digit support PIN. Defaults to "0000" if not set. |
| response.support_hash | string | Identity verification hash for support integration. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1653 (User Not Found) | 404 | "Unable to fetch the user details." | User not found |
1654 (Internal Error) | 500 | "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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {provider} | string | Yes | SSO provider name: google, apple, 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)
| Parameter | Type | Default | Description |
|---|---|---|---|
| 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 (used by custom domains). 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
| Field | Type | Description |
|---|---|---|
| response.provider | string | The provider name. |
| response.redirect_url | string | URL to redirect the user to for SSO authentication. |
| response.return_url | string | Callback URL the provider will redirect back to. |
POST: Process SSO Callback
Processes the OAuth2 callback with the authorization code from the provider.
| Parameter | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | Authorization code from the SSO provider. |
| state | string | Yes | State token for CSRF protection. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid provider name was supplied." | Invalid provider name format |
1605 (Invalid Input) | 400 | "An unknown provider name was supplied." | Provider not in allowed list |
1605 (Invalid Input) | 400 | "Cookies must be enabled and passed to this API." | Missing state cookie |
1680 (Access Denied) | 403 | "Permission was not granted by the provider." | OAuth error returned from provider |
1673 (SSO Auth Error) | 401 | "Invalid or missing input in a required field was received." | Missing code or state |
Notes
- Supported providers:
google,apple,microsoft. - GET generates a state token (requires cookies) and returns the redirect URL.
- POST exchanges the authorization code for tokens and creates/links the user account.
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
- Returns the schema/specifications for available asset types, not actual assets.
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
| Field | Type | Description |
|---|---|---|
| response.has_orgs | boolean | Whether the user has access to any organizations. |
| response.has_workspaces | boolean | Whether the user has access to any workspaces. |
| response.has_shares | boolean | Whether the user has access to any shares. |
| response.has_pending_invitations | boolean | Whether the user has any pending invitations awaiting their explicit acceptance. Computed for verified-email accounts only (false otherwise). |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1653 (User Not Found) | 404 | "Unable to fetch the user details." | User not found |
GET /current/user/{user_id}/details/
Get user profile details.
Auth: Required (JWT)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.user.id | string | 19-digit user ID. |
| response.user.account_type | string | "human" or "agent". |
| response.user.email_address | string | User's email address. |
| response.user.first_name | string | Given name. |
| response.user.last_name | string | Family name. |
| response.user.locked | boolean | Whether the account is locked. |
| response.user.profile_pic | string | Profile photo URL. |
Self-Only Fields (included only when viewing your own profile)
| Field | Type | Description |
|---|---|---|
| 2factor | boolean | Whether 2FA is enabled. |
| closed | boolean | Whether the account is closed. |
| country_code | string | Country of residence. |
| created | string | Registration date. |
| password_set | boolean | Whether the account has a password set (false = SSO-only). Self/manager view only. |
| phone_country | string | Phone country code. |
| phone_number | string | Phone number. |
| suspended | boolean | Suspension status. |
| tos_agree | string | ToS agreement date. |
| updated | string | Last profile update time. |
| valid_email | boolean | Email verified status. |
| valid_phone | boolean | Phone verified status. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1653 (User Not Found) | 404 | "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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1664 (Datastore Error) | 500 | "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
| Field | Type | Description |
|---|---|---|
| response.allowed | boolean | Whether the user's location allows resource creation. |
| response.reasons | array | Array 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."
}
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).
Response Fields
| Field | Type | Description |
|---|---|---|
| response.can_create_free_org | boolean | Whether the user can create a free organization. The free plan is retired for new orgs, so this is false. |
| response.existing_free_orgs | integer | Number of existing free organizations owned by the user. |
| response.cooldown_remaining | integer | Seconds remaining before next creation is allowed. |
| response.max_free_orgs | integer | Maximum number of free organizations allowed. |
| response.reason | string | Reason creation is not allowed. Only present when can_create_free_org is false. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1653 (User Not Found) | 404 | "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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| archived | string | No | "false" |
"true" to show archived shares, "false" to show non-archived. |
Request Example
curl -X GET "https://api.fast.io/current/user/me/list/shares/" \
-H "Authorization: Bearer {jwt_token}"
Success Response (200 OK)
{
"result": true,
"shares": [
{
"id": "1234567890123456789",
"name": "Project Files",
"type": "send",
"archived": false
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.shares | array | Array of share resource objects. Each includes parent workspace and org info. |
Notes
- Shares are gathered from three sources: owned by the user, invited to, and joined.
- Duplicates are removed by share ID.
- Does NOT include shares from workspaces the user has access to — only shares with direct user relationships.
GET /current/user/{user_id}/assets/
List set assets (e.g., profile photo) for a user.
Auth: Required (JWT)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| (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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1651 (Invalid Request Type) | 400 | "Only user may modify." | Non-owner attempting to modify |
1604 (File Missing) | 400 | "Asset upload missing" | No file in POST request |
1605 (Invalid Input) | 400 | "metadata invalid" | Invalid metadata parameter |
Notes
- Only the user themselves can modify their own assets.
- Uploading or deleting disables profile photo auto-sync.
GET|HEAD /current/user/{user_id}/assets/{asset_name}/read/
Read the binary content of a user asset.
Auth: Required (JWT)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {user_id} | string | Yes | 19-digit numeric user ID. |
| {asset_name} | string | Yes | Asset type name (e.g., profile_pic). |
Notes
- Returns raw binary bytes with appropriate content-type headers, not JSON.
- HEAD returns headers only.
Invitations
GET /current/user/invitation/{invitation_id}/details/
Get details for a specific invitation.
Auth: Required (JWT)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.invitation | object | Invitation resource. Embeds entity_type and the entity object (org/workspace/share). Includes invitation_key because this is the invitee's own authenticated view. |
| response.owner | object | User resource of the profile owner. |
| response.org | object or null | Org resource if the invitation is for an org-owned entity. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid invitation ID was supplied." | Invalid ID format |
1605 (Invalid Input) | 400 | "Invitation not found." | Invitation does not exist |
1654 (Internal Error) | 500 | "Failed to load the invitation profile or its owner." | Profile or owner load failure |
Notes
invitation_keyis included because this is the invitee's own authenticated view. It can be used with the per-entity join endpoints (POST /current/{org|workspace|share}/{entity_id}/members/join/{invitation_key}/{accept|decline}/), but prefer the by-id accept/decline endpoints below, which do not require the key.
GET /current/user/invitation/{invitation_id}/public/details/
Get public details for an invitation without authentication.
Auth: None (IP-throttled)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
- Returns a more limited view than the authenticated version.
- If the profile or owner cannot be loaded,
ownerwill benull. - The secret
invitation_keyis never included in this unauthenticated view.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.invitation | object | The updated invitation resource; state is now accepted. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid invitation ID was supplied." | Malformed ID |
1605 (Invalid Input) | 400 | "This invitation can no longer be accepted." | Already accepted/declined, or expired |
1609 (Not Found) | 404 | "Invitation not found." | No such invitation |
1680 (Access Denied) | 403 | "You are not authorized to act on this invitation." | The invitation is not addressed to the authenticated user |
Notes
- Requires a validated email; ownership is verified by matching the authenticated user's verified email (or user ID) to the invitation's invitee.
- On success the user is added as a member of the invitation's entity (org, workspace, or share).
- Idempotent: re-accepting an invitation you have already accepted returns success.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.invitation | object | The updated invitation resource; state is now declined. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid invitation ID was supplied." | Malformed ID |
1605 (Invalid Input) | 400 | "This invitation can no longer be declined." | Already accepted/declined |
1609 (Not Found) | 404 | "Invitation not found." | No such invitation |
1680 (Access Denied) | 403 | "You are not authorized to act on this invitation." | The invitation is not addressed to the authenticated user |
Notes
- A declined invitation is marked declined and no longer appears in
GET /current/user/invitations/list/; it will not reappear. - No membership is created. Idempotent: re-declining an already-declined (or expired) invitation returns success.
POST /current/user/invitations/acceptall/
Accept all pending invitations.
Auth: Required (JWT)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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
- If email is validated, all pending invitations matching that email are accepted.
- If email is not validated, use
invitation_keyto identify invitations.
GET /current/user/invitations/list/
List all pending invitations for the current user.
Auth: Required (JWT)
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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
| Field | Type | Description |
|---|---|---|
| response.invitations | array | Array of invitation resource objects. |
Notes
- Returns only pending invitations for the current user.
- Each invitation embeds its
entity_typeand the entity object (org/workspace/share) so cards can render without an extra details fetch. invitation_keyis included for the invitee's own list. Prefer acting on invitations with the by-idPOST /current/user/invitation/{invitation_id}/{accept|decline}/endpoints, which do not require the key.
User Authentication Endpoints
GET /current/user/auth/
Authenticate via HTTP Basic Auth. Returns JWT token.
Auth: HTTP Basic Auth (email:password)
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| 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 Example
curl -X GET "https://api.fast.io/current/user/auth/" \
-u "jane.doe@example.com:SecureP@ss123"
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:SecureP@ss123"
Success Response (200 OK)
{
"result": true,
"expires_in": 86400,
"auth_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"2factor": false
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.expires_in | integer | JWT token expiration time in seconds. |
| response.auth_token | string | JWT access token. If 2FA is enabled, has twofactor scope (restricted). Otherwise has user scope (full access). |
| response.2factor | boolean | true if 2FA is enabled. Token has limited scope until 2FA verification is completed. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1651 (Invalid Request Type) | 400 | "The expires time specified is invalid." | Invalid expires parameter |
1650 (Authentication Invalid) | 401 | "Your credentials were not supplied or invalid." | Missing Basic Auth header |
1650 (Authentication Invalid) | 401 | "Username is not valid." | Invalid email format |
1650 (Authentication Invalid) | 401 | "Password is not valid." | Invalid password format |
1650 (Authentication Invalid) | 401 | "Your credentials supplied are invalid." | Wrong password, unrecognized email, or SSO-only account (no password set) — identical response and matched timing prevent account enumeration |
1650 (Authentication Invalid) | 401 | "Your account is suspended..." | Account suspended (only after a correct password) |
1650 (Authentication Invalid) | 401 | "Your account is locked..." | Account locked |
1650 (Authentication Invalid) | 401 | "Your account is suspended due to abuse." | Account flagged for abuse |
1650 (Authentication Invalid) | 401 | "Your account is closed by you." | Account closed |
Notes
- Email tags (e.g.,
user+tag@example.com) are stripped before lookup. - If 2FA is enabled, complete the 2FA verification flow to upgrade the token.
- Account enumeration is intentionally not possible: an unknown email, an SSO-only account (no password), and a wrong password all return the identical
1650/401/"Your credentials supplied are invalid." response with matched timing. SSO-only accounts must sign in via their provider instead. - Pass
revocable=trueto mint a session-bound JWT that respects sign-out. The default (omitted) yields a stateless JWT that cannot be invalidated server-side and is appropriate for service-to-service tokens, AI tokens, and any non-browser caller.
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 (Bearer token — JWT or API key)
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
- Only affects JWTs minted with
revocable=trueonGET /current/user/auth/. API keys, OAuth/PKCE access tokens, agent tokens, and JWTs minted withoutrevocableare not session-bound and are unaffected. - The bump applies to all revocable tokens for this user across every browser and device. There is no per-device sign-out via this endpoint.
- The endpoint does not clear cookies or local storage on the client; the client is responsible for clearing local credential state after sign-out.
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
| Field | Type | Description |
|---|---|---|
| response.id | string | The 19-digit numeric user ID. |
Notes
- Lightweight health-check for token validity. Only validates that the JWT is structurally valid and not expired.
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
| Field | Type | Description |
|---|---|---|
| response.auth_type | string | Token type: "jwt_v2" (scoped JWT), "jwt_v1" (legacy JWT), "api_key" (unscoped API key), or "api_key_scoped" (API key with scopes). |
| response.scopes | array | Array 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_detail | array | Hydrated scope details with entity information. Empty when scopes are empty. |
| response.is_agent | boolean | Whether the token represents an agent. |
| response.agent_name | string or null | Agent display name. null if not set or not an agent. |
| response.full_access | boolean | Whether the token has unrestricted access. true for v1 JWTs and unscoped API keys. false for scoped API keys. |
API Keys
POST /current/user/auth/key/
Create a new API key.
Auth: Required (JWT, scope: user or admin)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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. |
| 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
| Field | Type | Description |
|---|---|---|
| response.api_key | string | The newly created API key. Only shown once — store it securely. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1650 (Authentication Invalid) | 401 | "Your credentials were not supplied or invalid." | Missing or invalid JWT |
1650 (Authentication Invalid) | 401 | "The scope of your credentials are not sufficient." | JWT scope not user or admin |
1667 (Max Limit) | 429 | "You are at the maximum number of API keys, {max}." | Maximum key limit reached |
1605 (Invalid Input) | 400 | "You provided an invalid Memo." | Invalid memo format |
Notes
- 2FA verification is required if 2FA is enabled.
- The full key value is only returned at creation time. Subsequent reads return masked versions.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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
| Field | Type | Description |
|---|---|---|
| response.api_key.id | string | Unique key identifier. |
| response.api_key.api_key | string | Masked API key (only last 4 characters visible). |
| response.api_key.memo | string | Key description/label. |
| response.api_key.created | string | Key creation timestamp in UTC. |
| response.api_key.scopes | string or null | JSON array of scope strings, or null for full access. |
| response.api_key.agent_name | string or null | Agent/application name, or null if not set. |
| response.api_key.expires | string or null | Expiration datetime in canonical Y-m-d H:i:s UTC format, or null for no expiration. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid Token to get details of." | Invalid key ID format |
1609 (Not Found) | 404 | Key not found | Key does not exist |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {key_id} | string | Yes | The API key's unique identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid Token to update." | Invalid key ID format |
1609 (Not Found) | 404 | Key not found | Key does not exist or belongs to another user |
1605 (Invalid Input) | 400 | Various | Invalid scopes, agent_name, or expires format |
Notes
- Only the fields you send are updated; omitted fields remain unchanged.
- Send empty string or
"null"to clear a nullable field. - 2FA verification is required if 2FA is enabled.
DELETE /current/user/auth/key/{key_id}/
Delete an API key.
Auth: Required (JWT, scope: user or admin)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "You provided an invalid Token to Delete." | Invalid key ID format |
1609 (Not Found) | 404 | "You provided a Token that was not found." | Key not found or belongs to another user |
1610 (Delete Failed) | 500 | "There was an error deleting the API Key." | Internal deletion failure |
Notes
- 2FA verification is required if 2FA is enabled.
- Returns "not found" if the key belongs to a different user (does not reveal ownership).
GET /current/user/auth/keys/
List all API keys for the user.
Auth: Required (JWT)
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
| Field | Type | Description |
|---|---|---|
| response.results | integer | Number of API keys. |
| response.api_keys | array or null | Array of API key objects, or null if none exist. |
| response.api_keys[].id | string | Unique key identifier. |
| response.api_keys[].api_key | string | Masked API key (only last 4 characters visible). |
| response.api_keys[].memo | string | Key description/label. |
| response.api_keys[].created | string | Key creation timestamp in UTC. |
| response.api_keys[].scopes | string or null | JSON array of scope strings, or null for full access. |
| response.api_keys[].agent_name | string or null | Agent/application name, or null if not set. |
| response.api_keys[].expires | string or null | Expiration 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
| Field | Type | Description |
|---|---|---|
| response.state | string | 2FA status: "enabled" (fully verified), "unverified" (added but not verified), or "disabled" (not configured). |
| response.totp | boolean | Whether 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| {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)
| Field | Type | Description |
|---|---|---|
| response.binding_uri | string | TOTP provisioning URI for QR code display. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1669 (Already Exists) | 409 | "2Factor already added, please remove first." | 2FA already enabled |
1605 (Invalid Input) | 400 | "An invalid channel was supplied." | Invalid channel name |
1658 (Not Acceptable) | 406 | "2Factor cannot be added, you need a valid phone_number and phone_country..." | No phone number configured |
Notes
- User must have a valid phone number and country code on their account before enabling 2FA (for non-TOTP channels).
- After adding 2FA, it enters
unverifiedstate. Must complete verification viaPOST /current/user/auth/2factor/verify/{token}/. - For TOTP, display the
binding_urias a QR code for the user to scan.
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)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid token was supplied to validate." | Invalid token format |
1658 (Not Acceptable) | 406 | "2Factor is not enabled." | 2FA not configured |
Notes
- If 2FA is already in the
enabledstate, returns success without modification. - This is the final step of the 2FA setup flow.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| {token} | string | Yes | Valid 2FA verification code (e.g., 6-digit TOTP or SMS code). |
Request Example
curl -X POST "https://api.fast.io/current/user/auth/2factor/auth/123456/" \
-H "Authorization: Bearer {twofactor_jwt_token}"
Success Response (200 OK)
{
"result": true,
"expires_in": 86400,
"auth_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.expires_in | integer | JWT expiration time in seconds. |
| response.auth_token | string | New JWT with full user scope. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid token was supplied to authenticate." | Invalid token format |
1658 (Not Acceptable) | 406 | "2Factor is not enabled on this account." | 2FA not enabled |
1658 (Not Acceptable) | 406 | "The supplied token failed to authenticate." | Wrong 2FA code |
1650 (Authentication Invalid) | 401 | "Internal Error." | JWT creation failure |
DELETE /current/user/auth/2factor/{token}/
Disable (remove) 2FA from the account.
Auth: Required (JWT, scope: user or admin)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | "An invalid token was supplied, valid token required to remove 2Factor." | Invalid token format |
1658 (Not Acceptable) | 406 | "The supplied token failed to authenticate." | Token verification failed |
1654 (Internal Error) | 500 | "2Factor could not be removed, please contact support." | Internal removal failure |
Notes
- If 2FA is
enabled(verified), a valid 2FA code is required to remove it. - If 2FA is
unverified, it can be removed without a code. - If 2FA is already disabled, returns success.
2FA Code Delivery Endpoints
Request a 2FA code via different channels. All require auth (accepts user, twofactor, or admin JWT scope).
/current/user/auth/2factor/send/sms/
Send code via SMS
/current/user/auth/2factor/send/call/
Send code via voice call
/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 Code | HTTP Status | Message | Cause |
|---|---|---|---|
1650 (Authentication Invalid) | 401 | "Your credentials were not supplied or invalid." | Invalid JWT |
1650 (Authentication Invalid) | 401 | "The scope of your credentials are not sufficient." | Wrong JWT scope |
1658 (Not Acceptable) | 406 | "2Factor is not enabled." | 2FA not configured on account |
Notes
- 2FA must be enabled (or in unverified state) for codes to be sent.
- Returns
result: falseif the code send fails (e.g., invalid phone number).
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
User Search
GET /current/users/search/
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| 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
| Field | Type | Description |
|---|---|---|
| response.contacts | object | Map of email address (key) to display name (value) for each matched user. Unchanged, backward-compatible. |
| response.users | array | List of matched people as {id, email, name} objects, deduplicated by email. Provides the user id the contacts map cannot. |
| response.users[].id | string | null | The 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[].email | string | The matched person's email address. |
| response.users[].name | string | The matched person's display name. |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1650 (Authentication Invalid) | 401 | "Authentication required" | Missing or invalid JWT token |
1605 (Invalid Input) | 400 | "This value should not be blank." | Missing or empty search parameter |
1654 (Internal Error) | 500 | "Internal error" | Search backend unavailable |
Notes
- Searches across two sources: the people you share access with and your contacts. Results are merged and deduplicated by email.
contactsis a flat email → name map kept for backward compatibility.usersis the richer, id-bearing list — prefer it when you need to act on a specific account.- A
users[].idis only present for matches reachable through a shared space; pure-contact matches carryid: null. When the same email matches both ways, the id-bearing entry wins.
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 400) 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 400) and aggregates every failed parameter so callers see all problems in one round trip. kind is one of missing, invalid, or type_mismatch. 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
| Code | Description | HTTP Status |
|---|---|---|
1600 | Internal Error | 500 Internal Server Error |
1605 | Invalid Input | 400 Bad Request |
1606 | Not Acceptable | 406 Not Acceptable |
1607 | Duplicate Entry / Already Exists | 409 Conflict |
1608 | Conflict | 409 Conflict |
1609 | Not Found / Resource Missing | 404 Not Found |
1610 | Delete Failed | 500 Internal Server Error |
1650 | Authentication Invalid | 401 Unauthorized |
1651 | Invalid Request Type | 400 Bad Request |
1653 | User Not Found | 404 Not Found |
1671 | Rate Limited | 429 Too Many Requests |
1680 | Access Denied | 403 Forbidden |
1670 | Restricted | 403 Forbidden |
1677 | Locked | 423 Locked |
1673 | SSO Auth Error | 401 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
- User IDs: 19-digit numeric string (e.g.,
"1234567890123456789") "me"can be used as user_id in user endpoints to reference the authenticated user- User endpoints also accept email address as an identifier
Token Types
| Type | Format | Lifetime | Use |
|---|---|---|---|
| JWT (Basic Auth) | RS256-signed JSON Web Token | Configurable (default varies) | General API access |
| JWT (OAuth) | RS256-signed JSON Web Token | 1 hour | OAuth-based API access |
| Refresh Token | Opaque string | Long-lived | Obtaining new access tokens (OAuth only) |
| API Key | Alphanumeric string | Configurable (default: no expiry) | Service-to-service communication. Optionally scoped with permissions, agent name, and expiration. |
Security Best Practices
- Always use HTTPS for all API communication.
- Store refresh tokens and API keys securely (OS keychain, encrypted storage).
- Never log tokens in client-side logs or analytics.
- Persist the
refresh_tokenfrom the response; it is long-lived and returned unchanged on refresh (no rotation needed). - Verify the
stateparameter in OAuth callbacks to prevent CSRF. - Handle 401 responses by attempting a token refresh; if refresh fails, re-authenticate.
- Revoke tokens on logout by calling the revoke endpoint and clearing local storage.