OAuth 2.1 + Dynamic Client Registration
Niyra implements the OAuth 2.1 authorization-server role for third-party clients — Claude Desktop, Cursor, ChatGPT, anything that wants to call /mcp or /v1/public/* on behalf of a Niyra user.
If you don't need OAuth (you only need your own scripts to call Niyra), use Personal Access Tokens instead.
Discovery
GET https://api.niyra.ai/.well-known/oauth-authorization-server
Returns the standard RFC 8414 metadata document, including:
authorization_endpoint—/oauth/authorizetoken_endpoint—/oauth/tokenregistration_endpoint—/oauth/registerrevocation_endpoint—/oauth/revokeintrospection_endpoint—/oauth/introspectjwks_uri—/.well-known/jwks.jsonscopes_supported— full catalogcode_challenge_methods_supported—["S256"]
Step 1 — Register your client (DCR, RFC 7591)
curl -X POST https://api.niyra.ai/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My App",
"redirect_uris": ["https://myapp.example.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "niyra:ask niyra:execute"
}'
Response:
{
"client_id": "niyra_abc123…",
"client_id_issued_at": 1717100000,
"client_name": "My App",
"redirect_uris": ["https://myapp.example.com/oauth/callback"],
"scope": "niyra:ask niyra:execute"
}
Public clients (CLIs, desktop apps, mobile) MUST use token_endpoint_auth_method: none and PKCE.
Step 2 — Authorization code with PKCE
Generate a code verifier + challenge:
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
Send the user to:
https://api.niyra.ai/oauth/authorize
?response_type=code
&client_id=niyra_abc123
&redirect_uri=https://myapp.example.com/oauth/callback
&scope=niyra:ask%20niyra:execute
&state=<random>
&code_challenge=<challenge>
&code_challenge_method=S256
&resource=https://api.niyra.ai/mcp
The resource parameter is RFC 8707 — it binds the resulting access token to a specific audience.
After consent, Niyra redirects to:
https://myapp.example.com/oauth/callback?code=<auth_code>&state=<state>
Step 3 — Exchange code for tokens
curl -X POST https://api.niyra.ai/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<auth_code>" \
-d "client_id=niyra_abc123" \
-d "redirect_uri=https://myapp.example.com/oauth/callback" \
-d "code_verifier=<verifier>" \
-d "resource=https://api.niyra.ai/mcp"
Response:
{
"access_token": "eyJhbGc…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rft_xyz…",
"scope": "niyra:ask niyra:execute"
}
Step 4 — Use the token
curl https://api.niyra.ai/v1/public/ask \
-H "Authorization: Bearer eyJhbGc…" \
-d '{"question": "What is on my calendar?"}'
Refresh rotation
Refresh tokens rotate on every use — the response includes a new refresh token and the old one is marked rotated. Replaying a rotated refresh token triggers OAuth 2.1 §6.1 cascade revocation: the entire token chain is invalidated, so a stolen refresh can be used at most once before the legitimate client discovers the theft.
curl -X POST https://api.niyra.ai/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=rft_xyz…" \
-d "client_id=niyra_abc123"
Revoking tokens
curl -X POST https://api.niyra.ai/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=<access_or_refresh>" \
-d "client_id=niyra_abc123"
Per RFC 7009, the endpoint always returns 200, regardless of whether the token existed.