Creating App Clients
Creating App Clients
An App Client is a non-human identity your backend uses to call Zus APIs. Unlike a user, an App Client authenticates with a clientId and clientSecret — no person needs to be in the loop.
This guide walks you through creating a machine-to-machine (M2M) App Client, exchanging its credentials for an access token, and making your first authenticated API call. Zus uses OAuth 2.0 under the hood; specifically, M2M App Clients use the Client Credentials flow.
Prefer to follow along in Postman?Steps 1–3 below are also available as the M2M App Client Creation Guide in the Auth & Permissions folder of the Zus Health API Postman collection.
Before you start
You'll need:
- A Zus Builder Org and Builder Admin credentials.
- Access to the Zus sandbox environment (
api.sandbox.zusapi.com). Swap in production URLs once you're ready to go live.
The full flow has four steps:
- Look up the role ID you want the App Client to have.
- Create the App Client and save its
clientIdandclientSecret. - Exchange those credentials for an access token.
- Call a Zus API with the token to confirm everything works.
Step 1: Look up the role ID
Every App Client is assigned exactly one role. The available roles are:
| Role | Use for |
|---|---|
Builder Admin | Recommended for M2M App Clients. Full administrative access to your Builder Org. |
Care Team User | Scoped access intended for clinical or operational users. |
Set the RoleName variable to your chosen role, then fetch its ID:
Request
GET https://api.sandbox.zusapi.com/auth/roles?filter[name]={{RoleName}}Response (abbreviated)
{
"data": {
"type": "auth/roles",
"id": "<RoleID>",
"attributes": {
"name": "<RoleName>",
"description": "<description>",
"isManaged": true,
"permissions": ["..."],
"createdAt": 1638828368,
"updatedAt": 1655996421
}
}
}Copy the id value — you'll pass it as RoleID in Step 2.
Step 2: Create the App Client
This call creates the App Client and assigns the role from Step 1. See the Create App Client API reference for the full schema.
Request
POST https://api.sandbox.zusapi.com/auth/app-clients{
"data": {
"type": "auth/app-clients",
"attributes": {
"type": "machine_to_machine",
"name": "{{AppClientName}}",
"userType": "builder"
},
"relationships": {
"auth/roles": {
"data": {
"type": "auth/roles",
"id": "{{RoleID}}"
}
}
}
}
}
Save the response. The response body contains three values you'll need:
id— the App Client's UUID (used in Step 4).clientId— used as the OAuth client identifier in Step 3.clientSecret— used as the OAuth client secret in Step 3. Store this in a secrets manager; it cannot be retrieved later.Lost or compromised secret? Use the rotate-secret endpoint to generate a new one.
Step 3: Request an access token
Exchange the App Client's credentials for a Bearer access token using the OAuth 2.0 Client Credentials flow.
Parameters
| Parameter | Value |
|---|---|
grant_type | client_credentials |
client_id | The clientId from Step 2. |
client_secret | The clientSecret from Step 2. |
audience | https://api.sandbox.zusapi.com |
Request
curl --request POST \
--url https://auth.sandbox.zusapi.com/oauth/token \
--header 'content-type: application/json' \
--data '{
"client_id": "YOUR_APPCLIENT_ID",
"client_secret": "YOUR_APPCLIENT_SECRET",
"audience": "https://api.sandbox.zusapi.com",
"grant_type": "client_credentials"
}'Response
HTTP/1.1 200 OK
Content-Type: application/json{
"access_token": "YOUR_APPCLIENT_ACCESS_TOKEN",
"expires_in": 3600,
"token_type": "Bearer"
}The token is valid for 1 hour (expires_in is in seconds). See Token expiry and rate limits below for guidance on refreshing.
Step 4: Verify the token works
Make a test API call using the token in the Authorization header. A good sanity check is to fetch the App Client you just created (substitute the id from Step 2 for <YOUR_APPCLIENT_UUID>):
Request
curl --request GET \
--url https://api.sandbox.zusapi.com/auth/app-clients/<YOUR_APPCLIENT_UUID> \
--header 'Accept: application/vnd.api+json' \
--header 'Authorization: Bearer <YOUR_ACCESS_TOKEN>'Response
{
"data": {
"type": "auth/app-clients",
"id": "YOUR_APPCLIENT_UUID",
"attributes": {
"clientId": "YOUR_APPCLIENT_ID",
"name": "YOUR_APPCLIENT_NAME",
"type": "machine_to_machine",
"userType": "builder",
"createdAt": 1651254340,
"updatedAt": 1651254340
},
"relationships": {
"auth/roles": {
"data": {
"type": "auth/roles",
"id": "ROLE_UUID"
}
}
}
}
}A 200 OK confirms your App Client and token are working. You can now use the same Authorization: Bearer <token> header against any Zus API.
Token expiry and rate limits
- Tokens expire after 1 hour. When a token expires, repeat Step 3 to get a new one. Cache the token and reuse it until it expires — don't request a new one per API call.
- Token endpoint rate limit: If your application requests more than 200 tokens per hour for the same App Client, the token endpoint returns a
429 Too Many Requestserror. This limit is separate from the rate limits on Zus data APIs.
Updated 8 days ago
