Authorization header:
1. Company MCP API keys
Coaches with administrative roles (Manager or Admin) can manually generate
static API keys in the company settings panel. Static API keys always create a
company/coach grant; they are not used for client AI access.
- Key Format: Prefixed with
fsc_followed by a secure hex string (e.g.,fsc_a1b2c3d4...). - Expiration: Configurable up to 365 days (defaults to 90 days).
- Usage: Provide the plaintext key directly in the
Authorizationheader. It is only shown once at creation time and cannot be retrieved later.
2. OAuth 2.1 Authorization Code Flow
For third-party AI clients, FITsociety implements an OAuth authorization code flow with PKCE (S256) and refresh token rotation.
Every authorization-code and refresh-token grant is bound to this exact
protected resource:
Company/coach OAuth requires the company MCP integration feature. Client OAuth
requires the same company MCP integration feature, an active client-company
relationship, enabled client AI access for that relationship, and the company
client MCP policy.
Token Types and Lifetimes
Step 1: Dynamic Client Registration
Before initiating authorization, the client application must register itself.- Route:
POST /mcp/v1/oauth/register - Request Body:
- Response: Returns a unique
client_id(e.g.,mcpc_a1b2c3d4...) with client settings.
Step 2: Redirect to Authorize Endpoint
The AI client redirects the signed-in user to the authorization URL, supplying the PKCE challenge parameters.- Route:
GET /mcp/v1/oauth/authorize - Query Parameters:
client_id: The registered client ID.redirect_uri: The matching callback URL.response_type: Must becode.code_challenge: The base64url SHA-256 digest of your randomcode_verifier.code_challenge_method: Must beS256.resource: Must be exactlyhttps://mcp.fitsociety.io/mcp/v1.scope: Space-separated list of requested module keys (e.g.clients bookingsfor coach access, orclient_ai_account client_ai_bookingsfor client AI access).state: Optional state parameter.
redirect_uri must exactly match one URI registered for the OAuth client.
The same exact URI must be sent again during the authorization-code exchange.
Step 3: Consent and Internal Approval
The authorize route redirects the signed-in user to the FITsociety consent page.- For company/coach access, a Manager or Admin coach selects modules, write
tools, and consent flags. Approving calls
POST /app/v1/company/mcp/oauth/approve. - For client AI access, the authenticated client selects only client AI
modules and consent flags. The company MCP integration feature must be
enabled, the client must have client AI access enabled for the active company
relationship, and the company client MCP policy must allow the requested
client modules. Approving calls
POST /app/v1/client/mcp/oauth/approve. - If the company MCP integration feature is disabled, approval fails with
COMPANY_MCP_FEATURE_DISABLEDand the token exchange must not continue. - If client AI access is disabled, approval fails with
COMPANY_MCP_CLIENT_AI_DISABLEDand the token exchange must not continue. - If the company client MCP policy is disabled or no requested module is allowed, approval fails before any grant or authorization code is created.
- The backend registers a persistent grant and returns a 10-minute authorization code.
- The user is redirected back to the AI client’s
redirect_uri?code=<code>&state=<state>.
Step 4: Exchanging the Authorization Code
The client exchanges the code for the credentials.- Route:
POST /mcp/v1/oauth/token - Request Body:
- Response:
Step 5: Refreshing Tokens & Rotation
When the access token expires, use the refresh token to get a new pair.- Route:
POST /mcp/v1/oauth/token - Request Body:
Cache-Control: no-store and Pragma: no-cache.
Refresh-token rotation is atomic: a refresh token can be claimed only once,
including when multiple application instances receive concurrent requests.
If an already rotated refresh token is presented again, FITsociety treats it as
token-family reuse and revokes the active access and refresh tokens for that
grant and OAuth client. The user must authorize the connection again.