Error codes
What each machine-readable error code the gateway returns actually means, and the runbook that resolves it.
- Version: 0.4
- Role: admin_user
- Type: troubleshooting
A CID error body carries a machine-readable code alongside its sentence. Clients key behaviour
on the code and never on the text, because the text is rewritten and translated and the code is
not. This page says what each of the sixteen codes means and where to go next.
The generated list — every code with its HTTP status, exception class and throw site — is the error codes reference. This page is the reading of it.
Note
A refusal with no code did not come from the licence guard, the setup guard or the role layer. Read the HTTP status and the response body instead, and start from A dashboard action fails.
The refusal family — 402, 403 and 423
These four decide whether the caller may act at all. They are deliberate refusals, not faults, and the dashboard renders several of them through the same generic failure toast — which is what makes them look like bugs.
| Code | HTTP | What it means | Next |
|---|---|---|---|
SETUP_REQUIRED | 423 | First-boot setup has not completed. Every product route is locked; administration stays reachable so the wizard can be finished. | The wizard loops |
LICENSE_EXPIRED | 402 | Enforcement is on and no licence resolves as valid. Check the host clock before requesting a renewal — a drifting clock expires a valid licence. | The licence has expired |
FEATURE_NOT_LICENSED | 403 | The endpoint belongs to a module outside the installed tier. Entitlements resolve as the union of the features signed into the licence and the features of its tier, so a key added after the licence was signed still grandfathers in. | 402, 403 or 423 |
LIMIT_EXCEEDED | 403 | The licence's tenant or user limit is reached, so the account cannot be created. | Licensing |
The product refuses an action with 402, 403 or 423
You might see: READ_ONLY_ROLE · This is a read-only demo account (viewer role) — actions and changes are disabled. · ROLE_NOT_FOR_CHAT · FEATURE_NOT_LICENSED
Checks
- 1
Read the machine-readable code in the error body, not the sentence
expected · You can name the code.
- 2
For a 423, check whether first-boot setup ever completed
diagnostics · appliance.setupComplete = true
- 3
For a 402, read the licence state
diagnostics · appliance.license.state = "active|trial"
- 4
For FEATURE_NOT_LICENSED, read which tier is installed and whether the feature is in it
diagnostics · appliance.license.tier
- 5
For a 403 naming a role, read what the account's role may do
expected · The role holds the page or capability the action needs.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | no-code-in-body (info) | A refusal with no code did not come from the licence guard, the setup guard or the role layer. Something else produced it — most often the provider, or a validation error on the request body. Fix: Read the HTTP status and the response body, and find the request in Event Logs. Verify: expected · You can attribute the refusal. |
| check 2 fails | setup-not-complete (critical) | Until first-boot setup completes, every product route answers 423 with SETUP_REQUIRED. Administration stays reachable so the wizard can be finished without a rescue procedure.Fix: Follow The wizard finishes and the appliance asks for setup again. Verify: diagnostics · appliance.setupComplete = true |
| check 3 fails | licence-not-valid (critical) | With enforcement on and no valid licence, product routes answer 402 LICENSE_EXPIRED while administration stays available. An expired licence and a licence that never verified produce the same status and different fixes.Fix: Follow The licence has expired and the product is locked, and check the host clock before requesting a renewal. Verify: diagnostics · appliance.license.state = "active|trial" |
| check 4 fails | feature-not-in-tier (warning) | Licence tiers gate whole modules. The guard refuses the endpoint and the dashboard hides or disables the page, so the symptom is a feature that visibly exists and never works rather than one that is absent. Fix: Compare the installed tier against the generated tier table and ask your CID representative to add the feature. See Licensing. Verify: expected · It is accepted. |
| check 5 fails | role-lacks-access (info) | Role refusals are deliberate and are not licence problems. The viewer role in particular is read-only by design: it passes role checks on GET, HEAD and OPTIONS and is refused globally on everything that mutates, including downloading signed licence material. The capability model is computed in shadow mode and enforces nothing yet — what enforces today is the page matrix and the fixed roles.Fix: Give the account a role that holds the access, or perform the action as an administrator. See Access control. Verify: expected · It is accepted. If that does not apply: Include the code, the HTTP status, the route and the account's role. |
The read-only and role family
| Code | HTTP | What it means | Next |
|---|---|---|---|
READ_ONLY_ROLE | 403 | The account holds the viewer role. It satisfies role checks on GET, HEAD and OPTIONS and is refused globally on everything that mutates. This is the demo account behaving correctly. | Access control |
READ_ONLY_ROLE_NO_SIGNED_MATERIAL | 403 | The same account asking to download signed licence material, which a read-only account never may. | Access control |
ROLE_NOT_FOR_CHAT | 403 | An audit role tried to use the chat. Auditors review the estate rather than adding traffic to it; use the dashboard. | Chat and detections |
The role-administration family
These come from editing the access model itself, not from using the product.
| Code | HTTP | What it means | Next |
|---|---|---|---|
UNKNOWN_ROLE | 400 | The request named a role that does not exist. | Correct the role name. |
ROLE_NOT_RELEASED | 400 | The role exists but its capabilities are not enforced by the API yet, so an account holding it would be refused everywhere. The refusal is deliberate. | Access control |
SCOPE_NOT_ENABLED | 400 | Role assignments apply to the whole system in this release. Scoping one to a tenant or group is not enabled. | Access control |
INVALID_SCOPE | 400 | A scope was supplied in the wrong shape. | Correct the request body. |
Note
The capability model — 60 capabilities across 5 fixed roles — currently runs in shadow mode: it is computed, compared and enforces nothing. What enforces today is the shipped page matrix and the fixed roles. Do not plan access around capabilities until they leave shadow mode.
The concurrency family — 409
A 409 is almost always two people on the same screen, not a fault.
| Code | HTTP | What it means | Next |
|---|---|---|---|
PAGE_ACCESS_STALE | 409 | Someone else changed page access while your screen was open. Reload, read their change, then save yours. | A dashboard action fails |
CAPABILITY_ACCESS_STALE | 409 | The same, for the capability table. | A dashboard action fails |
FINDING_ALREADY_OPEN | 409 | A compliance finding is already open for that detection; open the existing one rather than duplicating it. | Compliance |
The tenant-group family — 409
| Code | HTTP | What it means | Next |
|---|---|---|---|
GROUP_NOT_EMPTY | 409 | The group still holds members or credentials. Move them before deleting it — deleting a group silently would move who can use which provider key. | Tenant groups |
GROUP_MAPPED_BY_SYNC_SCOPE | 409 | A directory sync scope targets this group. Re-point the scope or clear the mapping first, or the next sync fails on a group that no longer exists. | Directory sync scopes |
A directory sync scope will not run, or imports the wrong people
You might see: Sync scope not found · This sync scope is disabled. Re-enable it before syncing. · Sync scope is mapped to a CID tenant group that no longer exists · Sync scope would assign a role which cannot be granted from a directory scope
Before you start
Checks
- 1
Read the scope's state before anything else
expected · The scope is enabled.
- 2
Check the CID tenant group the scope maps into
expected · The target group exists.
- 3
Read the role the scope assigns
expected · The role is one of those the message lists as allowed.
- 4
Read the result counts of the last run
expected · The run completed rather than reporting a partial answer.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | scope-disabled (info) | A disabled scope is skipped rather than run and failed, so membership simply stops changing. Nothing in the product degrades; it just stops tracking the directory. Fix: Re-enable the scope, then run it once by hand and read the counts. Verify: expected · It completes and reports counts. |
| check 2 fails | target-group-deleted (warning) | The scope names a CID tenant group by id. Deleting that group leaves the scope pointing at nothing, and the sync refuses rather than creating a replacement group and quietly changing who can see what. Deleting a group that is a sync target is refused for the same reason. Fix: Re-point the scope at an existing group, or clear the mapping, then sync again. See Tenant groups. Verify: expected · It completes without naming a missing group. |
| check 3 fails | role-not-grantable (warning) | A directory group is an authorisation input, not an authorisation decision. Roles that administer the appliance itself cannot be handed out by editing a group in AD, so the scope is refused at save time rather than silently downgrading the role. Fix: Set the scope's role to one of those the refusal lists, and grant the others in CID directly. Verify: expected · It saves. |
| check 4 fails | partial-directory-answer (warning) | When the directory answers only part of a query, the sync imports and updates what it saw and removes nobody. Removing on a partial answer would deprovision people whose entries simply were not returned, which is worse than leaving membership stale. Fix: Fix the directory-side limit — usually a server-side size or time limit on the search, or a base DN broader than the controller will page through — then run the scope again. Verify: expected · The run completes without the partial-answer warning. If that does not apply: Attach the support bundle and the scope's base DN, filter and reported counts. |
Limits and known gaps
- The registry does not exist yet. There is no consolidated
common/error-codes.ts; the generated table is recovered by scanning the gateway's TypeScript for object literals carrying acodeproperty. A code thrown in a shape the scan does not recognise is absent from both the table and this page. - Most gateway failures carry no code at all. Of the 638 user-visible messages in the catalogue, sixteen carry a code. For the rest, the status and the sentence are what you have — the message catalogue maps every one of them to a runbook or records why it needs none.
Related
- Error codes reference — the generated table.
- Guided runbooks — the full catalogue.
- Access control — what each role may do.
Last updated on