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.