| Location | Name | Required | Meaning |
|---|---|---|---|
| path | sub | Yes | Use the ID from an authorized staff response; source validators reject malformed IDs. |
| header | X-Admin-CSRF | Yes | Session-bound token from the staff page; not a developer credential. |
{
"oneOf": [
{
"type": "object",
"properties": {
"role": {
"type": "string",
"enum": [
"owner",
"support",
"billing",
"readonly"
]
}
},
"required": [
"role"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"active",
"disabled"
]
}
},
"required": [
"status"
],
"additionalProperties": false
}
]
}{error:{code,message}} for CloudError failures, attaches Request-Id, and disables caching. Handle 401 by signing in; show role refusal for 403 forbidden, reauthenticate only for step_up_required, and collect a reason for 400 reason_required. A CSRF failure needs a current page/session token, not blind retries. Operation-specific 404/409/413/422/502/503 errors retain their status. Honor Retry-After on 429./**
* PATCH /api/admin/admins/:sub {role} | {status: "active"|"disabled"} (admins.role / admins.disable, step-up).
* Refused (409) for your own role or status and when no active owner would remain; disabling also revokes every
* session. Audited in admins.ts.
*/
export const PATCH = adminRoute("admins.role", async (request, ctx, { params }: Context) => {
const { sub } = await params;
if (!SUB.test(sub)) throw new CloudError(404, "not_found", "Admin not found.");
const input = await readBody(request, body);
if ("role" in input) return adminJson({ admin: await setRole(ctx, sub, input.role) });
if (!can(ctx.admin.role, "admins.disable")) throw new CloudError(403, "forbidden", "Your admin role doesn't allow this.");
return adminJson(await setStatus(ctx, sub, input.status));
});