Field School

Integration

Campus API — Wave 1

Learner identity and Grok Bot progress on the live Next campus. Guests stay in the browser. Signed-in watch and quiz writes Postgres. This page is unlisted from search.

OpenAPI: /docs/openapi-wave1.json. Station 01: /c/grok-bot/s/briefing.

01

How it works

  1. Guest walks Grok Bot. Progress is localStorage. POST /api/events returns 401.
  2. A signed-in Auth.js member is upserted into members and given a memberships row on org field-school.
  3. Watch, quiz, and field work on station 01 post learning_events.
  4. GET /api/progress?course=grok-bot rebuilds that membership’s map. Every query is scoped by org_id and membership_id.
  5. Org household exists and does not see Field School operator catalog events. User B does not see user A.

Base URL https://portal.fieldschool.ai. university.benjohnson.ai 301s to portal. AUTH_URL is portal. Cookies are Auth.js session cookies.

02

Seed orgs

slugkindisolationgroup
field-schooloperatorpublic_catalogcohort-0
householdfamilystrictfamily
salescompanyplatform_plusdesk

New memberships default to stance learner. Staff emails get admin.

03

Postgres schema

Postgres 16 + pgvector. Database campus. Container field-school-campus-db. Data /opt/field-school/postgres. Not the leftover TanStack database field-school-db.

organizations

id uuid PK, slug unique, name, kind, isolation, homeschool_mode, created_by, created_at.

members

id uuid PK, email unique, name, created_at.

memberships

id uuid PK, org_id, member_id, stance, created_at. Unique (org_id, member_id).

groups / group_memberships

groups: org_id + slug unique. group_memberships: (group_id, membership_id).

assignments

org_id, membership_id, object_type, object_id, status, raw jsonb. Table is in Wave 1; station field work currently writes a learning_events row of kind assignment.

learning_events (spine)

org_id, membership_id, actor_membership_id, actor_stance, kind (watch | quiz | assignment), object_type, object_id (grok-bot:briefing), skill_ids text[], score numeric, raw jsonb, created_at.

04

GET /api/me

Guest:

{ "authenticated": false, "guest": true }

Signed in:

{
  "authenticated": true,
  "member": { "id": "uuid", "email": "a@example.com", "name": "Ada" },
  "org": { "slug": "field-school", "isolation": "public_catalog" },
  "memberships": [
    { "id": "uuid", "stance": "learner", "org": "field-school", "isolation": "public_catalog" }
  ]
}

05

GET /api/progress?course=grok-bot

Guest returns an empty module map. Signed in returns only this membership.

{
  "authenticated": true,
  "course": "grok-bot",
  "org": "field-school",
  "membershipId": "uuid",
  "modules": {
    "briefing": {
      "watched": true,
      "assignment": { "list8": true },
      "notes": "",
      "quizScore": 4,
      "quizPassed": true,
      "passed": true
    }
  }
}

06

POST /api/events

Guest is 401 sign_in_required. Kind must be watch, quiz, or assignment.

{
  "kind": "quiz",
  "course": "grok-bot",
  "station": "briefing",
  "score": 4,
  "raw": { "passed": true, "answers": { "q1": 1 } }
}

Success:

{ "ok": true, "id": "uuid", "org": "field-school", "membershipId": "uuid" }

Station 01 sends watch when the clip is marked, assignment when field work is saved, quiz on submit. object_id defaults to {course}:{station}.

07

curl

curl -sS https://portal.fieldschool.ai/api/me
curl -sS "https://portal.fieldschool.ai/api/progress?course=grok-bot"
curl -sS -X POST https://portal.fieldschool.ai/api/events \
  -H "content-type: application/json" \
  -d '{"kind":"watch","course":"grok-bot","station":"briefing"}'

POST without a session cookie is 401. Sign in on /login, then call from that browser.

08

What this is not

  • Not Cap or the edit MCP.
  • Not campus MCP (Wave 7) or wildcard hosts (Wave 2).
  • Not Better Auth / PGlite / migrations 0001–0003.
  • AUTH_URL is portal.fieldschool.ai. University 301s there.