{"openapi":"3.1.0","info":{"title":"Lakeview State Ethos Integration (mock)","version":"1.0.0","summary":"Read-only EEDM surface for the Capture Connect Banner lane.","description":"A wire-faithful mock of the Ellucian Ethos Integration proxy, built so the Capture\nConnect read-only lane can be developed, tested, and demonstrated before a live Banner\ntenant exists.\n\nTwo mechanics catch clients out and are preserved here on purpose:\n\n1. `POST /auth` answers with the RAW JWT as `text/plain`. It is not JSON and it is not\n   quoted. Read the body as text.\n2. Filters ride the query string as URL-encoded JSON in `criteria`.\n\nEverything under `/api` is GET-only. The mock's own mutations happen through the\nLakeview UIs and the private `/_mock` control plane, which are not part of this contract.\n\nLakeview State University is fictional. Nothing here is an Ellucian product, asset, or\ntrademark; the resource and field names are the published EEDM data model.","contact":{"name":"Capture Connect engineering"},"license":{"name":"Internal use"}},"servers":[{"url":"https://behavior-insights.imad-9e5.workers.dev/ethos","description":"Lakeview mock (this deployment)"},{"url":"http://localhost:3000/ethos","description":"Local development"}],"tags":[{"name":"Authentication","description":"Token lifecycle"},{"name":"Identity","description":"Flow A: resolve an SSO subject to a person GUID"},{"name":"Eligibility","description":"Flow B: the three-GET eligibility snapshot"},{"name":"Reference","description":"Flow C: code tables cached at startup"},{"name":"Administration","description":"Grant preflight and diagnostics"}],"paths":{"/auth":{"post":{"tags":["Authentication"],"summary":"Exchange an API key for a bearer token","description":"Send the Ethos application API key as `Authorization: Bearer <api key>`.\n\n**The response body is the raw JWT as `text/plain`.** Do not JSON.parse it.\n\nCache the token and refresh ahead of expiry; Ellucian's own client refreshes with a\none-second margin, which is the margin this mock's reference client uses too.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"expirationMinutes","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":720,"default":30}}],"responses":{"200":{"description":"The bearer token, verbatim, as plain text","content":{"text/plain":{"schema":{"type":"string"},"example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnQiOiJsYWtldmlldyJ9.signature"}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Unknown or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/persons":{"get":{"tags":["Identity"],"summary":"Resolve or search persons","description":"Flow A. One GET resolves an SSO-authenticated student to a person GUID:\n\n```\n/api/persons?criteria={\"credentials\":[{\"type\":\"bannerId\",\"value\":\"A00012345\"}]}\n```\n\n`type` also accepts `bannerUserName`, `bannerUdcId`, and `bannerSourcedId`, so the\nmatch key is whatever claim campus SSO releases.\n\nResult handling is strict: exactly one match proceeds. Zero or multiple matches mean\nthe session runs without SIS context and the mismatch is logged for adjudication.\nNever guess.\n\nOther filters: `{\"names\":[{\"lastName\":\"Rivera\"}]}` and\n`{\"roles\":[{\"role\":\"student\"}]}`.\n\nFallback path: some Banner versions reject the criteria filter on this resource. Pass\n`personFilter` instead, naming a saved list configured on the tenant. The mock can be\nmade to reproduce the failure on demand (fault `criteriaOnPersonsReturns400`).","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"personFilter","in":"query","required":false,"description":"A saved-list GUID from GET /api/person-filters, sent either bare or as {\"personFilter\":\"<guid>\"}. The fallback when criteria is rejected. This mock also accepts the saved list's code, as a convenience; do not rely on that against a live tenant.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Person"}},"examples":{"byBannerId":{"summary":"criteria={\"credentials\":[{\"type\":\"bannerId\",\"value\":\"A00012345\"}]}","value":[{"id":"8f2a4c6e-1d3b-4a5f-9c7e-2b8d0e4f6a1c","names":[{"type":{"category":"legal"},"fullName":"Jordan Rivera","firstName":"Jordan","lastName":"Rivera","preference":"preferred"}],"dateOfBirth":"2004-03-14","roles":[{"role":"student","startOn":"2022-08-22T00:00:00Z"}],"credentials":[{"type":"bannerId","value":"A00012345"}],"privacyStatus":{"privacyCategory":"unrestricted"},"metadata":{}}]}}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/persons/{id}":{"get":{"tags":["Identity"],"summary":"Read one person by GUID","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The person record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Person"}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/students":{"get":{"tags":["Eligibility"],"summary":"Student record for a person","description":"Flow B, call 1: `criteria={\"person\":{\"id\":\"<person guid>\"}}`.\n\nReturns level and class-year classification as GUID references. Join them against the\ntwo cached code tables rather than fetching per student.\n\nUnverified: some Banner versions are reported to accept a different criteria form on\nthis resource. Ship both paths behind one function.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Student"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/student-academic-programs":{"get":{"tags":["Eligibility"],"summary":"Program, major, and expected graduation","description":"Flow B, call 2: `criteria={\"student\":{\"id\":\"<person guid>\"}}`.\n\nCarries the fields the rules engine needs for both access and price: `disciplines`\n(the major), `enrollmentStatus.status`, `academicLevel`, and\n`expectedGraduationDate` (the graduating-senior gate).\n\nNote the reference: `student.id` holds the PERSON guid in the generated model. This\nmock also accepts the students-resource GUID, so a client that cached the wrong one\nstill works; a production tenant may not be so forgiving.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StudentAcademicProgram"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/person-holds":{"get":{"tags":["Eligibility"],"summary":"Active holds for a person","description":"Flow B, call 3: `criteria={\"person\":{\"id\":\"<person guid>\"}}`.\n\n**Only active holds are returned.** A hold whose `endOn` is in the past is excluded.\nReleasing a hold sets `endOn` to now, so the release is visible on the very next GET\nwith no cache to clear and no restart.\n\nCategories: financial, academic, administrative, disciplinary, health. Hold gating is\nper-service opt-in on the client side: a financial hold that blocks advising should\nnot block the IT queue.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PersonHold"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/academic-levels":{"get":{"tags":["Reference"],"summary":"Academic level code table (UG / GR)","description":"Flow C. Small and stable: cache at startup and refresh on a daily timer.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CodeTableEntry"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/student-classifications":{"get":{"tags":["Reference"],"summary":"Class-year classification code table (FR / SO / JR / SR)","description":"Flow C. Cache alongside academic-levels.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CodeTableEntry"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/person-filters":{"get":{"tags":["Identity"],"summary":"Saved lists (the personFilter fallback's catalog)","description":"How a client DISCOVERS the values the persons `personFilter` parameter accepts.\n\nWhen a Banner version rejects the persons criteria filter (the mock reproduces this\nwith the `criteriaOnPersonsReturns400` fault), the fallback is\n`GET /api/persons?personFilter={\"personFilter\":\"<id>\"}` against a saved list from\nthis resource. Cache the list at onboarding next to the code tables.\n\nMembership is never serialized on this resource; the persons call above is how\nmembers are read, exactly as on a live tenant.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PersonFilter"}},"examples":{"savedLists":{"summary":"Capture University's seeded saved lists","value":[{"id":"e59b8740-8b09-44f7-8d63-2f0d47a4737e","code":"FAMILY_PORTAL_CONSENTED","title":"Family Portal consented students","metadata":{}}]}}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/api/section-registrations":{"get":{"tags":["Eligibility"],"summary":"Current-term section registrations (optional)","description":"Only needed when a campus requires literal current-term enrollment rather than\nprogram-level status.\n\nFilter syntax differs by version, and both are implemented:\n\n- v7: `?registrant=<person guid>`\n- v16: `criteria={\"registrant\":{\"id\":\"<person guid>\"}}`\n\nSelect with the Accept header. Carry both forms behind one client function.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Accept","in":"header","required":false,"description":"`application/json` resolves to the latest version. `application/vnd.hedtech.integration.vN+json` pins a version; the resolved media type is echoed in Content-Type and X-Media-Type.","schema":{"type":"string","default":"application/json"}},{"name":"criteria","in":"query","required":false,"description":"URL-encoded JSON filter. Shape differs per resource; see each operation's examples.","schema":{"type":"string"}},{"name":"registrant","in":"query","required":false,"description":"v7 filter form: the person GUID as a plain query parameter.","schema":{"type":"string","format":"uuid"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":500},"description":"Page size. There is no verified total-count response header, so paginate blind: request the next page until a short page comes back."}],"responses":{"200":{"description":"A JSON array of records. No total-count header is emitted.","headers":{"X-Media-Type":{"schema":{"type":"string"},"description":"The resolved EEDM media type"}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SectionRegistration"}},"examples":{}}}},"400":{"description":"Malformed criteria, personFilter, or paging","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"403":{"description":"The Ethos application has not been granted this resource","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"404":{"description":"Unknown resource or record","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"406":{"description":"Unsupported version requested in Accept","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"429":{"description":"Rate limited. Rate limits are unpublished for production Ethos, so handle this defensively.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}},"500":{"description":"Server error. Retry with jitter.","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/admin/available-resources":{"get":{"tags":["Administration"],"summary":"Resources this Ethos application may read","description":"Flow C, the onboarding preflight. Call it once at tenant onboarding and compare the\nresult against the resources the lane needs. A college that forgot to add\n`person-holds` to the application grant becomes a checklist item instead of a support\nticket at go-live.\n\nThe seeded `lakeview-misconfigured` application omits `person-holds` so this failure\nmode is testable.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Granted resource names with the versions the application may request","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AvailableResource"}}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/errors":{"get":{"tags":["Administration"],"summary":"Errors this mock recorded","description":"Diagnostics only. Returns the mock's own recent rejections (bad keys, 403s, injected faults), newest first, and an empty array on a clean run.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Recent errors","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}},"/consume":{"get":{"tags":["Administration"],"summary":"Change-notification consumer stub","description":"Always returns an empty array. The read-only lane does not use change notifications: on-demand GET at the moment of need is the real-time mechanism.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"An empty array","content":{"application/json":{"schema":{"type":"array","items":{}}}}},"401":{"description":"Missing, malformed, or expired bearer token","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]}}}}}}}},"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","description":"The Ethos application API key, sent on POST /auth only."},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"The token returned by POST /auth."}},"schemas":{"Error":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"description":{"type":"string"}},"required":["message"]}}},"required":["errors"]},"Person":{"type":"object","description":"EEDM persons v12.4","properties":{"id":{"type":"string","format":"uuid"},"names":{"type":"array","items":{"type":"object","properties":{"type":{"type":"object","properties":{"category":{"type":"string","enum":["legal","birth","personal","favored"]},"detail":{"type":"object","properties":{"id":{"type":"string"}}}}},"fullName":{"type":"string"},"firstName":{"type":"string"},"middleName":{"type":"string"},"lastName":{"type":"string"},"preference":{"type":"string","enum":["preferred"]}}}},"dateOfBirth":{"type":"string","format":"date"},"roles":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["student","instructor","employee","alumni","prospectiveStudent","advisor"]},"startOn":{"type":"string","format":"date-time"}}}},"credentials":{"type":"array","description":"May contain entries a client must ignore, including an ssn on some tenants. Never store or log a credential other than your match key.","items":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"startOn":{"type":"string","format":"date-time"},"endOn":{"type":"string","format":"date-time","nullable":true}}}},"privacyStatus":{"type":"object","properties":{"privacyCategory":{"type":"string","enum":["unrestricted","restricted"]}}},"metadata":{"type":"object"}},"required":["id","names"]},"Student":{"type":"object","description":"EEDM students v16","properties":{"id":{"type":"string","format":"uuid"},"person":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"types":{"type":"array","items":{"type":"object"}},"residencies":{"type":"array","items":{"type":"object"}},"statuses":{"type":"array","items":{"type":"object"}},"levelClassifications":{"type":"array","description":"level and latestClassification are GUID references into academic-levels and student-classifications. Cache both code tables and join locally.","items":{"type":"object","properties":{"level":{"type":"object","properties":{"id":{"type":"string"}}},"latestClassification":{"type":"object","properties":{"id":{"type":"string"}}}}}},"metadata":{"type":"object"}}},"StudentAcademicProgram":{"type":"object","description":"EEDM student-academic-programs v17. `student.id` carries the PERSON guid, matching the generated model and the fixture pack.","properties":{"id":{"type":"string","format":"uuid"},"student":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"program":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"curriculumObjective":{"type":"string","examples":["matriculated"]},"preference":{"type":"string","examples":["primary"]},"academicLevel":{"type":"object","properties":{"id":{"type":"string"}}},"credentials":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}}}},"disciplines":{"type":"array","description":"The major. disciplines[0].discipline.id is the field of study.","items":{"type":"object","properties":{"discipline":{"type":"object","properties":{"id":{"type":"string"}}},"startOn":{"type":"string","format":"date"}}}},"startOn":{"type":"string","format":"date"},"academicPeriods":{"type":"object","properties":{"starting":{"type":"object","properties":{"id":{"type":"string"}}},"expectedGraduation":{"type":"object","properties":{"id":{"type":"string"}}}}},"enrollmentStatus":{"type":"object","properties":{"status":{"type":"string","enum":["active","inactive","complete"]}}},"expectedGraduationDate":{"type":"string","format":"date"},"metadata":{"type":"object"}}},"PersonHold":{"type":"object","description":"EEDM person-holds v6. Holds whose endOn is in the past are excluded from results: releasing a hold sets endOn to now, and the record disappears from the next GET.","properties":{"id":{"type":"string","format":"uuid"},"person":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"type":{"type":"object","properties":{"category":{"type":"string","enum":["financial","academic","administrative","disciplinary","health"]},"detail":{"type":"object","properties":{"id":{"type":"string"}}}}},"notificationIndicator":{"type":"string","enum":["notify","watch"]},"startOn":{"type":"string","format":"date-time"},"endOn":{"type":"string","format":"date-time","nullable":true},"comment":{"type":"string"},"metadata":{"type":"object"}}},"CodeTableEntry":{"type":"object","description":"academic-levels v6.1 and student-classifications v7 share this shape.","properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string","examples":["UG","GR","FR","SO","JR","SR"]},"title":{"type":"string"},"description":{"type":"string"},"metadata":{"type":"object"}}},"SectionRegistration":{"type":"object","description":"EEDM section-registrations. Optional: only campuses that require literal current-term enrollment need it. Filter syntax differs by version.","properties":{"id":{"type":"string","format":"uuid"},"registrant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"section":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"academicPeriod":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"status":{"type":"object","properties":{"registrationStatus":{"type":"string"}}},"academicLevel":{"type":"object","properties":{"id":{"type":"string"}}},"credit":{"type":"object"},"metadata":{"type":"object"}}},"PersonFilter":{"type":"object","description":"EEDM person-filters: a saved list. Membership is never serialized here; read it via GET /api/persons?personFilter=<id>.","properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string","examples":["FAMILY_PORTAL_CONSENTED","CAPTURE_CONNECT_ELIGIBLE"]},"title":{"type":"string"},"description":{"type":"string"},"metadata":{"type":"object"}},"required":["id","code","title"]},"AvailableResource":{"type":"object","properties":{"name":{"type":"string","enum":["persons","students","student-academic-programs","person-holds","academic-levels","student-classifications","section-registrations","person-filters"]},"versions":{"type":"array","items":{"type":"object"}}}}}},"x-unverified":[{"behavior":"Total-count response header on paged lists","stance":"Not emitted. The public collection paginates blind; do not depend on a count header."},{"behavior":"Rate limits","stance":"Unpublished. Handle 429 with Retry-After defensively; the mock can inject it on demand."},{"behavior":"Error body shape","stance":"The {\"errors\":[{message, description}]} envelope is verified for the Banner API family, not for Ethos itself. Key client logic off status codes."},{"behavior":"students criteria form on specific Banner versions","stance":"Both filter paths ship. Retire this once a live tenant confirms one."}]}