Skip to main content

Management API

TideCloak provides a Management API (Keycloak-compatible Admin REST) to manage realms, clients, users, roles, and more. It's ideal for automating provisioning, CI/CD, migrations, and back-office tasks.


1. Prerequisites

  • A running TideCloak server (dev, self-hosted, or managed).
  • An admin user or a confidential client with service account credentials.
  • Network access to the admin endpoints over HTTPS.

Base URLs

  • Modern Keycloak/TideCloak (Quarkus): https://<host>/realms/<realm>/protocol/openid-connect/token Admin REST base: https://<host>/admin/realms/<realm>/...
  • Legacy path (older distros include /auth): https://<host>/auth/realms/<realm>/protocol/openid-connect/token Admin REST base: https://<host>/auth/admin/realms/<realm>/...

Use whichever matches your deployment. Your earlier console URLs should hint which style you're on.


2. Try the API via Swagger

You can experiment with the API using the hosted Swagger UI: https://apidocs.tidecloak.com.

Prereqs

  • A realm (e.g., swagger-realm)
  • A client (e.g., swagger-ui)
  • A user with a linked Tide account

Steps

  1. Set Base URL to your TideCloak host.
  2. Enter the client_id (your client, not admin-cli unless that's what you use).
  3. Click Authorize and sign in with your Tide-linked account, or paste a Bearer token.

Create a confidential client with Service accounts enabled and grant it only the required realm-management roles (least privilege!):

  • Typical roles: view-users, manage-users, view-clients, manage-clients, view-realm, manage-realm (pick only what you need).

Token (Bash)

export HOST="https://<host>"
export REALM="master" # or the admin realm you use for tokens
export CLIENT_ID="admin-bot"
export CLIENT_SECRET="<client-secret>"
curl -sS -X POST "$HOST/realms/$REALM/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials" -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET"

4. Common Admin Tasks (cURL)

Replace TOKEN with your access token, and ADMREALM with the realm you are administrating (often a non-master realm).

List users

curl -sS "$HOST/admin/realms/ADMREALM/users?search=bob&first=0&max=20" -H "Authorization: Bearer $TOKEN"

Create a user

curl -sS -X POST "$HOST/admin/realms/ADMREALM/users" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"username": "bob",
"email": "bob@example.com",
"enabled": true
}' -i
# On success returns 201 and a Location header with the new user ID.

Assign a realm role to a user

# 1) Get the role representation
ROLE="admin"
ROLE_JSON=$(curl -sS "$HOST/admin/realms/ADMREALM/roles/$ROLE" -H "Authorization: Bearer $TOKEN")
# 2) Map it to the user
curl -sS -X POST "$HOST/admin/realms/ADMREALM/users/$USER_ID/role-mappings/realm" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "[$ROLE_JSON]"

Create a client and fetch its secret

# Create client
curl -sS -X POST "$HOST/admin/realms/ADMREALM/clients" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"clientId": "myclient",
"publicClient": false,
"serviceAccountsEnabled": true,
"redirectUris": ["https://app.example.com/*"]
}' -i
# Find client ID by clientId
CID=$(curl -sS "$HOST/admin/realms/ADMREALM/clients?clientId=myclient" -H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')
# Get client secret
curl -sS "$HOST/admin/realms/ADMREALM/clients/$CID/client-secret" -H "Authorization: Bearer $TOKEN"

5. C# HttpClient Examples

Get token (Client Credentials)

using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
var host = "https://<host>";
var realm = "master";
var clientId = "admin-bot";
var clientSecret = "<client-secret>";
using var http = new HttpClient();
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","client_credentials"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("client_secret",clientSecret),
});
var tokenRes = await http.PostAsync($"{host}/realms/{realm}/protocol/openid-connect/token", content);
var tokenJson = await tokenRes.Content.ReadAsStringAsync();
// parse access_token and use below

List users

http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var res = await http.GetAsync($"{host}/admin/realms/ADMREALM/users?first=0&max=20");
var body = await res.Content.ReadAsStringAsync();


6. Error Handling Cheat-sheet

  • 401 Unauthorized: Missing/expired token.
  • 403 Forbidden: Token lacks required realm-management permissions.
  • 404 Not Found: Wrong ID/realm or insufficient privileges hide resources.
  • 409 Conflict: Duplicate name/constraint violation (e.g., username exists).
  • 400 Bad Request: Invalid payload or parameters.

Log the response body; admin endpoints usually return a useful message.


7. Security & Best Practices

  • Prefer Client Credentials with a minimal-scope service account over password grant.
  • Assign only needed realm-management roles (least privilege).
  • Use HTTPS, rotate secrets, and store them in a secure secret manager.
  • Do not expose admin tokens client-side.
  • For production changes requiring governance, document how they're approved in your process (see the QEA (Quorum Enforced Authorization) / Change Requests section below).

8. QEA / Change Requests

With QEA on, a governed admin write returns 202 Accepted with a change request (CR) id instead of applying. Authorize records one approval and auto-commits at quorum; Commit applies an already-approved CR (412 QUORUM_NOT_MET below quorum).

Endpoints: /admin/realms/{realm}/iga/.... The master realm is exempt.

For a complete, code-grounded description of the Tide-specific admin endpoints (vendor setup, licensing, the full IGA change-request lifecycle, Ragnarok offboarding, and the public console config) that the tide-console uses, see the Admin API Reference.

8.1 A governed write

curl -i -X POST "$HOST/admin/realms/ADMREALM/users/$USER_ID/role-mappings/realm" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "[$ROLE_JSON]"
# HTTP/1.1 202 Accepted
# Location: /admin/realms/ADMREALM/iga/change-requests/<CR_ID>
# { "status":"PENDING", "changeRequestId":"<CR_ID>",
# "entityType":"USER", "actionType":"GRANT_ROLES", "message":"..." }

202 means "captured for approval", not "done". The Location header points at the CR.

8.2 Change-request lifecycle endpoints

Require an admin token with manage-realm (read-only listings: view-realm).

Method and pathPurposeNotes
GET /iga/change-requests?status=PENDINGList CRs (defaults to PENDING).Each item carries threshold, authorizationCount, readyToCommit, requiredApproverRoles, scopeMode.
GET /iga/change-requests/{id}Fetch a single CR.404 if unknown or in another realm.
POST /iga/change-requests/{id}/authorizeRecord one approval by the calling admin. Auto-commits when this approval reaches quorum.200 on success. 403 (missing approver role), 409 (not PENDING, or already signed by this admin).
POST /iga/change-requests/{id}/commitApply a CR that has already reached quorum (replay and apply).200 (now APPROVED), 403 (missing role), 412 (QUORUM_NOT_MET when below quorum, or an unmet dependency), 404 (ENTITY_VANISHED).
POST /iga/change-requests/{id}/denyDeny a CR.204 No Content.
POST /iga/change-requests/bulk-authorizeDrain many CRs in one call (typically the toggle-on ADOPT set).200 even when individual CRs reject; read the per-CR results array. 429 if another bulk run holds the per-realm lock.
PUT /iga/change-requests/{id}Replace the CR rows; wipes all authorizations (re-signing required).Body {"rows":[...]}.

End-to-end (capture, then authorize until quorum):

# 1. The governed write returned 202 with <CR_ID> (see 8.1).
# 2. Authorize with distinct admins. The approval that reaches quorum
# auto-commits, so the change applies and the CR becomes APPROVED.
curl -s -X POST "$HOST/admin/realms/ADMREALM/iga/change-requests/$CR_ID/authorize" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
# 3. (Only if needed) Commit applies a CR that already reached quorum.
# Below quorum it returns 412 QUORUM_NOT_MET.
curl -s -X POST "$HOST/admin/realms/ADMREALM/iga/change-requests/$CR_ID/commit" \
-H "Authorization: Bearer $TOKEN"

8.3 QEA response-code reference

StatusMeaning
202 AcceptedA governed admin write was captured into a CR. Body {status:"PENDING", changeRequestId, entityType, actionType, message}; Location points at the CR.
200 OKList / get, or an authorize / commit that succeeded.
204 No ContentDeny, or a policy delete.
403 ForbiddenMissing approver role on authorize / commit.
404 Not FoundUnknown CR id; also ENTITY_VANISHED on an ADOPT commit whose target was deleted out of band.
409 ConflictCR not PENDING, the same admin already signed it, or a conflicting CR is already pending for the entity.
412 Precondition FailedCommit gate not met: below quorum (QUORUM_NOT_MET) or an unmet dependency.
429 Too Many RequestsA bulk-authorize is already running for this realm.

9. Troubleshooting

  • Token URL mismatch: If you see 404s on /realms/.../protocol/openid-connect/token, try the legacy /auth/realms/... path (and vice-versa).
  • CORS: Admin REST is normally for server-to-server; use it from backend services to avoid CORS.
  • Swagger auth: If "Authorize" fails, verify client type, redirect URIs, and whether the client is public vs confidential.