Service account and M2M authentication

Use this path when a backend runs unattended, with no human to sign in. A service account authenticates with the OAuth 2.0 client-credentials grant: it presents a client ID and secret and gets back an access token. Unlike a public native client, an M2M client is confidential: it holds a secret, so keep it server-side.

Get a token with client credentials

POST the token endpoint with grant_type=client_credentials, your client ID and secret, and the audience.

Never hard-code a secret. Load <CLIENT_SECRET> from an environment variable or a secrets manager. The examples read it from the environment.

curl
curl -sS -X POST https://auth.cybersentriq.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=<CLIENT_ID>" \
  --data-urlencode "client_secret=$CSIQ_CLIENT_SECRET" \
  --data-urlencode "audience=https://api.cybersentriq.com" \
  --data-urlencode "scope=api:full:read api:full:write"
TypeScript / Node
const body = new URLSearchParams({
  grant_type: "client_credentials",
  client_id: process.env.CSIQ_CLIENT_ID!,
  client_secret: process.env.CSIQ_CLIENT_SECRET!,
  audience: "https://api.cybersentriq.com",
  scope: "api:full:read api:full:write",
});

const res = await fetch("https://auth.cybersentriq.com/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
const { access_token } = await res.json();
Python
import os
import requests

res = requests.post(
    "https://auth.cybersentriq.com/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": os.environ["CSIQ_CLIENT_ID"],
        "client_secret": os.environ["CSIQ_CLIENT_SECRET"],
        "audience": "https://api.cybersentriq.com",
        "scope": "api:full:read api:full:write",
    },
)
access_token = res.json()["access_token"]
C#
using System.Net.Http;
using System.Text.Json;

var http = new HttpClient();
var form = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["grant_type"] = "client_credentials",
    ["client_id"] = Environment.GetEnvironmentVariable("CSIQ_CLIENT_ID"),
    ["client_secret"] = Environment.GetEnvironmentVariable("CSIQ_CLIENT_SECRET"),
    ["audience"] = "https://api.cybersentriq.com",
    ["scope"] = "api:full:read api:full:write",
});

var res = await http.PostAsync("https://auth.cybersentriq.com/oauth/token", form);
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var accessToken = doc.RootElement.GetProperty("access_token").GetString();
Go
form := url.Values{
    "grant_type":    {"client_credentials"},
    "client_id":     {os.Getenv("CSIQ_CLIENT_ID")},
    "client_secret": {os.Getenv("CSIQ_CLIENT_SECRET")},
    "audience":      {"https://api.cybersentriq.com"},
    "scope":         {"api:full:read api:full:write"},
}

res, err := http.PostForm("https://auth.cybersentriq.com/oauth/token", form)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()

var tok struct {
    AccessToken string `json:"access_token"`
    ExpiresIn   int    `json:"expires_in"`
    TokenType   string `json:"token_type"`
}
json.NewDecoder(res.Body).Decode(&tok)

Request the coarse access scopes for now โ€” see Permissions.

The response is a bare access token. There is no refresh token for client credentials:

JSON
{
  "access_token": "eyJhbGciOiJSUzI1Ni(truncated)",
  "expires_in": 86400,
  "token_type": "Bearer"
}

Cache the token and reuse it until it is close to expiry, then request a new one. Requesting client-credentials tokens is cheap; there is nothing to refresh. See Refreshing tokens.

The token's granted scope reflects the coarse access scopes your service account's role allows (the API grants a coarse scope only where the role permits it). Its effective permissions are separate: they come from its role assignments and arrive in the https://cybersentriq.com/permissions claim. See Permissions.

Provisioning credentials today

There is no self-service minting UI yet. Today you provision M2M credentials programmatically, through the backend IAM API.

POST https://api.cybersentriq.com/iam/m2m-credentials creates a service account's credentials: a dedicated machine-to-machine client is minted for the audience, and the new client's ID and secret are returned. The same resource supports listing, rotating the secret, renaming and revoking. For the full lifecycle, with worked examples in every language, see Manage machine-to-machine credentials.

Requirements for the call:

  • A caller access token with the m2m:credentials:write permission.
  • Recent MFA (a step-up may be required if your last MFA is too old).
  • Service account identities do not need step up.

Both client_secret and private_key_jwt authentication are supported for the minted client.

Send the credential's name and type as a JSON body:

Shell
curl -sS -X POST "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0" \
  -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"nightly-backup-worker","type":"client_secret"}'

On success you receive the new service account's client_id and its client_secret (shown once, for the client_secret method). Store the secret in your secrets manager, then use it with the client-credentials grant above.

This is the supported path for API integrations: provision your own service account through the IAM API. For the full lifecycle โ€” create, list, rotate the secret, rename and revoke, with the request and response fields and worked examples in every language โ€” see Manage machine-to-machine credentials.

Next