Authentication
Every request carries a bearer token obtained through the OAuth2 client credentials grant. The tenant is derived from the token alone. There is no tenant header, and there never will be one: a header is something a caller sets, and a value that decides whose data you see must not be one of those.
Getting a token
Section titled “Getting a token”curl -sS -X POST https://auth.pacepayments.ai/oauth2/token \ -d grant_type=client_credentials \ -d client_id="$PACE_CLIENT_ID" \ -d client_secret="$PACE_CLIENT_SECRET"{ "access_token": "eyJhbGciOiJSUzI1NiIs…", "token_type": "Bearer", "expires_in": 1800, "scope": "orders:write orders:read events:read config:read"}Then on every call:
Authorization: Bearer <access_token>Token lifetime
Section titled “Token lifetime”Tokens last 30 minutes and there are no refresh tokens. Request a new one with the same credentials.
Do not treat a 401 as your signal to refresh. A client that only renews on failure
turns every expiry into at least one failed request, and under load that failed
request may be the one carrying an order. Track expires_in and renew a minute or
two early.
Tokens are signed RS256. Public keys are published at
https://auth.pacepayments.ai/.well-known/jwks.json, and discovery metadata at
/.well-known/oauth-authorization-server. You do not need to validate tokens
yourself - Pace does - but the keys are there if your gateway wants to.
Scopes
Section titled “Scopes”Eight scopes, one per sensitivity level rather than one per resource. A grant is meant to be a statement about what its holder can see, readable in one pass by whoever approves it.
| Scope | Grants |
|---|---|
orders:write |
Submit and control work: create, modify and cancel call orders, emit signals, control enrollments. |
orders:read |
Read orders, calls, enrollments and signals - the metadata of what happened. Does not include conversation content. |
events:read |
Poll the event feed. |
config:write |
Manage order types, sequences and webhook endpoints, including secret rotation and replay. |
config:read |
Read order types, sequences, webhook endpoints, the delivery log, the suppression list and your own tenant configuration. |
suppressions:write |
Add or remove do-not-call entries. |
transcripts:read |
Read transcript artifacts - conversation content. |
recordings:read |
Obtain recording URLs. Recording is switched off platform-wide, so this grants nothing today. |
Two separations in that table carry weight and should not be tidied away when you model permissions on your side.
Conversation content is its own level. transcripts:read sits beside
orders:read rather than inside it, so you can grant a system the ability to process
results without granting it the ability to read what was said. If your data
protection officer reviews API grants, that distinction is the one they are
looking for.
suppressions:write is not part of config:write. Deleting a do-not-call entry
is the highest-liability write in this API, and it must not ride along with
permission to rename an order type.
Requesting a subset
Section titled “Requesting a subset”Omit scope and you receive everything your registration grants. Pass it to narrow:
curl -sS -X POST https://auth.pacepayments.ai/oauth2/token \ -d grant_type=client_credentials \ -d client_id="$PACE_CLIENT_ID" \ -d client_secret="$PACE_CLIENT_SECRET" \ -d scope="orders:write events:read"Requesting a scope your registration does not hold is rejected with invalid_scope.
It is not silently dropped - a typo in a deployment should surface as a failure at
startup, not as a permission you thought you had.
Client registrations
Section titled “Client registrations”You can hold several registrations per tenant, one per integrating system, each with its own secret and its own scopes. That is the intended shape: your case management system and your reporting job should not share a credential, because revoking one should not stop the other.
Client secrets are prefixed pcs_. The prefix exists so a leaked secret in a log or
a repository is recognisable as a secret at a glance.
Rotation
Section titled “Rotation”A registration can hold two active secrets at once. Rotation therefore has no outage window:
-
Ask Pace for a second secret
Both the old and the new secret authenticate from this moment on.
-
Deploy the new secret
Roll it through your systems at whatever pace your release process allows.
-
Have the old secret revoked
Revocation takes effect immediately - there is no propagation delay to wait out.
Do not skip the overlap by replacing a secret in place. Every instance still holding the old value fails at once, and the failures land wherever your retry logic sends them.
IP allowlists
Section titled “IP allowlists”A registration can be restricted to a set of source addresses. A token request from
outside the list is refused, and so is an API call, with 403 ip_not_allowlisted.
This is worth using for a server-to-server integration, where the source addresses are known and stable. It is worth not using if your egress addresses change without warning, because the failure mode is a total outage on a change you did not schedule.
Failed attempts
Section titled “Failed attempts”Repeated authentication failures against the same registration lock it temporarily. The lock is on the registration, not the address, so a misconfigured deployment that retries in a tight loop will lock itself out rather than degrade quietly.
If you see this, the fix is on your side: find what is presenting the wrong secret before you retry. Backing off and trying the same wrong credential again just extends the lock.
What to do with 401 and 403
Section titled “What to do with 401 and 403”| Response | Meaning | What to do |
|---|---|---|
401 unauthorized |
Missing, malformed or expired token. | Get a new token. If it recurs immediately, your credentials are wrong - do not loop. |
403 insufficient_scope |
Valid token, but this operation needs a scope you did not receive. | A configuration problem, not a runtime one. Fix the grant; retrying never helps. |
403 ip_not_allowlisted |
Valid credentials from an address not on the list. | Your egress address changed. Retrying from the same address never helps. |
The distinction matters for your retry logic: 401 is sometimes transient, and both 403s never are.