Skip to content

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.

Terminal window
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"
Response
{
"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>

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.

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.

Omit scope and you receive everything your registration grants. Pass it to narrow:

Terminal window
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.

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.

A registration can hold two active secrets at once. Rotation therefore has no outage window:

  1. Ask Pace for a second secret

    Both the old and the new secret authenticate from this moment on.

  2. Deploy the new secret

    Roll it through your systems at whatever pace your release process allows.

  3. 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.

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.

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.

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.