---
title: "API clients"
---
<!-- licenses: platform -->

An ((API client)) is a credential made of a client ID and a client secret, exchanged for a short-lived access token through OAuth2. Unlike the [API keys and tokens](leveraging-the-api.md#api-keys-and-tokens), which are bearer strings with fixed access, an API client has an explicit ((grant)): a set of [permissions](custom-roles.md#permission-model) and the organizations they apply to.

The console URLs in this article use `console.runzero.com` as an example. Use the URL for your runZero deployment instead.

There are two kinds, and the difference matters:

| | User-bound | Account-bound |
| --- | ---------- | ------------- |
| Created from | [My API clients](https://console.runzero.com/settings/api-clients) | [Account API clients](https://console.runzero.com/account/api/clients) |
| Who can create one | Any user with access to grant | Superusers |
| Maximum access | The creator's current access | Administrator access to the account |
| When the creator's access changes | The credential narrows or recovers to match | Unaffected |
| When the creator is locked or removed | The credential stops working | Unaffected |
| Use it for | Automation on behalf of a person, AI clients, scripts | Service integrations, CI, unattended jobs |

The rule of thumb: if the credential is doing something on your behalf, make it user-bound so it dies with your access. If it belongs to a system rather than a person, make it account-bound so it does not break when someone changes jobs.

Both kinds require a Platform license.

## Grants {#grants}

A grant has two halves, and both are enforced on every request.

**Permissions.** A verb on each area, as described in [How permissions work](custom-roles.md#permission-model). A grant that does not include an area denies that area.

**Organizations.** Either a list of specific organizations, or every organization. Selecting every organization is an explicit choice. A grant with an empty organization list grants nothing rather than everything.

One grant applies one set of permissions uniformly across the organizations it names. To give a credential different permissions in different organizations, create separate API clients.

## Expiration {#expiration}

Every API client has an expiration, chosen when it is created. The default is 30 days, with presets for 90 days and a year, a maximum of 10 years, and an option for no expiration at all. Prefer a real expiration: a credential nobody has to renew is a credential nobody reviews.

An expired credential stops working rather than being deleted. It no longer exchanges its secret for an access token, and it stays in the list so you can see what expired and when. Access tokens are also capped by the credential's expiration, so a token issued shortly before a credential expires dies with it rather than outliving it.

Expiration is one of the two fields that can be changed after creation, so a credential that has expired, or is about to, can be given a new expiration without reissuing its secret or its grant. This is the way to renew a working integration. It is not a way to widen one, because permissions and organizations stay fixed.

Credentials created by approving an [MCP connection](mcp.md#mcp-oauth) are the exception. They expire 90 days after approval, and while their expiration can be shortened, it can never be moved past that original deadline or removed. Renewing one means reconnecting the application, which creates a new credential. Reviving an expired one counts against the limit of 40 active MCP connections per account.

## Creating a user-bound API client {#user-bound}

Go to **My API clients** at [console.runzero.com/settings/api-clients](https://console.runzero.com/settings/api-clients), which lists only the clients you created, and click to register a new one.

1. Enter an **API client name** and choose an **expiration**. See [Expiration](api-clients.md#expiration).
2. Choose the **organizations**. You can pick specific organizations or every organization you can reach. Choosing every organization records the organizations you can reach at that moment; organizations you gain access to later are not added automatically.
3. Optionally choose a **role**, either as a lock or as a starting point. See [Role locks](api-clients.md#role-locks).
4. Set the **Access** permissions. The form only offers permissions your current access allows. If you are an administrator in one organization and a viewer in another, selecting just the first organization lets you grant administrator permissions; selecting both clamps the grant to what the two have in common.
5. Review the summary and save. **Copy the client secret before closing the panel.** It is shown once and cannot be retrieved later.

### The creator's access is a continuing ceiling

A user-bound credential is checked against its creator every time it authenticates, not just when it was created:

- If you lose access, the credential loses it too, immediately.
- If you regain access, the credential recovers it, up to the permissions it was issued with.
- The credential can never gain access above its issued grant, even if you are later promoted.
- If your account is locked or removed, the credential stops working.

This is the property that makes user-bound credentials safe to hand to an AI client or a script: revoking the person revokes everything they issued.

## Creating an account-bound API client {#account-bound}

Superusers manage account-bound clients from **API clients** under **Account** in the left navigator, at [console.runzero.com/account/api/clients](https://console.runzero.com/account/api/clients).

The form is the same, with two differences. The ceiling is administrator access across every organization in the account rather than your own access, and selecting every organization stays dynamic, so organizations added to the account later are included.

An account-bound credential keeps the access it was issued regardless of what happens to the person who created it. That is the point, and also the risk: it needs to be tracked and rotated deliberately, because nothing else will retire it.

## Role locks {#role-locks}

When you choose a role while creating an API client, you pick one of two behaviors:

- **Lock this credential to the role.** The credential's permissions are re-derived from the role every time it authenticates. Editing the role changes what the credential can do. This keeps a fleet of credentials consistent, and lets you revoke a class of access by editing one role. A user-bound locked credential is still capped by its issued grant and by your current access, so a role gaining permissions later does not widen it beyond those.
- **Use the role as a starting point.** The role's permissions are copied into the form so you can adjust them. The result is a fixed grant, and later changes to the role do not apply.

If the locked role is deleted, the credential cannot authenticate until a role with that name exists again. The API clients list shows locked credentials whose role is disabled.

## Managing existing clients {#managing}

Once a credential exists, only its name and its [expiration](api-clients.md#expiration) can be changed. Its organizations, binding, permissions, and role lock are fixed at creation. To change any of those, create a new client and retire the old one.

The secret can be rotated without changing anything else about the credential, which is the right response to a leaked secret. Rotation is done by the credential itself, authenticated with the secret being replaced, and invalidates the old secret immediately:

```
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=<CLIENT_ID>" \
  --data-urlencode "client_secret=<CURRENT_SECRET>" \
  https://console.runzero.com/api/v1.0/account/api/rotate
```

The response includes the new secret. Rotation needs the current secret, so if it has been lost, the only option is to delete the client and create a new one. Rotation is also refused on an expired credential; give it a new [expiration](api-clients.md#expiration) first, then rotate.

Creation, metadata changes, deletion, and secret rotation are all recorded in the [audit log](managing-your-team.md#audit-log), with the organizations and permissions the credential was granted. Secrets are never recorded.

### Clients created before grants existed

API clients created before explicit grants were introduced show as a legacy grant in the list. They keep the access they have always had, which is unrestricted on the REST API and account-wide on MCP. They are not migrated automatically. Replace them with explicitly scoped clients when you can.

## Using an API client {#using}

Exchange the client ID and secret for an access token, then use the token as a bearer token:

```
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=<CLIENT_ID>" \
  --data-urlencode "client_secret=<CLIENT_SECRET>" \
  https://console.runzero.com/api/v1.0/account/api/token
```

The token is accepted anywhere an account API token is, subject to the grant. A call the grant does not permit is refused, and a call against an organization the grant does not name is refused whether or not the organization was requested explicitly.

API clients can also be presented to the [MCP endpoint](mcp.md#mcp-authentication), where the same grant decides which tools are offered.

The [IP address allowlist](leveraging-the-api.md#ip-address-allowlist) and the [rate limits](leveraging-the-api.md#api-rate-limiting) apply to API clients as they do to API keys.

## Credentials created by AI clients {#mcp-consent}

When you connect an AI client to runZero through [MCP](mcp.md#mcp-oauth), approving the connection creates an API client for you. Those credentials appear in the same lists as any other, are user-bound unless a superuser chose otherwise, and can be revoked the same way. They expire 90 days after approval, and that deadline cannot be moved. See [Expiration](api-clients.md#expiration) and [Connecting through OAuth](mcp.md#mcp-oauth).
