Skip to content

@guestcraft/sdk

A portable TypeScript client for your GuestCraft instance. MIT licensed, built on standard fetch, and generated from the repository’s OpenAPI contract. Hey API generates its operations, types and Zod validators; Zod is its runtime dependency. React query factories live separately in @guestcraft/react.

This is a private pnpm workspace package, version 0.0.0. It has not been published to npm. Its exports currently point to TypeScript source, so your workspace consumer needs to support TypeScript, as this repository does.

From the repository root, add the package to a workspace consumer:

Terminal window
pnpm --filter <your-workspace-package> add @guestcraft/sdk@workspace:*

In a browser already signed in to the same GuestCraft instance:

import { createGuestCraft } from "@guestcraft/sdk";
const guestcraft = createGuestCraft({
baseUrl: window.location.origin,
workspaceId: "ws_01k8b4c7k9f5qr7v1d2s3x4y5z", // Your dashboard’s workspace ID.
});
await guestcraft.health.get(); // { status: "ok" }
const { data: connection } = await guestcraft.channex.getConnection();
if (connection.connected) {
const { data: properties } = await guestcraft.channex.listProperties();
// [{ id: "channex-property-id", title: "Hotel Aurora" }]
}

baseUrl is an absolute HTTP(S) instance root, such as http://localhost:3000 or https://host.example/guestcraft. The SDK preserves that deployment prefix and appends /api/v1. Do not include /api/v1, credentials, a query or a fragment in baseUrl.

GuestCraft authenticates browser cookie sessions, native device sessions (a device token of the mobile app, see Device tokens below) and workspace API keys for integrations (see API keys below). For a cookie session, sign in through the instance’s /sign-in page or POST /api/auth/sign-in/email. HTTPS uses __Secure-guestcraft.session_token; HTTP localhost uses guestcraft.session_token. Your Channex API key is not a GuestCraft credential.

The SDK sets credentials: "include". Same-origin browser requests use the browser’s session; the SDK cannot read the HTTP-only cookie. Node.js fetch does not manage a cookie jar automatically. On a server, inject a cookie-aware fetch, or explicitly forward the current user’s trusted session cookie:

const guestcraft = createGuestCraft({
baseUrl: "https://guestcraft.example",
workspaceId,
// Obtain this from the current authenticated server request, never a public input.
headers: { Cookie: currentUserSessionCookie },
fetch: globalThis.fetch,
});

Create a client per user/session and workspace. Do not share a cookie-bearing client across users. headers are copied on construction; use them for Accept-Language or a request ID as well. workspaceId sets GuestCraft-Workspace-Id and overrides any workspace header in headers. It selects a workspace the user already has access to. It may be omitted when that user has exactly one workspace. Cross-origin browser use also depends on the instance’s origin and cookie policies.

A native app signs in through the browser and receives a device token; see discovery and device sessions. Pass it as a header:

import { createGuestCraft, discoverInstance } from "@guestcraft/sdk";
const instance = await discoverInstance("https://hotel.example.com"); // /.well-known/guestcraft
const guestcraft = createGuestCraft({
baseUrl: "https://hotel.example.com",
headers: { Authorization: `Bearer ${deviceToken}` }, // from the keystore, never logged
fetch,
});
const { data: workspaces } = await guestcraft.workspaces.list();

discoverInstance throws a GuestCraftError: http (a 404 means no discovery), network, or invalid_response. deviceSessions.create, list and revoke and deviceAuthorizations.create cover the rest of the flow; instance.get reads discovery at /api/v1/instance. To pair a phone with a QR code instead, a signed-in browser calls devicePairings.create(), shows its pairing_url, polls devicePairings.get until status is no longer pending, and withdraws the code with devicePairings.cancel when it stops showing it; the app sends { pairing_code, device_name } to deviceSessions.create.

A member with api_keys.manage creates a workspace API key in Settings → API keys and copies its secret, shown once: gc_live_… in a production workspace, gc_test_… in a sandbox one. The key acts in its workspace only, with the permissions and properties it was given, never more than its creator still holds; see API keys and webhooks.

import { createGuestCraft } from "@guestcraft/sdk";
const guestcraft = createGuestCraft({
baseUrl: "https://hotel.example.com",
apiKey: process.env.GUESTCRAFT_API_KEY, // from a secret store, never logged
});
const { data: properties } = await guestcraft.properties.list();

With apiKey the client sends Authorization: Bearer and no cookie. A revoked or expired key answers 401 auth.unauthenticated; more than 600 requests a minute answer 429 auth.rate_limited with Retry-After, and so do a key’s requests while the instance has no place to count them (it remembers 10,000 keys, 100 per workspace, each place freed a minute after its key’s last use). API keys and webhook endpoints are administered from a browser session only, through guestcraft.apiKeys, webhookEndpoints and webhookDeliveries, or with the generated operations (apiKeysCreate, webhookEndpointsUpdate, webhookDeliveriesList, …) sent through a transport (see Send only the operations you use):

import { createGuestCraftTransport } from "@guestcraft/sdk";
import { apiKeysCreate } from "@guestcraft/sdk/operations/api-keys";
const transport = createGuestCraftTransport({ baseUrl: window.location.origin, workspaceId });
const issued = await transport.send(apiKeysCreate, {
body: { name: "ERP", permissions: ["reservations.read"] },
});

An owner or administrator of the organization creates an organization token in Settings → Organization tokens and copies its secret, shown once (gc_org_…). It acts in the workspaces of the organization it names (all of them by default), one per client, with its permissions narrowed in each workspace to what its creator still holds there; see API keys and webhooks.

const headquarters = createGuestCraft({
baseUrl: "https://hotel.example.com",
organizationToken: process.env.GUESTCRAFT_ORGANIZATION_TOKEN, // from a secret store
workspaceId: "ws_01k8b4c7k9f5qr7v1d2s3x4y5z", // required when the token reaches several
});
const { data: workspaces } = await headquarters.workspaces.list(); // every workspace it reaches

Create one client per workspace, or pass headers: { "GuestCraft-Workspace-Id": … } to a single call. Tokens are administered from a browser session only, through guestcraft.organizationTokens.

Like Seam’s client session tokens, a client session lets a front end, such as a guest page or an embedded panel, call the API with no other credential. Your server mints it with its API key or organization token (client_sessions.manage), for one of your users and a narrow grant: some permissions on some properties of one workspace, or within one reservation, one guest or one property’s public booking (scope), for at most 24 hours (one hour by default).

// On your server, with the API key or organization token.
const { data: issued } = await guestcraft.clientSessions.create({
body: {
user_identifier: "guest-account-1042", // your identifier, never an e-mail address or a name
permissions: ["properties.read", "reservations.read"],
property_ids: ["prop_01k8b4c7k9f5qr7v1d2s3x4y5z"],
},
});
// Hand issued.token (gc_cst_…) to the front end, for example in the page you render.
// In the front end, with the session's token alone: no cookie, no key.
const session = createGuestCraft({
baseUrl: "https://hotel.example.com",
clientSessionToken: tokenFromYourServer,
});
const { data: reservations } = await session.reservations.list({ query: { property_id } });

The session’s requests may come from any origin: the API answers the CORS preflight and lets the page read the answers of requests carrying a client session token. A session holds on every request only what its minting credential may still do, and ends when that credential is revoked or expires; clientSessions.revoke ends it at once. An expired or revoked session answers 401 auth.unauthenticated: ask your server for a new one.

A page for one guest mints a narrower session, which reaches only the operations that name its object; any other answers 403 auth.forbidden:

const { data: issued } = await guestcraft.clientSessions.create({
body: {
user_identifier: "booking-4711",
permissions: ["reservations.read"],
scope: {
kind: "reservation",
property_id: "prop_01k8b4c7k9f5qr7v1d2s3x4y5z",
reservation_id: "rsv_01k8b4c7k9f5qr7v1d2s3x4y5z",
},
},
});

A webhook receiver verifies each request with the endpoint’s secret (whsec_…) before it trusts the body. Pass the body exactly as received:

import { WebhookVerificationError, webhooks } from "@guestcraft/sdk";
export async function POST(request: Request) {
const body = await request.text();
try {
const event = await webhooks.verify(body, request.headers, process.env.GUESTCRAFT_WEBHOOK_SECRET);
// event.id is the webhook-id: skip it if you processed it already.
// event.data names the object; read it again with your API key.
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(null, { status: 400 });
throw error;
}
return new Response(null, { status: 204 });
}

verify follows Standard Webhooks: it refuses a missing header, a timestamp more than five minutes away (toleranceSeconds) and a body that no v1 signature matches, comparing in constant time; during a secret rotation either secret’s signature is accepted.

createGuestCraft references every operation it wraps, so a bundle that uses it carries them all. A browser app that cares about its size sends the generated operations through a transport instead, and imports them from their OpenAPI tag’s module:

import { createGuestCraftTransport } from "@guestcraft/sdk";
import { reservationsUpdate } from "@guestcraft/sdk/operations/reservations";
const transport = createGuestCraftTransport({ baseUrl: window.location.origin, workspaceId });
const reservation = await transport.send(reservationsUpdate, {
path: { property_id: propertyId, id: reservationId },
headers: { "if-match": etag },
body: { notes: "Late arrival" },
});

createGuestCraftTransport takes the options of createGuestCraft. send answers the data of a successful response and throws a GuestCraftError otherwise, like the facade. Its input has the operation’s own types: a missing required field, a body of another operation or a wrong expected answer does not compile. No generated operation asks for the workspace header, because the client sends the one its transport was created with; if-match stays required where the operation needs it. transport.client is the generated client, for calling an operation directly or for the query factories of @guestcraft/react. When you call an operation directly, pass that client, or set GuestCraft-Workspace-Id on the request yourself: the default generated client sends no workspace, and the API refuses a workspace operation without one, although the compiler does not ask for it. propertiesFacade(transport) and channexConnectionFacade(transport) are the facade’s two groups that check their input before a request. channexConnectionFacade is also its own module, @guestcraft/sdk/channex-connection, so that a caller can load it only when needed.

Each OpenAPI tag has its module: @guestcraft/sdk/operations/<tag>, with the tag in kebab case (reservations, reservation-rooms, api-keys, …). The module carries the tag’s operations and the response validators only they use; what several tags use is in a shared module. A bundle keeps every module that a re-exporting module names, so import a tag module rather than @guestcraft/sdk/operations, which re-exports every tag, or the root’s operations namespace; both remain for existing code. The tags and their operations are in the API reference.

Transport problem validation uses its own generated schema module. It does not initialize shared guest or reservation response schemas on screens that only need the transport. Import GUEST_CONSENT_PURPOSES from @guestcraft/sdk/guests; this runtime catalog stays outside the root transport entry point.

The createGuestCraft facade exposes every operation in the committed OpenAPI contract. Methods are grouped as follows; each method takes the generated operation input, except the no-input operations health.get, instance.get, organizations.list, workspaces.list, deviceSessions.list, devicePairings.create, and webhookEndpoints.list, which take only { signal }, the checked properties, and the Channex connection helpers getConnection, connect, and listProperties documented below.

  • health: get
  • instance: get
  • deviceAuthorizations: create
  • devicePairings: create, get, cancel
  • deviceSessions: create, list, revoke
  • organizations: list, create, audit, auditExportCsv, auditExportJson
  • accountSecurity: getPolicy, updatePolicy, compliance, issuePasswordReset, withdrawPasswordReset, redeemPasswordReset
  • enterpriseIdentity: listDomains, addDomain, getDomain, verifyDomain, releaseDomain, listConnections, createConnection, getConnection, updateConnection, deleteConnection, listScimTokens, createScimToken, revokeScimToken, listScimGroups, updateScimGroupAccess, listScimUsers
  • workspaces: list, create, get, update, setArchival
  • nightAudits: context, settings, updateSettings, warnings, reviewWarnings, precheck, list, create, get, complete
  • cashShifts: context, current, list, create, get, close
  • cashShiftSettings: get, update
  • paymentConnection: get, update, verify, delete, retryDeliveries (browser session only)
  • reservationPayments: get
  • paymentRequests: create, get, cancel
  • cardCharges: create
  • otaCards: move
  • otaCardCharges: create
  • cardTokenization: get, acknowledge, stop (browser session only)
  • providerPayments: get, capture, release
  • providerRefunds: create
  • paymentsDue: list
  • cashSettlements: create
  • counterparties: list, create, get, replace, exposure, statement, statementCsv
  • receivableItems: list, get, writeOff
  • receivableEntries: release
  • receivableReceipts: list, create, get, allocate, void
  • receivables: aging, agingCsv
  • team: list, invite, resend, revoke, changeRole, remove, customRoles, createRole, audit, auditExportCsv, auditExportJson, accept, getRole, updateRole, deleteRole, getAccess, changeAccess, getSignInLock, unlockSignIn, roles, transferOwnership
  • apiKeys: list, create, get, revoke, rotate
  • organizationTokens: list, create, get, revoke, rotate
  • authorizedApplications: list, get, revoke
  • clientSessions: list, create, get, revoke
  • webhookEndpoints: list, create, get, update, delete, rotateSecret, test, replay
  • webhookDeliveries: list, redeliver
  • eventTypes: list
  • events: list, get
  • blocks: list, create, update, remove, end
  • maintenanceTickets: list, create, get, update, status, activity
  • maintenanceTickets.photos: create, get, remove
  • maintenanceAssignees: list
  • housekeepingSettings: get, update
  • housekeepingDays: get, generate
  • housekeepingTasks: list, create, get, update, status, assign, bulkStatus, bulkAssign
  • housekeepingAssignees: list
  • housekeepingRosters: get, update
  • housekeepingCredits: get, update
  • housekeepingAssignmentPlans: get
  • housekeepingAssignmentRuns: create, list, get, undo
  • housekeepingChecklists: list, create, get, update, remove
  • housekeepingTaskChecks: list, answer
  • ratePlans: list, create, update, archive
  • rates: get, apply, restore
  • quotes: create
  • reports: history, performance, performanceCsv, pickup, pickupCsv, cash, cashCsv, revenue, revenueCsv, cancellations, cancellationsCsv, consolidated, consolidatedCsv, organizationConsolidated, organizationConsolidatedCsv
  • exchangeRates: list, create, delete
  • roomTypes: list, create, get, update, archive, unarchive
  • units: list, create, update, get, archive, housekeeping, bulkHousekeeping, unarchive
  • reservationRooms: undoCheckIn, undoCheckOut, update, assign, move, split, resize, setDoNotMove, undo, checkIn, checkOut, markNoShow, undoNoShow, waiveNoShow, guarantee, checkoutBalance
  • guests: get, update, reservations, create, list, duplicates, guestDuplicates, dismissDuplicate, merge, consents, recordConsent, reassignOwnership, sharedContacts, guestSharedContacts, sharedContactGuests, merges, consentCurrent, contactNotices, dismissContactNotice, undoDuplicateDismissal
  • guestDataRetention: get, update
  • guestLegalHolds: list, create, release
  • guestErasure: create
  • guestDataExports: create
  • guestDataRequests: list, get, download
  • reservations: create, list, get, update, cancel, simulateCancellation, prepayments, listOtaReports, reportToOta, reconcileOtaReport
  • policies: list, create, update
  • availability: get
  • calendar: get
  • frontDesk: get
  • folios: get, postExtra, pay, rerate, refund, reverse, transfer
  • reservationFolios: list, get, create, update, close, reopen, linkCounterparty, unlinkCounterparty
  • rerates: apply
  • folioExtras: list, create, update
  • folioTaxCategories: list
  • folioCreditsDue: list
  • channex.ari: health, plans, mappings, map, repair, resync
  • channex.ari.retirements: create, get, cancel, resume
  • channex.bookingHistory: preview, import, list
  • channex: alerts, alertTransitions, sync, roomTypes, map, candidate, unlink, forgetRemoval, registerWebhook, requestDrain, retryRevision, getConnection, connect, patchConnection, listProperties
  • channexMessages: list, get, put, confirmInstallation, forgetInstallation, synchronize
  • messageThreads: list, summary, get, messages, putReadState, variables, reply, attach, outbox, retry, dismiss, noReplyNeeded
  • messageRequests: list, get, decide
  • messageRetention: get, update
  • messageGuests: eraseContent
  • messageTemplates: list, create, get, update, delete
  • properties: get, update, list, create

organizations.list, create, audit, auditExportCsv and auditExportJson, and workspaces.create, get, update and setArchival require a browser session. Their scope comes from the signed-in person and resource path, so they do not need workspaceId on the client. Creating an organization requires instance administration; creating or archiving a workspace requires organization ownership or administration. Workspace reads and name changes require workspaces.configure with access to all properties. Pass the returned strong ETag in headers: { "if-match": etag } for update and setArchival. Archival retains saved connections and data, and requires linked Channex properties and unfinished cleanup to be settled first. See the organization and workspace contract.

These methods use the generated OpenAPI request/response types. For blocks, dates are half-open property-local ISO dates; out_of_order and available are returned by availability reads. See ADR 0033.

SDK method HTTP operation Result
health.get(options?) GET /api/v1/health { status: "ok" }; public process liveness only
properties.list({ limit?, cursor?, signal? }?) GET /api/v1/properties { data, next_cursor, has_more }
properties.create(settings, options?) POST /api/v1/properties New local property, HTTP 201
properties.update(id, settings, options?) PUT /api/v1/properties/{id} Saved local property
properties.get(id, options?) GET /api/v1/properties/{id} Raw local Property with id, workspace_id, name, time_zone, currency
channex.getConnection(options?) GET /api/v1/channex/connection { data: { connected: false } } or connected status with connected_at and property_count
channex.connect({ api_key }, options?) PUT /api/v1/channex/connection { data: { connected: true, connected_at, property_count } }
channex.listProperties(options?) GET /api/v1/channex/properties { data: [{ id, title }] }

Connect through dashboard onboarding, or from your own secure credential input:

// channexKey comes from a secure input; do not log it or put it in a URL.
await guestcraft.channex.connect({ api_key: channexKey });

Connection validates a Channex key with the workspace’s Channex environment (staging for a sandbox workspace, production otherwise) and saves an encrypted credential for that workspace. The key is never returned. Connection status and count are a saved snapshot; listing fetches Channex again. Listing does not import or map properties. Channex IDs are distinct from local GuestCraft prop_… IDs. Booking ingestion and guarded ARI have separate workflows; connecting alone does not enable them. See the developer import and ARI contracts.

import { GuestCraftError } from "@guestcraft/sdk";
const controller = new AbortController();
try {
await guestcraft.channex.listProperties({ signal: controller.signal });
} catch (error) {
if (error instanceof GuestCraftError) {
// Branch on code; title and detail can be translated.
const code = error.code;
const requestId = error.requestId;
const fields = error.problem?.errors;
} else {
throw error; // Cancellation preserves the caller’s AbortSignal reason.
}
}
  • kind: "http": status, requestId, and a validated RFC 9457 problem when provided. code is the problem’s stable code. Non-JSON proxy errors still retain HTTP status and the request ID header.
  • kind: "network": fetch or response-body reading failed. The SDK omits untrusted transport causes, request bodies and credentials from its errors.
  • kind: "invalid_response": a successful response failed JSON or structural schema validation. Date-time formats remain strings and are checked by the generated Zod schema.

The SDK logs nothing. Error messages omit response detail; problem deliberately exposes the server’s public problem details. Treat those as untrusted text in your UI and avoid logging credentials yourself.

There are no automatic retries, including on reads. Credential writes are never replayed. A failed or cancelled write may already have reached the server: inspect connection status before deciding to reconnect. Pass an AbortSignal for cancellation or a caller-owned deadline, such as AbortSignal.timeout(25_000).

Terminal window
pnpm --filter @guestcraft/api openapi # Backend owner exports the API contract.
pnpm --filter @guestcraft/sdk generate
pnpm --filter @guestcraft/sdk generate:check
pnpm --filter @guestcraft/sdk typecheck
pnpm --filter @guestcraft/sdk test

Pinned Hey API in tooling/codegen generates types, operations, Zod response schemas and TanStack Query factories from openapi/v1.json. The local script splits them into one module per tag, then compiles and packages them; it reads only each operation’s tag from the contract. SDK output is JavaScript plus declarations, checked by strict handwritten TypeScript consumers (ADR 0022 and its 2026-10-30 amendment). The drift test checks the generated files byte for byte and verifies that all current operations are covered. Transport tests exercise tenant headers, session options, API/proxy errors, response validation, cancellation and credential-write failures.

See the instance’s /docs hub, /api/v1/docs reference and /api/v1/openapi.json, or the repository’s developer guides.

Local settings use { name, time_zone, currency }. Creating and updating need properties.configure (built-in: administrators); reading needs properties.read. See property setup. No automatic retries are added for property writes.

roomTypes, units, guests, reservations, reservationRooms, availability, calendar and frontDesk expose the generated operations through the caller transport. Use typed Hey API options (path, body, query, headers, signal); reservation and guest edits require headers: { "if-match": '"1"' }. Responses validate frozen decimal prices and totals. No retries are added. See the complete workflow and method examples. reservationRooms.move and reservationRooms.split (ADR 0042) take the same if-match header and an optional query: { validate_only: "true" } preview. reservationRooms.resize changes stay dates through the existing date-change rules. setDoNotMove persists the permissioned stay flag; protected changes require confirm_do_not_move: true. A successful planning write returns an optional planning_change_id; pass that receipt to reservationRooms.undo with the exact ETag returned by the original write. Undo is actor-bound and never retries a version conflict.

client.team exposes the member, invitation, custom-role, property-access and sign-in-lock operations listed in the facade above, with generated options (body, query, signal). Configure workspaceId on the client to send GuestCraft-Workspace-Id; deprecated workspace_id aliases must match it. Legacy role/removal writes also require If-Match. Writes require the instance Origin header with the session cookie; browsers send it automatically. Invitation links are secrets: do not log/cache the mutation result. Creation/rotation returns a link regardless of account existence. The link expires in 48 hours. See ADR 0030 for workflow and pagination limits.

The role catalog has 69 capabilities, custom-role resources and a member-access sub-resource with strong preconditions. Use the role workflow for permission codes and selected-property rules; delegation is bounded by the actor’s live permissions and property context fails closed.

ratePlans.{list,create,update,archive}, rates.{get,apply,restore} and quotes.create expose all eight generated rate operations through the same transport. Setup mutations use plan If-Match; nightly mutations use calendar If-Match. Quotes hold no inventory. See the rates contract for examples, booking input changes and planned capabilities.

The handwritten channex group wraps the connection, booking-import mapping, sync health, drain, webhook registration and recovery operations, the property’s open alerts and their openings and resolutions (alertTransitions, each to be shown once by its event id), channex.ari the ARI health, plan, mapping, repair and resync operations, and channex.bookingHistory the preview, import and list of a property’s existing Channex bookings; generated React factories expose the same contracts without retaining credential data. channex.patchConnection takes body.account_mode and the connection version as a quoted headers["if-match"]; connection reads include the mode and version. Shared listProperties returns mapped properties; link an explicit property ID with room_types: {} before room discovery.

channex.candidate({path:{id}}) reads the candidate title/ID/connection version; channex.map accepts the confirmed ID/version for an initial shared link. channex.unlink({path:{property_id},headers:{"if-match":connectionEtag}}) deletes the mapping workflow. Unlink preserves imported reservations; failed webhook cleanup is visible in sync().data.pending_unlinks and can be retried. See the booking import and ARI publication contracts.

reports.history({ path: { property_id } }) discovers the captured history boundary. reports.performance({ path: { property_id }, query: { from, to, group_by } }) returns performance with historical capacity (definition version 2, API revision 5.0.0). Older ranges use labelled baseline capacity; pre_history_nights counts affected stay dates. reports.pickup adds two as-of dates and compares booked rooms/revenue for each stay date. reports.cash and reports.revenue group the append-only ledger by service date. Each has a matching Csv method returning text (pass parseAs: "text"). Generated operations and React query factories expose the same contracts; the MIT client imports no core code. See the report contract for exact inputs and coverage limits.

reports.consolidated({ query: { from, to, reporting_currency } }) sums the workspace’s authorized properties per currency; with reporting_currency each currency’s subtotal is converted once with the organization’s stored rates, listed next to the converted amounts, and a missing or stale rate answers exchange_rate.missing. reports.organizationConsolidated adds include_sandbox and covers the organization’s active workspaces for its owners and administrators in a browser session. exchangeRates.list reads the stored rates; exchangeRates.create and exchangeRates.delete need a browser session.

housekeepingSettings, housekeepingDays, housekeepingTasks and housekeepingAssignees cover a property’s stayover and inspection settings, the on-demand generation of a day’s tasks, manual tasks, assignment and the task lifecycle, one by one or up to 100 at once (the bulk operations take each task’s version in the body). maintenanceTickets reports and edits faults, sets their status with a housekeeping follow-up, reads their activity and manages their photos; maintenanceTickets.photos.create takes a JPEG or PNG in base64 and maintenanceTickets.photos.get answers the bytes as a Blob (the facade reads it as one; with the generated operation, pass parseAs: "blob"). Creations accept a client id, so that a repeated request answers the same task, ticket or photo. housekeepingRosters and housekeepingCredits hold who works on a day, with how many credits of work, and how many credits each task takes; housekeepingAssignmentPlans.get previews a day’s automatic assignment and housekeepingAssignmentRuns.create applies it with the preview’s basis, which undo reverses exactly. housekeepingChecklists manages checklist templates, copied onto each new task, and housekeepingTaskChecks answers a task’s items with the task’s version. See ADR 0046.

guestDataRetention reads and replaces the workspace’s retention periods, with the country pack defaults they replace. guestLegalHolds places and releases a guest’s legal hold, which blocks erasure and retention. guestErasure.create anonymizes a guest and its merged identities, and guestDataExports.create queues an access export; guestDataRequests lists and reads the requests, and guestDataRequests.download answers a ready export’s ZIP archive once, as a Blob (the facade reads it as one; with the generated operation, pass parseAs: "blob"). Every operation needs a browser session. See ADR 0052.

Reservation folios and manual settlement are documented in the folio workflow and ADR 0029. The ledger, frozen room charges, property extras, manual payments/refunds, a check-out that settles only at the last room and never holds a credit (an owed balance needs settlement or an audited administrator approval), refunds due, net and full-ledger views, a non-fiscal statement, split folios with payers, routes and transfers, per-room settlement and bulk re-rating are implemented. Taxes need a country module on the TaxCalculator port (with a re-rate operation); fiscal documents and online providers remain planned.

Property policies, immutable sold conditions, simulated cancellation fees, explicit no-show/recovery, guarantee attestations and prepayment tracking are documented in the policy workflow and ADR 0032, with permissions, deadlines, migration semantics, SDK operations and planned OTA reporting/provider capabilities.

nightAudits covers property business dates, automatic and manual night audits; see the workflow contract and ADR 0043. Local-time scheduling, configured no-shows and fee-free expired options use that same durable path.

Night audit settings, configured no-shows and expired tentative options share the durable property-date audit workflow. Cash drawer shifts provide currency-aware opening float, server-linked cash activity and versioned counted closure. See the night audit contract and cash shift contract for public operations, permissions, recovery, persistence and the Vercel daily-trigger cadence limit.

Counterparties are an organization directory reached from the organization’s active workspaces; open items, their append-only entries (debits, allocations, write-offs, releases, cancellations) and receipts belong to one workspace. Closing a folio linked to a counterparty books one receivable_transfer movement and opens its item; reopening reverses it exactly. OTA-collected bookings open expected payouts from the revision’s facts. See the receivables guide and ADR 0051.

guests.duplicates and guestDuplicates page reasoned duplicate pairs. dismissDuplicate remembers an operator decision. merge takes the survivor If-Match, absorbed version and per-field sources; reassignOwnership changes owning property separately. consents and recordConsent preserve independent append-only evidence histories. Merge never combines consents. See the guest workflow and ADR 0045 for permissions, tombstones and every current guest-reference policy.

contactNotices returns newest-first OTA snapshots with only eligible fields in contact. Overlay those fields on the current guest for ordinary update, retaining its notes. dismissContactNotice keeps one field through a versioned resource: path: { id, reservation_id, field }, headers: { "if-match": '"3"' }, and body: { revision_id, expected_value }. Its returned guest carries the current version. Dismissed values remain suppressed for that guest and field across later snapshots; other fields remain independent. Refetch the guest and notices after a 412 guest version conflict or 409 guest.contact_notice_changed. Do not automatically retry these writes.

API 6.4.0 adds guestcraft.organizations.list/create/audit and guestcraft.workspaces.create/get/update/setArchival, also available from generated operations/organizations and operations/workspaces with React Query factories. These administration methods require a browser session; writes require the instance Origin. Item reads return a strong ETag (also etag in the body), passed as headers: { "if-match": workspace.etag } on updates and archival. Browser workspaces.list() includes reachable organizations and archived recovery entries.

API 6.11.0 pages workspace Activity like organization Activity: pass the previous page’s next_keyset_cursor to team.audit as keyset_cursor; has_more says whether another page follows. The numeric cursor and next_cursor of 6.6.0 still work, deprecated; never send both. team.auditExportCsv/auditExportJson and organizations.auditExportCsv/auditExportJson download the entries that match the same filters, with from and until required at most 366 days apart. Call the CSV methods with parseAs: "text". More than 10,000 entries or 20 MiB answer activity.export_too_large with max_entries and max_bytes; narrow the range. Each export is audited.