Developers / API reference
Every endpoint, every field
Seven read-only operations, scoped to one organization. Here is what each one returns, what it accepts, and where its result set stops.
Before you start
The rules that apply everywhere
- Base URL is
https://api.hobbstack.com. Every path below is complete — append nothing. - Authentication is
Authorization: Bearer hbs_pk_…on every endpoint except the calendar feed, which takes the key as atokenquery parameter. Without it you get401. That query parameter is the same full key, so a feed URL is a live credential for everything the key can read — give calendar feeds their ownread:reservations-only key. - Scope is enforced per endpoint; a valid key without the right scope gets
403and the codeINSUFFICIENT_SCOPE. - Organization is taken from the key. There is no org parameter, and no key can read another organization’s records.
- Timestamps are ISO 8601 in UTC. Date-only fields are
YYYY-MM-DD. - Nothing here writes. There is no POST, PUT, PATCH or DELETE in the public API. A leaked key is a confidentiality problem, never an integrity one — revoke it in the app.
Response shapes are documented here, not in the OpenAPI document
Scopes
Which scope unlocks which endpoint
| Scope | Endpoints |
|---|---|
read:reservations | /reservations /reservations.ics |
read:aircraft | /aircraft |
read:squawks | /squawks |
read:members | /members |
read:members.full | /members |
read:maintenance | /maintenance-logs /work-orders |
Full descriptions are in the authentication section, along with the two rules that surprise people: read:members.full does nothing on its own, and every key needs at least one scope, because a key with none can’t read anything.
read:reservations
Reservations
GET /api/v1/external/reservations
Reservations for the key's organization, newest start time first, with optional filters and a cursor. Soft-deleted reservations are excluded.
Response fields
| Field | Type | Notes |
|---|---|---|
id | string | — |
aircraft_id | string | null | — |
pilot_id | string | null | — |
instructor_id | string | null | — |
activity_type | string | dual_training, solo, ground, checkride, flight_review, checkout |
start_time | string | null | ISO 8601, UTC |
end_time | string | null | ISO 8601, UTC |
status | string | confirmed, standby, completed, cancelled, no_show, expired |
checked_in_at | string | null | ISO 8601, UTC |
completed_at | string | null | ISO 8601, UTC |
{
"items": [
{
"id": "6f2f1c9e-6a3b-4a1c-9a6a-2b1f3c4d5e6f",
"aircraft_id": "b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6",
"pilot_id": "11111111-2222-4333-8444-555555555555",
"instructor_id": null,
"activity_type": "solo",
"start_time": "2026-08-06T14:00:00+00:00",
"end_time": "2026-08-06T16:00:00+00:00",
"status": "confirmed",
"checked_in_at": null,
"completed_at": null
}
],
"next_cursor": null
}Result set
This is the only paginated endpoint. limit defaults to 100 and caps at 500; next_cursor is non-null whenever a full page came back. Pass it back as after, with the same filters, to get the next page. Pages continue in the same order (newest start time first), so walking them returns every reservation once, with no gaps or repeats. A cursor that did not come from this organization's results is refused with 400 and the code INVALID_CURSOR.
read:aircraft
Aircraft
GET /api/v1/external/aircraft
Every aircraft in the organization that has not been soft-deleted. Returns a bare JSON array, not an envelope.
Response fields
| Field | Type | Notes |
|---|---|---|
id | string | — |
tail_number | string | — |
aircraft_type | string | — |
display_name | string | null | — |
is_active | boolean | false while grounded |
hobbs_current | number | null | — |
tach_current | number | null | — |
annual_due_date | string | null | ISO 8601 date |
last_100hr_hobbs | number | null | — |
[
{
"id": "b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6",
"tail_number": "N172SP",
"aircraft_type": "C172S",
"display_name": "172SP (G1000)",
"is_active": true,
"hobbs_current": 4821.6,
"tach_current": 4390.2,
"annual_due_date": "2027-03-31",
"last_100hr_hobbs": 4780.0
}
]Result set
Unpaginated — the whole fleet comes back in one array.
read:squawks
Squawks
GET /api/v1/external/squawks
Reported discrepancies for the organization, newest first. Filter by status, aircraft, or whether the squawk grounds the aircraft.
Response fields
| Field | Type | Notes |
|---|---|---|
id | string | — |
aircraft_id | string | — |
description | string | — |
severity | string | minor, major, grounding |
status | string | open, in_progress, resolved, deferred |
is_grounding | boolean | — |
created_at | string | null | ISO 8601, UTC |
resolved_at | string | null | ISO 8601, UTC |
[
{
"id": "9c8b7a65-4321-4fed-8cba-0987654321fe",
"aircraft_id": "b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6",
"description": "Left main tire worn to cord on outboard shoulder",
"severity": "grounding",
"status": "open",
"is_grounding": true,
"created_at": "2026-08-04T18:22:10+00:00",
"resolved_at": null
}
]Result set
Hard-capped at the 500 most recent squawks with no cursor. An organization with more history will silently see only that window, so filter (status, aircraft_id, is_grounding) rather than expecting the full set.
read:members
Members
GET /api/v1/external/members
Membership records for the organization. Contact details appear only when the key also carries read:members.full.
Response fields
| Field | Type | Notes |
|---|---|---|
user_id | string | — |
role_id | string | null | — |
role_name | string | null | — |
status | string | — |
is_discovery | boolean | — |
joined_at | string | null | ISO 8601, UTC |
email | string | read:members.full only |
first_name | string | read:members.full only |
last_name | string | read:members.full only |
phone | string | null | read:members.full only |
[
{
"user_id": "11111111-2222-4333-8444-555555555555",
"role_id": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
"role_name": "Member",
"status": "active",
"is_discovery": false,
"joined_at": "2026-02-11T09:04:33+00:00"
}
]Result set
Unpaginated — every non-deleted membership in one array.
read:maintenance
Maintenance logs
GET /api/v1/external/maintenance-logs
FAR 43.9 maintenance record entries, most recent event date first. Built for audit exports.
Response fields
| Field | Type | Notes |
|---|---|---|
id | string | — |
aircraft_id | string | — |
event_type | string | 100hr, annual, oil_change, ad, other |
event_date | string | null | ISO 8601 date |
hobbs_at_event | number | null | — |
tach_at_event | number | null | — |
ap_cert_number | string | null | — |
ia_cert_number | string | null | — |
ia_name | string | null | — |
ia_sign_off | string | null | — |
return_to_service | boolean | null | — |
performed_by_name | string | null | — |
performed_by_cert_number | string | null | — |
performed_at | string | null | ISO 8601, UTC |
last_100hr_hobbs | number | null | — |
annual_due_date | string | null | ISO 8601 date |
work_performed | string | truncated to the first 1000 characters |
[
{
"id": "3d3d3d3d-1111-4222-8333-444444444444",
"aircraft_id": "b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6",
"event_type": "100hr",
"event_date": "2026-07-28",
"hobbs_at_event": 4780.0,
"tach_at_event": 4351.4,
"ap_cert_number": "3512345",
"ia_cert_number": null,
"ia_name": null,
"ia_sign_off": null,
"return_to_service": true,
"performed_by_name": "R. Alvarez",
"performed_by_cert_number": "3512345",
"performed_at": "2026-07-28T22:10:00+00:00",
"last_100hr_hobbs": 4780.0,
"annual_due_date": "2027-03-31",
"work_performed": "Performed 100-hour inspection per Cessna 172S maintenance manual..."
}
]Result set
Hard-capped at the 500 most recent entries with no cursor. Filter by aircraft_id for a complete per-aircraft history.
read:maintenance
Work orders
GET /api/v1/external/work-orders
Open and historical work orders for the organization, newest first.
Response fields
| Field | Type | Notes |
|---|---|---|
id | string | — |
aircraft_id | string | — |
title | string | null | — |
work_type | string | null | — |
status | string | open, in_progress, completed, cancelled |
ap_signer_id | string | null | — |
ia_signer_id | string | null | — |
created_at | string | null | ISO 8601, UTC |
completed_at | string | null | ISO 8601, UTC |
[
{
"id": "7e7e7e7e-8888-4999-8aaa-bbbbbbbbbbbb",
"aircraft_id": "b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6",
"title": "Replace left main tire",
"work_type": "unscheduled",
"status": "open",
"ap_signer_id": null,
"ia_signer_id": null,
"created_at": "2026-08-04T18:40:02+00:00",
"completed_at": null
}
]Result set
Hard-capped at the 500 most recent work orders with no cursor. Filter by status to narrow.
read:reservations
Reservation calendar feed
GET /api/v1/external/reservations.ics
The same reservations as an iCalendar feed, so a member can subscribe in Apple Calendar, Google Calendar or Outlook. Authenticated with the key in the URL because no calendar client sends an Authorization header on a subscription URL. Returns text/calendar, not JSON.
Mint a read:reservations-only key for calendar feeds
Response fields
| Field | Type | Notes |
|---|---|---|
UID | string | hbs-resv-<reservation id>@hobbstack.com |
DTSTART / DTEND | string | UTC basic format, e.g. 20260806T140000Z |
SUMMARY | string | aircraft id followed by the activity type |
STATUS | string | the reservation status, upper-cased |
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Hobbstack//External API//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:hbs-resv-6f2f1c9e-6a3b-4a1c-9a6a-2b1f3c4d5e6f@hobbstack.com
DTSTART:20260806T140000Z
DTEND:20260806T160000Z
SUMMARY:b1c2d3e4-f5a6-4718-8293-a1b2c3d4e5f6 solo
STATUS:CONFIRMED
END:VEVENT
END:VCALENDARResult set
The 500 most recent reservations by start time. Reservations missing a start or end time are skipped.
Want the machine-readable version?
The OpenAPI document is served without authentication, so any code generator can read it directly: https://api.hobbstack.com/api/v1/external/openapi.json