PUT /api/principals/{id}/flags
Set named boolean flags on a principal, by read-modify-write.
Sets delegation and/or disabled. The handler reads the current flag word, ORs or
clears the named bit, writes back, and RE-READS -- so after is the re-read, never the
object that was sent.
WARNING: a raw numeric flags field is refused, and that is a real trap rather than
house style. The only whole-word setter the key server exposes ASSIGNS the flag word
rather than ORing into it, so a caller sending 1024 to mean DELEGATION would also CLEAR
the ENABLE bit in the same write and disable the principal they were trying to empower.
That failure then surfaces at the TLS layer, pointing nowhere near the flag write.
An empty body -- naming neither flag -- is refused rather than treated as a no-op, because
a request that names nothing would be answered 200 having changed nothing, which is
indistinguishable from a write that silently failed.
before and after carry the WHOLE word in hex beside the decoded bits, because this
route changes one bit and the word carries several. In particular check after.disabled.
Authentication
Send the session id as Authorization: Bearer <session id>, and an RFC 9449 DPoP proof in the DPoP header if your session is bound to a key. The two are required together, not as alternatives — see Authenticating and Proving you hold your key.
There is no session cookie on any response and none is read on any request. Do not set credentials: "include" on a browser fetch.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | A principal id as 16 hex digits. This is the id field of a principal projection -- NOT the principalId decimal string beside it. The two are the same eight bytes in two spellings, and only the hex one is a path segment. |
Request
| Field | Type | Required | Description |
|---|---|---|---|
delegation | boolean | no | Bit 0x400. Required before this principal can be impersonated. |
disabled | boolean | no | The inverse of the ENABLE bit. |
Example
{
"delegation": true,
"disabled": true
}
Responses
200
The flags were written and re-read, and the re-read agrees.
| Field | Type | Required | Description |
|---|---|---|---|
code | string | no | Absent on success. |
id | string | no | |
requested | object | no | Only the flags you named. |
asked | array of string | no | |
saved | boolean | no | |
before | PrincipalFlags | no | A principal's flag word, reported WHOLE alongside the decoded bits. The whole word travels because the only whole-word setter the key server exposes ASSIGNS rather than ORs, so seeing the word is how you confirm nothing else moved. |
after | PrincipalFlags or variant | no | The RE-READ flag word, never the object that was sent. Check after.disabled in particular. |
detail | string | no |
requested object
| Field | Type | Required | Description |
|---|---|---|---|
delegation | boolean | no | |
disabled | boolean | no |
after — PrincipalFlags
| Field | Type | Required | Description |
|---|---|---|---|
flags | integer | no | |
flagsHex | string | no | |
delegation | boolean | no | Bit 0x400. Required before this principal can be impersonated through a delegated login. Enrolment does NOT set it. |
disabled | boolean | no | The INVERSE of the ENABLE bit (0x1). A principal whose ENABLE bit was cleared fails at the TLS layer with a refusal that points nowhere near the flag write. |
systemDefined | boolean | no | Bit 0x2. The key server SKIPS updates to a system-defined principal and reports success, so a write against one appears to land and changes nothing. |
PrincipalFlags
| Field | Type | Required | Description |
|---|---|---|---|
flags | integer | no | |
flagsHex | string | no | |
delegation | boolean | no | Bit 0x400. Required before this principal can be impersonated through a delegated login. Enrolment does NOT set it. |
disabled | boolean | no | The INVERSE of the ENABLE bit (0x1). A principal whose ENABLE bit was cleared fails at the TLS layer with a refusal that points nowhere near the flag write. |
systemDefined | boolean | no | Bit 0x2. The key server SKIPS updates to a system-defined principal and reports success, so a write against one appears to land and changes nothing. |
Example
{
"code": "...",
"id": "...",
"requested": {
"delegation": true,
"disabled": true
},
"asked": [
"..."
],
"saved": true,
"before": {
"flags": 0,
"flagsHex": "...",
"delegation": true,
"disabled": true,
"systemDefined": true
},
"after": {
"flags": 0,
"flagsHex": "...",
"delegation": true,
"disabled": true,
"systemDefined": true
},
"detail": "..."
}
400
BAD_REQUEST -- a raw flags number was sent (see the warning), a named flag is not a boolean, the id is not 16 hex digits, or neither flag was named.
401
No live session, or a DPoP proof that did not verify.
SESSION_NOT_FOUND -- the credential names no session this Gateway holds.
SESSION_EXPIRED -- it aged out. Both carry reauth: true; obtain a new credential
rather than retrying.
PROOF_* -- the sender constraint refused. See the DPoP section of this document for the
full table; PROOF_MALFORMED is a 400 and PROOF_REPLAY_CACHE_FULL a 503, and the
rest are here.
SCOPED_CREDENTIAL_EXPIRED -- a scoped browser credential passed its fixed lifetime. Use
does not extend it: an idle timeout would be refreshed by exactly the traffic a stolen
handle produces.
403
BUILT_IN_PRINCIPAL -- the target is system-defined and its flags cannot be changed.
Refused HERE rather than sent, because the key server SKIPS the update on a
system-defined principal and RETURNS SUCCESS: the write would appear to be accepted
and would change nothing.
OWN_ACCOUNT -- the key server refuses ANY update whose target is the calling
session's own principal: a flag change, a disable, a role assignment, anything. Use a
different administrative identity. Reported as 403 rather than 500 because it is
a refusal you can act on.
404
NOT_FOUND.
409
NO_FLAG_WORD -- this principal type carries no flag word, so there is nothing to set. Or NOT_SAVED -- the key server did not refuse and the re-read does not agree, most likely a principal this Gateway could not tell was system-defined; compare after.systemDefined.
500
A fault rather than a refusal.
OP_FAILED is the catch-all: the key server refused in words that match no classified
pattern, so the Gateway cannot say more than that the operation did not happen. Some key
server refusals that are conceptually a caller error arrive here rather than as a 4xx,
because the wire carries no distinguishing code -- where a route can recognise one from
its message it maps it, and each such mapping is documented on the operation.
503
A capacity ceiling or a missing deployment prerequisite. retryable says which.
A scope of session or process names the ceiling that was hit, and the response
carries Retry-After.
Error handling
Every failure answers JSON carrying at least code and detail. Match on code — detail is written for a human debugging the call and its wording is not part of the contract. See the error model.
| Status | Meaning |
|---|---|
400 | BAD_REQUEST -- a raw flags number was sent (see the warning), a named flag is not a boolean, the id is not 16 hex digits, or neither flag was named. |
401 | No live session, or a DPoP proof that did not verify. SESSION_NOT_FOUND -- the credential names no session this Gateway holds. SESSION_EXPIRED -- it aged out. Both carry reauth: true; obtain a new credential rather than retrying. PROOF_* -- the sender constraint refused. See the DPoP section of this document for the full table; PROOF_MALFORMED is a 400 and PROOF_REPLAY_CACHE_FULL a 503, and the rest are here. SCOPED_CREDENTIAL_EXPIRED -- a scoped browser credential passed its fixed lifetime. Use does not extend it: an idle timeout would be refreshed by exactly the traffic a stolen handle produces. |
403 | BUILT_IN_PRINCIPAL -- the target is system-defined and its flags cannot be changed. Refused HERE rather than sent, because the key server SKIPS the update on a system-defined principal and RETURNS SUCCESS: the write would appear to be accepted and would change nothing. OWN_ACCOUNT -- the key server refuses ANY update whose target is the calling session's own principal: a flag change, a disable, a role assignment, anything. Use a different administrative identity. Reported as 403 rather than 500 because it is a refusal you can act on. |
404 | NOT_FOUND. |
409 | NO_FLAG_WORD -- this principal type carries no flag word, so there is nothing to set. Or NOT_SAVED -- the key server did not refuse and the re-read does not agree, most likely a principal this Gateway could not tell was system-defined; compare after.systemDefined. |
500 | A fault rather than a refusal. OP_FAILED is the catch-all: the key server refused in words that match no classified pattern, so the Gateway cannot say more than that the operation did not happen. Some key server refusals that are conceptually a caller error arrive here rather than as a 4xx, because the wire carries no distinguishing code -- where a route can recognise one from its message it maps it, and each such mapping is documented on the operation. |
503 | A capacity ceiling or a missing deployment prerequisite. retryable says which. A scope of session or process names the ceiling that was hit, and the response carries Retry-After. |