Rensei docs

M2M Clients

OAuth client_credentials for machine-to-machine auth.

M2M (machine-to-machine) clients implement the OAuth 2.0 client_credentials grant. They are designed for backend services and CI pipelines that need to call the Rensei API without a human user session. Unlike rsk_live_* API keys, M2M clients exchange credentials for short-lived JWTs, which limits the blast radius of a leaked credential.

How it works

Tokens are signed HS256 JWTs. The default lifetime is 3600 seconds (1 hour), configurable via M2M_TOKEN_LIFETIME (seconds). Issued tokens are not stored - validation is pure JWT signature checking, making the system stateless and horizontally scalable.

Token claims

Every M2M JWT carries the following claims:

{
  "sub": "m2m_abc123...",
  "org_id": "org_xxxxxxxxxxx",
  "workspace_id": "ws_yyyyyyyyyyy",
  "scopes": ["workers:read", "sessions:read"],
  "iss": "rensei-platform",
  "aud": "rensei-api",
  "iat": 1748870400,
  "exp": 1748874000
}
ClaimDescription
subThe client_id (format: m2m_<uuid>)
org_idThe organization the client belongs to
workspace_idThe workspace the client belongs to
scopesDeclared scopes from the client definition
issAlways rensei-platform
audAlways rensei-api

Creating a client

M2M clients are managed via the API only - there is no settings UI for them (/settings/m2m-clients is a legacy alias that redirects to the API-keys page).

Valid scopes are workers:read, workers:write, sessions:read, sessions:write, agents:read, agents:write, and *.

curl -X POST https://app.rensei.ai/api/admin/m2m-clients \
  -H "Authorization: Bearer rsk_live_<org-wide-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deploy-pipeline",
    "scopes": ["workers:read", "sessions:read"]
  }'

Response (201):

{
  "client": {
    "clientId": "m2m_3f9e2a1b4c5d6e7f8a9b0c1d2e3f4a5b",
    "clientSecret": "m2ms_6e7f8a9b0c1d...",
    "name": "deploy-pipeline",
    "orgId": "org_xxxxxxxxxxx",
    "scopes": ["workers:read", "sessions:read"],
    "createdAt": 1748870400000
  }
}

Store the clientSecret immediately - it is not retrievable after this response.

Obtaining a token

curl -X POST https://app.rensei.ai/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "m2m_3f9e2a1b4c5d6e7f8a9b0c1d2e3f4a5b",
    "client_secret": "m2ms_6e7f8a9b0c1d..."
  }'

Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Error responses:

Error codeMeaning
invalid_clientUnknown client_id or wrong client_secret
invalid_tokenToken failed signature validation or has expired

Using the token

Pass the access token as a Bearer header:

curl https://app.rensei.ai/api/org/projects \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

Tokens expire after expires_in seconds. Your service should request a new token before the current one expires. A common pattern is to cache the token and refresh when the remaining lifetime drops below 5 minutes.

TypeScript example

let cachedToken: { value: string; expiresAt: number } | null = null

async function getM2MToken(): Promise<string> {
  const now = Math.floor(Date.now() / 1000)
  if (cachedToken && cachedToken.expiresAt > now + 300) {
    return cachedToken.value
  }

  const res = await fetch('https://app.rensei.ai/api/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: process.env.RENSEI_CLIENT_ID,
      client_secret: process.env.RENSEI_CLIENT_SECRET,
    }),
  })

  const data = await res.json()
  cachedToken = {
    value: data.access_token,
    expiresAt: now + data.expires_in,
  }
  return cachedToken.value
}

Listing clients

curl https://app.rensei.ai/api/admin/m2m-clients \
  -H "Authorization: Bearer rsk_live_<org-wide-key>"

The list response omits clientSecretHash - it returns only metadata (clientId, name, scopes, createdAt, lastUsedAt).

Revoking a client

curl -X DELETE "https://app.rensei.ai/api/admin/m2m-clients?client_id=<clientId>" \
  -H "Authorization: Bearer rsk_live_<org-wide-key>"

Revocation is immediate. Existing tokens issued to the client continue to pass signature validation until they expire (up to 1 hour). If you need immediate revocation, rotate M2M_JWT_SECRET - this invalidates all outstanding M2M tokens across all clients.

Audit trail

M2M client events are recorded in the audit trail:

EventTrigger
m2m.client_createdNew client created
m2m.token_issuedToken issued (logged per issuance)
m2m.client_revokedClient revoked

Environment variables

VariableRequiredDescription
M2M_JWT_SECRETYesHS256 signing secret. Generate with openssl rand -hex 32. Rotating this immediately invalidates all outstanding tokens.
M2M_TOKEN_LIFETIMENo (default: 3600)Token lifetime in seconds.

M2M_JWT_SECRET is a shared secret. Treat it with the same sensitivity as a root credential. Store it in your secrets manager and rotate it on a schedule.

On this page