# Canonical People & Orgs Alpha Surface

Open these first:

- Customer website: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/customer_website
- Data dictionary: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/data_dictionary
- Architecture: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/architecture
- Surface QC: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/surface_qc
- Integration proof: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/integration
- Implementation API: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/implementation/api
- Convergence answers: https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/integration/convergence.json

## Credentials

Demo:

```bash
export PEOPLE_AND_ORGS_BASE_URL="${PEOPLE_AND_ORGS_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/people_and_orgs/alpha/implementation/api}"
export PEOPLE_AND_ORGS_TOKEN="$(curl -fsS -X POST "$PEOPLE_AND_ORGS_BASE_URL/dev/mint?tenantId=demo" | jq -r '.token')"
```

Real or reviewer tenant:

```bash
export PEOPLE_AND_ORGS_BASE_URL="${PEOPLE_AND_ORGS_BASE_URL:?set by operator}"
export PEOPLE_AND_ORGS_TOKEN="${PEOPLE_AND_ORGS_TOKEN:-${PEOPLE_AND_ORGS_REVIEWER_JWT:?set by operator}}"
```

Never print, commit, or persist bearer tokens.

## Object Vocabulary

| Alpha object | Plain meaning | OneRoster source |
|---|---|---|
| `person` | One roster human. Roles live in memberships. | `oneroster.users` view plus `alpha.person_ext` |
| `place` | District, school, level, or roster place. | `oneroster.orgs` view plus `alpha.place_ext` and governed tags |
| `place_membership` | Dated link from person to place. | `oneroster.roles` view |
| `class_membership` | Dated link from person to school and learning group. | `oneroster.enrollments` view |
| `school_period` | Typed school year or term. | `oneroster.academic_sessions` view |
| `school_calendar` | School-day facts with typed reasons. | `edfi.calendar_date` view through `alpha.school_calendar` |
| `person_sensitive_profile` | Lawful sensitive demographic profile. | `oneroster.demographics` view |
| `app_credential` | App credential existence and rotation status. | Alpha extension table |
| `tag_definition` | Governed tag registry. | Alpha extension table |
| `object_tag` | Governed tag assignment for reporting slices. | Alpha extension table |

Person lifecycle dates are exactly `alpha.person.admission_date` and
`alpha.person.exit_date`, both sourced from `alpha.person_ext` and surfaced on
`alpha.person`. `exit_date` is an exclusive close date; null means the person
lifecycle remains open after `admission_date`. Do not derive person lifecycle
dates from membership `begin_date`/`end_date` or school-period
`start_date`/`end_date`.

## Endpoint Families

Discovery:

- `GET /` returns module, surface, docs links, and endpoint names.
- `POST /dev/mint?tenantId=demo` returns a demo bearer token. Non-demo tenant ids are rejected.

Core collections:

- `people`
- `places`
- `place_memberships`
- `class_memberships`
- `school_periods`
- `person_sensitive_profiles`
- `app_credentials`
- `tag_definitions`
- `object_tags`

Every collection supports list and per-resource create/read/update/logical-delete according to the customer website. Writes are per-resource, not bulk.

Subcollections:

- `GET /people/{personId}/memberships?asOfDate=YYYY-MM-DD`
- `GET /places/{placeId}/people?asOfDate=YYYY-MM-DD&roleKind=student`
- `GET /places/{placeId}/schools`
- `GET /guides/{guidePersonId}/students?asOfDate=YYYY-MM-DD`
- `GET /school_periods/{periodId}/active_memberships?asOfDate=YYYY-MM-DD`

School calendar reads:

- `GET /places/{placeId}/school_calendar/{date}`
- `GET /places/{placeId}/school_calendar?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD`
- `GET /places/{placeId}/school_days_between?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD`
- `GET /places/{placeId}/school_days_remaining?asOfDate=YYYY-MM-DD`
- `GET /places/{placeId}/next_school_year_start?asOfDate=YYYY-MM-DD`

Policy config:

- `GET /policy_config`

## Typed Query Fields

Use these as fields, not parsed strings:

- `asOfDate`: required for point-in-time roster, place membership, class membership, and active-period questions.
- `startDate` and `endDate`: required for school calendar range and school-day count questions.
- `roleKind`: canonical role value such as `student`, `guardian`, `guide`, `staff`, or `administrator`.
- `filter`: documented typed comparisons such as `placeKind==district`, `studentSourcedId==person_ada_lovelace`, or `learningGroupId==class_algebra_1_a AND roleKind==student`.
- `limit` and `cursor`: cursor paging.
- `modifiedSince`: poll for changed rows using source-updated timestamps.

Unsupported filters fail loud with typed Problem JSON. Do not silently drop a filter in client code.

## Headers

- `Authorization: Bearer <token>` on every non-mint call.
- `Idempotency-Key` on POST, PATCH, and DELETE.
- `If-Match` on PATCH and DELETE after a detail read.
- `ETag` and `X-People-Orgs-ETag` are returned on mutable reads/writes. On deployed Vercel conditional writes, `X-People-Orgs-ETag` is the saved validator for immediate chaining.

## Point-In-Time Rule

Point-in-time is the default shape. Membership rows have `begin_date`, `end_date`, and `is_time_locatable`.

- `begin_date` null means the row has no locatable start and is excluded from point-in-time answers.
- `end_date` null means still active for any `asOfDate` on or after `begin_date`.
- both null means always excluded.
- consumers filter on `is_time_locatable`; they do not guess from nulls.

## NWEA Account Boundary

For MAP grouping, use the district parent place as the NWEA account boundary. The district row carries `nwea_district_id`, `brand`, and `modality`. A school reads its account through `parent_place_id` and `GET /places/{districtId}/schools`.

Do not group by school-level local values. Do not combine physical and virtual accounts.

## School Calendar Boundary

People & Orgs serves school-day facts from `alpha.school_calendar`, backed by Ed-Fi CalendarDate rows. Use the calendar endpoints for `is_school_day`, typed `reason`, range counts, remaining-days counts, and next-school-year start. Do not count weekdays, infer holiday/MAP windows, or maintain local reason enums in client code.

Use the range route when you need the legal reason values present in a date span:

```text
GET /places/place_north_valley_school/school_calendar?startDate=2025-08-13&endDate=2026-07-24
```

The response returns `count` calendar rows, `school_days_in_range`, and `data[]` rows with `calendar_date`, `is_school_day`, and `reason`.

## Secrets Boundary

`app_credential` returns `has_credential`, `created_at`, and `last_rotated_at`. It never returns `secret_ref` or a secret value. If a workflow needs a third-party password value, stop and report that this surface intentionally does not expose it.

## Error Handling

Errors are Problem JSON. Preserve `status`, `code`, `detail`, `requestId`, `traceId`, and `fieldErrors` in diagnostics.

Common examples:

- No token: `401` authentication problem.
- Unsupported filter: `400` with a field error naming the parameter.
- Missing `If-Match` on conditional mutation: `428`.
- Stale validator: `412`.
- Missing row: `404`.

Do not hide Problem JSON behind broad retry loops. Retry only idempotent network failures or writes with `Idempotency-Key`.
