OAuth 2.0 PKCE flow, token exchange, session management, DCR, resource indicators

Base URL: https://api.fast.io/current/ Request format: application/x-www-form-urlencoded Response format: JSON

Overview

Fastio implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure authorization without passing user credentials through the client application. This is the recommended auth method for desktop apps, mobile apps, and MCP-connected agents.

Key characteristics:

Endpoint Summary

MethodPathAuthDescription
GET/.well-known/oauth-authorization-server/NoneRFC 8414 authorization server metadata
GET/.well-known/oauth-protected-resource/NoneRFC 9728 protected resource metadata
POST/current/oauth/register/NoneRFC 7591 dynamic client registration
PUT/current/oauth/register/Registration access token (Bearer)RFC 7592 update client registration
GET/current/oauth/authorize/NoneInitiate auth flow — 302 redirect to login page (or JSON with response_format=json)
POST/current/oauth/authorize/Session (JWT)Complete authorization, issue code
GET/current/oauth/authorize/info/NoneGet client info for consent screen
POST/current/oauth/token/NoneExchange code for tokens, or refresh tokens
POST/current/oauth/revoke/NoneRevoke a refresh token
GET/current/oauth/sessions/BearerList all active sessions
GET/current/oauth/sessions/{id}/BearerGet session details
PATCH/current/oauth/sessions/{id}/BearerUpdate session display names, or narrow its scopes
DELETE/current/oauth/sessions/{id}/BearerRevoke a specific session
DELETE/current/oauth/sessions/BearerRevoke all sessions
GET/current/auth/scopes/BearerToken scope introspection

Metadata Discovery

GET /.well-known/oauth-authorization-server/

RFC 8414 Authorization Server Metadata. Returns server configuration for automated client setup.

GET /.well-known/oauth-authorization-server/

Auth: None

curl -X GET "https://api.fast.io/.well-known/oauth-authorization-server/"

Response (200 OK):

{
  "issuer": "https://go.fast.io",
  "authorization_endpoint": "https://go.fast.io/api/current/oauth/authorize",
  "token_endpoint": "https://go.fast.io/api/current/oauth/token",
  "revocation_endpoint": "https://go.fast.io/api/current/oauth/revoke",
  "registration_endpoint": "https://go.fast.io/api/current/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["user", "org", "workspace", "all_orgs", "all_workspaces", "all_shares", "all_sign_envelopes"],
  "access_modes_supported": ["r", "rw", "rwa"],
  "client_id_metadata_document_supported": true,
  "resource_indicators_supported": true,
  "service_documentation": "https://go.fast.io/api/current/llms/"
}

Response Fields:

FieldTypeDescription
issuerstringAuthorization server issuer identifier URL
authorization_endpointstringURL of the authorization endpoint. The four endpoint URLs are emitted without a trailing slash.
token_endpointstringURL of the token endpoint
revocation_endpointstringURL of the token revocation endpoint
registration_endpointstringURL of the dynamic client registration endpoint
response_types_supportedarraySupported response types (code only)
grant_types_supportedarraySupported grant types (authorization_code, refresh_token)
token_endpoint_auth_methods_supportedarraySupported auth methods (none — public clients only)
code_challenge_methods_supportedarraySupported PKCE methods (S256 only)
client_id_metadata_document_supportedbooleanClient ID Metadata Document support (true) — an HTTPS URL may be used as client_id (see Client ID Metadata Document below)
resource_indicators_supportedbooleanRFC 8707 support (true)
scopes_supportedarrayOAuth scope type selectors supported by the authorization server (see the scope-type values table under GET /current/oauth/authorize/)
access_modes_supportedarrayAccess modes the authorization server accepts on access_mode: ["r","rw","rwa"]
service_documentationstringURL to service documentation

Clients validating access_modes_supported against {r, rw} must accept rwa. The list now carries a third mode — read, write and administer — so a client that rejects unknown values will fail against it. scopes_supported is unchanged: userdetails is an entity type inside a credential's scope list, never a scope type, and is never advertised here.

Error Responses:

Reading the error tables: the four-digit 16xx/17xx values below are HTTP-status classes, not error.code. The error.code a client actually receives is assigned per endpoint, so use the HTTP status as the gate and a documented error.code — five or six digits, plus the 9661-9669 family — only as a refinement. A 16xx value identifies the status class — useful for telling which kind of failure occurred — but comparing one against error.code will never match. Five- and six-digit codes (and the 9661-9669 family) are real error.code values. If you widen a check from a specific code to a status, widen what you assert with it — a status covers failures the narrower code did not, so a message written for that one code becomes a confident falsehood on the rest.

ScenarioHTTP StatusError
Wrong HTTP method40510003

GET /.well-known/oauth-protected-resource/

RFC 9728 Protected Resource Metadata. Returns resource server configuration.

GET /.well-known/oauth-protected-resource/

Auth: None

curl -X GET "https://api.fast.io/.well-known/oauth-protected-resource/"

Response (200 OK):

{
  "result": true,
  "resource": "https://mcp.fast.io/mcp",
  "authorization_servers": ["https://go.fast.io"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["user", "org", "workspace", "all_orgs", "all_workspaces", "all_shares", "all_sign_envelopes"]
}

Response Fields:

FieldTypeDescription
resultbooleantrue (this endpoint answers in the standard platform envelope)
resourcestringProtected resource identifier URL
authorization_serversarrayAuthorization server issuer URLs that can issue tokens for this resource
bearer_methods_supportedarrayMethods for presenting bearer tokens (header — Authorization header only)
scopes_supportedarrayOAuth scopes supported by this resource

Error Responses:

ScenarioHTTP StatusError
Wrong HTTP method40510003

Dynamic Client Registration

POST /current/oauth/register/

RFC 7591 Dynamic Client Registration. Register a new OAuth client programmatically.

POST /current/oauth/register/

Auth: None · Content-Type: application/json or application/x-www-form-urlencoded

Request Parameters:

ParameterTypeRequiredDefaultDescription
client_namestringNo"Unknown Client"Human-readable client name (max 128 bytes after trimming; reserved names are refused)
redirect_urisJSON arrayYes—Allowed redirect URIs. 1–10 URIs. HTTPS required (localhost/127.0.0.1 exempt). No fragment (#) components.
token_endpoint_auth_methodstringNo"none"Auth method (only public clients supported)
grant_typesJSON arrayNo["authorization_code", "refresh_token"]Requested grant types. Not validated: echoed in the response; the client can only ever use authorization_code and refresh_token
response_typesJSON arrayNo["code"]Requested response types. Not validated: echoed in the response; only code is supported
curl -X POST "https://api.fast.io/current/oauth/register/" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["http://localhost:3000/callback", "http://127.0.0.1:3000/callback"]
  }'

Response (200 OK):

{
  "result": true,
  "client_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "client_name": "My MCP Client",
  "redirect_uris": [
    "http://localhost:3000/callback",
    "http://127.0.0.1:3000/callback"
  ],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "registration_access_token": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "registration_client_uri": "https://go.fast.io/api/current/oauth/register/"
}

Response Fields:

FieldTypeDescription
resultbooleantrue on success
client_idstringAssigned client identifier for OAuth flows (32-character lowercase-hex string, no prefix)
client_namestringRegistered client name
redirect_urisarrayRegistered redirect URIs
token_endpoint_auth_methodstringToken endpoint auth method ("none")
grant_typesarrayThe grant_types sent in the request (or the default)
response_typesarrayThe response_types sent in the request (or the default)
registration_access_tokenstringOne-time token for managing registration via PUT (64-character lowercase-hex string, no prefix). Shown once only — store securely. Server stores only a SHA-256 hash.
registration_client_uristringURI for managing this client registration

Error Responses (RFC 7591 format — bare JSON, no platform envelope):

Error CodeHTTP StatusDescription
invalid_client_metadata400client_name exceeds 128 bytes
invalid_client_metadata400client_name is a reserved name
invalid_client_metadata400redirect_uris is not a valid JSON array
invalid_client_metadata400token_endpoint_auth_method is not "none"
invalid_redirect_uri400redirect_uris must contain 1–10 entries
invalid_redirect_uri400A redirect_uris entry is not a non-empty string
invalid_redirect_uri400Redirect URI is not a valid URL
invalid_redirect_uri400Redirect URI contains a fragment (#)
invalid_redirect_uri400Redirect URI must use HTTPS (except localhost)
invalid_request400redirect_uris is missing
invalid_request400JSON request body is not a JSON object, or is larger than 64 KB
server_error500Failed to register client

PUT /current/oauth/register/

RFC 7592 Dynamic Client Registration Management. Update an existing client registration. Requires the registration_access_token from the POST registration response. Only clients whose redirect URIs all point to localhost, 127.0.0.1, [::1], or ::1 may self-update.

PUT /current/oauth/register/

Auth: Registration access token (Bearer) · Content-Type: application/json or application/x-www-form-urlencoded

Request Parameters:

ParameterTypeRequiredDescription
client_idstringYesMust match a registered client
client_namestringNoUpdated client name (max 128 characters)
redirect_urisJSON arrayNoUpdated redirect URIs (full replacement, same validation as POST)

At least one of client_name or redirect_uris must be provided.

curl -X PUT "https://api.fast.io/current/oauth/register/" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -d '{
    "client_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "client_name": "My Updated MCP Client",
    "redirect_uris": ["http://localhost:8080/callback"]
  }'

Response (200 OK):

{
  "result": true,
  "client_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "client_name": "My Updated MCP Client",
  "redirect_uris": ["http://localhost:8080/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Response Fields:

FieldTypeDescription
resultbooleantrue on success
client_idstringClient identifier (unchanged)
client_namestringUpdated client name
redirect_urisarrayUpdated redirect URIs
token_endpoint_auth_methodstringAuth method (unchanged, always "none")
grant_typesarrayGrant types (unchanged)
response_typesarrayResponse types (unchanged)

Error Responses (RFC 7591 format — bare JSON):

Error CodeHTTP StatusDescription
invalid_request400client_id missing
invalid_client_metadata400client_name is a reserved name
invalid_request400Neither client_name nor redirect_uris provided
invalid_request400Only localhost clients can self-update
invalid_request401Invalid or missing registration access token
invalid_client404Client not found or disabled
invalid_client_metadata400Invalid client metadata
invalid_redirect_uri400Invalid redirect URIs (same rules as POST)
server_error500Failed to update registration

The grant_types, response_types, and token_endpoint_auth_method fields cannot be changed via self-update. If redirect_uris is provided, it fully replaces the existing set. If only client_name is provided, existing redirect URIs are preserved.

Client ID Metadata Document (CIMD)

CIMD allows OAuth clients to use an HTTPS URL as their client_id instead of pre-registering or using Dynamic Client Registration. The authorization server fetches metadata from the URL to get client information on-the-fly. This is the MCP specification's preferred client registration method.

How It Works

  1. Detection: If the client_id parameter starts with https://, the server treats it as a CIMD URL.
  2. Fetch: The server fetches the JSON metadata document from the CIMD URL.
  3. Validation: The document must contain:
    • client_id matching the fetched URL exactly
    • redirect_uris array containing the requested redirect URI, with 1–10 entries that each pass the same redirect-URI rules as dynamic registration
    • grant_types including authorization_code
    • response_types including code
    • token_endpoint_auth_method set to none
  4. Caching: Validated documents are cached for 1 hour to avoid repeated fetches.
  5. Flow: The CIMD URL is used as the client_id throughout the authorization flow (authorize, token exchange, refresh).

CIMD Document Format

The metadata document is a JSON file served at an HTTPS URL with Content-Type: application/json:

{
  "client_id": "https://example.com/oauth/client-metadata",
  "client_name": "Example App",
  "redirect_uris": ["http://localhost:8080/callback"],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

Optional fields: client_uri (URL to the client's home page), logo_uri (URL to the client's logo image).

Security Constraints

ConstraintValue
ProtocolHTTPS only (HTTP URLs rejected)
Fetch timeout5 seconds
Max document size10 KB
Cross-domain redirectsRejected
Cache duration1 hour

Using CIMD in the Authorization Flow

Use the CIMD URL directly as the client_id parameter:

GET /current/oauth/authorize/?response_type=code&client_id=https://example.com/oauth/client-metadata&redirect_uri=http://localhost:8080/callback&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=xyz123

The token endpoint also uses the CIMD URL as client_id — it matches by string comparison against the value stored during authorization.

Error Scenarios

ScenarioError
CIMD URL is not HTTPS1605 (Invalid Input)
Cannot fetch the document (timeout, DNS failure, non-200 response)1605 (Invalid Input)
Document is not valid JSON1605 (Invalid Input)
client_id in document does not match the URL1605 (Invalid Input)
Required fields missing or invalid1605 (Invalid Input)
redirect_uri not found in document's redirect_uris1605 (Invalid Input)

Complete PKCE Flow

Step 0: Discover Server Configuration (Optional)

Fetch server metadata for automated setup:

1. CLIENT -> API: Discover authorization server
   GET /.well-known/oauth-authorization-server/
   -> Returns endpoints, supported grant types, PKCE methods

2. CLIENT -> API: Discover protected resource (if needed)
   GET /.well-known/oauth-protected-resource/
   -> Returns resource URL and authorization servers

Step 0b: Register Client (If Needed)

If the client does not have a registered client_id, there are two options:

Option A: Use a CIMD URL as client_id — If the client publishes a metadata document at an HTTPS URL, use that URL directly as the client_id. No registration step needed.

Option B: Use Dynamic Client Registration:

CLIENT -> API: Register client
POST /current/oauth/register/
Content-Type: application/json

{"client_name": "My Agent", "redirect_uris": ["http://localhost:8080/callback"]}

-> Returns client_id, redirect_uris, registration_access_token, etc.

Step 1: Generate PKCE Parameters (Client-Side)

Before initiating the flow, generate the PKCE code verifier and challenge:

code_verifier = random_string(43-128 characters, URL-safe: [A-Za-z0-9-._~])
code_challenge = base64url_encode(sha256(code_verifier))
code_challenge_method = "S256"

The code_challenge will always be exactly 43 characters.

Step 2: Initiate Authorization

GET /current/oauth/authorize/

Initiates the authorization flow. By default, issues a 302 Found redirect to the login/consent page. This is the browser-facing entry point that MCP clients open in the user's browser.

GET /current/oauth/authorize/

Auth: None

JSON mode: Add response_format=json to receive a JSON response instead of a redirect (for programmatic callers).

Query Parameters:

ParameterTypeRequiredDefaultDescription
client_idstringYes—Registered OAuth client ID, or an HTTPS URL pointing to a CIMD
redirect_uristringYes—Must match a registered URI for the client
response_typestringNo—Send "code" (the only supported type); the value is not currently checked
code_challengestringYes—Exactly 43 characters (BASE64URL-encoded SHA-256)
code_challenge_methodstringYes—Must be "S256"
statestringYes—Opaque value for CSRF protection, returned unchanged in callback
scopestringNo"user"Scope type selector (see scope values table)
access_modestringNo— (consent then treats the ceiling as rw)"r", "rw" or "rwa". The ceiling for the whole authorization — consent may narrow it, never widen it. An empty value is treated as omitted; any other non-empty value returns 406 180242
account_settingsstringNo"0""1" to allow account-settings access (userdetails:*:rw) to be granted at consent, "0", an empty value, or omission for off. Any other value — including "true" and "false" — returns 406 117053
resourcestringNo—RFC 8707 resource indicator (e.g., https://mcp.fast.io/mcp)
agent_namestringNo—Display name of the requesting agent (truncated to 128 characters; reserved names are refused with 406)
response_formatstringNo—Set to "json" for JSON response instead of 302 redirect
display_codestringNofalseCopy-paste mode for clients that cannot receive a redirect (headless CLIs, remote agents): "true" or "false" ("1" / "0" also accepted; anything else is refused with 406). After approval the browser shows the authorization code for the user to paste back into the client instead of redirecting to redirect_uri. redirect_uri is still required, must still be registered, and must be sent unchanged to the token endpoint. Recorded with the request: GET /current/oauth/authorize/info/ and the consent response then report redirect_mode: "display_code", and the returned login_url carries the mode

Scope type values (scope parameter):

ValueBehavior
userFull access (default, backward compatible with v1.0 JWT)
orgUser picks specific organizations
workspaceUser picks specific workspaces
all_orgsWildcard access to all user's organizations
all_workspacesWildcard access to all user's workspaces
all_sharesAll shares the user is a member of
all_sign_envelopesWildcard access to all sign envelopes the user can reach

These seven are the complete offered set — they match the scopes_supported array in the authorization-server metadata (GET /.well-known/oauth-authorization-server). Request one of these named values, or omit scope for the default user. Values are case-sensitive. Words outside this set are ignored only if they are the OpenID Connect words openid, profile, email or offline_access; a request made only of those is treated like an omitted scope (the default user type at the consented access mode). A scope that names none of the seven otherwise is refused with a 406 input error (invalid_scope); the one exception is the retired all_workflows value, which is accepted here but never grants anything (consent fails). If several recognised values are sent space-separated, the broadest one is used. Always read scopes in the token response for what was actually granted.

access_mode and account_settings are ceilings recorded with the authorization request. They are set here, on the initiate request, and the consent step (POST /current/oauth/authorize/) can only narrow them. access_mode bounds the access mode of every granted scope; account_settings decides whether account-settings access may be granted at all. A client that never sends them cannot obtain administration (rwa) or account settings through OAuth — administration is opt-in on a request the client made explicitly, and the consent screen never offers it unprompted.

Retired scope — all_workflows: no longer offered. The Workflows feature has been removed, so all_workflows is deliberately omitted from scopes_supported and must not be requested. The authorization server retains it only as a fail-closed tombstone, so a pre-existing token that still carries it resolves to nothing rather than erroring — it is not a capability you can request. (This is why the offered set is the seven above, not eight.)

# Standard browser flow (302 redirect)
curl -v "https://api.fast.io/current/oauth/authorize/?client_id=my-app&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=abc123xyz"

# JSON mode (programmatic)
curl "https://api.fast.io/current/oauth/authorize/?client_id=my-app&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=abc123xyz&response_format=json"

Response (default — 302 Found):

Location: https://go.fast.io/connect?auth_request_id={auth_request_id}

Response (response_format=json — 200 OK):

{
  "result": true,
  "auth_request_id": "{auth_request_id}",
  "client_name": "My App",
  "scope": "user",
  "login_url": "https://go.fast.io/connect?auth_request_id={auth_request_id}"
}

Response Fields (JSON mode):

FieldTypeDescription
resultbooleantrue on success
auth_request_idstring64-character hex identifier for this authorization request (valid for 10 minutes)
client_namestringHuman-readable name of the OAuth client application
scopestringThe requested scope type as resolved at initiate: the broadest recognised value, or user when scope was omitted (a value made only of ignored OpenID Connect words is echoed as sent). This is the request, not the grant — read scopes in the token response
agent_namestringAgent display name (present if agent_name was provided)
login_urlstringThe URL to open in the user's browser for this authorization — the same page the default 302 redirects to. Open it verbatim; do not build it yourself from auth_request_id. The user signs in there (on the Fastio sign-in page when they use email and password) and approves access

Error Responses (standard platform envelope):

ScenarioError CodeHTTP Status
client_id missing1605 (Invalid Input)406
redirect_uri missing1605 (Invalid Input)406
code_challenge missing1605 (Invalid Input)406
code_challenge_method missing1605 (Invalid Input)406
code_challenge_method not "S256"1605 (Invalid Input)406
code_challenge not 43 characters1605 (Invalid Input)406
state missing1605 (Invalid Input)406
client_id not found1605 (Invalid Input)406
Client is disabled1605 (Invalid Input)406
redirect_uri mismatch1605 (Invalid Input)406
Invalid resource indicator1605 (Invalid Input)406
scope names none of the seven scope types and is not made only of ignored OpenID Connect words (invalid_scope)138232406
agent_name is a reserved name125566406
display_code not "true", "false", "1" or "0"109495406
CIMD client_id document could not be validated106954406
access_mode non-empty and not "r", "rw" or "rwa"180242406
account_settings non-empty and not "1" or "0"117053406
Internal storage failure1654 (Internal Error)500

POST /current/oauth/authorize/

Complete authorization after user login. Issues the authorization code. Called by the web application (not the client directly) with the user's active session.

POST /current/oauth/authorize/

Auth: Required (JWT — website session)

Request Parameters:

ParameterTypeRequiredDefaultDescription
auth_request_idstringYes—64-character hex authorization request ID from the GET request
scopesstringConditional—JSON array of entity IDs or scope strings (e.g., "[3814271023567182934, 3829104758203948571]" or '["org:3814271023567182934:rw"]'). Must be a non-empty JSON list when supplied, and is required for scope=org and scope=workspace
access_modestringNothe value stored at initiate"r", "rw" or "rwa" — may only narrow the initiate value, never widen it
account_settingsstringNo"0""1" appends userdetails:*:rw server-side, and only when the authorization was initiated with account_settings=1
agent_namestringNo—Override the agent display name from the GET request (max 128 characters)

Consent rules — scopes, access mode and account settings:

curl -X POST "https://api.fast.io/current/oauth/authorize/" \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "auth_request_id={auth_request_id}"

Response (200 OK):

{
  "result": true,
  "redirect_uri": "http://localhost:8080/callback?code=abc123def456abc123def456abc123def456abc123def456abc123def456abc123de&state=abc123xyz",
  "redirect_mode": "redirect"
}

Response Fields:

FieldTypeDescription
resultbooleantrue on success
redirect_uristringFull redirect URI with code (64 hex chars, valid 5 min, single-use) and state query parameters
redirect_modestring"redirect" for HTTP/HTTPS redirect URIs, "button" for custom scheme URIs, "display_code" when the authorization was initiated with display_code=true (show the code from redirect_uri for the user to copy instead of navigating)
button_labelstring or nullCustom button label for "button" redirect mode (if configured)

Error Responses:

ScenarioError CodeHTTP Status
User not authenticated1650 (Authentication Invalid)401
auth_request_id missing1605 (Invalid Input)406
Request expired, not found, or already consented to1609 (Not Found)404
agent_name override is a reserved name108127406
Invalid access_mode1605 (Invalid Input)406
Scope validation failed1605 (Invalid Input)406
Unrecognised access_mode value161579406
access_mode broader than the initiate value, a client-supplied userdetails scope, or account_settings=1 without it at initiate10768 (access_mode_exceeds_initiate)403
scopes not a non-empty JSON list168998 / 128552406
scopes omitted for scope=org or scope=workspace121987406
Consent made with a narrowed (non-account-level) credential10175403
Granted scopes exceed the consenting credential10768 (scope_exceeds_issuer)403
More than 20 scopes after the server-side append157349406
Granted scope exceeds the governing org's credential_policy (error.params.reason credential_policy_mode or credential_policy_scope)116292403
The governing org's stored credential_policy is corrupt (error.params.reason credential_policy_unreadable)116292403, permanent
A granted scope names an entity whose owning org no longer exists (error.params.reason credential_policy_org)116292403, permanent
The governing org, or its credential_policy, could not be READ (error.params.reason credential_policy_unavailable)116292503, retry
Internal processing error1654 (Internal Error)500

GET /current/oauth/authorize/info/

Validate an authorization request and return client information (app name, requested scope). Used by the web frontend to display a consent screen before the user confirms.

GET /current/oauth/authorize/info/

Auth: None

Query Parameters:

ParameterTypeRequiredDescription
auth_request_idstringYesThe authorization request ID to validate
curl "https://api.fast.io/current/oauth/authorize/info/?auth_request_id={auth_request_id}"

Response (valid request — 200 OK):

{
  "result": true,
  "valid": true,
  "client_name": "My App",
  "scope": "user",
  "agent_name": "My MCP Agent",
  "redirect_mode": "redirect",
  "redirect_uri": "http://localhost:8080/callback",
  "resource": "https://mcp.fast.io/mcp",
  "access_mode": "rw",
  "account_settings": false
}

resource is the RFC 8707 resource indicator the client supplied at initiate (or null), access_mode is the requested access mode ("r", "rw" or "rwa", or null when the client did not supply one), and account_settings is a boolean that is always present. All three are returned server-authoritatively from the stored authorization request, so consent screens can render them without trusting URL parameters.

Show the account-settings toggle only when account_settings is true, and default it to off. When it is false the authorization was not initiated for account settings, and consent cannot grant them.

Response (invalid or expired — 200 OK):

{
  "result": true,
  "valid": false
}

Response Fields:

FieldTypeDescription
resultbooleanAlways true
validbooleantrue if the auth request is valid and client is active
client_namestringClient name (only when valid is true)
scopestringRequested scope (only when valid is true)
agent_namestringAgent display name (only when set and valid is true)
redirect_modestring"redirect", "button", or "display_code" when the authorization was initiated with display_code=true (only when valid is true)
button_labelstringCustom button label (only when configured and valid is true)
redirect_uristringClient redirect URI (only when valid is true)
resourcestring or nullThe RFC 8707 resource indicator recorded at initiate, or null when the client supplied none. Always present when valid is true
access_modestring or nullRequested access mode — "r" (read-only), "rw" (read-write) or "rwa" (read-write-administer) — or null when the client supplied none. It is the ceiling for consent. Always present when valid is true
account_settingsbooleanWhether the authorization was initiated with account_settings=1, i.e. whether account-settings access (userdetails:*:rw) may be granted at consent. Always present when valid is true; render the toggle only when it is true, defaulted to off

This endpoint never returns an error for invalid auth_request_id. It returns valid: false instead, preventing information leakage about authorization request existence.

Step 3: User Approves in Browser

The user opens the authorization URL (the login_url from JSON mode, or the 302 target), signs in (supports SSO; email-and-password users sign in on the Fastio sign-in page, which also handles 2FA), and approves access. The browser either:

Loopback redirect URIs (native apps, RFC 8252). A client registered with an http loopback redirect URI such as http://127.0.0.1:8080/callback may send the same URI with any port at authorize time, so a CLI or desktop app can listen on an ephemeral port. Only the port may differ — scheme, host, path and query must match the registered URI exactly, and localhost and 127.0.0.1 are different hosts. The token request must send exactly the redirect_uri used at authorize, including the port.

Step 4: Exchange Code for Tokens

POST /current/oauth/token/

Exchange an authorization code for access and refresh tokens, or refresh an existing access token.

POST /current/oauth/token/

Auth: None · Content-Type: application/x-www-form-urlencoded

Important: This endpoint returns bare JSON responses (RFC 6749 format, no platform envelope) for compatibility with standard OAuth clients, including MCP hosts.

Authorization Code Exchange

Request Parameters:

ParameterTypeRequiredDescription
grant_typestringYesMust be "authorization_code"
codestringYes64-character hex authorization code from the callback
code_verifierstringYesOriginal PKCE code verifier (43–128 characters, [A-Za-z0-9-._~])
client_idstringYesMust match original request
redirect_uristringYesMust match original request
resourcestringNoRFC 8707 resource indicator URL (must match authorize request)
device_namestringNoHuman-readable device name for session tracking
device_typestringNoDevice category for session tracking
curl -X POST "https://api.fast.io/current/oauth/token/" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=abc123def456abc123def456abc123def456abc123def456abc123def456abc123de&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&client_id=my-app&redirect_uri=http://localhost:8080/callback"

Response (200 OK — bare JSON, no envelope):

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "{refresh_token}",
  "scope": "org",
  "scopes": "[\"org:3814271023567182934:rw\",\"org:3829104758203948571:rw\"]",
  "agent_name": "My MCP Agent"
}

Response Fields:

FieldTypeDescription
access_tokenstringJWT for API requests. Use as Authorization: Bearer {access_token}. Expires in 1 hour.
token_typestringAlways "Bearer"
expires_inintegerToken lifetime in seconds (3600 = 1 hour)
refresh_tokenstringLong-lived opaque token (64-character hex string) for obtaining new access tokens. Store securely. Returned unchanged on refresh (no rotation).
scopestringThe scope type of the grant, derived from the granted scopes (not echoed from the request): user, all_orgs, all_workspaces, all_shares, all_sign_envelopes, org or workspace. A grant spanning several types reports the broadest; a grant on specific shares or sign envelopes reports all_shares / all_sign_envelopes. On refresh it reflects any narrowing. Not a scope list — persist scopes.
scopesstringJSON-encoded array of scope strings in entity_type:entity_id:access_mode format. Emitted for every grant — a plain scope=user grant is stored explicitly as ["user:*:rw"] and reports it here. Persist this, not scope
agent_namestringAgent display name (v2.0 only, present when set during authorization)

scope versus scopes. scope (singular) is a single scope-type word summarising the grant; scopes (plural) is a JSON-encoded string listing the granted entity scopes. Persist scopes, not scope.

scopes is now returned for every grant, including a plain scope=user grant that previously carried no list — such a grant is stored explicitly as ["user:*:rw"]. Clients must not treat a missing scopes as “this is a full session”, and must not branch on auth_type === "jwt_v1": those grants now report jwt_v2 at introspection.

This response carries no admin and no legacy field — derive administrative capability from GET /current/auth/scopes/.

Refresh Token Exchange

ParameterTypeRequiredDescription
grant_typestringYesMust be "refresh_token"
refresh_tokenstringYesCurrent valid refresh token
client_idstringYesMust match the original token request
device_namestringNoUpdated device name for session tracking
device_typestringNoUpdated device type for session tracking
curl -X POST "https://api.fast.io/current/oauth/token/" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token={refresh_token}&client_id=my-app"

Response (200 OK — bare JSON): Same format as authorization code exchange. Returns a new access_token. The refresh_token is long-lived and is returned unchanged (no per-refresh rotation).

Refresh narrows a demoted administrative grant. A grant naming a concrete entity at rwa whose human is no longer an administrator of it comes back as rw, and the response's scopes echoes the narrowed set — which is then what the session holds. Wildcard rwa grants are never narrowed this way; they are capped live on every request against the human's role. Losing access to an entity entirely still revokes, as before.

Error Responses (RFC 6749 SS5.2 format — bare JSON):

{
  "error": "invalid_grant",
  "error_description": "The authorization code is invalid or has expired."
}
ScenarioRFC 6749 ErrorHTTP Status
Missing/invalid parametersinvalid_request400
Invalid grant_type valueunsupported_grant_type400
code_verifier invalid length (not 43–128 chars)invalid_request400
Unrecognized resource indicatorinvalid_request400
Invalid, expired or already-used authorization code (codes are single-use: a repeated or concurrent exchange of the same code is refused)invalid_grant400
PKCE code_verifier mismatchinvalid_grant400
client_id mismatchinvalid_grant400
redirect_uri mismatchinvalid_grant400
Resource indicator mismatch (RFC 8707)invalid_grant400
Invalid/expired/revoked refresh tokeninvalid_grant400
client_id mismatch on refreshinvalid_grant400
Inactive user accountinvalid_grant400
Per-user active-session cap reached on authorization-code exchange (error_description begins "You have reached the maximum number of active connections") — revoke an existing session via DELETE /current/oauth/sessions/{session_id}/ and retryinvalid_grant400
Internal server failureserver_error500

Resource Indicator Enforcement: The resource value is stored in the authorization code during the authorize step. At token exchange, the value is strictly compared. If they do not match — including if one is null and the other is not — the exchange fails. This prevents downgrade attacks where a client omits the resource to obtain an unrestricted token.

Step 5: Use the Access Token

Include the access token in all API requests:

Authorization: Bearer {access_token}

When the access token expires (after 1 hour), use the refresh token to get a new one without requiring user interaction. Refresh proactively before expiration (5-minute buffer recommended) to avoid interruptions.

Token Revocation

POST /current/oauth/revoke/

Revoke a refresh token (logout). Implements RFC 7009 (OAuth 2.0 Token Revocation). Always returns success to prevent token enumeration attacks.

POST /current/oauth/revoke/

Auth: None · Content-Type: application/x-www-form-urlencoded

Request Parameters:

ParameterTypeRequiredDescription
tokenstringYesThe refresh token to revoke
curl -X POST "https://api.fast.io/current/oauth/revoke/" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token={refresh_token}"

Response (200 OK):

{
  "result": true
}

Error Responses (RFC 6749 format — bare JSON):

ScenarioErrorHTTP Status
token parameter missinginvalid_request400

Per RFC 7009, this endpoint always returns success regardless of whether the token was found, was already revoked, or never existed. Always call this endpoint on user logout and clear local token storage regardless of the response.

Session Management

OAuth sessions represent active token grants. Each authorization code exchange creates a session. Sessions have a stable session_id (32-character hex string) that persists across refreshes.

GET /current/oauth/sessions/

List all active (non-expired, non-revoked) OAuth sessions for the authenticated user.

GET /current/oauth/sessions/

Auth: Required (Bearer JWT)

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

Response (200 OK):

{
  "result": true,
  "sessions": [
    {
      "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
      "client_id": "my-app",
      "scopes": "[\"org:3814271023567182934:rw\"]",
      "agent_name": "My MCP Agent",
      "device_name": "Chrome on macOS",
      "device_type": "desktop",
      "ip_address": "203.0.113.42",
      "last_used": "2026-01-22 14:00:00 UTC",
      "created": "2026-01-21 14:00:00 UTC",
      "expires": "2036-01-21 14:00:00 UTC",
      "admin": false,
      "legacy": false,
      "country": "US",
      "last_ip": "203.0.113.42",
      "last_country": "US",
      "mcp": false
    }
  ],
  "count": 1
}

Response Fields:

FieldTypeDescription
resultbooleantrue on success
sessionsarrayList of active session objects
sessions[].session_idstring32-character hex session identifier
sessions[].client_idstringOAuth client that created the session
sessions[].scopesstring or nullJSON-encoded array of granted scope strings in entity_type:entity_id:access_mode format — the same encoding as scopes in the token response, so parse it. A scope=user grant is stored explicitly as "[\"user:*:rw\"]"; null means a legacy session that declares no scopes claim at all.
sessions[].agent_namestring or nullAgent display name if set during authorization, otherwise null
sessions[].device_namestring or nullHuman-readable device description (the device_name the client sent, or set via PATCH), otherwise null
sessions[].device_typestring or nullDevice category as sent by the client in device_type at the token endpoint (free-form, e.g. desktop), otherwise null
sessions[].ip_addressstring or nullIP address from the most recent refresh
sessions[].last_usedstring or nullDatetime of last token refresh (YYYY-MM-DD HH:MM:SS UTC), null if never refreshed
sessions[].createdstringDatetime when the session was created (YYYY-MM-DD HH:MM:SS UTC)
sessions[].expiresstringDatetime when the session expires (YYYY-MM-DD HH:MM:SS UTC)
sessions[].adminbooleanWhether the session carries at least one rwa scope, i.e. whether it can perform administrative operations (still capped by the human's live role)
sessions[].legacybooleanWhether the session declares no scopes claim at all. A legacy OAuth session behaves as user:*:rw: whole-account read and write, no administration, no account settings. legacy never implies admin, and admin never implies legacy
sessions[].countrystring or nullISO-3166 alpha-2 country of the IP that created the session. Set once, never updated. null for a session minted before this field existed.
sessions[].last_ipstring or nullClient IP of the most recent refresh. Written periodically, not on every refresh. null if never refreshed since tracking began.
sessions[].last_countrystring or nullISO-3166 alpha-2 country of last_ip. Same write cadence.
sessions[].mcpbooleantrue when this grant's OAuth resource (token audience) is the Fastio MCP server; false for a grant whose resource is an ordinary API audience, and false when no resource was recorded on the grant at all. Matches the mcp field on org credential-inventory rows (see Compliance & Audit in Organizations).
countintegerTotal number of active sessions

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
Internal error1654 (Internal Error)500

GET /current/oauth/sessions/{session_id}/

Get details of a specific OAuth session. The session must belong to the authenticated user.

GET /current/oauth/sessions/{session_id}/

Auth: Required (Bearer JWT)

Path Parameters:

ParameterTypeRequiredDescription
session_idstringYes32 hexadecimal characters
curl -X GET "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "session": {
    "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
    "client_id": "my-app",
    "scopes": "[\"org:3814271023567182934:rw\"]",
    "agent_name": "My MCP Agent",
    "device_name": "Chrome on macOS",
    "device_type": "desktop",
    "ip_address": "203.0.113.42",
    "last_used": "2026-01-22 14:00:00 UTC",
    "created": "2026-01-21 14:00:00 UTC",
    "expires": "2036-01-21 14:00:00 UTC",
    "admin": false,
    "legacy": false,
    "country": "US",
    "last_ip": "203.0.113.42",
    "last_country": "US",
    "mcp": false
  }
}

Field meanings are the same as the list response above (country = creation IP, never updated; last_ip/last_country = last refresh, written periodically, not on every refresh; mcp = whether this grant's audience is the Fastio MCP server).

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
session_id missing1605 (Invalid Input)406
session_id not 32 hex chars1605 (Invalid Input)406
Session not found or wrong user1609 (Not Found)404
Internal error1654 (Internal Error)500

PATCH /current/oauth/sessions/{session_id}/

Update the device_name, agent_name and/or scopes of a specific OAuth session. The session must belong to the authenticated user and must not be revoked.

PATCH /current/oauth/sessions/{session_id}/

Auth: Required (Bearer JWT)

Path Parameters:

ParameterTypeRequiredDescription
session_idstringYes32 hexadecimal characters

Body Parameters:

ParameterTypeRequiredDescription
device_namestringNoNew device name (max 128 characters; empty string clears to null)
agent_namestringNoNew agent name (max 128 characters; empty string clears to null)
scopesstringNoJSON-encoded list of scope strings, the same shape as an API key's. Must be narrower than or equal to both what the session already holds and what the calling credential holds

At least one of device_name, agent_name or scopes must be provided, so a scopes-only request is valid.

Narrowing only — there is no widening path. Submitted scopes are checked against the session's current scopes and against the credential making the request; a broader set is refused with 403 and 10768 (error.params.reason: scope_exceeds_issuer). An empty list ([]) — or a value that does not parse as a list of scope strings — is refused with 406 136396 — to end a session entirely use DELETE /current/oauth/sessions/{session_id}/. A narrowing takes effect for the access token at the next refresh.

curl -X PATCH "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "device_name=My%20Work%20Laptop"

Response (200 OK):

{
  "result": true,
  "session": {
    "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
    "client_id": "my-app",
    "scopes": "[\"org:3814271023567182934:rw\"]",
    "agent_name": "My MCP Agent",
    "device_name": "My Work Laptop",
    "device_type": "desktop",
    "ip_address": "203.0.113.42",
    "last_used": "2026-01-22 14:00:00 UTC",
    "created": "2026-01-21 14:00:00 UTC",
    "expires": "2036-01-21 14:00:00 UTC",
    "admin": false,
    "legacy": false,
    "country": "US",
    "last_ip": "203.0.113.42",
    "last_country": "US",
    "mcp": false
  }
}

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
session_id missing1605 (Invalid Input)406
session_id not 32 hex chars1605 (Invalid Input)406
None of device_name, agent_name or scopes provided1605 (Invalid Input)406
scopes broader than the session or than the calling credential10768 (scope_exceeds_issuer)403
scopes submitted as an empty list ([]) or unparseable136396406
scopes no longer grantable to this user176886406
The session's scopes changed while the request was in flight — re-read the session and retry172160409
device_name exceeds 128 chars1605 (Invalid Input)406
agent_name exceeds 128 chars1605 (Invalid Input)406
agent_name is a reserved name121806406
Session is revoked1605 (Invalid Input)406
Session not found or wrong user1609 (Not Found)404
Internal error1654 (Internal Error)500

DELETE /current/oauth/sessions/{session_id}/

Revoke a specific OAuth session. The session must belong to the authenticated user. This operation is idempotent — if the session is already revoked, success is still returned.

DELETE /current/oauth/sessions/{session_id}/

Auth: Required (Bearer JWT)

Path Parameters:

ParameterTypeRequiredDescription
session_idstringYes32 hexadecimal characters
curl -X DELETE "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "message": "Session has been revoked."
}

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
session_id missing1605 (Invalid Input)406
session_id not 32 hex chars1605 (Invalid Input)406
Session not found or wrong user1609 (Not Found)404
Internal error1654 (Internal Error)500

DELETE /current/oauth/sessions/

Revoke all OAuth sessions (logout everywhere). Optionally exclude the current session for "log out everywhere else" functionality.

DELETE /current/oauth/sessions/

Auth: Required (Bearer JWT)

Query Parameters:

ParameterTypeRequiredDefaultDescription
exclude_currentstringNo—Set to "true" or "1" to keep the current session active
current_session_idstringNo—Session ID to preserve when exclude_current is set. If omitted (or exclude_current is unset), all sessions are revoked.
# Revoke ALL sessions
curl -X DELETE "https://api.fast.io/current/oauth/sessions/" \
  -H "Authorization: Bearer {jwt_token}"

# Revoke all EXCEPT current session
curl -X DELETE "https://api.fast.io/current/oauth/sessions/?exclude_current=true&current_session_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "message": "All sessions have been revoked."
}

Or when exclude_current is used:

{
  "result": true,
  "message": "All other sessions have been revoked."
}

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
Internal error1654 (Internal Error)500

When exclude_current is "true" but current_session_id is not provided, all sessions are revoked.

Token Scope Introspection

GET /current/auth/scopes/

Returns the current token's scope information, auth type, and agent status. Enables clients to discover their token's capabilities without decoding the JWT.

GET /current/auth/scopes/

Auth: Required (Bearer JWT or API Key)

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

Response (200 OK):

{
  "result": true,
  "auth_type": "jwt_v2",
  "scopes": ["org:3814271023567182934:rw", "org:3829104758203948571:rw"],
  "scopes_detail": [
    {
      "entity_type": "org",
      "entity_id": "3814271023567182934",
      "access_mode": "rw",
      "admin": false,
      "name": "Acme Corp",
      "domain": "acme"
    },
    {
      "entity_type": "org",
      "entity_id": "3829104758203948571",
      "access_mode": "rw",
      "admin": false,
      "name": "Beta Inc",
      "domain": "beta"
    }
  ],
  "is_agent": true,
  "agent_name": "My MCP Agent",
  "full_access": false,
  "admin": false,
  "legacy": false
}

Response (200 OK — API-key auth with scopes and expiration):

{
  "result": true,
  "auth_type": "api_key_scoped",
  "scopes": ["org:3814271023567182934:rw"],
  "scopes_detail": [
    {
      "entity_type": "org",
      "entity_id": "3814271023567182934",
      "access_mode": "rw",
      "admin": false,
      "name": "Acme Corp",
      "domain": "acme"
    }
  ],
  "is_agent": true,
  "agent_name": "CI Pipeline",
  "full_access": false,
  "admin": false,
  "legacy": false,
  "expires": "2026-12-31 23:59:59 UTC"
}

Response Fields:

FieldTypeDescription
resultbooleantrue on success
auth_typestring"jwt_v2" (scoped JWT, or a sign-in session that declared agent_name — still unscoped, so legacy stays true), "jwt_v1" (legacy JWT: a browser login session without a declared agent), "api_key" (legacy API key that declares no scopes claim), or "api_key_scoped" (API key whose stored token record has a non-empty scopes claim)
scopesarrayScope strings in entity_type:entity_id:access_mode format. Empty only for a legacy credential that declares no scopes claim at all (a browser login session — including one that declared agent_name, which is unscoped — or a pre-scopes API key).
scopes_detailarrayHydrated scope objects with entity names and metadata, one per entry in scopes. Empty when scopes is empty.
is_agentbooleanWhether the token represents an agent. true for a sign-in session that declared agent_name at GET /current/user/auth/ (the name is reported in agent_name).
agent_namestring or nullAgent display name if set, otherwise null
full_accessbooleanWhether the credential is account-wide and may write — user:*:rw or user:*:rwa. true for legacy (unscoped) API keys and browser login sessions; false for user:*:r and for entity-scoped credentials. It is not an administration flag — read admin for that
adminbooleanWhether the credential can perform administrative operations: true for a browser login session and for any credential holding an rwa scope (still capped by the human's live role). Always present
legacybooleanWhether the credential declares no scopes claim at all — a pre-scopes API key, a pre-scopes OAuth session, and every browser login session. Always present. legacy never implies admin, and admin never implies legacy
expiresstringAPI-key auth only: token expiration timestamp in canonical YYYY-MM-DD HH:MM:SS UTC format. Omitted entirely when the token has no expiration set or when auth is JWT-based.

Scope Detail Fields:

Each entry in scopes_detail contains entity-specific fields:

FieldTypePresent ForDescription
entity_typestringAlluser, org, workspace, share, sign_envelope, fileshare, memory, or userdetails
entity_idstringAllNumeric ID or * for wildcard
access_modestringAllr (read), rw (read/write) or rwa (read/write/administer)
adminbooleanAlltrue when this entry's access mode is rwa, so a client need not re-parse the scope string
labelstringFull access / wildcardHuman-readable label (e.g., "Full Access", "All Organizations")
namestringOrg, WorkspaceEntity display name
domainstringOrgOrganization subdomain
folder_namestringWorkspaceWorkspace URL slug
org_idstringWorkspace, Share, Sign envelope, File shareParent organization ID
org_namestringWorkspace, Share, Sign envelope, File shareParent organization name
org_domainstringWorkspace, Share, Sign envelope, File shareParent organization subdomain
titlestringShareShare display title
share_typestringSharesend, receive, or exchange
envelope_statusstringSign envelopeThe envelope's current status
workspace_idstringShare, Sign envelope, File shareParent workspace ID
workspace_namestringShare, Sign envelope, File shareParent workspace name
load_errorstringOn failure"Entity not found" if the referenced entity could not be loaded

Labels follow the access mode: user:*:rw renders as "Full Access", user:*:rwa as "Full Access (Admin)", user:*:r as "Entire account (Read Only)" and userdetails:*:rw as "Account settings"; other wildcards render in the same style, e.g. "All Organizations (Read/Write/Admin)".

How the flags combine:

Credentialfull_accessadminlegacyauth_type
user:*:rwatruetruefalsejwt_v2 / api_key_scoped
user:*:rwtruefalsefalsejwt_v2 / api_key_scoped
user:*:rfalsefalsefalsejwt_v2 / api_key_scoped
Scoped, e.g. org:123:rwafalsetruefalsejwt_v2 / api_key_scoped
Legacy key with no scopestruefalsetrueapi_key
Browser login sessiontruetruetruejwt_v1
Sign-in session that declared agent_nametruetruetruejwt_v2

Error Responses:

ScenarioError CodeHTTP Status
Not authenticated1650 (Authentication Invalid)401
Wrong HTTP method109463405

Complete PKCE Example Flow

Here is a complete example of the PKCE authorization flow:

0. CLIENT -> API: Discover server configuration (optional)
   GET /.well-known/oauth-authorization-server/
   -> Returns endpoints, grant types, PKCE methods, resource_indicators_supported

0b. CLIENT -> API: Register client dynamically (if no client_id)
   POST /current/oauth/register/
   Content-Type: application/json
   {"client_name": "My Agent", "redirect_uris": ["http://localhost:8080/callback"]}
   -> Returns client_id, registration_access_token

   OR use a CIMD URL as client_id (no registration needed)

1. CLIENT: Generate PKCE parameters
   code_verifier  = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
   code_challenge = base64url(sha256(code_verifier))
                  = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

2. CLIENT -> BROWSER: Open authorization URL in user's browser
   GET /current/oauth/authorize/
   ?client_id=my-app
   &redirect_uri=http://localhost:8080/callback
   &response_type=code
   &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
   &code_challenge_method=S256
   &state=xyz123
   &resource=https://mcp.fast.io/mcp

3. API -> BROWSER: 302 redirect to login/consent page
   (programmatic clients add &response_format=json and open the returned login_url verbatim)
   -> Browser lands on login page, user signs in, approves access

4. BROWSER -> CLIENT: Authorization code returned
   http://localhost:8080/callback?code=AUTH_CODE_HERE&state=xyz123
   (or, with display_code=true, displayed on screen for the user to copy)

5. CLIENT -> API: Exchange code for tokens
   POST /current/oauth/token/
   Content-Type: application/x-www-form-urlencoded

   grant_type=authorization_code
   &code=AUTH_CODE_HERE
   &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
   &client_id=my-app
   &redirect_uri=http://localhost:8080/callback
   &resource=https://mcp.fast.io/mcp

6. API -> CLIENT: Tokens returned (bare JSON)
   {
     "access_token": "eyJ...",
     "token_type": "Bearer",
     "expires_in": 3600,
     "refresh_token": "4f9a2c...",
     "scope": "user"
   }

7. CLIENT -> API: Use access token for requests
   GET /current/user/details/
   Authorization: Bearer eyJ...

8. CLIENT -> API: Refresh when access token expires
   POST /current/oauth/token/
   Content-Type: application/x-www-form-urlencoded

   grant_type=refresh_token
   &refresh_token=4f9a2c...
   &client_id=my-app

   -> Returns a new access_token; the same refresh_token is returned unchanged

9. CLIENT -> API: Revoke on logout
   POST /current/oauth/revoke/
   Content-Type: application/x-www-form-urlencoded

   token=4f9a2c...

Error Handling

Token and Revoke Endpoints (RFC 6749 SS5.2)

The token (POST /current/oauth/token/) and revoke (POST /current/oauth/revoke/) endpoints return RFC 6749 compliant error responses — bare JSON, not the standard platform envelope:

{
  "error": "invalid_grant",
  "error_description": "The authorization code is invalid or has expired."
}

Registration Endpoint (RFC 7591)

The registration endpoint (POST /current/oauth/register/ and PUT /current/oauth/register/) also returns bare JSON error responses with RFC 7591 error codes:

{
  "error": "invalid_client_metadata",
  "error_description": "The client_name must be between 1 and 128 characters."
}

Other OAuth Endpoints

All other OAuth endpoints (authorize, authorize/info, sessions) use the standard platform error envelope:

{
  "result": false,
  "error": {
    "code": 116775,
    "text": "The client_id is invalid or not found.",
    "documentation_url": "https://api.fast.io/llms.txt",
    "resource": "GET /current/oauth/authorize/"
  }
}
ScenarioError CodeHTTP Status
Rate limited1671 (Rate Limited)429
↑ Back to top