OAuth2 Client Credentials
The OAuth2 Client Credentials grant is a machine-to-machine authentication flow standardized by RFC 6749. Instead of a long-lived API key, a client exchanges a client_id and client_secret for a short-lived access token, then uses that token to call Vengtoo APIs.
Use this flow when:
- A backend service, worker, or cron job needs to call Vengtoo on its own behalf (no end user involved).
- You manage Vengtoo configuration with Terraform and apply it through a CI/CD pipeline.
- You are exposing an MCP server to AI agents and want each agent instance to authenticate with its own credentials.
Vengtoo handles authorization — it does not authenticate end users. Bring your own identity provider (Auth0, Clerk, Cognito, your own login) and pass the authenticated user's identity as the subject when calling the evaluation endpoint.
When to use OAuth2 vs API keys
| Use case | Recommended |
|---|---|
| Long-lived backend integration | API key (simpler) |
| Short-lived service-to-service | OAuth2 CC (tokens expire, safer) |
| Third-party integration with scoped access | OAuth2 CC (scopes) |
| Compliance / rotating credentials | OAuth2 CC (tokens auto-expire) |
Step 1 — Create an OAuth client
- Sign in to the Vengtoo Console.
- Go to Settings → API Access and open the OAuth Clients tab.
- Click Create Client and fill in:
- Name — a human-readable label (for example,
ci-deploy-bot). - Description — optional context about what the client is for.
- Scopes — any combination of
read,write,admin.
- Name — a human-readable label (for example,
- Click Save.
You will be shown a client_id and a client_secret of the form:
client_id: client_a3f8b2c1...
client_secret: azx_cs_9f3a8b4c2d1e...
Copy the client_secret immediately. For security reasons it is shown only once. If you lose it, regenerate a new secret from the same client entry in the Console.
Step 2 — Request an access token
Exchange the client credentials for an access token at the token endpoint:
curl -X POST https://api.vengtoo.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=client_a3f8b2c1..." \
-d "client_secret=azx_cs_9f3a8b4c2d1e..."
A successful response looks like:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}
access_token— the bearer token to send with subsequent API calls.token_type— alwaysBearer.expires_in— lifetime in seconds. Re-request a token before or after expiry.scope— the scopes granted to this token.
Step 3 — Use the token
Pass the access token in the Authorization header on every request:
curl -X POST https://api.vengtoo.com/access/v1/evaluation \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6..." \
-H "Content-Type: application/json" \
-d '{
"subject": { "external_id": "alice-uuid-from-your-db", "type": "user" },
"resource": { "external_id": "doc-uuid-from-your-db", "type": "document" },
"action": { "name": "read" }
}'
The same token works against the Management API, the Authorize API, and any other Vengtoo endpoint — subject to the scopes on the token.
Scopes
| Scope | Grants |
|---|---|
read | Read-only endpoints (GET: list, detail, authorize checks) |
write | Everything in read, plus POST, PUT, PATCH, DELETE (CRUD) |
admin | All methods — short-circuits scope checks entirely |
Scopes are cumulative: admin implies write, and write implies read. Request only the minimum scopes your integration needs.
Token lifetime
- Default: 1 hour (
expires_in: 3600). - Configurable per client in the Console.
Shorter lifetimes reduce the blast radius if a token leaks; longer lifetimes reduce token-endpoint traffic. One hour is a reasonable default for most backend integrations.
Refreshing tokens
The Client Credentials flow does not use refresh tokens. This is intentional and matches the OAuth2 specification — since the client already holds its own credentials, it can simply request a new token whenever one expires.
Typical pattern:
- Request a token.
- Cache it in memory until roughly 60 seconds before
expires_in. - On expiry (or on a
401response), request a new token with the sameclient_id/client_secret.
Security best practices
- Store
client_secretin a secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Kubernetes Secrets) — never in source code or unencrypted config files. - Rotate secrets periodically. From the Console, open the client and click Regenerate secret. Deploy the new secret, then revoke the old one.
- Use separate clients per environment (development, staging, production) so a leaked dev secret cannot reach production data.
- Limit scopes to the minimum required. Prefer
readoverwrite, andwriteoveradmin. - Monitor token issuance in the Audit Log (filter by OAuth client) to detect unusual issuance patterns.
- Do not log tokens. Strip
Authorizationheaders from request logs.
Error responses
Errors follow the OAuth2 standard error format:
{ "error": "invalid_client" }
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_request | Missing grant_type, client_id, or client_secret, or the body is not form-encoded |
| 400 | unsupported_grant_type | Only client_credentials is supported at this endpoint |
| 401 | invalid_client | client_id or client_secret is wrong, or the client has been revoked |
| 500 | server_error | Internal error (e.g. signing key unavailable) |