mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
An owner can now narrow a grant to the parts of their record they mean to share, and can offer the third level. Both decisions are made once, on the invitation, and neither widens afterwards. The invite schema takes `scope`: absent or null is the entire record, which is what every stored grant means and what an owner who does not narrow still gets. An empty array, a key outside the vocabulary and the whole-record sentinel are each refused with a 422 naming the field; `inviteGrant` refuses the same three underneath, and its `invalid_scope` now has an HTTP mapping instead of falling into the generic refusal. Offering MANAGE asks for a fresh second factor when the account has one. That gate resolves through the session cookie and has no Bearer fall-through, so a token caller is refused first with `sharing.invite.manage_browser_only` rather than being told it is not authenticated by a route that had just authenticated it. The consequence is deliberate: management of a health record is offered from a browser. The account payload gains `sections` and `level` beside `access` and `canWrite`, resolved server-side and ordered as the consent screen reads. `access` was `grant.access === "WRITE" ? "write" : "read"`, which would have published a grant that can delete entries as read-only; it is a total map now, so the next level cannot ship silent. The capabilities hook carries both facts and turns neither into an affordance yet — the edit and delete controls come back in the release where the routes behind them answer. The consent surface asks two questions in order rather than laying out a nineteen-cell grid. The level is three radios, narrowest preselected. What they can open is two radios, the entire record preselected, and only the narrow branch expands the eight sections with a sentence each. Both honest limits are stated above the checkboxes, where the decision is being made: a section shares everything written in it, and the cross-record surfaces do not appear for a narrowed grant. Documents carries its own warning, cycle appears only for an owner who tracks it, and Manage collapses the question to the one line that answers it. The Manage sentence says the other person can add, change and remove entries, including ones the owner entered themselves, and says in the same breath what it does not reach. The acceptance side gets its own responsibility line rather than the write one wearing a stronger adjective. Six locales.
HealthLog API Spec
openapi.yaml is the canonical OpenAPI 3.1 description for the HealthLog
Next.js API. It is the source of truth for the native iOS client's
DTO codegen.
The MCP server is documented separately — it is a distinct contract over JSON-RPC, not part of the REST OpenAPI spec:
mcp-capabilities.md— the tool, resource, and prompt catalogue, the grounding contract, and the write model.mcp-skills.md— building a connector or skill on top of it.../self-hosting/mcp.md— enabling and connecting the surface (operator guide).
Preview locally
Open an interactive Redoc preview in your browser:
npx @redocly/cli preview docs/api/openapi.yaml
Generate static HTML for sharing:
npx @redocly/cli build-docs docs/api/openapi.yaml --output docs/api/index.html
Validate
npx @redocly/cli lint docs/api/openapi.yaml
The spec is expected to be lint-clean (errors = 0). Some warnings are intentional:
no-server-example.com—http://localhost:3000is the dev server.operation-2xx-response/operation-4xx-response— webhook + redirect endpoints intentionally omit one or the other.no-unused-components—ReminderPhaseis exposed for client codegen even though no path response references it directly.
Layout
info— title, version (mirrored frompackage.json), description.servers— productionhttps://healthlog.bombeck.ioplushttp://localhost:3000for local development.tags— domain groupings (Auth,Measurements,Medications,Mood,Dashboard,Insights,Achievements,DoctorReport,Integrations,User,Tokens,Notifications,Onboarding,Health,Settings,Analytics,Export,AuditLog,Monitoring,Webhooks,Ingest,Admin).securitySchemes:bearerAuth— long-livedhlk_*API tokens issued viaPOST /api/tokens. Required on/api/ingest/medication.sessionCookie— HttpOnlyhealthlog_sessioncookie set by the password and passkey login flows. Used by the web app and any iOS client running inside a web view.
components.schemas— DTOs for all routes plus the underlying Prisma models (e.g.Measurement,Medication,MedicationIntakeEvent,MoodEntry,User,DashboardLayout,ApiEnvelope,ApiError).paths— all routes fromsrc/app/api/**/route.ts, including the/api/admin/*family which is taggedAdminand marked internal.
Conventions
- Successful responses use the envelope
{ data, error: null }. - Error responses use the envelope
{ data: null, error: '<message>' }. - Pagination uses
limit+offsetquery parameters and ameta: { total, limit, offset }block on the response. - All timestamps are ISO 8601 / RFC 3339 (
format: date-time) in UTC. - IDs are CUIDs (opaque strings).
Updating the spec
When adding or changing routes:
- Update the relevant route handler under
src/app/api/**/route.ts. - Mirror the change in
openapi.yaml. KeepoperationIdcamelCase and stable — the iOS codegen keys off it. - Run
npx @redocly/cli lint docs/api/openapi.yamland resolve all errors. Warnings are tolerated when documented above. - Bump
info.versionif the change is breaking for clients.