Skip to content
Help Center

Connect a machine client to Persona with OAuth

Last updated on .

View Markdown Contact support Contact support 7 min read
On this page

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 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.

# RSA (RS256)
openssl genrsa -out machine-client-private.pem 3072
openssl rsa -in machine-client-private.pem -pubout -out machine-client-public.pem
# 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:

FieldValue
grant_typeclient_credentials
client_assertion_typeurn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionThe JWT that you signed with your private key
scopeOptional. A subset of the scopes that you gave the client
resourceOptional. 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:

ClaimValue
issYour client ID, for example oac_…
subYour client ID, the same value as iss
audhttps://withpersona.com/api/v1/oauth/token
iatThe time now, in seconds since the Unix epoch. It must not be in the future
expA time at most 5 minutes after iat
jtiA 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.

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:

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).

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

Use the access token

Send the token in the Authorization header:

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

ErrorWhat it means
invalid_clientPersona 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_grantPersona 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_clientPersona 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.

Was this page helpful?If something is missing, let us know and we will take a look.
Thanks for the feedback. It helps us improve these docs.