README — POST /api/admin/orgs/{id}/api-access
POST /api/admin/orgs/{id}/api-access#
POST /api/admin/orgs/:id/api-access {blocked} (orgs.api_access: owner/support, step-up, X-Admin-Reason; §8) → {orgId, apiBlocked, changed}.Caller and authorization#
Staff cookie session; permission orgs.api_access; fresh password + TOTP step-up (10 minutes); X-Admin-Reason required (3–300 characters). Write requests need same-origin JSON and X-Admin-CSRF from this session.This is the private staff console on https://admin.handwrites.io. A personal API key, organisation token, student login or isolated app-testing credential does not authenticate this route.Request contract#
| Location | Name | Required | Meaning |
|---|
| path | id | 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. |
| header | X-Admin-Reason | Yes | URI-encoded written audit reason; Cloud/billing may reuse a fresh target-specific session marker. |
The following is the accepted request shape, expressed as JSON Schema. It is not a runnable example. Runtime refinements and operation rules also apply.{
"type": "object",
"properties": {
"blocked": {
"type": "boolean"
}
},
"required": [
"blocked"
]
}
Response and failures#
Success: 200. POST /api/admin/orgs/:id/api-access {blocked} (orgs.api_access: owner/support, step-up, X-Admin-Reason; §8) → {orgId, apiBlocked, changed}.The wrapper returns {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.Usage notes#
Current working-tree developer-access control. The Admin role calls a signed Web service operation; deployment of this addition was not verified. This reference includes no working key.
Implementation contract#
Source snapshot: admin/src/app/api/admin/orgs/[id]/api-access/route.ts. This reference describes the current working tree, not proof of deployment. The response service types and validators remain authoritative. The exact exported handler below makes conditional behavior reviewable. For helpers such as customer-routes or cloudRoute, see the shared API specification notes and the named service module./** POST /api/admin/orgs/:id/api-access {blocked} (orgs.api_access: owner/support, step-up, X-Admin-Reason; §8) → {orgId, apiBlocked, changed}. */
export const POST = adminRoute("orgs.api_access", async (request, ctx, { params }: Context) => {
const { id } = await params;
return adminJson(await setOrgApiAccess(ctx, id, (await readBody(request, BODY)).blocked));
}, { rateLimit: "account-actions" });
Do not use production customer data to test this operation. Use an authorized staff test account and a disposable target in an appropriate environment; email, deletion, role and credential mutations have real effects.Current-source enforcement limitation#
Current-source enforcement limitation (not live-verified): setting apiBlocked is not proof that every organisation-owned token stops authenticating. In the captured checkout, http.ts policyGate skips enforceCredential for caller.orgToken, and developer.ts orgTokenCaller does not check subscription.apiBlocked. During containment, inspect and revoke affected organisation-owned keys and verify denial separately; do not rely on the block flag alone.Modified at 2026-10-26 13:41:16