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./meand/orgssit 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’sstart_localandend_local:HH:MMwall-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-Keyparameter 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 gets422, a retry while the first is still running gets409(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 gets409(agent-key-secret-not-replayableorcalendar-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 otherPOST, such as rotating a calendar feed, repeats it. - Optimistic updates. An update that lists an
If-Matchparameter below takes theETagfrom your last read, and a stale one gets412. The others, such as updating a category, are last write wins. - Merge patches. A body sent as
application/merge-patch+jsonfollows RFC 7396: a field you leave out is unchanged, andnullclears it. - Cursor pages. A list that takes
limit(up to 200) andcursorreturnsnext_cursor: pass it as the next request’scursor, until it comes backnull. 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:
409booking conflict:conflictslists the bookings in the way, with their resources and times.422booking blocked:checkslists each failed check, with a hint that resolves it.412: yourIf-Matchis stale. Read again, then retry.401or404: a missing or expired credential, or an organisation you can’t see. An organisation you have no access to answers404, not403.
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.Medefault: 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, withitems(array ofSession)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. (headerClear-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. (headerClear-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, withitems(array ofOrg)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(headerETag)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(headerETag)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.Orgdefault: 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.BrandColourdefault: 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(headerETag)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(headerETag)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 (statusisactive, orprovisioningfor an unfinished org).Orgdefault: 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, withitems(array ofMember)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).Memberdefault: 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, withitems(array ofAgentKey)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;secretpresent only here.AgentKeyWithSecretdefault: 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, withitems(array ofPerson)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(headerETag)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(headerETag)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(headerETag)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(headerETag)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, withitems(array ofAircraft)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(headerETag)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(headerETag)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(headerETag)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, withitems(array ofMeterEvent)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.MeterEventdefault: 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, withitems(array ofResource)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.Availabilitydefault: 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, withitems(array ofAvailabilityRule)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.AvailabilityRuledefault: 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(headerETag)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(headerETag)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, withitems(array ofPilotAuthorization)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.PilotAuthorizationdefault: 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, withitems(array ofPolicy)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(headerETag)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(headerETag)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(headerETag)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, withitems(array ofCategory)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.Categorydefault: 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.Categorydefault: 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, withitems(array ofBooking)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 (confirmedorheld); evaluation attached.BookingWithEvaluation(headerETag)409: A reserved resource is already booked in that window (ADR-0007).ConflictProblem(application/problem+json)422: Ablock-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.BookingPreviewdefault: 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(headerETag)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(headerETag)409: A reserved resource is already booked in that window (ADR-0007).ConflictProblem(application/problem+json)422: Ablock-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(headerETag)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.Bookingdefault: 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(headerETag)412: The booking has changed since theIf-Matchversion; nothing was recorded.Problem(application/problem+json) (headerETag)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(headerETag)412: The booking has changed since theIf-Matchversion; nothing was recorded.Problem(application/problem+json) (headerETag)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, withitems(array ofEvaluation)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, withitems(array ofFlight)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(headerETag)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(headerETag)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(headerETag)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.Flightdefault: 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, withitems(array ofAuditEvent)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, withitems(array ofCalendarFeed)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;tokenand URLs present only here.CalendarFeedWithTokendefault: 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 newtokenand URLs are present only here.CalendarFeedWithTokendefault: 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) (headerETag)304: Not modified — the feed body matches theIf-None-Matchvalidator.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 theidto 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 browserEventSourcenever sees the status of a refused reconnect, so read the reason:access_revokedandroles_changedmean drop everything held for this org before reconnecting;credential_expiredmeans sign in or refresh the token first;session_endedmeans the app host’s session ended, so drop everything held and sign in again;server_shutdownmeans 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, withitems(array ofNotification)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.Notificationdefault: Any error — RFC 9457 (ADR-0005 §3).Problem(application/problem+json)