Skip to main content

OAuth

Connect apps and agents to Talonic with OAuth 2.1 and PKCE: discovery, dynamic client registration, scopes and defaults, the tier claim, and apps:decide.

Besides API keys, Talonic runs an OAuth 2.1 authorization server so an application or agent can act for a person in a workspace without ever handling an API key. It supports the authorization-code grant with PKCE (S256 only) and refresh tokens, for public clients (token_endpoint_auth_methods_supported: ["none"]). A client discovers the endpoints, registers itself dynamically, sends the person to the consent page, and exchanges the code for an access token it presents as Authorization: Bearer <token> on the /v1 API, exactly where an API key would go.

Discovery and registration

GET /.well-known/oauth-authorization-server returns the server metadata (RFC 8414): issuer, authorization_endpoint (/oauth/authorize), token_endpoint (/oauth/token), userinfo_endpoint, registration_endpoint (/oauth/register), revocation_endpoint (/oauth/revoke), scopes_supported, response_types_supported: ["code"], grant_types_supported: ["authorization_code", "refresh_token"], and code_challenge_methods_supported: ["S256"]. Clients connecting through Talonic's hosted MCP server also find protected-resource metadata at /.well-known/oauth-protected-resource on the MCP host, which points them at this authorization server.

Dynamic client registration (POST /oauth/register, RFC 7591) needs no authentication and is rate-limited per IP. Send client_name (required) and redirect_uris (1 to 10; https://, or http:// only for localhost), plus optional client_uri and grant_types. The response returns your client_id with token_endpoint_auth_method: "none". Then send the person to /oauth/authorize with response_type=code, client_id, redirect_uri, code_challenge, code_challenge_method=S256, scope, and state, and exchange the returned code at /oauth/token with your code_verifier. Authorization codes are single-use and expire after 10 minutes; access tokens last one hour; refresh tokens last 30 days and rotate on every use.

Discover, register, and exchange a code

curl -s https://api.talonic.com/.well-known/oauth-authorization-server

curl -s -X POST https://api.talonic.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{ "client_name": "Ops Assistant", "redirect_uris": ["https://ops.example.com/callback"] }'

curl -s -X POST https://api.talonic.com/oauth/token \
  -d grant_type=authorization_code -d client_id=CLIENT_ID -d code=CODE \
  -d redirect_uri=https://ops.example.com/callback -d code_verifier=VERIFIER

Token response

{
  "access_token": "eyJhbGciOi…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…",
  "scope": "documents:read apps:read"
}

Scopes and defaults

Requestable scopes

extract:writedefaultRun extractions and other write-scoped public API calls.
documents:readdefaultRead documents and their results.
schemas:readdefaultRead schemas.
sources:readdefaultRead sources.
webhooks:managedefaultManage webhook configurations.
apps:readopt-inRead Apps and the surrounding /v1 read surface.
apps:operateopt-inOperate Apps (start runs and act on them), up to the person's role.
records:writeopt-inReserved for record-set write routes; maps to the records_write API scope.
apps:decideopt-inClaim and decide App decision tasks as the person. Never pre-consented.
identity:readopt-inRead the person's identity at /oauth/userinfo only. An identity-only token gets no refresh token and no public-API access.

If the authorization request omits scope, the token receives the five default scopes (extract:write, documents:read, schemas:read, sources:read, webhooks:manage). The Apps scopes, records:write, apps:decide, and identity:read are never granted implicitly: a client gets them only by naming them. An unknown scope is a 400. An installed App can pre-consent apps:read, apps:operate, records:write, and identity:read for the workspace, so a request naming only those skips the consent page; every other scope needs the person to click Authorize.

Role, tier, and apps:decide

Every access token carries the person's workspace role. When the scopes reach the Apps surface, the token also carries a `tier` claim: read, run, operate, or configure. Tiers are cumulative, so a tier admits everything below it, and the token's tier is the lower of what its scopes reach and what the person's role reaches (Viewer → read, Member → run, Senior Member → operate, Owner → configure). A token can never outrank the person it was issued to. A route the token's tier does not reach is refused with 403 and reason: insufficient_tier; a missing scope is refused with reason: insufficient_scope and required_scopes.

`apps:decide` lets an OAuth session claim and decide App decision tasks as the person. It is never pre-consented: the person consents to it in person, every time. Holding the scope is not enough on its own. Each decide request re-reads the person's live membership, which must be active and at Senior Member or above; a lower role is refused with insufficient_tier, and no active membership with decide_grant_required. Membership is also checked when a token is refreshed: a person who has left the workspace cannot refresh, the presented refresh token is revoked, and the request fails with 401.

Do not cache a decision that a token "can decide". Role changes and departures take effect on the next request, not at token expiry, so handle insufficient_tier, decide_grant_required, and a 401 on refresh as normal outcomes and send the person back through authorization.

Frequently asked questions

Which scopes does a token get if I do not pass scope?+
The five default scopes: extract:write, documents:read, schemas:read, sources:read, and webhooks:manage. The Apps scopes, records:write, apps:decide, and identity:read are only granted when the request names them.
Does Talonic support client secrets?+
No. Clients are public: registration returns token_endpoint_auth_method none, and every authorization-code exchange must use PKCE with the S256 method.
Why was my token refused with insufficient_tier?+
The route needs a higher Apps tier than the token reaches. The tier is the lower of the scopes' tier and the person's workspace role, so either request a broader scope or have a workspace owner raise the person's role.
Why did refreshing a token fail with 401?+
The person is no longer an active member of the workspace. The refresh token is revoked on the attempt; the person has to be re-invited and authorize the client again.
Can an App pre-consent apps:decide?+
No. apps:decide is never pre-consentable. The person must approve it on the consent page each time, and it only works while they hold an active Senior Member or Owner role.