Manage machine-to-machine credentials

/iam/m2m-credentials is where you provision and manage the machine-to-machine credentials your organization uses to call the API. A credential is an OAuth client your services authenticate as with the client-credentials grant: you create one here, receive its client_id and client_secret, then exchange them for access tokens. This guide covers the full lifecycle — create, list, rotate the secret, rename and revoke.

Managing credentials is an organization access-and-security operation, so it lives under /iam/* (identity and access), not /self/* (your own profile). Every credential is scoped to the caller's own organization, taken from the access token.

Before you start

You need a valid access token for a caller permitted to manage credentials. Build one with either sign-in path first:

Two permissions gate this resource:

  • m2m:credentials:read — list credentials.
  • m2m:credentials:write — create, rotate, rename and revoke credentials.

See Permissions for how permissions arrive in your token. Creating or changing a credential may require recent MFA; if your last sign-in was too long ago you get a step-up challenge, which the write call surfaces as a 403. See Step-up MFA.

Create a credential

POST to /iam/m2m-credentials with a name and a type. Use client_secret for the common case — the API mints a dedicated client and returns its secret. Pin api-version to protect your integration from future changes:

curl
curl -sS -X POST "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"nightly-backup-worker","type":"client_secret"}'
TypeScript / Node
const res = await fetch(
  "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "nightly-backup-worker",
      type: "client_secret",
    }),
  },
);
const credential = await res.json();
Python
import requests

res = requests.post(
    "https://api.cybersentriq.com/iam/m2m-credentials",
    params={"api-version": "1.0"},
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "name": "nightly-backup-worker",
        "type": "client_secret",
    },
)
credential = res.json()
C#
using System.Net.Http;
using System.Text;

var http = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post,
    "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0");
req.Headers.Add("Authorization", $"Bearer {accessToken}");
req.Content = new StringContent(
    "{\"name\":\"nightly-backup-worker\",\"type\":\"client_secret\"}",
    Encoding.UTF8, "application/json");

var res = await http.SendAsync(req);
var credential = await res.Content.ReadAsStringAsync();
Go
body := strings.NewReader(`{"name":"nightly-backup-worker","type":"client_secret"}`)
req, _ := http.NewRequest("POST",
    "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0", body)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()
// Decode res.Body into your model.

You get a 201 with the new credential, including its client_secret:

JSON
{
  "id": "cred_abc123",
  "name": "nightly-backup-worker",
  "client_id": "aB3xY7...",
  "client_secret": "s3cr3t-shown-once-only",
  "org_id": "org_abc123"
}

The secret is shown once. client_secret is returned only in this response and cannot be retrieved again. Store it in your secrets manager immediately. If you lose it, [rotate the secret](#rotate) to get a new one.

Then authenticate with it using the client-credentials grant — see Service account and M2M authentication.

Request fields

  • name (required): a human-friendly name for the credential.
  • type (required): client_secret for a generated shared secret, or public_key to register your own key for private_key_jwt.
  • service_identity_id (optional): the service identity to bind the credential to.
  • public_key (required for public_key): your public key in PEM form.
  • valid_for_days (optional, public_key): days until the key expires; omit or 0 for no expiry.
  • expires_at (optional, public_key): expiry read from a certificate, as an RFC 3339 timestamp. Takes precedence over valid_for_days.

List credentials

GET /iam/m2m-credentials returns the credentials bound to your organization. The secret is never included here — only on create and rotate. Needs m2m:credentials:read:

curl
curl -sS "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
TypeScript / Node
const res = await fetch(
  "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0",
  { headers: { Authorization: `Bearer ${accessToken}` } },
);
const { credentials } = await res.json();
Python
import requests

res = requests.get(
    "https://api.cybersentriq.com/iam/m2m-credentials",
    params={"api-version": "1.0"},
    headers={"Authorization": f"Bearer {access_token}"},
)
credentials = res.json()["credentials"]
C#
using System.Net.Http;

var http = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get,
    "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0");
req.Headers.Add("Authorization", $"Bearer {accessToken}");

var res = await http.SendAsync(req);
var credentials = await res.Content.ReadAsStringAsync();
Go
req, _ := http.NewRequest("GET",
    "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()
// Decode res.Body into your model.

The response is a credentials array. Each entry describes a credential without its secret:

JSON
{
  "credentials": [
    {
      "id": "cred_abc123",
      "name": "nightly-backup-worker",
      "client_id": "aB3xY7...",
      "org_id": "org_abc123",
      "credential_type": "client_secret",
      "created_at": "2026-07-01T09:30:00Z",
      "last_rotated_at": "2026-07-01T09:30:00Z"
    }
  ]
}

Use each credential's id as the credentialId path segment to rotate, rename or revoke it below. Additional fields may be present, so decode leniently.

Rotate the secret

POST /iam/m2m-credentials/{credentialId}/rotate-secret mints a new secret and immediately invalidates the previous one. Use it on a schedule, or when a secret may have leaked. For a client_secret credential you send no body. Needs m2m:credentials:write:

curl
curl -sS -X POST \
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret?api-version=1.0" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
TypeScript / Node
const res = await fetch(
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret?api-version=1.0",
  {
    method: "POST",
    headers: { Authorization: `Bearer ${accessToken}` },
  },
);
const rotated = await res.json();
Python
import requests

res = requests.post(
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret",
    params={"api-version": "1.0"},
    headers={"Authorization": f"Bearer {access_token}"},
)
rotated = res.json()
C#
using System.Net.Http;

var http = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post,
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret?api-version=1.0");
req.Headers.Add("Authorization", $"Bearer {accessToken}");

var res = await http.SendAsync(req);
var rotated = await res.Content.ReadAsStringAsync();
Go
req, _ := http.NewRequest("POST",
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret?api-version=1.0", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()
// Decode res.Body into your model.

You get a 200 with the same shape as create — the new client_secret, shown once:

JSON
{
  "id": "cred_abc123",
  "name": "nightly-backup-worker",
  "client_id": "aB3xY7...",
  "client_secret": "new-s3cr3t-shown-once-only",
  "org_id": "org_abc123"
}

Rotation is immediate: the old secret stops working as soon as this call returns. Roll the new secret out to your service before rotating, or during a window where a brief failure is acceptable. To rotate a public_key credential, send {"new_public_key": "<PEM>"} as the body.

Rename a credential

PATCH /iam/m2m-credentials/{credentialId} updates the credential's name. It does not change the client_id or secret. Needs m2m:credentials:write:

curl
curl -sS -X PATCH \
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"nightly-backup-worker-eu"}'
TypeScript / Node
const res = await fetch(
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0",
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: "nightly-backup-worker-eu" }),
  },
);
Python
import requests

res = requests.patch(
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123",
    params={"api-version": "1.0"},
    headers={"Authorization": f"Bearer {access_token}"},
    json={"name": "nightly-backup-worker-eu"},
)
C#
using System.Net.Http;
using System.Text;

var http = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Patch,
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0");
req.Headers.Add("Authorization", $"Bearer {accessToken}");
req.Content = new StringContent(
    "{\"name\":\"nightly-backup-worker-eu\"}",
    Encoding.UTF8, "application/json");

var res = await http.SendAsync(req);
Go
body := strings.NewReader(`{"name":"nightly-backup-worker-eu"}`)
req, _ := http.NewRequest("PATCH",
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0", body)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()

A successful rename returns 200.

Revoke a credential

DELETE /iam/m2m-credentials/{credentialId} deletes the underlying client and removes the binding. Tokens already issued to it stop working, so revoke only when the service is retired or the credential is compromised. Needs m2m:credentials:write:

curl
curl -sS -X DELETE \
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
TypeScript / Node
const res = await fetch(
  "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0",
  {
    method: "DELETE",
    headers: { Authorization: `Bearer ${accessToken}` },
  },
);
Python
import requests

res = requests.delete(
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123",
    params={"api-version": "1.0"},
    headers={"Authorization": f"Bearer {access_token}"},
)
C#
using System.Net.Http;

var http = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Delete,
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0");
req.Headers.Add("Authorization", $"Bearer {accessToken}");

var res = await http.SendAsync(req);
Go
req, _ := http.NewRequest("DELETE",
    "https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()

A successful revoke returns 204 with no body.

Versioning

api-version is a query parameter in major.minor form. Omit it and you get the pinned default, 1.0. Send an unsupported version and the call returns 400 with an api-supported-versions response header listing the versions you can use. Every response carries that header, so you can always see what is available.

Pin api-version in production clients so a new default cannot change your integration's behavior underneath it.

When it fails

  • 400: the request body or the api-version you sent is not valid. Check the api-supported-versions response header for the versions you can use.
  • 401: the access token is missing, invalid or expired. Build a fresh token (see the sign-in guides above) and try again. For how to keep a token alive, see Refreshing tokens.
  • 403: your caller does not hold the permission the call needs (m2m:credentials:read to list, m2m:credentials:write to change), or a step-up MFA challenge is required because your last sign-in is too old. See Step-up MFA.
  • 404: no credential with that credentialId exists in your organization.

Next