# fam. API > 41 published endpoints, one base URL (https://app.fam.services/api/v1), one envelope, a bearer key that acts as its owner (a person, or an integration: a system account of the organization). The whole contract: https://app.fam.services/api/v1/openapi ## The wire - Auth: `Authorization: Bearer `; a key carries scopes (`all`, or per resource `:read` / `:write`, write includes read: `all`, `assets:read`, `bookings:read`, `bookings:write`, `files:write`, `locations:read`, `notifications:read`, `notifications:write`, `search:read`, `tickets:read`, `tickets:write`, `time_off:read`, `time_off:write`, `users:read`), may expire, can be rotated with a day of grace; each endpoint below names the scope it wants. A key belongs to a person (their access, ends with their seat) or to an integration (its own access profile, licensed per integration). - Every reply: `Request-Id` (quote it to support; `call_id` in every refusal), `Fam-Version`, `RateLimit` / `RateLimit-Policy` (600 calls per 60 s per key, 3000 with the high-volume add-on; a 429 carries `Retry-After`). - A list: `limit` + `cursor` in, `has_more` + `next_cursor` out; a lookup: `limit` alone. - An instant is RFC 3339 (`2026-09-18T09:12:00.000Z`); a day is `YYYY-MM-DD`; a local wall-clock time is `YYYY-MM-DD HH:mm` in the location's zone. - A write may carry `Idempotency-Key` (up to 255 chars): the same key replays the first reply for 24 h. - A refusal: `{ ok: false, error, message, doc_url, call_id, param?, reason? }`; every word is at https://fam.services/developers#errors. - Webhooks: signed `Fam-Signature` (HMAC-SHA256 over `t.body`), retried 8 times; events at https://fam.services/developers#webhooks. ## assets A thing at a location that tickets are about: a machine, a door, a printer, a vehicle. - [List assets](https://fam.services/developers/md/assets.list): `GET /assets` (scope `assets:read`). Returns the assets at the locations the caller may see, by name (50 a page, `limit` up to 200); `location_id` keeps one location's, `q` the ones whose name, type, reference, manufacturer or serial number matches. - [Get an asset](https://fam.services/developers/md/assets.read): `GET /assets/{id}` (scope `assets:read`). Returns one asset at a location the caller may see, by its id. ## attachments A file on a record (a ticket, a comment, a cost, an asset, a location): a photo, a worksheet, a certificate, an invoice; stored once, read through a short-lived signed URL, kept when its record is deleted. - [Get a signed URL](https://fam.services/developers/md/attachments.signed-url): `GET /attachments/signed-url` (scope `tickets:read`). Returns a URL for one stored file's bytes, good for five minutes. Address the file the way an attachment reply hands it out: the `s3` address (`kind`, `record_id`, `location_id`) and the file's `file_name`; a file on a connection or a time-off request is scoped by `organization_id` instead of the location. `download` asks for a download (saved as `download_name`) rather than an inline view; `verify` checks the object exists first (404 `attachment_missing`); `deleted` with the `entry_id` of the deletion signs a file that was deleted but is still retained, for an admin or the person who deleted it. ## bookings One person holding one bookable thing for one window: pending, approved, checked in, released. - [Create a booking](https://fam.services/developers/md/bookings.create): `POST /bookings` (scope `bookings:write`). Books a desk (`asset_id`) or a room (`area_connection_id`) over a window, for the caller or, with the grant to book for others, for `user_id`. The window is local wall-clock time in the location's zone (`start_local`, `end_local`) or two instants (`start_at`, `end_at`); with `repeat_until_local` the same hours repeat every day up to that date, one booking per day (`created` says how many). The booking is `pending` when the thing's rules ask an approver, else confirmed; a window that touches a booking the person already holds on the same thing is `merged` into it. Needs the Bookings add-on. - [List the caller's bookings](https://fam.services/developers/md/bookings.mine): `GET /bookings/mine` (scope `bookings:read`). Returns the caller's bookings a page at a time (50 by default, 200 at most), live and upcoming, the soonest first; `past` returns the ones that already ended instead, the latest first. Page on: send the reply's `next_cursor` as `cursor` while `has_more` is true. Needs the Bookings add-on. - [Check availability](https://fam.services/developers/md/bookings.availability): `GET /bookings/availability` (scope `bookings:read`). Returns, for each day of a span at a location (`from` and `to`; today and the thirteen days after it when absent, in the location's zone), the bookable types there and how many things of each are still free that day for the caller (the thing's rules, opening hours and closures already applied), whether the caller holds a booking that day, and the closure when the location is closed. Needs the Bookings add-on. - [Release a booking](https://fam.services/developers/md/bookings.delete): `DELETE /bookings/{id}` (scope `bookings:write`). Releases a booking of the caller's own (or, with the grant to book for others, anyone's at that location): the person no longer holds the thing for that window. A booking already released changes nothing (`already`). Needs the Bookings add-on. - [Check in to a booking](https://fam.services/developers/md/bookings.check-in): `POST /bookings/{id}/check-in` (scope `bookings:write`). Checks the person in to a booking whose check-in window is open: from `check_in_opens_at` until `check_in_deadline` (a booking not checked in by then is released as a no-show). The caller's own address on the location's network counts as a `network` check-in, anything else as `manual`. Checking in twice changes nothing (`already`). Needs the Bookings add-on. - [Approve a booking](https://fam.services/developers/md/bookings.approve): `POST /bookings/{id}/approve` (scope `bookings:write`). Approves a pending booking at a location where the caller is an admin or may approve bookings. Approving one already approved changes nothing (`already`); one already rejected is refused (`already_decided`). Needs the Bookings add-on. - [Reject a booking](https://fam.services/developers/md/bookings.reject): `POST /bookings/{id}/reject` (scope `bookings:write`). Rejects a pending booking at a location where the caller is an admin or may approve bookings. Rejecting one already rejected changes nothing (`already`); one already approved is refused (`already_decided`). Needs the Bookings add-on. ## comments What people write on a ticket, a planned ticket, a booking: the conversation on the thing. - [Update a comment](https://fam.services/developers/md/comments.update): `PUT /comments/{id}` (scope `tickets:write`). Replaces the text of a comment of the caller's own (never a comment the system wrote). - [Delete a comment](https://fam.services/developers/md/comments.delete): `DELETE /comments/{id}` (scope `tickets:write`). Deletes a comment of the caller's own (never one the system wrote): the timeline keeps a mark that it was deleted, by whom and when. A comment already deleted changes nothing. It cannot be undone. ## files A file a caller uploaded through the API (POST /files), waiting to be attached to a record by its id; unattached, it is dropped after a day. - [Upload a file](https://fam.services/developers/md/files.create): `POST /files` (scope `files:write`). Stages a file to attach with a later write: send it as `multipart/form-data` with one `file` part (up to 200 MB). Answers the file's `id`; name it in the `attachments` of a comment within a day, once, or it is dropped. The bytes are stored in the organization's own file storage (`no_bucket` when it has none). ## locations A place the organization runs: an address with areas, assets, tickets and the people who have access there. - [List locations](https://fam.services/developers/md/locations.list): `GET /locations` (scope `locations:read`). Returns the locations the caller may see, by organization and name (50 a page, `limit` up to 200). `q` keeps the ones whose name, reference, address or city matches. - [Get a location](https://fam.services/developers/md/locations.read): `GET /locations/{id}` (scope `locations:read`). Returns one location the caller may see, by its id. - [List what may be requested at a location](https://fam.services/developers/md/locations.requests): `GET /locations/{id}/requests` (scope `locations:read`). Returns the requests the caller may raise at the location (`POST /tickets` takes one of their ids), each with its `level`: a `location` request is raised on the location, an `asset` request names one of its assets (`GET /assets?location_id=…`). ## me - [Get the caller](https://fam.services/developers/md/me.read): `GET /me`. Returns the person the key acts as: their id, name, email, title, seat and organization. ## notifications One line in a person's inbox: what happened, to what, by whom; read, done, snoozed. - [List notifications](https://fam.services/developers/md/notifications.feed): `GET /notifications` (scope `notifications:read`). Returns the caller's notifications a page at a time (30 by default, 100 at most), by tab (`all`, `mentions`: what addressed the caller, `mine`: on their own records, `watching`: on the records they watch) and view (`active`, or the `snoozed` ones), newest first or unread first, with the counts beside them. Page on: send the reply's `next_cursor` as `cursor` while `has_more` is true. - [Mark notifications read](https://fam.services/developers/md/notifications.mark-read): `POST /notifications/mark-read` (scope `notifications:write`). Marks the notifications named read and returns how many changed. - [Mark notifications unread](https://fam.services/developers/md/notifications.mark-unread): `POST /notifications/mark-unread` (scope `notifications:write`). Marks the notifications named unread again and returns how many changed. - [Mark notifications done](https://fam.services/developers/md/notifications.mark-done): `POST /notifications/mark-done` (scope `notifications:write`). Marks the notifications named done: they leave the active view. Returns how many changed. - [Undo done](https://fam.services/developers/md/notifications.undo-done): `POST /notifications/undo-done` (scope `notifications:write`). Brings done notifications back to the active view and returns how many changed. - [Snooze notifications](https://fam.services/developers/md/notifications.snooze): `POST /notifications/snooze` (scope `notifications:write`). Snoozes the notifications named until an instant (`until`); with `until` null or absent, unsnoozes them. Returns how many changed. - [Mark all read](https://fam.services/developers/md/notifications.mark-all-read): `POST /notifications/mark-all-read` (scope `notifications:write`). Marks every notification of a tab read and returns how many changed. ## search One box over everything the person may see: tickets, people, locations, assets and more, matched on a number, a name or a few words, narrowed by `key:value` tokens, answered grouped by kind. - [Search](https://fam.services/developers/md/search.query): `GET /search` (scope `search:read`). Searches what the caller may see and returns the matches grouped by kind, the best first (5 per kind by default, `limit` up to 20): hub pages, organizations, people, locations, tickets (with planned tickets) and costs, a person, a location or a ticket as its own read answers it; `type` narrows to one kind. The text is a reference, a name or a few words (two characters at least). `key:value` tokens in it narrow the tickets (`status:open`, `type:planned`, `priority:high`), the costs (`status:`) and the people (`admin:yes`, `online:yes`); a kind that cannot honour a token is left out. The reply says which tokens it read (`filters`) and the text that remained (`q_clean`). ## tickets One piece of work at a location: reported, accepted, scheduled, done, rated. - [Update a ticket](https://fam.services/developers/md/tickets.update): `PATCH /tickets/{id}` (scope `tickets:write`). Changes one or more of a ticket's attributes: its description, its finance references (the purchase order, the invoice and its date, the cost center, the nominal code) and an organization's own reference for it (`requestor_internal_id` by the requesting organization, `assignee_internal_id` by the assigned one). Words not sent are left as they are; a word the ticket does not have is refused (`invalid_body`, the `reason` `unknown_field`), an empty body too (`no_fields`); the reply names what changed (`updated`). On a planned ticket, `visit_id` aims a finance reference at one visit and `apply_all` at every visit. - [Get a ticket](https://fam.services/developers/md/tickets.read): `GET /tickets/{id}` (scope `tickets:read`). Returns one ticket or planned ticket the caller may see, by its id. - [Create a comment](https://fam.services/developers/md/tickets.comments.create): `POST /tickets/{id}/comments` (scope `tickets:write`). Writes a comment on a ticket (or on one visit of a planned ticket, `visit_id`), with files (`attachments`, as `attachment_type` says) and @-mentions in the text, in reply to another comment (`reply_to_id`) or asking a person for feedback (`awaiting_feedback_id`). An operator (never a requester) may ride an action beside it: a new status (`status_id`, with the `completion_date` it sets, or one `completion_date_suggested` for the requester to confirm), a `schedule_date`, an `assignee_id` (a person) or a `field_operator_id`. A comment with no text, no file and no action is refused (`comment_required`). - [List a ticket's attachments](https://fam.services/developers/md/tickets.attachments): `GET /tickets/{id}/attachments` (scope `tickets:read`). Returns every file posted on a ticket's timeline (or on one visit of a planned ticket, `visit_id`), newest entry first: each with its stored name (`file_name`), its name, its size, who posted it and when, whether the caller may delete it (an admin, the person who posted it, or the grant to edit attachments), plus the address (`s3`) a signed URL for its bytes is asked with and, for a caller who may confirm documents, the labels of the document types. A ticket with no files answers 404 `no_attachments`. - [List mentionable people](https://fam.services/developers/md/tickets.mentions): `GET /tickets/{id}/mentions` (scope `tickets:read`). Returns who a comment on this ticket may @-mention: everyone who can post on it, minus the caller (the requester who raised it included), widened by a person the comment is about to name as its assignee (`assignee_id` with `assignee_type`) or field operator (`field_operator_id`) before that is saved. Two group rows come first when the ticket has them: `A`, the assigned organization, and `T`, everyone at the location. - [Search tickets](https://fam.services/developers/md/tickets.search): `GET /tickets/search` (scope `tickets:read`). Returns the tickets, planned tickets and costs the caller may see that match a reference (`RNT-37`) or a few words of the description, the best matches first (12 by default, `limit` up to 50): a ticket as `GET /tickets/{id}` answers it, a cost with its id, reference and title. `exclude` leaves one record out (the one asking). - [Nudge a ticket](https://fam.services/developers/md/tickets.nudge): `POST /tickets/{id}/nudge` (scope `tickets:write`). Reminds up to 25 people who have access to the ticket that it waits on them, with a reason, and returns how many were reached (`sent`): the caller themself, people without access and fam. admins are left out. At most 20 nudges a minute per caller. - [List tickets](https://fam.services/developers/md/tickets.list): `GET /tickets` (scope `tickets:read`). Returns the tickets the caller may see, newest first (50 a page, `limit` up to 200; a planned ticket is read by its id). `status` keeps one state, `location_id` one location, `requester_id` one person's; `updated_since` keeps the ones changed at or after an instant, for a sync (keep the newest `updated_at` you saw and ask again). - [Create a ticket](https://fam.services/developers/md/tickets.create): `POST /tickets` (scope `tickets:write`). Raises a ticket at a location (`location_id`) for one of its requests (`request_id`; `GET /locations/{id}/requests` lists what the caller may raise there): an `asset` request names the asset (`asset_id`), any request may name the area (`area_connection_id`). The description, a priority the caller may pick (`priority_id`), an `emergency` confirmation and files (`attachments`, uploaded through `POST /files`) ride beside. The ticket is routed, assigned and given its SLA the way the app does it; the reply is the ticket as `GET /tickets/{id}` answers it. ## time-off Days a person is away: requested, approved, counted against a balance, shown on the team's calendar. - [Create a time-off request](https://fam.services/developers/md/time-off.requests.create): `POST /time-off/requests` (scope `time_off:write`). Requests time off of one type for the caller: the days `from` … `to`, or on one day a half (`part`) or hours (`from_min`, `to_min`) when the type allows it, with a comment and proof (`attachments`) when the type asks for them and who covers (`substitute_id`). A request that needs no approver, or is short enough for the policy's automatic approval, is `approved` at once (`auto`); otherwise it is `pending`. Refused as `invalid_field` with the `reason`: `note_required`, `document_required`, `nothing` (no working day in the span), `overlap`, `notice` (asked too late), `max_run`, `per_year`, `insufficient`. - [Cancel a time-off request](https://fam.services/developers/md/time-off.requests.delete): `DELETE /time-off/requests/{id}` (scope `time_off:write`). Cancels a time-off request of the caller's own, pending or approved, as long as it has not started. The days return to the allowance (`returned`). A request already cancelled changes nothing (`already`); one rejected, or already started, is refused (`invalid_state`, the `reason` `decided` or `started`). - [Approve a time-off request](https://fam.services/developers/md/time-off.requests.approve): `POST /time-off/requests/{id}/approve` (scope `time_off:write`). Approves a pending time-off request the caller may decide (an admin of the organization, or the person's approver), with a comment for the person when there is one. A request no longer pending changes nothing (`already`). - [Reject a time-off request](https://fam.services/developers/md/time-off.requests.reject): `POST /time-off/requests/{id}/reject` (scope `time_off:write`). Rejects a pending time-off request the caller may decide (an admin of the organization, or the person's approver), with a comment that tells the person why. A request no longer pending changes nothing (`already`). - [Get the day grid](https://fam.services/developers/md/time-off.days): `GET /time-off/days` (scope `time_off:read`). Returns the caller's day grid for a span: each day by date with its `state` (`open`, `past`, `off`, `closed`, `shut`), the label of what closes or holds it, and what is already held on an open day (`taken`: a half, or hours). Empty when the person has no country on their employment, so no policy applies. ## users A human with a seat. Copy names the person or says "person" / "people"; the table is `users`. - [List people](https://fam.services/developers/md/users.list): `GET /users` (scope `users:read`). Returns the people the caller may see, by name (50 a page, `limit` up to 200): their own organization's and, for an operator, the organizations connected to it; `q` keeps the ones whose name or email matches. `email` is answered for the caller's own organization only. - [Get a person](https://fam.services/developers/md/users.read): `GET /users/{id}` (scope `users:read`). Returns one person the caller may see, by their id. ## Events - `booking.created`: A booking was made: confirmed, or pending an approver (`pending` says which). Delivered to every endpoint subscribed to `booking.created`; answer 2xx within 10 seconds or it is retried. - `booking.approved`: A pending booking was approved. Delivered to every endpoint subscribed to `booking.approved`; answer 2xx within 10 seconds or it is retried. - `booking.rejected`: A pending booking was rejected. Delivered to every endpoint subscribed to `booking.rejected`; answer 2xx within 10 seconds or it is retried. - `booking.canceled`: A booking was canceled by its holder, or released by an admin. Delivered to every endpoint subscribed to `booking.canceled`; answer 2xx within 10 seconds or it is retried. - `booking.checked_in`: The person checked in to their booking. Delivered to every endpoint subscribed to `booking.checked_in`; answer 2xx within 10 seconds or it is retried. - `ticket.created`: A ticket was raised through the API (`POST /tickets`); `data` is the ticket as `GET /tickets/{id}` answers it. Delivered to every endpoint subscribed to `ticket.created`; answer 2xx within 10 seconds or it is retried. - `ticket.updated`: A ticket's attributes were changed through the API; `updated` names them. Delivered to every endpoint subscribed to `ticket.updated`; answer 2xx within 10 seconds or it is retried. - `comment.created`: A comment was posted on a ticket or a planned ticket. Delivered to every endpoint subscribed to `comment.created`; answer 2xx within 10 seconds or it is retried. - `time_off.requested`: A time-off request was made (`status` is `pending`, or `approved` when no approver was needed). Delivered to every endpoint subscribed to `time_off.requested`; answer 2xx within 10 seconds or it is retried. - `time_off.approved`: A time-off request was approved. Delivered to every endpoint subscribed to `time_off.approved`; answer 2xx within 10 seconds or it is retried. - `time_off.rejected`: A time-off request was rejected. Delivered to every endpoint subscribed to `time_off.rejected`; answer 2xx within 10 seconds or it is retried. - `time_off.canceled`: A time-off request was taken back by the person. Delivered to every endpoint subscribed to `time_off.canceled`; answer 2xx within 10 seconds or it is retried. ## Errors - `not_signed_in` (401): No key on the request. Send `Authorization: Bearer `. - `api_key_invalid` (401): The bearer is not a live key: it was never minted, it was revoked, or its owner is switched off (`reason`: `integration_inactive` for a deactivated integration, `owner_inactive` for a deactivated person). - `api_key_expired` (401): The key passed its expiry. Mint a new one on the console, or rotate it before it expires. - `insufficient_scope` (403): The key's scopes do not reach this operation; `reason` names the scope it wants (`tickets:write`). Mint a key with that scope, or with `all`. - `forbidden` (403): The user the key acts as (a person, or an integration) may not do this here: their permission profile at the location does not allow it. - `not_found` (404): No such record, or none the person may see. An id of the wrong kind answers the same. - `invalid_field` (400): A word's value is not what it should be: `param` names it, `reason` says how (`type`, `enum`, `format`, `unknown`, `id_kind`, or the handler's own word). - `invalid_body` (400): The body is not a JSON object, or not what the operation takes; `reason` says why. - `missing_field` (400): A required word is missing; `param` names it. For a choice (`asset_id` or `area_connection_id`), `param` names the first alternative and `reason` is `either`. - `plan_required` (403): The organization's plan does not include this surface (bookings, time off). - `rate_limited` (429): Too many calls for this key in the window. Wait `Retry-After` seconds; the `RateLimit` header says when the window resets. - `idempotency_key_reused` (422): The `Idempotency-Key` was already used for a different call (another path, method or body). A key replays one call only. - `idempotency_in_flight` (409): The call with this `Idempotency-Key` is still running. Retry in a moment to receive its result. - `no_country` (409): The person has no country set, so no holidays and no time-off policy apply to them yet. - `no_policy` (409): No time-off policy of the organization covers the person for this type. - `no_approver` (409): Nobody is in place to approve: the request needs an approver and the organization has not named one for the person. - `no_bucket` (409): The organization has no file storage set up, so attachments cannot be posted. - `invalid_state` (409): The request is not in a state this operation may change: `reason` says which (`decided`, `started`). - `comment_required` (400): The comment's text is empty. - `no_attachments` (404): The record has no attachments. - `purged` (410): The attachments were purged: the record's files are no longer retained. - `no_recipients` (400): No `user_ids` were named. - `no_valid_recipients` (403): None of the people named may be nudged about this ticket (they are not in its audience). - `too_many_recipients` (400): More people were named than a nudge may reach at once (25). - `kind_invalid` (400): The `kind` is not one of the attachment kinds. - `invalid_request` (400): The address is incomplete: `record_id`, `file_name` and the scope (`location_id` or `organization_id`) are all needed. - `attachment_missing` (404): No stored file answers to that address. - `no_assignment` (409): Nothing at the location answers that request for the caller: no active assignment matches the request (and the asset, for an asset request), or the caller may not raise it there. - `asset_required` (400): The request is raised on an asset (`level: asset`) and none was named: send `asset_id`. - `asset_no_category` (409): The asset has no category, so no request can be routed by it. - `floor_inactive` (409): The area named is on a floor that is no longer available. - `checklist_required` (409): The request asks for a checklist to be answered when the ticket is raised, which the API does not take: raise it in the app. - `file_too_large` (413): The file is over the cap (200 MB). - `target_has_no_access` (400): The person the booking is for has no access to the location. - `not_bookable` (409): The desk or room is not bookable: its type is not set up for booking. - `already_booked` (409): The window clashes with a booking that already holds the desk or room. - `too_long` (409): The window is longer than the type allows. - `too_soon` (409): The window starts sooner than the type allows. - `too_far_ahead` (409): The window starts further ahead than the type allows. - `too_many_days` (409): The series covers more days than the type allows. - `multi_day_not_allowed` (409): The window spans more than one day and the type allows one. - `outside_opening_hours` (409): The window lies outside the location's opening hours. - `space_closed` (409): The desk or room is closed for that window. - `company_closed` (409): The organization is closed that day (a holiday or a closure it set). - `away` (409): The person is away that day (time off), so nothing is booked for them. - `profile_not_allowed` (409): The person's permission profile may not book this type. - `no_check_in_required` (409): The booking's type needs no check-in. - `too_early` (409): Check-in has not opened yet; `check_in_opens_at` says when. - `too_late` (409): The check-in deadline passed; the booking was released. - `off_network` (403): Check-in from this network is not allowed: the person is not on the location's own network. - `not_live` (409): The booking is not live: it is pending, released, canceled or over. - `released_no_show` (409): The booking was released as a no-show and cannot be changed. - `booking_canceled` (409): The booking was canceled. - `already_decided` (409): The booking was already approved or rejected. ## Changelog - 2026-09-19 (added): Identity: an INTEGRATION is an account of the organization's own (a system, with a permission profile and keys of its own, licensed per integration); a key belongs to a person or to an integration, and `GET /me` says which (`seat: integration`). A deactivated owner answers `api_key_invalid` with `reason` (`integration_inactive`, `owner_inactive`). - 2026-09-19 (changed): Scopes: a key carries `scopes` (`all`, or per resource `:read` / `:write`, write includes read) in place of `read` / `write`; every operation names the scope it wants (the document's `security`), and `insufficient_scope` names it in `reason`. Existing keys were carried over (`write` → `all`, `read` → every `:read`). - 2026-09-19 (added): The high-volume API add-on: 3,000 calls a minute per key in place of 600, and a year of calls kept; `RateLimit-Policy` says which. - 2026-09-19 (added): The records: the caller (`GET /me`), the tickets (a list with `status`, `location_id`, `requester_id` and `updated_since` for a sync; one by id; one created with `POST /tickets` for a request `GET /locations/{id}/requests` lists), the locations, the assets and the people. One shape per record; a search match is the record too. The event `ticket.created`. - 2026-09-19 (added): Files: `POST /files` takes a file as `multipart/form-data` and answers its id; a comment or a ticket names the id in `attachments` (`file_not_found` for an id that was spent or never uploaded). - 2026-09-19 (added): Webhooks: an organization registers https endpoints on Console › API and receives signed events (`Fam-Signature`, HMAC-SHA256) for bookings, comments, ticket updates and time off, retried on a backoff ladder. - 2026-09-19 (added): Every reply carries `Request-Id` (the call's id, `call_id` in every refusal), `Fam-Version` and the `RateLimit` headers; a key is limited per minute; a write honours `Idempotency-Key` for a day. - 2026-09-19 (added): Every refusal carries `message` (the word's sentence), `doc_url` (its anchor on the reference) and `param` (the word it is about); every word has a page under Errors. - 2026-09-19 (changed): ONE page shape on every list: `limit` + `cursor` in, `has_more` + `next_cursor` out. `before_ms` / `before_unread` and the `cursor` object are gone (the first published version was never called). - 2026-09-19 (changed): ONE spelling of an instant: RFC 3339 (`2026-09-18T09:12:00.000Z`). `start_at` / `end_at` replace `start_ts` / `end_ts`, `until` replaces `until_ms`, `check_in_opens_at` / `check_in_deadline` / `created_at` / `snooze_until` are instants. - 2026-09-19 (changed): A lookup takes `limit` (its ceiling on the row). - 2026-09-19 (changed): A request is validated at the door from the document: an unknown word, a wrong type, a missing required word or choice is refused with `param` and `reason` before the handler runs. - 2026-09-18 (added): The first published contract: 29 endpoints, one envelope, opaque ids, bearer keys minted on Console › API.