RALPH API reference

The RALPH API is a REST API described by one OpenAPI 3.1 contract. This page is built from that contract, and the contract itself is at /developers/api/openapi.v1.yaml.

The contract

openapi.v1.yaml is the whole API: every path, schema and error. The endpoints below are written from it when the site is built, so they can’t drift from it. The MCP server and RALPH’s own apps are built on the same file. Each endpoint below names its schemas, such as Booking. The contract defines them in full.

Authentication

Send a Bearer credential in Authorization: Bearer <token>. It is one of:

  • an access token from RALPH’s OAuth server, for an app or agent acting for a person;
  • a sign-in token for a person signed in with Google; or
  • an agent key (rk_…), for unattended use. Mint one with the CLI.

The exceptions say so in their entry below: the web app’s session operations take its session cookie instead, and a calendar feed’s URL carries its own token. The session operations are the web app’s own: they answer only on the app host, to a same-origin request, and a POST or DELETE must also send Ralph-CSRF: 1, or it gets 403.

Access tokens last 15 minutes. An access token or an agent key acts for one person in one organisation. A person’s sign-in token reaches every organisation they belong to, which /me lists. Either way, the audit trail names who acted.

Conventions

  • Organisations in the path. Organisation-scoped operations sit under /orgs/{org}/…, by the organisation’s slug. /me and /orgs sit outside.
  • UTC times. Every timestamp is ISO 8601 in UTC. An organisation’s home time zone is an IANA name, such as Europe/London. The exception is an availability rule’s start_local and end_local: HH:MM wall-clock times in that home time zone, so a rule keeps its hours across a clock change.
  • Safe retries. An operation that lists an Idempotency-Key parameter below can be retried within 24 hours, with the same key and the same body, without repeating its effect: you get the first answer again. Creating a booking requires a key. The same key with a different body gets 422, a retry while the first is still running gets 409 (try again shortly), and after 24 hours the key counts as new. The exceptions are creating an agent key and creating a calendar feed. Their secret is shown only once, so a retry gets 409 (agent-key-secret-not-replayable or calendar-feed-token-not-replayable) instead: nothing is created twice, but the secret can’t be recovered, so revoke that one and create another. Retrying any other POST, such as rotating a calendar feed, repeats it.
  • Optimistic updates. An update that lists an If-Match parameter below takes the ETag from your last read, and a stale one gets 412. The others, such as updating a category, are last write wins.
  • Merge patches. A body sent as application/merge-patch+json follows RFC 7396: a field you leave out is unchanged, and null clears it.
  • Cursor pages. A list that takes limit (up to 200) and cursor returns next_cursor: pass it as the next request’s cursor, until it comes back null. A list without them, such as your sessions, returns everything at once.

Errors and hints

Every error is an RFC 9457 problem (application/problem+json) with a stable type, a title, the HTTP status and usually a detail. Some carry more:

  • 409 booking conflict: conflicts lists the bookings in the way, with their resources and times.
  • 422 booking blocked: checks lists each failed check, with a hint that resolves it.
  • 412: your If-Match is stale. Read again, then retry.
  • 401 or 404: a missing or expired credential, or an organisation you can’t see. An organisation you have no access to answers 404, not 403.

Endpoints

control-plane

Identities, organizations, agent keys — everything outside an org DB (ADR-0001).

GET /me

The calling identity, its organizations, and who it acts for in each.

  • 200: Caller context. Me
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /me/sessions

Where the caller is signed in to the web app — their live sessions, this one first.

The app host only (ADR-0028): a session operation authenticates by the session cookie, never a Bearer credential, so the API host answers 401. Every live session is in the one page, this one first, then the rest by last use.

Authentication: sessionCookie, not a Bearer token.

  • 200: The caller’s live sessions. Page, with items (array of Session)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /me/sessions/{sessionId}

End one of the caller’s sessions, such as one on a lost phone.

Its next request is refused. Ending this session is signing out, answered as signOut answers. Ending another needs a sign-in in the last 10 minutes (ASVS 5.0 V7.5.2), or it answers 403 with type …/problems/fresh-sign-in-required: sign in again, then retry. Another person’s session, or none, is 404. The app host only.

Authentication: sessionCookie, not a Bearer token.

Parameter In Type Required Description
sessionId path Uuid yes
  • 204: Ended. (header Clear-Site-Data)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /me/sessions/end-others

Sign out everywhere else — end every session of the caller’s but this one.

Needs a sign-in in the last 10 minutes (ASVS 5.0 V7.5.2), or it answers 403 with type …/problems/fresh-sign-in-required. The app host only.

Authentication: sessionCookie, not a Bearer token.

  • 204: Every other session ended.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /me/sign-out

Sign out — end this session at once and clear the site.

Deletes the session, clears its cookie, and answers Clear-Site-Data: "cache", "cookies", "storage", so the browser also drops what it cached, the offline cache included (ADR-0028). A session that has already ended answers 401. The app host only.

Authentication: sessionCookie, not a Bearer token.

  • 204: Signed out. (header Clear-Site-Data)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs

Organizations the caller can access.

Parameter In Type Required Description
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of organizations. Page, with items (array of Org)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}

One organization. Suspended orgs are served read-only; deleted orgs are 404, always.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
  • 200: The organization. Org (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}

Update org settings (admin). The slug is immutable — renames change name only.

Changing home_timezone re-anchors day-boundary interpretation (availability rules, flight dates) from the moment of change; history is not reinterpreted.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): object, with name (string); home_timezone (string); accent_colour (any): The org’s colour, which paints only the primary action (ADR-0034); null clears it, and the primary action is white again. A colour too close to a coded colour is saved but falls back to white — branding.accent_colour.fallback says why. Preview one with previewOrgBrand first.

  • 200: Updated. Org (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}

Request deletion of the org (admin). Moves it to pending_delete with a grace window; reversible via undeleteOrg until the deadline.

The org moves to pending_delete with a deleted_after grace deadline: it stays readable and exportable, and can be undeleted, until then. Billing is revoked and the org database destroyed by the service-principal teardown only after the grace window (ADR-0020), so an org reclaimed during grace keeps its live entitlement. Authorized by the customer’s grant (customer_access). Idempotent during grace.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
  • 202: Deletion scheduled; the org is now pending_delete. Org
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/brand-preview

What a colour would do as the org’s colour, without saving it.

The pair the server generates for the primary action in each theme, or the fallback to white with its reason when the colour is too close to a coded colour (ADR-0034). Persists nothing; any member may ask. updateOrg saves the same colour to the same answer.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.

Request body (required, application/json): object, with accent_colour (ColourInput, required)

  • 200: What the colour comes to. BrandColour
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PUT /orgs/{org}/logos/{slot}

Upload one of the org’s logos (admin). It is rasterised on upload and served only as a PNG.

A PNG or an SVG, base64 in a JSON body, at most 512 KiB decoded. It is redrawn as a PNG, 128 px high for a logo (no wider than 6:1, no taller than 1:2) or 256 px square for the icon, and only that PNG is ever served (ADR-0030). An SVG draws its shapes only: no text, fonts or images.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
slot path LogoSlot yes For light surfaces, for dark surfaces, or the square icon.

Request body (required, application/json): object, with content_type (string: image/png, image/svg+xml, required); data (string, required)

  • 200: The org, with the logo’s new URL. Org (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/logos/{slot}

Remove one of the org’s logos (admin). Idempotent.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
slot path LogoSlot yes For light surfaces, for dark surfaces, or the square icon.
  • 200: The org, without the logo. Org (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/undelete

Reverse a pending deletion during the grace window (admin), restoring the org to the state it was deleted from.

Restores a pending_delete org to the state it was in when deletion was requested: active for a servable org, or provisioning for an org an operator retired before its provisioning finished — the provisioning sweep then resumes it, and it is not yet servable (poll getOrg until status is active). Allowed only until the grace deadline and only with a current active/trialing entitlement, so an entitlement-lost org cannot dodge teardown by reclaiming it during grace (ADR-0020). Idempotent: an org already restored is returned as it is.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
  • 200: The org is restored (status is active, or provisioning for an unfinished org). Org
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/members

Identity↔person access links for this org (admin).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of members. Page, with items (array of Member)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/members

Grant a person API access (admin) — the membership workflow.

Links an identity to an existing person by verified email match: if an identity with that email exists it is linked now; otherwise the link activates on the person’s first login with a matching verified email. People are created first (ADR-0009); the active-email dedupe guard prevents duplicates. Person merge is deliberately deferred.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): object, with person_id (Uuid, required); email (string, email, required)

  • 201: Access granted (or pending first login). Member
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/members/{memberId}

Revoke an identity’s access to this org (admin; effective next request).

Does not archive the person — history stays — but ends the agents acting for them through this membership (ADR-0039): the apps connected through its login, whose access tokens stop at once, and — unless the person still holds another active or pending membership in this org — their agent keys and any other connected apps. Giving access again brings none of them back. Archiving a person separately auto-revokes their agent keys and access.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
memberId path Uuid yes
  • 204: Revoked.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/agent-keys

Agent keys in this organization (metadata only, never secrets).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of keys. Page, with items (array of AgentKey)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/agent-keys

Mint an agent key acting for a person.

The secret is returned once, in this response only (ADR-0003).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): AgentKeyCreate

  • 201: Key created; secret present only here. AgentKeyWithSecret
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/agent-keys/{keyId}

Revoke an agent key (immediate, irreversible).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
keyId path Uuid yes
  • 204: Revoked.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

people

People the organization knows about; a person is not a login (ADR-0009).

GET /orgs/{org}/people

People the organization knows about. Members see names only (plus their own full record).

Field visibility follows the authorization matrix (docs/design/architecture.md) — contact details and notes are instructor/staff+.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
status query string: active, archived no
role query Role no
q query string no Name/email substring search.
  • 200: Page of people. Page, with items (array of Person)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/people

Add a person (no login required — ADR-0009).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): PersonCreate

  • 201: Created. Person (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/people/{personId}

One person.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
personId path Uuid yes
  • 200: The person. Person (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/people/{personId}

Update a person (contact details, status, bookability).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
personId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): PersonUpdate

  • 200: Updated. Person (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PUT /orgs/{org}/people/{personId}/roles

Replace the person’s role set (admin; audited; bumps the Person entity version).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
personId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/json): object, with roles (array of Role, required)

  • 200: New role set. Person (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

aircraft

The fleet, including derived meter readings and audited adjustments.

GET /orgs/{org}/aircraft

The fleet.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
status query string: airworthy, grounded, retired no
kind query AircraftKind no
  • 200: Page of aircraft. Page, with items (array of Aircraft)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/aircraft

Add an aircraft (creates its resource).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): AircraftCreate

  • 201: Created. Aircraft (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/aircraft/{aircraftId}

One aircraft, including current derived meter readings.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
aircraftId path Uuid yes
  • 200: The aircraft. Aircraft (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/aircraft/{aircraftId}

Update aircraft details or status (e.g. ground it).

Correcting kind or type_designator requires the admin role (a 403 otherwise): policy selectors (applies_to) and type-scoped pilot authorizations match on them, so a correction changes how every later booking of the airframe is judged. Restating the current value needs nothing more. Every other field needs staff.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
aircraftId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): AircraftUpdate

  • 200: Updated. Aircraft (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/aircraft/{aircraftId}/meter-events

The append-only, totally-ordered meter trail (ADR-0010).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
aircraftId path Uuid yes
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
meter_id query Uuid no
  • 200: Page of events, trail order (per-meter seq). Page, with items (array of MeterEvent)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/aircraft/{aircraftId}/meter-events

Append an adjustment or meter-replacement event (staff; audited).

Adjustments may set any value (that is what they are for); flight readings are appended via flight logging, never here.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
aircraftId path Uuid yes
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): MeterAdjustmentCreate

  • 201: Appended; the meter’s cached reading updated. MeterEvent
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

resources

The generic bookable abstraction over aircraft and instructors (ADR-0007).

GET /orgs/{org}/resources

Everything bookable (aircraft, instructors — ADR-0007).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
kind query ResourceKind no
  • 200: Page of resources. Page, with items (array of Resource)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/resources/{resourceId}/availability

Free/busy timeline for a resource over a window.

The primary “find a time” query for agents; busy blocks reference their booking.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
resourceId path Uuid yes
from query string, date-time yes
to query string, date-time yes
  • 200: Ordered busy blocks; gaps are free. Availability
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/availability-rules

Org-wide and per-resource availability rules.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
resource_id query Uuid no
  • 200: Page of rules. Page, with items (array of AvailabilityRule)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/availability-rules

Add an availability rule (staff) — opening hours, working days, closures.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): AvailabilityRuleCreate

  • 201: Created. AvailabilityRule
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/availability-rules/{ruleId}

One availability rule, with the ETag its update is conditioned on.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
ruleId path Uuid yes
  • 200: The rule. AvailabilityRule (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/availability-rules/{ruleId}

Correct an availability rule in place (staff). Moving a rule to a different resource is not a correction but a different rule, so resource_id is not patchable — create the new rule and delete the old one.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
ruleId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): AvailabilityRuleUpdate

  • 200: Updated. AvailabilityRule (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/availability-rules/{ruleId}

Remove an availability rule (staff).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
ruleId path Uuid yes
  • 204: Removed.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

authorizations

Pilot authorizations — facts that eligibility checks consume (ADR-0008).

GET /orgs/{org}/pilot-authorizations

Pilot authorizations (facts consumed by eligibility — ADR-0008).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
person_id query Uuid no
active query boolean no Only authorizations that are unexpired and unrevoked.
  • 200: Page of authorizations. Page, with items (array of PilotAuthorization)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/pilot-authorizations

Grant a pilot authorization (staff/instructor action).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): PilotAuthorizationCreate

  • 201: Granted. PilotAuthorization
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/pilot-authorizations/{authorizationId}

Revoke an authorization (kept for history, marked revoked).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
authorizationId path Uuid yes
  • 204: Revoked.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

policies

Org-authored booking policies with per-policy enforcement (ADR-0008).

GET /orgs/{org}/policies

The organization’s booking policies (ADR-0008).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
enabled query boolean no
  • 200: Page of policies. Page, with items (array of Policy)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/policies

Author a policy (data, not code).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): PolicyCreate

  • 201: Created. Policy (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/policies/{policyId}

One policy.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
policyId path Uuid yes
  • 200: The policy. Policy (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/policies/{policyId}

Update a policy (params, enforcement, enabled).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
policyId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): PolicyUpdate

  • 200: Updated. Policy (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/policies/{policyId}

Archive a policy (the row survives — evaluations/overrides reference it forever).

Sets archived_at; archived policies stop being evaluated. There is no hard delete.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
policyId path Uuid yes
  • 204: Archived.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

bookings

Scheduling with race-proof conflicts and explained eligibility.

GET /orgs/{org}/categories

The org’s category catalogue (ADR-0013).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
active query boolean no
  • 200: Page of categories. Page, with items (array of Category)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/categories

Author a category (staff).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): CategoryCreate

  • 201: Created. Category
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/categories/{categorySlug}

Rename, recode, describe, or archive a category (staff). Slug is immutable; archive = active false.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
categorySlug path string yes

Request body (required, application/merge-patch+json): object, with name (string); code (string, matching ^[A-Za-z0-9]{1,4}$): A new code, stored in capitals. A code another category has, in any case, is a 409 (ADR-0035).; description (string or null); active (boolean)

  • 200: Updated. Category
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/bookings

Bookings, filterable by window, resource, person, status.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
from query string, date-time no
to query string, date-time no
resource_id query Uuid no
person_id query Uuid no Matches made-for person or any participant.
status query BookingStatus no
category query string no Category slug filter (ADR-0013).
  • 200: Page of bookings. Page, with items (array of Booking)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings

Create a booking (evaluated, race-proof).

Runs eligibility (ADR-0008) and reserves the resources atomically (ADR-0007). Outcomes: 201 confirmed; 201 with status held when a require_approval check fired (the slot IS held); 409 booking-conflict with the conflicting bookings; 422 booking-blocked with the blocking checks. Idempotency-Key is REQUIRED — agents retry.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters yes REQUIRED here — booking creation must be retry-safe (ADR-0005 §5).

Request body (required, application/json): BookingCreate

  • 201: Booked (confirmed or held); evaluation attached. BookingWithEvaluation (header ETag)
  • 409: A reserved resource is already booked in that window (ADR-0007). ConflictProblem (application/problem+json)
  • 422: A block-enforced policy check failed (ADR-0008). BlockedProblem (application/problem+json)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings/evaluate

Dry-run eligibility + conflicts without creating anything.

Agents call this before proposing times; persists nothing.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.

Request body (required, application/json): BookingCreate

  • 200: What WOULD happen — evaluation plus any conflicts. BookingPreview
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/bookings/{bookingId}

One booking with its current evaluation.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
  • 200: The booking. BookingWithEvaluation (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/bookings/{bookingId}

Amend a booking (reschedule, notes, participants) — re-evaluated.

Only held and confirmed bookings are amendable (terminal states return a problem). Changing times or resources re-runs conflicts and eligibility exactly like creation and VOIDS prior overrides — a confirmed booking regresses to held if a new require_approval outcome fires. Note/participant-only changes are not re-evaluated.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): BookingUpdate

  • 200: Updated. BookingWithEvaluation (header ETag)
  • 409: A reserved resource is already booked in that window (ADR-0007). ConflictProblem (application/problem+json)
  • 422: A block-enforced policy check failed (ADR-0008). BlockedProblem (application/problem+json)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings/{bookingId}/evaluate

Dry-run an amendment — what updateBooking would do, without changing anything.

Merges the BookingUpdate onto the current booking exactly as updateBooking would, then runs eligibility and the conflict probe with this booking excluded (it never conflicts with itself). Persists nothing. Readable by anyone who can read the booking (getBooking visibility; otherwise 404). A terminal booking returns the same problem as updateBooking. The ETag names the version evaluated — send it as the PATCH’s If-Match so the amendment applies only to what was previewed.

A note/title/participant-only change is not re-evaluated by updateBooking, so the preview returns the booking’s current evaluation unchanged (same id) and would is what its current status stands for (held → hold, confirmed → confirm).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes

Request body (required, application/merge-patch+json): BookingUpdate

  • 200: What the amendment WOULD do — evaluation plus any conflicts. BookingPreview (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings/{bookingId}/cancel

Cancel a booking, releasing its slots (the booking’s person or staff).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (application/json): object, with reason (string)

  • 200: Cancelled. Booking
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings/{bookingId}/deny

Deny a held booking (instructor/staff — the authority that approves it).

The approver’s counterpart to overrideBookingCheck: the booking is cancelled with the reason, its slots released, and the denial audited as booking.denied. Only a held booking can be denied; any other state is a 409 booking-not-held.

Send the ETag of the booking as shown in If-Match to deny only that version: if the booking has changed since, the denial is refused with a 412 and nothing is recorded.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
Idempotency-Key header string, up to 128 characters yes REQUIRED here — booking creation must be retry-safe (ADR-0005 §5).
If-Match header string no The booking’s ETag as the caller saw it; if the booking has changed since, the action is refused with a 412 carrying the current ETag. Omit it to act on the current booking.

Request body (required, application/json): object, with reason (string, matching \S, required): Shown to the booking’s person; must not be blank.

  • 200: Denied; the booking is returned cancelled. Booking (header ETag)
  • 412: The booking has changed since the If-Match version; nothing was recorded. Problem (application/problem+json) (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/bookings/{bookingId}/overrides

Approve a require_approval check (instructor/staff; audited — ADR-0008).

When every outstanding require_approval check is overridden, a held booking becomes confirmed. Overrides are scoped to the evaluation they approve — amending the booking’s times or resources voids them.

Send the ETag of the booking as shown in If-Match to approve only that version: if the booking has changed since, the override is refused with a 412 and nothing is recorded. The 201 carries the booking’s new ETag, so the next override can name it without a re-read.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
Idempotency-Key header string, up to 128 characters yes REQUIRED here — booking creation must be retry-safe (ADR-0005 §5).
If-Match header string no The booking’s ETag as the caller saw it; if the booking has changed since, the action is refused with a 412 carrying the current ETag. Omit it to act on the current booking.

Request body (required, application/json): OverrideCreate

  • 201: Override recorded; booking returned with new state. BookingWithEvaluation (header ETag)
  • 412: The booking has changed since the If-Match version; nothing was recorded. Problem (application/problem+json) (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/bookings/{bookingId}/evaluations

Full evaluation history (“why was this held?” — forever answerable).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
bookingId path Uuid yes
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of evaluations, newest first. Page, with items (array of Evaluation)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

flights

Tech-log flight records; fields provisional pending FR-sheet reconciliation (ADR-0010).

GET /orgs/{org}/flights

Flight records (tech-log lines — ADR-0010).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
aircraft_id query Uuid no
person_id query Uuid no Matches any crew role.
from query string, date no
to query string, date no
status query string: draft, logged no
category query string no Category slug filter (ADR-0013).
  • 200: Page of flights. Page, with items (array of Flight)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/flights

Record a flight (draft until logged).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): FlightCreate

  • 201: Created (draft). Flight (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/flights/{flightId}

One flight.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
flightId path Uuid yes
  • 200: The flight. Flight (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/flights/{flightId}

Edit a flight — draft or logged — via audited in-place corrections (ADR-0010).

Logged flights are editable, not frozen: a logged flight is corrected by direct, audited edits exactly like a draft (ADR-0010 reverses the earlier “logging freezes the flight” stance), including its meter_readings, which are part of the flight record. Editing a logged flight’s readings corrects the recorded readings on the flight only; it does NOT rewrite the append-only meter_event trail — the immutable source of truth behind the aircraft’s current readings — so correcting the meter itself is a separate adjustment event (POST …/aircraft/{aircraftId}/meter-events), not a side effect of this patch. The draft-to-logged transition is one-way: reverting a logged flight to draft is refused. Advance a draft to logged via POST …/log, not this endpoint.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
flightId path Uuid yes
If-Match header string yes ETag from the last read; mismatch returns 412 (ADR-0005 §6).

Request body (required, application/merge-patch+json): FlightUpdate

  • 200: Updated. Flight (header ETag)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/flights/{flightId}/log

Log a draft flight — advance its aircraft’s meter trails atomically (one-way draft-to-logged).

Validates each recorded reading against its meter’s trail (flight readings must advance monotonically; regressions get a problem pointing at the adjustment path), materializes one flight-kind meter event per reading, and advances the flight’s status to logged. This transition is one-way — a logged flight cannot revert to draft. Logging does NOT freeze the record: a logged flight stays editable and is corrected by direct, audited edits, while meter corrections remain adjustment events (ADR-0010).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
flightId path Uuid yes
Idempotency-Key header string, up to 128 characters yes REQUIRED here — booking creation must be retry-safe (ADR-0005 §5).
  • 200: Logged; meter events appended, cached readings updated. Flight
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

audit

The append-only audit trail with full actor attribution.

GET /orgs/{org}/audit-events

The append-only audit trail (ADR-0005 §8). Staff/admin only.

Payloads are PII-safe by rule — for person entities, changed field names only, never values.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
entity_type query string no
entity_id query Uuid no
actor_identity_id query Uuid no
from query string, date-time no
to query string, date-time no
  • 200: Page of audit events, newest first. Page, with items (array of AuditEvent)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

calendar-feeds

Read-only iCalendar subscription feeds of bookings, at secret capability URLs (ADR-0024).

GET /orgs/{org}/calendar-feeds

List calendar feeds (metadata only, never tokens or URLs).

scope=person (the default) lists the acting person’s own feeds — self-service, any role. scope=org lists the organization’s org-scope feeds, and scope=resource lists its resource-scope feeds (aircraft/instructor schedules) — both staff/admin only (ADR-0024); a member requesting either is refused.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
scope query string: person, org, resource (default person) no Which feeds to list — the caller’s own (person), the org’s (org), or the organization’s resource feeds (resource).
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of feeds. Page, with items (array of CalendarFeed)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/calendar-feeds

Create a calendar feed (person-scope by default; org/resource for staff/admin).

scope: person (the default) creates a feed of the acting person’s own bookings — self-service, any role. scope: org creates a feed of every booking in the organization; scope: resource creates a feed of one bookable resource’s bookings (an aircraft’s, or an instructor’s) named by resource_id. Org and resource feeds are staff/admin only (ADR-0024).

The feed’s secret token and its https/webcal subscription URLs are returned once, in this response only; subsequent lists carry metadata only (ADR-0024).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Idempotency-Key header string, up to 128 characters no Makes this request safely retryable (ADR-0005 §5).

Request body (required, application/json): CalendarFeedCreate

  • 201: Feed created; token and URLs present only here. CalendarFeedWithToken
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

DELETE /orgs/{org}/calendar-feeds/{feedId}

Revoke a calendar feed (immediate, idempotent).

The feed’s URL goes dark at once, answering the same non-disclosing 404 as an unknown token. A person may revoke their own feeds; staff/admin may revoke any feed in the org (ADR-0024). Idempotent: revoking an already-revoked feed is a no-op that still answers 204.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
feedId path Uuid yes The calendar feed’s id (from a create response or a directory listing).
  • 204: Revoked.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

POST /orgs/{org}/calendar-feeds/{feedId}/rotate

Rotate a calendar feed’s secret token, invalidating the old URL.

Swaps the feed’s token for a fresh one, keeping the feed’s id and name. The old URL stops working immediately; the new token and its https/webcal URLs are returned once, in this response only. A person may rotate their own feeds; staff/admin may rotate any feed in the org (ADR-0024).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
feedId path Uuid yes The calendar feed’s id (from a create response or a directory listing).
  • 200: Rotated; the new token and URLs are present only here. CalendarFeedWithToken
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

GET /orgs/{org}/calendar-feeds/{token}/calendar.ics

The iCalendar feed itself (public; the URL token is the capability, not a Bearer).

Authentication: feedToken, not a Bearer token.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
token path string, matching ^cf_[0-9a-f]{64}$ yes The feed’s secret capability token (cf_...). The URL itself is the credential — no Bearer auth (ADR-0024). Unknown, malformed, and revoked tokens all answer 404.
If-None-Match header string no A previously served ETag; an unchanged feed answers 304.
  • 200: The iCalendar feed (RFC 5545/7986). string (text/calendar) (header ETag)
  • 304: Not modified — the feed body matches the If-None-Match validator.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

events

The org’s live change stream — server-sent events naming what changed, never its contents (ADR-0031).

GET /orgs/{org}/events

The org’s live change stream, as server-sent events (ADR-0031).

A long-lived text/event-stream, kept alive with a comment every 15 seconds. Events are hints, not data: each names what changed by type and id, never field values, and the client refetches through this API. Every write publishes once it commits, whichever client made it — a person, an agent, or RALPH itself.

Each subscriber sees only what its reads would show it (G-125). Someone who can’t see a booking learns only that its resources’ availability changed (availability.changed); someone an edit takes a booking away from gets revoked for it.

Event types, each with a JSON data line:

  • hello (StreamHello): the stream is open. On a first load it carries the id to resume from; fetch after it arrives, so nothing that commits in between is missed.
  • change (ChangeEvent): something changed.
  • revoked (RevokedEvent): drop this entity; you can no longer see it.
  • notification (NotificationEvent): a new notification for you, and only you; it is already in your inbox (listNotifications).
  • reset (StreamReset): what was missed can’t be replayed, or what you may read changed (a resource linked or unlinked); refetch everything for this org.
  • bye (StreamBye): the server is ending the stream, and says why. A browser EventSource never sees the status of a refused reconnect, so read the reason: access_revoked and roles_changed mean drop everything held for this org before reconnecting; credential_expired means sign in or refresh the token first; session_ended means the app host’s session ended, so drop everything held and sign in again; server_shutdown means reconnect.

Each event’s SSE id is an opaque cursor for this org. To resume, reconnect with Last-Event-ID (a browser EventSource sends it by itself) or ?after=: events still buffered are replayed, and anything else — a restart, a deploy, a cursor too old — gets reset.

A person may hold at most 5 streams to an org at once, across all their credentials; another is refused 429 (too-many-streams) until one closes. A stream lasts no longer than the credential that opened it: it says bye once a change made through this API takes its access away or changes its roles, when the credential expires, and when the session that opened it ends.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
Last-Event-ID header string no The id of the last event received, to resume after it.
after query string no The same cursor as Last-Event-ID, for readers that can’t set the header.
  • 200: The event stream. string, binary (text/event-stream)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

notifications

Your in-app inbox — what happened to bookings you’re involved in (ADR-0031).

GET /orgs/{org}/notifications

Your notifications, unread first, then newest first (ADR-0031).

Only your own. Each is written with the change it reports, so none is lost. One whose booking you can no longer see (your roles changed, or you were taken off it) is left out. New ones also arrive on the live stream as notification events (streamOrgEvents).

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
cursor query string no Opaque cursor from a previous page (ADR-0005 §4).
limit query integer, 1–200 (default 50) no
  • 200: Page of notifications. Page, with items (array of Notification)
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/notifications

Mark all your notifications read.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.

Request body (required, application/merge-patch+json): NotificationUpdate

  • 204: All marked read.
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)

PATCH /orgs/{org}/notifications/{notificationId}

Mark one of your notifications read or unread.

Parameter In Type Required Description
org path string, matching ^[a-z0-9][a-z0-9-]{1,62}$ yes Organization slug (e.g. hq-aviation). Unknown or inaccessible orgs return 404.
notificationId path Uuid yes

Request body (required, application/merge-patch+json): NotificationUpdate

  • 200: Updated. Notification
  • default: Any error — RFC 9457 (ADR-0005 §3). Problem (application/problem+json)