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:
- Sign in as a person with the user and device-code flow.
- Authenticate a backend service with the service account (M2M) flow.
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 -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"}'
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();
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()
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();
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:
{
"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_secretfor a generated shared secret, orpublic_keyto register your own key forprivate_key_jwt.service_identity_id(optional): the service identity to bind the credential to.public_key(required forpublic_key): your public key in PEM form.valid_for_days(optional,public_key): days until the key expires; omit or0for no expiry.expires_at(optional,public_key): expiry read from a certificate, as an RFC 3339 timestamp. Takes precedence overvalid_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 -sS "https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0" \
-H "Authorization: Bearer <ACCESS_TOKEN>"
const res = await fetch(
"https://api.cybersentriq.com/iam/m2m-credentials?api-version=1.0",
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const { credentials } = await res.json();
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"]
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();
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:
{
"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 -sS -X POST \
"https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123/rotate-secret?api-version=1.0" \
-H "Authorization: Bearer <ACCESS_TOKEN>"
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();
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()
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();
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:
{
"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 -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"}'
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" }),
},
);
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"},
)
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);
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 -sS -X DELETE \
"https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0" \
-H "Authorization: Bearer <ACCESS_TOKEN>"
const res = await fetch(
"https://api.cybersentriq.com/iam/m2m-credentials/cred_abc123?api-version=1.0",
{
method: "DELETE",
headers: { Authorization: `Bearer ${accessToken}` },
},
);
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}"},
)
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);
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 theapi-versionyou sent is not valid. Check theapi-supported-versionsresponse 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:readto list,m2m:credentials:writeto 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 thatcredentialIdexists in your organization.
Next
- Authenticate a service with a credential you created: Service account and M2M authentication.
- See a full flow: End-to-end example.