Build your own client
Most MCP hosts handle all of this for you; just give them the URL. This page is for anyone writing a client from scratch. The connector follows the MCP authorization spec: OAuth 2.1 with PKCE, dynamic client registration and protected-resource metadata.
Transport
| Endpoint | https://api.outlai.io/mcp |
|---|---|
| Transport | Streamable HTTP, stateless. Every response is plain JSON (never an SSE stream), and there's no session to keep. |
| Protocol versions | Negotiated at initialize: 2024-11-05 through 2026-07-28. Tested most with 2025-06-18. |
| Capabilities | Tools only. |
| Auth | Authorization: Bearer olc_… header on every request. |
| Request limits | Body up to 64 KiB; 15-second timeout. |
Discovery
A request without a token gets 401 with a pointer to the metadata:
WWW-Authenticate: Bearer resource_metadata="https://api.outlai.io/.well-known/oauth-protected-resource"
| Document | URL |
|---|---|
| Protected resource (RFC 9728) | /.well-known/oauth-protected-resource (also served under …/mcp) |
| Authorization server (RFC 8414) | /.well-known/oauth-authorization-server |
The authorization server advertises:
grant_types_supported:authorization_code,refresh_token,urn:ietf:params:oauth:grant-type:device_codecode_challenge_methods_supported:S256token_endpoint_auth_methods_supported:none. Every client is public; no client secret is issued.authorization_response_iss_parameter_supported:true. Checkisson the redirect.
Client registration
Register once with dynamic client registration (RFC 7591):
POST https://api.outlai.io/oauth/register
Content-Type: application/json
{
"client_name": "My finance agent",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"grant_types": ["authorization_code", "refresh_token"]
}
The response (201) includes your client_id. Rules for redirect URIs:
https, orhttpon a loopback address.- All of a client's URIs must share one host. There can be at most 10, and the host must be plain ASCII.
- The host is shown to the user on the consent screen, so make it recognisable.
Authorization code + PKCE
- Create a PKCE verifier (43–128 characters) and its
S256challenge (43 characters). PKCE is required. - Send the user's browser to:
https://api.outlai.io/oauth/authorize ?response_type=code &client_id=… &redirect_uri=http://127.0.0.1:8765/callback &scope=finance:read finance:write &state=… &code_challenge=… &code_challenge_method=S256 &resource=https://api.outlai.io/mcpresourceis optional, but if you send it, it must be exactlyhttps://api.outlai.io/mcp. - The user signs in on
app.outlai.io, picks what to allow and approves. They have 10 minutes. - Outlai redirects back with
code,stateandiss. The code is valid for 5 minutes and works once. - Exchange it. Parameters go in the form body, never the query string:
POST https://api.outlai.io/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=…&redirect_uri=…&client_id=…&code_verifier=…
Device flow
For clients that can't open a browser (RFC 8628). Register with the device-code grant in grant_types, then:
POST https://api.outlai.io/oauth/device_authorization
Content-Type: application/x-www-form-urlencoded
client_id=…&scope=finance:read
The response contains:
device_code;user_code, such asBCDF-GHJK;verification_uri(https://app.outlai.io/#/connect/device) andverification_uri_complete, which includes the code;expires_in: 600andinterval: 5.
Show the user the code and the link, then poll /oauth/token every interval seconds with grant_type=urn:ietf:params:oauth:grant-type:device_code, device_code and client_id. While you wait you get authorization_pending; slow down by 5 seconds on slow_down. Stop on access_denied, expired_token or invalid_grant.
Tokens and refresh
{
"access_token": "olc_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "olc_…",
"scope": "finance:read finance:write"
}
| Item | Lifetime |
|---|---|
| Access token | 1 hour |
| Refresh token | 30 days, replaced on every use |
| Connection | 90 days, then the user must approve again |
- Refresh rotates. Store the new refresh token every time. Sending a spent one again within 10 seconds returns
invalid_grant, so a retry after a network failure is safe. Sending it after that is treated as theft and revokes the whole connection. - Revoke with
POST /oauth/revoke(RFC 7009). Revoking a refresh token ends the connection; revoking an access token ends only that token. - Tokens are opaque
olc_…strings. They work only on/mcp; the rest of the Outlai API refuses them.
Scopes
| Scope | Grants |
|---|---|
finance:read | The 7 read tools |
finance:write | The 7 write tools |
Asking for no scope gets finance:read; write is never implied. The user can approve less than you asked for, so read scope in the token response, or call tools/list to see what you got.
Errors
| Response | Meaning |
|---|---|
401 {"error":"invalid_token"} | Missing, expired or revoked token. Refresh, or start the OAuth flow again. |
403 {"error":"origin_not_allowed"} | Browser requests from another website are refused. Call the connector from a server or native app. |
429 {"error":"rate_limited"} | Wait for Retry-After seconds. |
429 {"error":"too_many_failed_attempts"} | Too many malformed or unknown tokens from your IP in a minute. |
503 {"error":"connector_disabled"} | The connector is paused. OAuth endpoints answer 503 {"error":"temporarily_unavailable"} at the same time. |
Tool result with isError: true | The tool refused the input or failed. The message says why. |
OAuth errors use the standard {"error": "…"} codes. Registration errors add error_description.
Rate limits
| What | Limit |
|---|---|
/mcp | 60 requests/min per connection, 120/min per user |
| Writes | 100 per connection per UTC day |
/oauth/register | 10/min per IP |
/oauth/authorize | 30/min per IP |
/oauth/token | 60/min per IP |
/oauth/revoke | 30/min per IP |
/oauth/device_authorization | 10/min per IP |
OAuth request bodies are limited to 16 KiB.