M2M OAuth
OAuth client_credentials token endpoint.
Machine-to-machine (M2M) authentication lets external services and CI/CD pipelines obtain short-lived access tokens without a user login. Rensei implements the standard OAuth 2.0 client_credentials grant.
When to use M2M vs API keys
| Scenario | Recommended mechanism |
|---|---|
| CI/CD pipeline triggers, external service integrations | M2M client_credentials token |
| CLI operator, long-running daemon worker | rsk_live_* API key |
| Single daemon worker registration | Worker registration key → runtime JWT |
Use M2M tokens when you need org-scoped access from a service identity that is not a human user and where short token lifetimes (automatic expiry) are a compliance requirement.
Creating an M2M client
M2M clients are managed in the Rensei admin panel:
- Navigate to Admin > M2M Clients.
- Click Create Client.
- Note the
client_idandclient_secret- the secret is shown only once.
M2M clients are org-scoped. Each client is associated with a specific organization and inherits the org's policy boundaries.
Token endpoint
POST /api/oauth/tokenAccepts application/x-www-form-urlencoded or application/json. Returns a standard OAuth 2.0 token response.
Request fields
| Field | Required | Description |
|---|---|---|
grant_type | Yes | Must be client_credentials |
client_id | Yes | Your M2M client ID |
client_secret | Yes | Your M2M client secret |
Example - form-encoded
curl -X POST https://app.rensei.ai/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=m2m_abc&client_secret=secret_xyz"Example - JSON body
curl -X POST https://app.rensei.ai/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "m2m_abc",
"client_secret": "secret_xyz"
}'Success response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}The response includes Cache-Control: no-store and Pragma: no-cache headers to prevent token caching by intermediaries.
Error responses
| HTTP status | Condition |
|---|---|
400 Bad Request | grant_type is not client_credentials, or client_id/client_secret missing |
401 Unauthorized | Invalid client_id or client_secret |
Using the access token
Pass the token as a bearer token in the Authorization header:
curl https://app.rensei.ai/api/public/sessions \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Token lifecycle
Request a token via POST /api/oauth/token with your client_id and client_secret.
Cache the token for its expires_in duration (typically 3600 seconds). The endpoint emits Cache-Control: no-store, so caching is the client's responsibility.
Refresh before expiry. Request a new token before the current one expires. The client_credentials grant always issues a fresh token - there is no refresh token in this flow.
TypeScript example
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
}
async function getM2MToken(clientId: string, clientSecret: string): Promise<string> {
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: clientId,
client_secret: clientSecret,
}),
});
if (!res.ok) {
throw new Error(`Token request failed: ${res.status}`);
}
const data = (await res.json()) as TokenResponse;
return data.access_token;
}Security considerations
- Store
client_secretin a secrets manager - never in source code or environment files committed to version control. - Token lifetimes are short by design. Treat each token as ephemeral; your client must handle re-issuance.
- M2M tokens carry org-scoped permissions. Audit client creation and deletion events in the Audit log.