Authentication
OAuth 2.0 for the Configo API — the authorization code flow with PKCE, exchanging and refreshing tokens, token lifetimes, revocation and dynamic client registration.
The Configo API uses OAuth 2.0 authorization code grants. A user signs in to Configo, approves your application, and your application receives tokens that act on that user's behalf, limited to the scopes they approved.
Before you start, register an application under OAuth Apps in your account (see
OAuth Applications). You get a client_id, and, unless it is a public client, a
client_secret.
| Endpoint | Purpose |
|---|---|
https://configo.org/oauth/authorize |
The user approves your application |
https://configo.org/api/auth/token |
Exchange a code or a refresh token for tokens |
https://configo.org/api/auth/token/revoke |
Revoke a token |
https://configo.org/oauth/register |
Dynamic client registration (RFC 7591) |
https://configo.org/.well-known/oauth-authorization-server |
Authorization server metadata (RFC 8414) |
Only the authorization_code and refresh_token grants are supported. There are no client credentials, password or
implicit grants.
Token lifetimes
| Token | Lifetime | Notes |
|---|---|---|
| Authorization code | 5 minutes | Single use |
| Access token | 2 hours | Sent as Authorization: Bearer <access_token> |
| Refresh token | 30 days | Single use: each refresh returns a new refresh token |
1. Send the user to authorize
Redirect the user's browser to:
https://configo.org/oauth/authorize
?response_type=code
&client_id=0199b6ff-0000-7000-8000-000000000001
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=projects%20orders%20contacts
&state=af0ifjsldkj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
| Param | Required | Description |
|---|---|---|
| response_type | yes | Must be code |
| client_id | yes | Your application's ID |
| redirect_uri | yes | Must exactly match one of the application's registered redirect URIs |
| scope | yes | Space-separated scope names. Unknown names are ignored. Every scope must be enabled for the application, or the request is rejected |
| state | recommended | Opaque value returned unchanged; use it against CSRF |
| code_challenge | public clients | PKCE challenge: BASE64URL(SHA256(code_verifier)) |
| code_challenge_method | no | Only S256 is accepted; omitted means S256. plain is rejected |
The user signs in if needed and sees a consent screen listing the scopes. Trusted applications skip the consent screen.
After approval, Configo redirects to:
https://app.example.com/callback?code=Splx10BeZQQYbYS6WxSbIA&state=af0ifjsldkj
Invalid parameters (unknown client, redirect URI not registered, a scope the application does not have, a PKCE method
other than S256) are shown to the user as an error page and are not redirected back.
PKCE
PKCE protects the code exchange. It is required for public clients and
recommended for everyone else. Generate a random code_verifier (43–128 characters from A–Z a–z 0–9 - . _ ~) and send
its SHA-256 as the challenge:
import {createHash, randomBytes} from 'node:crypto'
const code_verifier = randomBytes(32).toString('base64url')
const code_challenge = createHash('sha256').update(code_verifier).digest('base64url')
If a code was issued with a challenge, the exchange must include the matching code_verifier.
2. Exchange the code for tokens
POST /api/auth/token with an application/x-www-form-urlencoded body:
| Param | Required | Description |
|---|---|---|
| grant_type | yes | authorization_code |
| client_id | yes | Your application's ID |
| client_secret | confidential clients | Your application's secret. Omit for public clients |
| code | yes | The code from the redirect |
| redirect_uri | yes | The same redirect_uri used in step 1 |
| code_verifier | with PKCE | The verifier whose hash you sent as the challenge |
curl https://configo.org/api/auth/token \
-d grant_type=authorization_code \
-d client_id=0199b6ff-0000-7000-8000-000000000001 \
-d client_secret=$CLIENT_SECRET \
-d code=Splx10BeZQQYbYS6WxSbIA \
-d redirect_uri=https://app.example.com/callback \
-d code_verifier=$CODE_VERIFIER
Token Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "kFJ4mQ2...40 characters",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "projects orders contacts"
}
| Field | Type | Description |
|---|---|---|
| access_token | string | Bearer token for API requests |
| refresh_token | string | Use it to get a new pair |
| token_type | string | Always Bearer |
| expires_in | integer | Seconds until the access token expires: 7200 |
| scope | string | Granted scopes, space-separated |
Store both tokens securely. Treat the access token as opaque; do not rely on its contents.
3. Refresh the tokens
Before or after the access token expires, POST /api/auth/token:
| Param | Required | Description |
|---|---|---|
| grant_type | yes | refresh_token |
| client_id | yes | Your application's ID |
| client_secret | confidential clients | Your application's secret |
| refresh_token | yes | The current refresh token |
The response is a new token pair. Refresh tokens rotate: the old refresh token, and the access token issued with it, stop working immediately. Always replace both with the new ones.
- If two requests refresh with the same token at once (for example, two tabs), the second one within 60 seconds receives the same new pair instead of an error.
- A refresh fails if the user's account is no longer active.
- The new pair carries the scopes the user granted, narrowed to those the application still has. If the application owner removed a scope from the application, it disappears on the next refresh.
- When a refresh token expires after 30 days, the user has to authorize again.
Revoke a token
POST /api/auth/token/revoke (RFC 7009), form-encoded:
| Param | Required | Description |
|---|---|---|
| client_id | yes | Your application's ID |
| client_secret | confidential clients | Your application's secret |
| token | yes | An access or refresh token |
| token_type_hint | no | access_token or refresh_token |
Revoking either token of a pair revokes both. The response is 200 {} even if the token was already invalid.
Token endpoint errors
The token and revoke endpoints follow OAuth 2.0 and return {"error": "...", "message": "..."}:
| Status | error | When |
|---|---|---|
| 400 | invalid_request |
A required parameter is missing; a public client used a code issued without PKCE |
| 400 | invalid_grant |
The code or refresh token is invalid, expired or already used; redirect_uri does not match; code_verifier is missing or wrong |
| 400 | unsupported_grant_type |
grant_type is not authorization_code or refresh_token |
| 401 | invalid_client |
Unknown client_id, or a wrong or missing client_secret |
| 429 | slow_down |
Too many failed client authentications, see below |
Failed client authentication is limited per client_id: after 20 failures within 15 minutes, the token and revoke
endpoints return 429 slow_down with Retry-After: 900 for that client until the window passes. A successful
authentication resets the count.
Public clients
An application with no backend (a CLI, a desktop or single-page app) cannot keep a secret. Register it as a public
client: it gets no client_secret, and PKCE is mandatory for it. Omit client_secret from every request.
Dynamic client registration
MCP clients and similar tools can register themselves with
RFC 7591: POST /oauth/register with a JSON body.
{
"client_name": "My MCP client",
"redirect_uris": [
"http://127.0.0.1:33418/callback"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"scope": "projects configurators materials"
}
{
"client_id": "0199b7aa-0000-7000-8000-000000000001",
"client_id_issued_at": 1790870000,
"client_name": "My MCP client",
"redirect_uris": [
"http://127.0.0.1:33418/callback"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"token_endpoint_auth_method": "none",
"scope": "projects configurators materials"
}
- Clients registered this way are always public (
token_endpoint_auth_method: none) and marked unverified on the consent screen. - Only the scopes
projects,materialsandconfiguratorscan be requested; others are dropped from the response. - At most 10 redirect URIs.
- Unused registrations are deleted after a while; register again if your
client_idstops working. - Registration is limited to 500 per hour across all callers; over it, the response is
429 slow_downwithRetry-After.
Trusted applications
Configo's own applications are trusted: they skip the consent screen and are not limited by scopes. Some endpoints (sessions, passkeys, OAuth app management, support tickets, account deletion, partner tools) are available only to trusted applications and are not part of the public API.