1. Schools
Handwrite Admin
  • Handwrite Regular Accounts
    • Schemas
      • Error
      • Principal
      • Role
      • Access
      • ContentType
      • Language
      • FileRef
      • Document
      • Tombstone
      • DocumentPage
      • CreateDocument
      • UpdateDocument
      • FileInfo
      • CommitUpload
      • Upload
      • DownloadLink
      • Extracted
      • ExtractionFailed
      • CommittedDocument
      • ExtractedDocument
      • SearchHit
      • SearchPage
      • Export
      • Permission
      • Share
      • ChangeRole
      • Invitation
      • Permissions
      • ReceivedInvitation
      • AcceptedInvitation
      • Me
      • WebhookEventType
      • Webhook
      • CreateWebhook
      • CreatedWebhook
      • WebhookDelivery
      • AudioFormat
      • Speech
      • WordTiming
      • SpeechMarks
      • AudioLink
      • Voice
      • Quota
      • SettingEntry
      • Settings
      • SettingsPatch
      • PatchedSettings
      • OAuthToken
      • OAuthError
  • Handwrite Team
    • Schemas
      • Error
      • Principal
      • Access
      • ContentType
      • Language
      • FileRef
      • Document
      • Tombstone
      • DocumentPage
      • OrgKind
      • OrgRole
      • Organisation
      • CreateOrganisation
      • RenameOrganisation
      • Member
      • AddMember
      • ChangeMember
      • InviteMember
      • OrgInvitation
      • ReceivedOrgInvitation
  • Handwrite Classroom
    • Schemas
      • Error
      • Principal
      • Access
      • ContentType
      • Language
      • FileRef
      • Document
      • Tombstone
      • DocumentPage
      • OrgKind
      • OrgRole
      • Organisation
      • CreateOrganisation
      • RenameOrganisation
      • Member
      • AddMember
      • ChangeMember
      • InviteMember
      • OrgInvitation
      • ReceivedOrgInvitation
  • 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
        POST
      • GET /api/admin/schools/{orgId}
        GET
      • PATCH /api/admin/schools/{orgId}
        PATCH
      • GET /api/admin/schools
        GET
    • 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. Schools

README — PATCH /api/admin/schools/{orgId}

PATCH /api/admin/schools/{orgId}#

PATCH /api/admin/schools/:orgId {suspended, cursor?} | {reinvite: true} (schools.manage, step-up); each audited. A
status answer with a cursor means students are still being turned off/on: send it back to carry on.

Caller and authorization#

Staff cookie session; permission schools.manage; fresh password + TOTP step-up (10 minutes). 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#

LocationNameRequiredMeaning
pathorgIdYesUse the ID from an authorized staff response; source validators reject malformed IDs.
headerX-Admin-CSRFYesSession-bound token from the staff page; not a developer credential.
The following is the accepted request shape, expressed as JSON Schema. It is not a runnable example. Runtime refinements and operation rules also apply.
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "suspended": {
          "type": "boolean"
        },
        "cursor": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2048
        }
      },
      "required": [
        "suspended"
      ]
    },
    {
      "type": "object",
      "properties": {
        "reinvite": {
          "const": true
        }
      },
      "required": [
        "reinvite"
      ]
    }
  ]
}

Response and failures#

Success: 200. PATCH /api/admin/schools/:orgId {suspended, cursor?} | {reinvite: true} (schools.manage, step-up); each audited. A
status answer with a cursor means students are still being turned off/on: send it back to carry on.
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#

School status changes and closure may return cursors. Continue the same operation with the returned cursor until completion; do not treat the first response as completion. Closing is destructive and requires the school name.

Implementation contract#

Source snapshot: admin/src/app/api/admin/schools/[orgId]/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.
/**
 * PATCH /api/admin/schools/:orgId {suspended, cursor?} | {reinvite: true} (schools.manage, step-up); each audited. A
 * status answer with a `cursor` means students are still being turned off/on: send it back to carry on.
 */
export const PATCH = adminRoute("schools.manage", async (request, ctx, { params }: Context) => {
  const orgId = schoolOrgParam((await params).orgId);
  const body = await readBody(request, PATCH_BODY);
  if (body.reinvite) return adminJson(await reinvite(ctx, orgId));
  return adminJson(await setSchoolStatus(ctx, orgId, body.suspended!, body.cursor));
}, { stepUp: true, rateLimit: "school-manage" });
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.
Modified at 2026-10-26 13:41:16
Previous
README — GET /api/admin/schools/{orgId}
Next
README — GET /api/admin/schools
Built with