Developer docs menu

PACE Partner API

Authentication

OAuth 2.0 client credentials, tokens and scopes.

The Partner API uses the OAuth 2.0 client credentials grant (RFC 6749, section 4.4). Your system authenticates as itself, not as a person.

Credentials

ValueFormatNotes
client_idpc_ followed by 24 charactersIdentifies your client. Not secret.
client_secretpace_cs_ followed by 43 charactersShown once when the client is created. PACE stores only a hash.

Each client belongs to one organisation and holds a fixed list of permissions (scopes). Keep the secret in a secrets manager or environment variable. If it is exposed, ask PACE to rotate it: rotation issues a new secret and immediately invalidates every token issued with the old one.

Request a token

POST /api/v1/oauth/token/ with Content-Type: application/x-www-form-urlencoded.

Send the credentials with HTTP Basic authentication (preferred):

Shell
curl -s -X POST https://mypacehub.com/api/v1/oauth/token/ \
  -u "$PACE_CLIENT_ID:$PACE_CLIENT_SECRET" \
  -d grant_type=client_credentials

or as form fields:

Shell
curl -s -X POST https://mypacehub.com/api/v1/oauth/token/ \
  -d grant_type=client_credentials \
  -d client_id="$PACE_CLIENT_ID" \
  -d client_secret="$PACE_CLIENT_SECRET"
FieldRequiredDescription
grant_typeYesAlways client_credentials.
scopeNoSpace separated. Narrows the token to fewer scopes than your client holds. Omit it to receive all of them.

Response

JSON
{
  "access_token": "pace_at_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "programme.metrics.read"
}

Tokens are valid for one hour. Cache the token and request a new one shortly before it expires. There are no refresh tokens in the client credentials grant.

Call the API

Send the token in the Authorization header on every request:

Shell
curl -s https://mypacehub.com/api/v1/me/ \
  -H "Authorization: Bearer $PACE_ACCESS_TOKEN"

Example clients

Python
import os, time, requests

BASE = "https://mypacehub.com"
_token = {"value": None, "expires": 0}

def token():
    if _token["value"] and time.time() < _token["expires"] - 60:
        return _token["value"]
    r = requests.post(
        f"{BASE}/api/v1/oauth/token/",
        auth=(os.environ["PACE_CLIENT_ID"], os.environ["PACE_CLIENT_SECRET"]),
        data={"grant_type": "client_credentials"},
        timeout=10,
    )
    r.raise_for_status()
    body = r.json()
    _token.update(value=body["access_token"], expires=time.time() + body["expires_in"])
    return _token["value"]

me = requests.get(f"{BASE}/api/v1/me/", headers={"Authorization": f"Bearer {token()}"}, timeout=10)
print(me.json())
Node
const BASE = "https://mypacehub.com";
let cached = { value: null, expires: 0 };

async function token() {
  if (cached.value && Date.now() < cached.expires - 60_000) return cached.value;
  const basic = Buffer.from(`${process.env.PACE_CLIENT_ID}:${process.env.PACE_CLIENT_SECRET}`).toString("base64");
  const res = await fetch(`${BASE}/api/v1/oauth/token/`, {
    method: "POST",
    headers: { Authorization: `Basic ${basic}`, "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({ grant_type: "client_credentials" }),
  });
  if (!res.ok) throw new Error(`token request failed: ${res.status}`);
  const body = await res.json();
  cached = { value: body.access_token, expires: Date.now() + body.expires_in * 1000 };
  return cached.value;
}

const me = await fetch(`${BASE}/api/v1/me/`, { headers: { Authorization: `Bearer ${await token()}` } });
console.log(await me.json());

Token errors

Errors follow RFC 6749 and always carry error and error_description.

StatuserrorMeaning
400invalid_requestThe body is not form fields or a JSON object.
400unsupported_grant_typegrant_type is not client_credentials.
400invalid_scopeYou asked for a scope your client does not hold.
401invalid_clientUnknown client, wrong secret or revoked client. The answer is the same for all three.
429slow_downMore than 30 token requests in a minute for this client.

Scopes

ScopeGives access toAvailable
programme.metrics.readDe-identified programme KPIs: enrolment, adherence, persistence, dropout reasonsNext release
events.subscribeRegister webhook endpoints and receive signed event notificationsNext release
patients.readCare team read of your own patientsNext release, hospitals only
referrals.writeRefer a patient for enrolmentNext release, hospitals only

Patient level scopes are never granted to a pharma client.