Skip to main content

OAuth apps (client credentials)

Alongside static API keys, Permissio supports OAuth 2.0 client-credentials — the right fit when you're building a platform or reselling on top of Permissio and want per-app credentials and short-lived, scoped tokens instead of a single long-lived shared key.

The flow is standard RFC 6749 client credentials:

  1. Register an app → you get a client_id and a client_secret.
  2. Exchange those at POST /oauth/token for a short-lived access token.
  3. Call the API with Authorization: Bearer <access_token> — exactly like an API key.

1. Register an app​

In the dashboard go to Settings → Developers → OAuth apps and create an app. Choose its environment (live / sandbox) and the scopes it may request (the same scope catalog as API keys — envelopes:read, envelopes:write, templates:read, templates:write, webhooks:read, webhooks:write, evidence:read).

You'll be shown the client_secret once — store it securely. If it leaks, rotate it (a new secret is issued and the old one stops working immediately).

2. Exchange for an access token​

Send the credentials to the token endpoint — either as form fields or via HTTP Basic auth. Optionally narrow the token to a subset of the app's scopes with scope (space-delimited); omit it to receive all the app's scopes.

POST /oauth/token
curl https://api.permissio.us/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=oacl_1a2b3c4d5e6f7a8b" \
-d "client_secret=csec_…" \
-d "scope=envelopes:read envelopes:write"
{
"access_token": "at_9f8e7d6c5b4a…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "envelopes:read envelopes:write"
}

Access tokens are valid for 1 hour — request a fresh one when it expires (cache and reuse it until then rather than minting one per request).

Errors follow RFC 6749: bad credentials return 401 { "error": "invalid_client" }, an ungranted scope returns 400 { "error": "invalid_scope" }, and any grant other than client_credentials returns 400 { "error": "unsupported_grant_type" }.

3. Call the API​

Use the access token as a Bearer token on any /v1 endpoint. Its scopes are enforced identically to an API key's:

GET /v1/me
curl https://api.permissio.us/v1/me \
-H "Authorization: Bearer at_9f8e7d6c5b4a…"
Node
// Fetch a token, then point the SDK at it (tokens are Bearer tokens):
const res = await fetch("https://api.permissio.us/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.PERMISSIO_CLIENT_ID!,
client_secret: process.env.PERMISSIO_CLIENT_SECRET!,
}),
});
const { access_token } = await res.json();

import { Permissio } from "permissio-sdk";
const client = new Permissio({ apiKey: access_token }); // Bearer token

API keys vs OAuth apps​

API keyOAuth app
Credentialone long-lived sk_… keyclient_id + client_secret → short-lived token
Best foryour own backendplatforms / reselling, per-app isolation
Rotationrotate the keyrotate the secret; tokens expire hourly
Scopesfixed on the keychosen per token request (subset of the app's grant)
note

Authorization-code + a consent screen (acting on behalf of another tenant's end-customers) is on the roadmap. Client-credentials — an app acting as its own tenant — is available today.