1. Handwrite Admin
Handwrite Admin
  • Handwrite Admin
    • Start here — staff console
    • Admin API specification notes
    • Admin socket specification notes
    • Staff role and permission matrix
    • Staff workflows and architecture
    • Admin source and validation status
    • Customer accounts
      • README — GET /api/admin/accounts/{sub}/billing
      • README — GET /api/admin/accounts/{sub}/cloud/documents/{id}
      • README — GET /api/admin/accounts/{sub}/cloud/documents
      • README — POST /api/admin/accounts/{sub}/cloud/keys/{keyId}/revoke
      • README — GET /api/admin/accounts/{sub}/cloud/keys
      • README — GET /api/admin/accounts/{sub}/cloud/oauth
      • README — GET /api/admin/accounts/{sub}/cloud/orgs
      • README — GET /api/admin/accounts/{sub}/cloud/settings
      • README — GET /api/admin/accounts/{sub}/cloud/usage
      • README — GET /api/admin/accounts/{sub}/cloud/webhooks
      • README — POST /api/admin/accounts/{sub}/delete
      • README — POST /api/admin/accounts/{sub}/disable
      • README — POST /api/admin/accounts/{sub}/enable
      • README — POST /api/admin/accounts/{sub}/mfa-reset
      • README — POST /api/admin/accounts/{sub}/password-reset
      • README — POST /api/admin/accounts/{sub}/purge
      • README — POST /api/admin/accounts/{sub}/restore
      • README — GET /api/admin/accounts/{sub}
      • README — PATCH /api/admin/accounts/{sub}
      • README — POST /api/admin/accounts/{sub}/sign-out
      • README — GET /api/admin/accounts
      • GET /api/admin/accounts/{sub}/billing
      • GET /api/admin/accounts/{sub}/cloud/documents/{id}
      • GET /api/admin/accounts/{sub}/cloud/documents
      • POST /api/admin/accounts/{sub}/cloud/keys/{keyId}/revoke
      • GET /api/admin/accounts/{sub}/cloud/keys
      • GET /api/admin/accounts/{sub}/cloud/oauth
      • GET /api/admin/accounts/{sub}/cloud/orgs
      • GET /api/admin/accounts/{sub}/cloud/settings
      • GET /api/admin/accounts/{sub}/cloud/usage
      • GET /api/admin/accounts/{sub}/cloud/webhooks
      • POST /api/admin/accounts/{sub}/delete
      • POST /api/admin/accounts/{sub}/disable
      • POST /api/admin/accounts/{sub}/enable
      • POST /api/admin/accounts/{sub}/mfa-reset
      • POST /api/admin/accounts/{sub}/password-reset
      • POST /api/admin/accounts/{sub}/purge
      • POST /api/admin/accounts/{sub}/restore
      • GET /api/admin/accounts/{sub}
      • PATCH /api/admin/accounts/{sub}
      • POST /api/admin/accounts/{sub}/sign-out
      • GET /api/admin/accounts
    • Staff administration
      • README — POST /api/admin/admins/{sub}/password-reset
      • README — PATCH /api/admin/admins/{sub}
      • README — DELETE /api/admin/admins/{sub}/sessions
      • README — GET /api/admin/admins
      • README — POST /api/admin/admins
      • POST /api/admin/admins/{sub}/password-reset
      • PATCH /api/admin/admins/{sub}
      • DELETE /api/admin/admins/{sub}/sessions
      • GET /api/admin/admins
      • POST /api/admin/admins
    • Operations
      • README — GET /api/admin/alarms
      • README — GET /api/admin/audit
      • README — GET /api/admin/billing
      • README — GET /api/admin/config
      • README — PUT /api/admin/config
      • README — GET /api/admin/deletions
      • README — GET /api/admin/icloud/probe
      • README — POST /api/admin/jobs/rollup
      • README — GET /api/admin/me
      • README — GET /api/admin/overview
      • README — GET /api/admin/usage/accounts/{sub}
      • README — GET /api/admin/usage/orgs/{id}
      • README — GET /api/admin/usage/rollup
      • GET /api/admin/alarms
      • GET /api/admin/audit
      • GET /api/admin/billing
      • GET /api/admin/config
      • PUT /api/admin/config
      • GET /api/admin/deletions
      • GET /api/admin/icloud/probe
      • POST /api/admin/jobs/rollup
      • GET /api/admin/me
      • GET /api/admin/overview
      • GET /api/admin/usage/accounts/{sub}
      • GET /api/admin/usage/orgs/{id}
      • GET /api/admin/usage/rollup
    • Authentication
      • README — POST /api/admin/auth/challenge
      • README — POST /api/admin/auth/sign-in
      • README — POST /api/admin/auth/sign-out-all
      • README — POST /api/admin/auth/sign-out
      • README — POST /api/admin/auth/step-up
      • POST /api/admin/auth/challenge
      • POST /api/admin/auth/sign-in
      • POST /api/admin/auth/sign-out-all
      • POST /api/admin/auth/sign-out
      • POST /api/admin/auth/step-up
    • Staff webhooks
      • README — DELETE /api/admin/hooks/{id}
      • README — POST /api/admin/hooks/{id}/test
      • README — GET /api/admin/hooks
      • README — POST /api/admin/hooks
      • README — staff webhook delivery
      • DELETE /api/admin/hooks/{id}
      • POST /api/admin/hooks/{id}/test
      • GET /api/admin/hooks
      • POST /api/admin/hooks
    • Organisations
      • README — POST /api/admin/orgs/{id}/api-access
      • README — GET /api/admin/orgs/{id}/audit
      • README — GET /api/admin/orgs/{id}/members
      • README — GET /api/admin/orgs/{id}
      • README — GET /api/admin/orgs/{id}/usage
      • POST /api/admin/orgs/{id}/api-access
      • GET /api/admin/orgs/{id}/audit
      • GET /api/admin/orgs/{id}/members
      • GET /api/admin/orgs/{id}
      • GET /api/admin/orgs/{id}/usage
    • Schools
      • README — POST /api/admin/schools/{orgId}/close
      • README — GET /api/admin/schools/{orgId}
      • README — PATCH /api/admin/schools/{orgId}
      • README — GET /api/admin/schools
      • POST /api/admin/schools/{orgId}/close
      • GET /api/admin/schools/{orgId}
      • PATCH /api/admin/schools/{orgId}
      • GET /api/admin/schools
    • Sessions
      • README — DELETE /api/admin/sessions/{id}
      • README — GET /api/admin/sessions
      • DELETE /api/admin/sessions/{id}
      • GET /api/admin/sessions
    • Support
      • README — GET /api/admin/support/canned/{id}
      • README — PUT /api/admin/support/canned/{id}
      • README — DELETE /api/admin/support/canned/{id}
      • README — GET /api/admin/support/canned
      • README — POST /api/admin/support/canned
      • README — GET /api/admin/support/tickets/{id}/attachments/{n}
      • README — GET /api/admin/support/tickets/{id}/link
      • README — POST /api/admin/support/tickets/{id}/link
      • README — POST /api/admin/support/tickets/{id}/note
      • README — POST /api/admin/support/tickets/{id}/reply
      • README — GET /api/admin/support/tickets/{id}
      • README — PATCH /api/admin/support/tickets/{id}
      • README — POST /api/admin/support/tickets/{id}/school
      • README — POST /api/admin/support/tickets/{id}/spam
      • README — GET /api/admin/support/tickets
      • GET /api/admin/support/canned/{id}
      • PUT /api/admin/support/canned/{id}
      • DELETE /api/admin/support/canned/{id}
      • GET /api/admin/support/canned
      • POST /api/admin/support/canned
      • GET /api/admin/support/tickets/{id}/attachments/{n}
      • GET /api/admin/support/tickets/{id}/link
      • POST /api/admin/support/tickets/{id}/link
      • POST /api/admin/support/tickets/{id}/note
      • POST /api/admin/support/tickets/{id}/reply
      • GET /api/admin/support/tickets/{id}
      • PATCH /api/admin/support/tickets/{id}
      • POST /api/admin/support/tickets/{id}/school
      • POST /api/admin/support/tickets/{id}/spam
      • GET /api/admin/support/tickets
    • Health
      • README — GET /api/health
      • GET /api/health
  1. Handwrite Admin

Admin API specification notes

Scope and identity#

Base URL: https://admin.handwrites.io. The staff pool and application are separate from customer, team and student identities. Staff API keys are not an authentication mechanism. The production session cookie is __Host-hwa_session, with HttpOnly, Secure, SameSite=Strict and Path=/; development uses hwa_session. The cookie value is opaque, while server storage identifies it by its hash. Absolute session lifetime is twelve hours; idle lifetime is thirty minutes. The session is bound to its recorded browser user agent and an active admitted staff account.
POST sign-in and challenge accept same-origin JSON before a staff session exists. Password-only tokens are refused: the completed flow must prove MFA_SETUP or SOFTWARE_TOKEN_MFA before issuing a session. The temporary Cognito challenge session and TOTP enrollment secret are sensitive transient values, not customer API tokens. Login attempts are limited to ten per IP or email in fifteen minutes. Staff self-service password recovery is not supplied; an authorized owner uses the staff reset workflow.

Request gates#

The common wrapper checks session, active account, permission, fresh step-up where required, audit reason where required, then write CSRF. It returns a no-store response with Request-Id and logs a restricted event. Specific handlers add target checks, refusal rules and their own audit records.
For writes, send JSON with Content-Type: application/json from the console's own origin and include X-Admin-CSRF from the current staff page/session. Safe HTTP methods skip write CSRF, but still need authorization where applicable. The staff frontend exposes the current CSRF value in its rendered page; the value cannot be derived from a public API key.
A required X-Admin-Reason is URI-encoded text, decoded and trimmed server-side, with 3–300 characters. State why this particular access/action is necessary; do not include passwords, tokens or document text. Password plus TOTP step-up is fresh for ten minutes. See the permission matrix for defaults and individual READMEs for overrides.
Cloud metadata inspection uses an account/organisation reason marker for fifteen minutes and writes cloud.view before reading data. content=1 additionally requires accounts.content.read and a fresh step-up, with its own audit. It reveals permitted titles, names, emails and settings; it never returns document text, original files or audio bytes. Account billing has a separate target marker; aggregate platform billing does not require a reason.

Responses, pagination and retries#

Errors normally have { "error": { "code": "...", "message": "..." } }. Keep Request-Id for diagnosis without retaining the request's secrets or customer content.
ConditionClient behavior
401 unauthenticated or invalid credentialsStart the appropriate staff sign-in flow; do not substitute a customer token.
403 forbiddenExplain the role restriction. Do not loop on reauthentication.
403 step_up_requiredRun the staff step-up UI, then deliberately retry the intended operation.
400 reason_requiredAsk for a reason for the specific target/action.
403 csrfReload/refresh the session's page token and verify same-origin JSON.
404 not_foundRecheck the selected target and access; do not enumerate identities.
409 conflictRefresh current state and preserve the user's intent; version/owner invariants may have changed.
429 rate_limitedHonor Retry-After and stop repetitive submissions.
502/503 service unavailableSurface the failed dependency; reconcile before replaying an uncertain mutation.
Use returned opaque cursors as provided. List limits differ by resource: many DynamoDB lists allow 1–100, filtered Cognito account search returns at most 60, and the sessions list is capped at 50. Do not assume every response uses the same pagination envelope. GET health reports liveness only.
There is no general idempotency-key promise for the staff API. Do not automatically replay email, invitations, account actions or configuration writes after an uncertain response. Credential revoke/API block operations are designed to report unchanged state on repetition, while school status/close operations explicitly use continuation cursors. A 202 response for purge, scheduled deletion or rollup only confirms acceptance.

Request and response schemas#

The machine-readable reference covers every exported staff/health operation in the captured checkout, including re-exported customer handlers. Request schemas describe accepted fields; runtime trimming, validation, target checks and service constraints remain authoritative. Some service-projected success responses intentionally remain open object schemas: the README contains the exact handler and points to its source service. These are not generated public SDK guarantees.
Individual handler source snapshots support review, but imported historical planning comments are not deployment evidence. The source-status page identifies current-working-tree additions. Private mail and SNS callbacks are documented in the Internal edition, not as staff-cookie endpoints.
Modified at 2026-10-26 13:41:16
Previous
Start here — staff console
Next
Admin socket specification notes
Built with