# Connect a machine client to Persona with OAuth

> Register a machine client, sign a token request with your own key, and connect an agent or script to MCP Server V2 with no person signing in.

Source: https://help.withpersona.com/articles/Qm7vXk2TnR9cLwB4aHy1Ze/
Section: Persona AI > Persona Extend > Key Concepts

_Last updated on November 4, 2026._

## Overview

A machine client is an OAuth client that you register for your organization. An agent, a script, or a scheduled job uses it to connect to Persona with no person to sign in. It proves who it is with a key pair that you control. You do not share a secret with Persona.

Use a machine client with [MCP Server V2](https://help.withpersona.com/articles/3Ev9FHB3kXENDUwbGg20KX/) when your agent runs unattended and cannot use the browser sign-in that Claude and ChatGPT use. If a person can sign in, use the browser sign-in instead. It is simpler and it keeps each action tied to that person.

A machine client is not an API key. Its token works on MCP Server V2 and on the Persona CLI API. It does not work on the public Persona REST API at `/api/v1`. If you need the full Persona API, use MCP Server V1 with an API key.

## How a machine client works

1. You register the client in the Dashboard. You give Persona the public half of a key pair. You keep the private half.
2. Your code signs a short-lived JSON Web Token (JWT) with the private key. This JWT is the client assertion.
3. Your code sends the client assertion to the Persona token endpoint.
4. Persona checks the signature against your public key and returns an access token.
5. Your agent sends the access token in the `Authorization` header on each request.

The access token expires after 15 minutes. Persona does not give a refresh token. When the token expires, sign a new client assertion and request a new access token.

⚠️

A machine client acts as the Dashboard user who registered it. It has the same data access and the same permissions as that user. If Persona deactivates that user, or the user loses the API key write permission, the client stops working. Register the client from a dedicated user that has only the permissions the agent needs. Do not register it from a personal account.

## Before you start

- You need the API key view and write permissions in the Dashboard.
- You need to confirm your password when you register the client.
- You need `openssl` to make a key pair. You can use any tool that makes an RSA or EC key.

## Make a key pair

The key must be one of these types:

- RSA, 2048 bits or longer. You sign with RS256.
- EC on the P-256 curve. You sign with ES256.

Use one of these sets of commands. The first makes an RSA key. The second makes an EC key.

```bash
# RSA (RS256)
openssl genrsa -out machine-client-private.pem 3072
openssl rsa -in machine-client-private.pem -pubout -out machine-client-public.pem
```

```bash
# EC P-256 (ES256)
openssl ecparam -name prime256v1 -genkey -noout -out machine-client-ec.pem
openssl pkcs8 -topk8 -nocrypt -in machine-client-ec.pem -out machine-client-private.pem
openssl pkey -in machine-client-private.pem -pubout -out machine-client-public.pem
```

You upload only `machine-client-public.pem`. Keep `machine-client-private.pem` in a secrets manager. Do not commit it to source control and do not send it to Persona.

## Register the machine client

1. In the Dashboard, open **API Keys**, then open the **Machine clients** tab.
2. Select **Create machine client**.
3. Enter a name that tells your team what the client is for, for example `Case summary agent`.
4. Give Persona your public key. Use one of these:
   - Paste the public key in PEM format.
   - Enter a JWKS URL. The URL must use `https`, must be on a public host, and must not contain a user name or password. Use a JWKS URL if you want to rotate keys without registering a new client.
5. Select the scopes. Select the fewest scopes that the agent needs.

   Machine clients on MCP can never approve or decline an Inquiry, whatever scopes you choose.

6. Optional: set an expiry date. After this date the client stops working.
7. Optional: enter an IP allowlist. Persona checks the caller's IP address when your code asks for a token.
8. Confirm your password and save.
9. Copy the client ID. It starts with `oac_`. You can also copy it later from the **Machine clients** list.

## Request an access token

Send a `POST` request to `https://withpersona.com/api/v1/oauth/token` with these form fields:

| Field                   | Value                                                                    |
| ----------------------- | ------------------------------------------------------------------------ |
| `grant_type`            | `client_credentials`                                                     |
| `client_assertion_type` | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`                 |
| `client_assertion`      | The JWT that you signed with your private key                            |
| `scope`                 | Optional. A subset of the scopes that you gave the client                |
| `resource`              | Optional. `https://withpersona.com/mcp` to get a token for MCP Server V2 |

### Build the client assertion

The client assertion is a JWT. Sign it with RS256 or ES256, to match your key. It must have these claims:

| Claim | Value                                                                           |
| ----- | ------------------------------------------------------------------------------- |
| `iss` | Your client ID, for example `oac_…`                                             |
| `sub` | Your client ID, the same value as `iss`                                         |
| `aud` | `https://withpersona.com/api/v1/oauth/token`                                    |
| `iat` | The time now, in seconds since the Unix epoch. It must not be in the future     |
| `exp` | A time at most 5 minutes after `iat`                                            |
| `jti` | A unique ID of at least 32 characters. Persona accepts each `jti` only one time |

Make a new client assertion for each token request. If you send the same `jti` again, Persona rejects the request with `invalid_grant`.

### Example in Python

This example signs a client assertion with the PyJWT library and requests a token for MCP Server V2. To install the libraries, run `pip install pyjwt cryptography requests`.

```python
import secrets
import time

import jwt  # PyJWT
import requests

CLIENT_ID = "oac_..."  # your client ID
TOKEN_URL = "https://withpersona.com/api/v1/oauth/token"

with open("machine-client-private.pem") as f:
    private_key = f.read()

now = int(time.time())
assertion = jwt.encode(
    {
        "iss": CLIENT_ID,
        "sub": CLIENT_ID,
        "aud": TOKEN_URL,
        "iat": now,
        "exp": now + 300,
        "jti": secrets.token_urlsafe(32),
    },
    private_key,
    algorithm="RS256",  # use "ES256" for an EC key
)

response = requests.post(
    TOKEN_URL,
    data={
        "grant_type": "client_credentials",
        "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        "client_assertion": assertion,
        "resource": "https://withpersona.com/mcp",
    },
    timeout=30,
)
response.raise_for_status()
token = response.json()
print(token["access_token"], token["expires_in"])
```

### Example with curl

If you already have a signed JWT in the `ASSERTION` variable, you can make the request with `curl`:

```bash
curl -X POST https://withpersona.com/api/v1/oauth/token \
  -d grant_type=client_credentials \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION" \
  -d resource=https://withpersona.com/mcp
```

### The response

A successful response contains a Bearer access token that starts with `puat_`, and `expires_in` set to `900` (15 minutes).

```json
{
  "access_token": "puat_...",
  "token_type": "Bearer",
  "expires_in": 900
}
```

## Use the access token

Send the token in the `Authorization` header:

```text
Authorization: Bearer puat_...
```

- For MCP Server V2, connect your agent to `https://withpersona.com/mcp` and send this header. Ask for the token with `resource=https://withpersona.com/mcp`.
- The same token type also works on the Persona CLI API.
- The token does not work as an API key. Do not send it to the public REST API at `/api/v1`.

Request a new token before the current one expires. Do not store tokens for longer than 15 minutes.

## Limits

Persona limits how often a client can make requests. If your agent gets a `429` error, wait and try again with more time between each attempt.

MCP writes also have their own limits. A machine client can make up to 20 writes each minute. One user can make up to 60 writes each minute.

## Rotate or revoke a client

To revoke a client, open it in the **Machine clients** list, select **Revoke**, and confirm. If your organization requires it, enter your password. Persona expires the client and all its live access tokens within seconds.

To rotate keys with no gap in service:

1. Make a new key pair.
2. Add a new machine client with the new public key. Or, if the client uses a JWKS URL, add the new public key to your JWKS.
3. Update your agent to sign with the new private key.
4. Check that your agent can get tokens with the new key.
5. Revoke the old client, or remove the old key from your JWKS.

## Troubleshooting

| Error                 | What it means                                                                                                                                                                                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_client`      | Persona does not know the client ID, the client has expired or was revoked, or Persona could not use your key. Check the client ID, the expiry date, and that the key matches.                                                                                |
| `invalid_grant`       | Persona did not accept the client assertion. Check that you signed it with the private key that matches the registered public key, `aud`, that `exp` is no more than 5 minutes after `iat`, that you used a new `jti`, and that your server clock is correct. |
| `unauthorized_client` | Persona has turned off machine client sign-in for your organization. Contact Persona support.                                                                                                                                                                 |

If you use an IP allowlist, a token request from an address that is not on the list fails. Check that your agent runs from an allowed address.

If you need further assistance, [reach out to our support team](https://app.withpersona.com/dashboard/contact-us?category=other).
