HomeDocsDeveloperOAuth authorization
Developer
OAuth authorization
How Reqio's MCP server uses OAuth 2.1 with PKCE to authorize AI agents, issue opaque tokens, and enforce per-project audience binding.
The Reqio MCP server uses OAuth 2.1 with PKCE (RFC 7636 S256) to authorize agent connections. There are no API keys to paste. The agent discovers all endpoint URLs automatically via standard discovery documents, registers itself, and opens a browser consent screen where you approve the scopes you want to grant.
Access tokens are opaque and database-backed, not JWTs. Every token is audience-bound to a single project. The server re-reads live project membership on every tool call, so revoking a team member's access takes effect immediately without any token revocation step.
- Token format
- Opaque, cryptographically random, base64url-encoded. Only its SHA-256 hash is stored.
- Verification
- Database lookup on every call. No offline (stateless) verification.
- Audience binding
- Each token stores the project resource URL (
https://reqio.app/p/{projectId}) as its audience. A mismatch returnsTOKEN_AUDIENCE_MISMATCH. - Access token lifetime
- 1 hour (3600 seconds).
- Refresh token lifetime
- 30 days, rotating on every use.
- Authorization code lifetime
- Single-use, approximately 60 seconds.
The handshake
Discover the authorization server and resource server
MCP clients read two well-known documents before any other step:
GET /.well-known/oauth-authorization-server HTTP/1.1 Host: reqio.app{ "issuer": "https://reqio.app", "authorization_endpoint": "https://reqio.app/oauth/authorize", "token_endpoint": "https://reqio.app/api/oauth/token", "registration_endpoint": "https://reqio.app/api/oauth/register", "revocation_endpoint": "https://reqio.app/api/oauth/revoke", "scopes_supported": ["backlog:read", "status:write", "..."], "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"] }GET /.well-known/oauth-protected-resource HTTP/1.1 Host: reqio.app{ "resource": "https://reqio.app", "authorization_servers": ["https://reqio.app"], "scopes_supported": ["backlog:read", "..."], "bearer_methods_supported": ["header"] }Because the Authorization Server and Resource Server are co-located, both
resourceandauthorization_servers[0]point to the same origin. When a tool call arrives without a valid token, the server responds with a401and aWWW-Authenticateheader pointing at the protected-resource document, which is how MCP clients locate discovery and begin the flow automatically:HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="reqio", resource_metadata="https://reqio.app/.well-known/oauth-protected-resource"Register the client (RFC 7591)
MCP agents register themselves dynamically before initiating authorization. Registration is open (no approval queue) and rate-limited.
POST /api/oauth/register HTTP/1.1 Host: reqio.app Content-Type: application/json { "redirect_uris": ["http://localhost:PORT/callback"], "client_name": "My Agent" }The response includes a
client_idthe agent uses in subsequent requests. Noclient_secretis issued: Reqio uses public clients only (token_endpoint_auth_methods_supported: ["none"]). The registered client is inert until a human approves a consent screen, which shows theclient_idand the redirect host, not the self-declaredclient_name, so you can see exactly what is being connected.Build the authorization URL and redirect
https://reqio.app/oauth/authorize ?response_type=code &client_id=CLIENT_ID &redirect_uri=http%3A%2F%2Flocalhost%3APORT%2Fcallback &scope=backlog%3Aread+status%3Awrite &state=RANDOM_STATE &code_challenge=BASE64URL_SHA256_OF_VERIFIER &code_challenge_method=S256PKCE S256 is required.
code_challengeisBASE64URL(SHA256(code_verifier))with no padding.stateshould be a random value the agent verifies on return.Approve the consent screen
The Reqio consent screen shows the project name, the client ID and redirect host of the agent, and each requested scope in plain English with a description of what it allows. Scopes that exceed your plan entitlement or your role in the project are blocked and will not appear: you cannot accidentally grant more authority than you hold.
Exchange the code for tokens
After approval, the browser is redirected to the agent's
redirect_uriwithcodeandstateparameters. The agent exchanges the code at the token endpoint:POST /api/oauth/token HTTP/1.1 Host: reqio.app Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTH_CODE &redirect_uri=http%3A%2F%2Flocalhost%3APORT%2Fcallback &client_id=CLIENT_ID &code_verifier=CODE_VERIFIERThe endpoint also accepts
application/json. On success:{ "access_token": "OPAQUE_TOKEN", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "OPAQUE_REFRESH_TOKEN", "scope": "backlog:read status:write" }Authorization codes are single-use and short-lived. PKCE verification runs on every exchange; a mismatched verifier is rejected.
Make authenticated MCP calls
The agent sends the access token as a Bearer credential on every request to the project's MCP endpoint (
https://reqio.app/p/{projectId}/mcp). The server verifies the token, checks its audience and scope, then re-runsassertCanagainst live project membership before executing the tool. See Available tools for the full per-call pipeline.
Token refresh
POST /api/oauth/token HTTP/1.1
Host: reqio.app
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=CLIENT_IDEach refresh issues a new access token and a new refresh token. The old refresh token is immediately revoked.
Refresh token reuse is treated as a compromise signal
If a previously rotated refresh token is presented again (reuse detection), the server revokes the entire successor chain rather than issuing new tokens. This forces the agent to re-authorize from scratch, on the assumption that a rotated token being replayed means it leaked.
Token revocation (RFC 7009)
POST /api/oauth/revoke HTTP/1.1
Host: reqio.app
Content-Type: application/x-www-form-urlencoded
token=TOKEN_TO_REVOKE
&client_id=CLIENT_IDThe endpoint always returns 200, even for unknown tokens, to avoid leaking whether a given token exists. Revocation deletes the token row; the agent loses access on the next tool call. You can also revoke any active grant from Dashboard → Settings → Connected apps by clicking Revoke next to the connection.
Scopes
Each scope maps to a required OAuth 2.1 scope string and an internal capability from the project permission matrix (packages/core/src/config/permissions.json). The server checks both independently on every call: the scope says what the agent was consented to attempt, assertCan says whether the acting user's role actually holds that capability.
Read-only scopes are available to every connected project on every plan:
backlog:readscopeconversations:readscopewidget:readscopemembers:readscopeemail:readscopeidentity:readscoperequesters:readscopenotifications:readscopeanalytics:readscopeship:readscopebacklog:read: excludes SUPPORT-role connections at the capability layer.Write scopes require mcpWrite: true on the project owner's plan. Every plan, including Free, has mcpWrite: true (Free at a lower daily call quota), so write access is not itself plan-gated - only the per-plan daily quota differs. See MCP server for the quota table.
backlog:writescopestatus:writescopenotes:writescopecomments:writescopecomments:deletescopefeatures:deletescopeconversations:writescopebroadcasts:writescopewidget:writescopemembers:writescopemembers:deletescopeproject:writescopeemail:writescopeidentity:writescopeAuthority model
Token scopes define what an agent is permitted to attempt. The project permission matrix (assertCan) defines whether the acting user actually holds that permission. Both checks must pass on every call.
Removing a member neuters their agent immediately
Granting members:write to an agent acting as a DEVELOPER-role member is denied at the capability check, even if the scope was consented, because inviteMembers and removeMembers are OWNER/SUPERVISOR-only in the permission matrix. Removing a member from the project has the same immediate effect on their agent: the next tool call fails the assertCan check and returns FORBIDDEN, without any explicit token revocation step.