OAuth
Endpoint reference
Every /oauth/* endpoint, token format, error code, and rate limit.
https://mayaapi.teamcast.ai/oauth/*), not under /api/v1. The discovery document is the authoritative source of URLs.Discovery
| Method | Endpoint | Description |
|---|---|---|
| GET | /.well-known/oauth-authorization-server | RFC 8414 authorization server metadata |
| GET | /.well-known/jwks.json | RSA public keys for offline access-token verification |
curl https://mayaapi.teamcast.ai/.well-known/oauth-authorization-server | jqProtocol endpoints
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
| GET | /oauth/authorize | browser session | Start an authorization code flow |
| POST | /oauth/token | client (basic/post/none) | Exchange code or refresh; client_credentials |
| POST | /oauth/introspect | client (basic/post) | RFC 7662 — is this token active? |
| POST | /oauth/revoke | client (basic/post/none) | RFC 7009 — revoke an access or refresh token |
Grants supported
| Grant | Purpose |
|---|---|
| authorization_code (+ PKCE S256) | User login via browser ("Sign in with Teamcast") |
| refresh_token | Long-lived access — rotating with reuse detection |
| client_credentials | Server-to-server, no user. CONFIDENTIAL clients only |
GET /oauth/authorize
Required query params:
| Param | Description |
|---|---|
| response_type | Must be "code". |
| client_id | Your registered client id. |
| redirect_uri | Exact match of one of the registered redirect URIs. |
| scope | Space-delimited — subset of the client's registered scopes. |
| state | Opaque random string; echoed back (CSRF defense). |
| code_challenge | base64url(SHA256(code_verifier)) — 43–128 chars. |
| code_challenge_method | Must be "S256". plain is rejected. |
Optional:
| Param | Description |
|---|---|
| prompt | Space-delimited subset of none | login | consent | select_account. |
| nonce | Reserved for OIDC — ignored today. |
https://mayaapi.teamcast.ai/oauth/authorize?response_type=code
&client_id=<CID>
&redirect_uri=https://partner.example.com/oauth/callback
&scope=interview:read%20interview:create
&state=<csrf-random>
&code_challenge=<base64url-s256>
&code_challenge_method=S256
&prompt=select_accountPOST /oauth/token
authorization_code
curl -X POST https://mayaapi.teamcast.ai/oauth/token \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d "grant_type=authorization_code" \
-d "code=<received>" \
-d "redirect_uri=https://partner.example.com/oauth/callback" \
-d "code_verifier=<original>"refresh_token (rotating)
curl -X POST https://mayaapi.teamcast.ai/oauth/token \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d "grant_type=refresh_token" \
-d "refresh_token=<old>"refresh_token — you must persist and use it on the next refresh. Presenting a rotated-out refresh token triggers full token-family revocation.client_credentials
curl -X POST https://mayaapi.teamcast.ai/oauth/token \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d "grant_type=client_credentials" \
-d "scope=interview:read"Token response (all grants):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "opaque-string",
"scope": "interview:read interview:create"
}Access token — RS256 JWT
Signed with RS256, header typ=at+jwt. Verify offline with the JWKS.
{
"iss": "https://mayaapi.teamcast.ai",
"sub": "<userId> | client:<clientId>",
"aud": "teamcast-api",
"client_id": "<clientId>",
"tenant_id": "<tenantId> | null",
"scope": "interview:read interview:create",
"jti": "<uuid>",
"iat": 1776200000,
"exp": 1776200900,
"auth_time": 1776199980,
"token_use": "access"
}POST /oauth/introspect (RFC 7662)
Client-authenticated. Returns { active: false } for unknown, expired, revoked, or foreign-client tokens. PUBLIC clients may not introspect.
curl -X POST https://mayaapi.teamcast.ai/oauth/introspect \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d "token=<access_or_refresh>"POST /oauth/revoke (RFC 7009)
Client-authenticated. Revoking a refresh token revokes its entire family. Always returns 200 per spec.
curl -X POST https://mayaapi.teamcast.ai/oauth/revoke \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d "token=<refresh_token>" \
-d "token_type_hint=refresh_token"User consent management
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
| GET | https://mayaapi.teamcast.ai/api/v1/oauth/consents | user JWT | List the authenticated user's connected apps |
| DELETE | https://mayaapi.teamcast.ai/api/v1/oauth/consents/:clientId | user JWT | Revoke consent + cascade (tokens wiped) |
Scopes and resource-server behavior
Each scope maps to a permission in the RBAC registry (interview:read, interview:create, etc). Endpoints that accept OAuth tokens carry @RequireScope(...); missing scopes → 403 with a Missing required scope(s) message.
Endpoints that do not declare @RequireScope reject OAuth tokens outright. Partner access is explicit, per-endpoint.
Error codes (RFC 6749)
| Code | Meaning |
|---|---|
| invalid_request | Missing / malformed parameter |
| invalid_client | Unknown client_id or failed client authentication |
| invalid_grant | Code / refresh unknown, expired, reused, or client mismatch |
| unauthorized_client | Client not allowed to use this grant |
| unsupported_response_type | Only "code" is supported |
| invalid_scope | Requested scope not allowed for this client |
| access_denied | User denied consent, or tenant-audience mismatch |
Rate limits (per IP unless noted)
| Endpoint | Limit |
|---|---|
| GET /oauth/authorize | 30 / min |
| POST /oauth/authorize/decision | 30 / min |
| POST /oauth/token | 60 / min |
| POST /oauth/introspect | 120 / min |
| POST /oauth/revoke | 60 / min |