Authenticate with a machine-to-machine client
Create an integration application, exchange its client credentials for an OAuth 2.0 access token, and call the API with it
The Cases API is secured with OAuth 2.0. Every endpoint that touches case or company data requires a bearer access token, and a server-side integration gets one using the client credentials grant — a machine-to-machine (M2M) flow, with no user sitting in front of a browser.
There are three steps: create an application, exchange its credentials for a token, and send that token on every request.
Step 1 — Create an application
Client credentials are issued from the Exizent platform, not from this API.
- Sign in to Exizent and open Company → Integrations (legal.exizent.com/case-management/company/integrations).
- Create a new application with the type Other.
- Give it a name that identifies the system connecting — the name appears in support conversations and audit trails, so "Practice Management Sync" beats "Test 1".
- Save. The Client ID and Client Secret are now shown on the application.
The client secret is a credential with the same reach as the integration itself. Store it in a secret manager or an environment variable, never in source control, and never ship it to a browser or mobile app — the client credentials grant is for server-side use only.
You will need a separate application for Sandbox and for Production. Credentials are not shared between environments.
If your firm's plan does not show the Integrations screen, or you need credentials issued another way, contact [email protected].
Step 2 — Exchange the credentials for an access token
POST to the /oauth/token endpoint of the identity server for your environment.
Authentication base URLs
| Environment | Identity base URL |
|---|---|
| Sandbox | https://identity.testing.exizent.com |
| Production | https://identity.production.exizent.com |
The request
curl --request POST \
--url https://identity.testing.exizent.com/oauth/token \
--header 'Content-Type: application/json' \
--data '{
"client_id": "{{your_client_id}}",
"client_secret": "{{your_client_secret}}",
"grant_type": "client_credentials",
"audience": "https://resources.exizent.com",
"scope": "read:case create:case update:case delete:case"
}'| Field | Value |
|---|---|
client_id | From your integration application |
client_secret | From your integration application |
grant_type | Always client_credentials |
audience | Always https://resources.exizent.com — this is what tells the identity server the token is for the Cases API |
scope | A space-separated list of the permissions you need (see below) |
The response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRHYkhJYWxBUXZ4NU5ZS3h5Nz...",
"scope": "read:case create:case update:case delete:case",
"expires_in": 86400,
"token_type": "Bearer"
}Check the scope that comes back. The identity server grants the intersection of what you asked for and what your application is authorised to hold, so a scope you requested may simply be absent — which will show up later as a 403 rather than an error here.
Step 3 — Call the API
Put the token in an Authorization header on every request:
curl --request GET \
--url https://cases.api.testing.exizent.com/cases \
--header 'Authorization: Bearer {{access_token}}'Authorization: Bearer {{access_token}}
That is the whole of it — there is no session, no cookie and nothing else to send.
Scopes
Scopes decide which endpoints a token may reach. Request only what your integration genuinely needs.
| Scope | Grants |
|---|---|
read:case | Read cases and everything on them — the deceased, people, estate items, documents |
create:case | Create new cases |
update:case | Change a case and anything on it: the deceased, people, organisations, trusts, estate items, expenses, distributions, settings, status and owner |
delete:case | Delete case data |
read:company | Read company details |
update:company | Change company settings |
delete:company | Delete company data |
Mapping the endpoints in these guides to the scope each one needs:
| Endpoint | Scope |
|---|---|
GET /cases, GET /cases/{id} | read:case |
POST /cases | create:case |
PUT /cases/{id}/deceased | update:case |
POST /cases/{caseId}/people | update:case |
PUT /cases/{caseId}/people/{personId} | update:case |
Note that adding a person is an update to the case, not a case creation — it needs update:case, not create:case. That is true of everything that hangs off a case.
Roles
Scope alone is not always enough. The endpoints that change a case also require the token to carry one of the platform roles — CompanyOwner, Administrator, CaseOwner or Delegate — which is configured against your integration application when it is created.
In practice this is transparent: an application created through the Integrations screen arrives with what it needs. But it explains a particular failure. If a request returns 403 even though the token clearly carries update:case, the missing piece is the role, not the scope. Contact [email protected] with the application name and they can check how it was provisioned.
Token lifetime and caching
Tokens are short-lived by design, and the identity server tells you how long yours lasts: read expires_in from the token response — it is a number of seconds — rather than assuming a fixed lifetime, because the value differs between environments and can change.
Fetch a token once, cache it in memory, and reuse it until it is close to expiring. Refresh a little early — thirty to sixty seconds before expires_in elapses — so that a token does not lapse mid-flight between your check and the API receiving it.
Two habits to avoid:
- Do not request a fresh token per API call. It is slow, and the identity server rate-limits aggressive clients.
- Do not persist tokens to disk or to a shared cache that outlives the process. Re-requesting from the credentials is cheap; a leaked token is not.
There is no refresh token in the client credentials grant. When the token expires, you simply request another one the same way.
Troubleshooting
| Symptom | Likely cause |
|---|---|
401 Unauthorized | No Authorization header, a malformed one (the Bearer prefix is required), or an expired token |
401 with a freshly issued token | The token was minted for the wrong environment — a Sandbox token will not open Production, or vice versa |
403 Forbidden | The token is genuine but lacks the scope the endpoint requires, or the required role — check the scope on the token response first |
403 on every endpoint | audience was omitted or wrong on the token request, so the token was not issued for the Cases API |
404 on a case you know exists | The token belongs to a different company. Cases are only ever visible to the company that owns them |
429 from the identity server | Too many token requests — cache the token as described above |
Next
With a token in hand, work through Manage the deceased and people on a case.
Updated 29 days ago

