Skip to main content
Use this page when the UI loads but an API fails, Google sign-in bounces to /login, or two people can edit the same step. Related: Errors and rate limits, Environment variables, Role preview.

FAQ

All of these must be true:
  1. Email ends with @schools.nyc.gov.
  2. A users document exists for that lowercase email.
  3. isActive is not false.
  4. Google OAuth redirect URI matches the origin (/api/auth/callback/google).
  5. NEXTAUTH_URL is that same origin (http://localhost:3000 locally).
Seed with npm run seed-users. Demo mailboxes (d79.demo.*) cannot Google-sign-in — preview them from a Super Admin session.
Presence and locks use canViewForm in src/lib/formAccess.js. Same-school levels 2, 3, and 4 may read editors/locks and edit answers (one plan per school per year). Level 1 still needs an assignment or share.If you preview as Principal or AP on a plan at the demo school, editors should return 200. Preview as a viewer (level 1) without a share and expect 403 on write.
Intended. Level 3 can edit the school’s plan (canEditForm) but cannot create, duplicate, attest, or submit. Those stay level ≥ 4. See Roles and access.
Logout adds sess:deny:{jti} in Redis. Without REDIS_URL, the JWT cookie remains valid until the 8-hour maxAge. Set Redis in production.
Auth POSTs are limited to 20 / 60s per IP. User mutations and form create are limited per user. In production, if Redis is down those limits fail closed (you get 429 instead of unlimited traffic). Check /admin/system → Redis, then Retry Redis.
Step locks last 5 minutes (form:{id}:step:{stepKey}). Presence heartbeats last 60s. Two serverless instances without Redis cannot see each other’s locks — configure REDIS_URL. Closing the tab should POST .../step/:n/unlock.
Auth no longer throws at import time (that broke Vercel Preview when the secret was Production-only). NextAuth still needs NEXTAUTH_SECRET at request time. Set it on Production and Preview. Copy .env.example to .env.local and run openssl rand -base64 32.
GEO_RESTRICT=deny is on, or a WAF deny rule is live. HTML requests redirect to /unavailable. APIs return 403 JSON (This application is only available from New York State.). Allowed geo is US + NY state, not city = New York. Local hosts without Vercel geo headers are allowed. Leave GEO_RESTRICT unset until Firewall logs look clean.
Next.js 16 type-checks page.js. Only the default page export (plus reserved fields such as metadata) is allowed. Keep helpers as local functions or move them to src/components.
Restart npm run dev. The connection is cached on global.mongoose.
GET /api/public/overview is cached 60s in Redis. Super Admin → System → Flush caches.

Diagnose a 403

1

Confirm who the API thinks you are

Session email and level come from the JWT. During role preview they are the demo user, not your Super Admin mailbox.
2

Compare with the form document

userId (owner), principalEmail, schoolName, assignedTo / collaborator assignedForms, sharedWithEmails.
3

Match the handler

canViewForm / canEditForm: Super Admin, owner, matching principalEmail, or same-school levels 2–4. Level 1 and off-school users need assignedForms or sharedWithEmails. Attest/submit/create still require level ≥ 4.

Local checklist

Open http://localhost:3000 → Sign in. Super Admin: /admin/system should show MongoDB connected.

Support