Brightwild Booking TypeScript SDK

SDK 0.2.0-preview.6 implements wire contract 0.2.0-preview.2 of the Brightwild Booking API. It contains a typed server client, an in-memory simulator, the brightwild-booking CLI, an optional local MCP server, read-only agent tools, and a plain JavaScript reference app with a same-origin browser bridge. The client and OpenAPI document are generated from the API's router, and the client ignores response fields it does not know.

This page covers the SDK. The partner guide (dist/docs/guide.md in this package, also published on the developer site) explains the API itself: environments, authentication, the reservation flow, idempotency, version preconditions, errors, rate limits, and onboarding. Installing the SDK grants no API access; Brightwild issues each application its ID and key.

Install

Install the tarball Brightwild provides in your own project. It needs Node 22+ and its npm dependencies (Zod and oRPC). Pin the exact version, then run the packaged example:

npm install /path/to/brightwild-booking-contracts-0.2.0-preview.6.tgz
node node_modules/@brightwild/booking-contracts/starter/journey.mjs
node node_modules/@brightwild/booking-contracts/starter/partner-journey.mjs

The first example exercises discovery through simulated payment and confirmation; the second, a partner-settled application's reservation request, with no checkout or payment step. Both run without network calls and print only non-sensitive journey state. dist/openapi.json is the contract, generated from the runtime schemas. Copy the packaged dist/skill/building-booking-apps directory into your agent's skill collection if you build with a coding agent.

Server client

Import createBookingClient from @brightwild/booking-contracts/client in your server only. Give it the API base URL and your application ID and key:

import { BOOKING_API_ENVIRONMENTS } from "@brightwild/booking-contracts";
import {
  createBookingClient,
  newResourceId,
} from "@brightwild/booking-contracts/client";

const client = createBookingClient({
  baseUrl: BOOKING_API_ENVIRONMENTS.sandbox.url,
  applicationId: process.env.BOOKING_APPLICATION_ID!,
  applicationKey: process.env.BOOKING_APPLICATION_KEY!,
});
const listingId = "<a listingId from distribution.listing.list>";
const quote = await client.reservation.quote.create({
  quoteId: newResourceId(),
  listingId,
  checkIn: "2026-12-04",
  checkOut: "2026-12-07",
  party: { adults: 2, children: 0, infants: 0, pets: 0 },
});

BOOKING_API_ENVIRONMENTS names the production and sandbox base URLs.

The client exchanges the key at the API's token endpoint (POST /oauth/token, OAuth client_credentials) for an access token of at most 15 minutes, and caches the token until a minute before it expires; concurrent calls share one exchange. The key is sent only to the token endpoint, and each operation carries the token. When the API refuses a cached token (it was revoked or has expired), the client exchanges again and resends that call once: the API refuses an unauthenticated call before any operation runs, so no write repeats. A refused key is unauthenticated; a token endpoint outage is unavailable and retryable.

The client has one typed method per operation, nested by name: client.distribution.listing.search(input). Per-call options go in { context }: sessionCapability for checkout operations, idempotencyKey for a create whose caller cannot send IDs, and consumerKind. The client refuses insecure non-local origins and redirects, checks each input against the operation's schema before sending, and never retries a write that the API may have run. Failures throw BookingApiError with code, status, requestId, retryable and retryAfterSeconds.

Name each new quote, checkout and signal yourself with newResourceId() (a UUIDv7) and reuse it after a lost response: the API returns the same resource. Every checkout command sends the version of the checkout it last read (expectedVersion); precondition_failed means read the checkout again.

CLI

The package installs brightwild-booking. Run it from your project:

npx --no-install brightwild-booking --version
npx --no-install brightwild-booking describe
npx --no-install brightwild-booking doctor --simulation
npx --no-install brightwild-booking read distribution.listing.list --simulation --input '{"limit":5}'
npx --no-install brightwild-booking read distribution.listing.search --simulation --input '{"checkIn":"2027-11-16","checkOut":"2027-11-18","party":{"adults":1,"children":0,"infants":0,"pets":0}}'

Discovery supports the five operations listed by describe. Input and output use the same contract as the SDK; search excludes journey and consent fields. Results are the operation's JSON output; lists and live reads carry meta with asOf, completeness and definitionVersion. Errors are JSON on stderr: exit 2 for usage or configuration, exit 1 for failed requests. Requests time out after 30 seconds and are not retried, except once with a new access token when the API refused the old one. For 429 errors, preserve retryAfterSeconds; a failed search is not availability.

With an application ID and key in your trusted terminal or server environment, use doctor --api, then read distribution.site.get --api. doctor only checks configuration syntax; it does not verify network access, authorization or live readiness. API mode requires BOOKING_API_ORIGIN (the base URL), BOOKING_APPLICATION_ID and BOOKING_APPLICATION_KEY; it exchanges the key for an access token first. Do not share a production key among team members or put keys in commands, prompts or Git.

An authorized operator can read a single owned checkout with status --api --input '{"checkoutId":"<UUID>"}', additionally supplying the existing BOOKING_SESSION_CAPABILITY through the trusted environment. It does not recover missing sessions, poll automatically or submit a reservation. No CLI command creates quotes, checkouts, payments or reservations.

Local MCP server

Install the optional MCP transport beside the SDK:

npm install --save-exact @modelcontextprotocol/server@2.0.0

Configure a local MCP-capable agent host with this stdio server. Replace the absolute executable path with the bin installed in your project:

{
  "mcpServers": {
    "brightwild-booking": {
      "command": "/absolute/path/to/project/node_modules/.bin/brightwild-booking",
      "args": ["mcp", "--simulation", "--tools", "distribution.site.get,distribution.listing.list,distribution.listing.get,distribution.listing.search,inventory.calendar.get"]
    }
  }
}

Tools are named by operation, such as distribution_listing_search. Choose only the tools needed for the task. Switching to --api requires the same trusted environment as the CLI. This is a local stdio connection using one application's grant, not a shared HTTP endpoint. Credentials are never tool arguments, and each request still passes the API's authorization, publication and quota checks. There are no quote, checkout, status, payment, signal or private guest tools, and model instructions cannot grant them. Documentation can explain a command without granting permission to execute it.

Agent discovery tools

Runtime agents can use the same public discovery operations through a server client:

import { createBookingClient } from "@brightwild/booking-contracts/client";
import { createBookingReadTools } from "@brightwild/booking-contracts/tools";

const client = createBookingClient({
  baseUrl: process.env.BOOKING_API_ORIGIN!,
  applicationId: process.env.BOOKING_APPLICATION_ID!,
  applicationKey: process.env.BOOKING_APPLICATION_KEY!,
});
const tools = createBookingReadTools(client, [
  "distribution.listing.search",
  "distribution.listing.get",
]);

Each tool has name, description, inputSchema and execute(input); adapt those to your agent runtime. Selection is explicit, and only distribution.site.get, distribution.listing.list, distribution.listing.get, distribution.listing.search and inventory.calendar.get can be selected. A tool releases only the operation's declared fields, with their freshness metadata, so simulated or stale information cannot pass as a confirmed reservation. Agents cannot supply credentials, account IDs or session tokens through tool arguments, and the search schema excludes journey and consent fields, so a model cannot fabricate consent. These tools do not expose quotes, checkout, payment, signals or guest data.

Reference app

The package includes a complete plain JavaScript reference app: search, quote, guest details, hosted payment setup, promotion codes, explicit acceptance, pending, recovery and confirmation. It has no build step. Copy starter/ into your own project to customize it. The local runner uses HTTPS so its secure session cookies behave like a deployment:

mkdir -p .local-booking-tls
openssl req -x509 -newkey rsa:2048 -nodes -days 2 \
  -keyout .local-booking-tls/key.pem -out .local-booking-tls/cert.pem \
  -subj '/CN=localhost' -addext 'subjectAltName=DNS:localhost,IP:127.0.0.1'
chmod 600 .local-booking-tls/key.pem
BOOKING_TLS_KEY=.local-booking-tls/key.pem \
BOOKING_TLS_CERT=.local-booking-tls/cert.pem \
node node_modules/@brightwild/booking-contracts/starter/server.mjs --simulation

Open https://localhost:3443; use your browser's development-certificate exception or a locally trusted certificate. Keep .local-booking-tls/ out of Git. Use synthetic guest details in simulation. Set BOOKING_SIMULATION_SCENARIO=failed, uncertain or requires_action to inspect those states. Explicit submit schedules a simulated worker; polling never creates or advances a reservation. Simulation is in memory, resets with its server process, and its interface is deliberately non-indexable.

For the API, use --api and server-only BOOKING_API_ORIGIN, BOOKING_APPLICATION_ID and BOOKING_APPLICATION_KEY; the runner serves the BOOKING_SITE_ORIGIN you set. The loopback runner is a local reference: deploy createStarterHandler({ client, siteOrigin }) or createBookingBridge (imports: @brightwild/booking-contracts/starter/server and /starter/bridge) in your own hosting adapter with your approved public origin and TLS. Do not include the simulator in a live handler. Brightwild must enable reservation submission for your application before live checkouts work.

Browser bridge

starter/bridge.mjs is a small same-origin Web Request/Response adapter between your pages and the server client. Mount it at /api/booking/<operation> (for example /api/booking/reservation.checkout.get; your route must accept the dots in the name), and inject the server client and the exact HTTPS site origin. Browser POST requests must carry the matching Origin, and all requests must be same-origin. Checkout session capabilities live in HttpOnly, Secure, SameSite cookies: the bridge removes the capability from the checkout-creation JSON and adds it from the cookie to later checkout calls. Do not log request bodies, authorization, session cookies or payment setup secrets. Apply your own rate limits as well as the API's quotas before exposing the bridge publicly.

Card payment in the browser

For a card-paying application, use hosted card fields only when paymentSetup.kind is stripe. The simulator returns simulation and must never render a card-entry form.

Building an application

Entity Consumer rule
Site Read distribution.site.get; its capabilities and environment decide which flows to offer. Your deployment does not own a second inventory.
Listing Use canonical listingIds and the API's content; do not clone property facts or keep your own price list. Display content is not availability.
Listing publication Placement, visibility, order, slug and collection come from the API. Respect indexable.
Quote and checkout The API owns price, expiry and terms; your app collects explicit human acceptance.
Application grant Allowed origin, environment, capabilities, catalog and quotas are set by Brightwild; installing the SDK, CLI or MCP cannot expand them.

Display the time zone the API returns; dates are property-local. Render safe error text with the request ID and respect retry delays. Every simulation screen must say it is a simulation and stay non-indexable.

Journey analytics

First-party journey context on search and checkout and the bounded cdp.signal.capture operation preserve anonymous and campaign attribution in Brightwild's first-party measurement. Use consent: "not_required" for ordinary Brightwild measurement; this is not a marketing permission. Omit it or use consent: "denied" to avoid optional visitor and campaign capture. Operational search observations remain first-party, with anonymous identifiers omitted. Event and visitor IDs are namespaced per application and never grant checkout access. Send signals only for actual user actions. Do not send guest details, card data, arbitrary event names or reservation success claims. There is no marketing subscription endpoint.

Use the packaged starter/public/journey-analytics.mjs helper to keep stable event payloads and IDs through retry and reload, serialize observations, and clear its storage on an explicit collection denial. First-party visitor IDs may persist across visits with not_required; an explicit denied choice disables optional capture. The reference app has no marketing consent module or external marketing destinations. Enable a marketing module only deliberately on a surface that needs it, default its visitor choice to off, and require opt-in before sharing with Google, Meta, GTM or a marketer. That choice must not disable Brightwild's first-party measurement or imply payment or marketing-subscription consent. Keep checkout, session and payment credentials and guest fields out of these payloads.

Signal Meaning and enrichment
search_submitted Actual search action, with optional exact date, party and collection search selection. Emit before the search request so failures are still observed.
search_viewed Results rendered; pass the response's searchObservationId. Brightwild validates tenant, origin and namespaced visitor and derives outcome, complete-result count, coverage and single-currency price distribution from its retained search. Counts describe the complete result set, not a paginated page or a custom reorder.
listing_viewed Listing content rendered; pass listingId, optionally the current search. Fetching listing JSON alone is not a view.
quote_viewed Price and terms rendered; pass quoteId. Brightwild supplies the delivered selection and price. Quote generation alone is not a view.
reserve_clicked Actual Reserve action; pass its quoteId. Emit before checkout creation.
checkout_started Guest-details UI opened after successful checkout creation or resumption. Pass checkoutId.
details_completed Guest save succeeded; the owned checkout must contain completed details.
payment_submitted Submit accepted, with a recorded submission or a pending, uncertain or confirmed state; a later failure does not erase the accepted submission. This is an observation, not evidence of collected funds.
confirmation_viewed Confirmation rendered after the confirmed state. It measures reach of the success page; reservation totals come from Brightwild's records.

Checkout signals require checkoutId and the existing session; the bridge adds it from the HttpOnly cookie. Never place a token in signal JSON. Brightwild validates state and derives quote, checkout and listing joins rather than accepting browser-supplied prices or success claims. Use one stable logical key per checkout transition and quote view; each intentional search or Reserve action is a separate event. There is no generic Generated or AI recommendation event.

journey and signals accept safe route families (home, search, listing, checkout, confirmation), origin-only referrerOrigin, deviceClass, and UUID correlationId. Signals optionally retain a client occurredAt within the last 24 hours and five minutes of future clock skew. Raw URLs, query strings, experiments, personal data and arbitrary properties are rejected. UTMs and supported click IDs follow the bounded campaign schema. Full search result arrays and raw coupon values stay in Brightwild's governed records; analytics destinations receive bounded fields and hashed joins.

The bridge reports web consumer provenance; a staff-only host can configure consumerKind: "staff". The CLI reports cli; read tools and MCP report agent. These are measurement provenance, not verified identity or authorization. Agent, CLI and staff signals cannot populate the human funnel, delegated agent searches are labelled as such, and test, staff and bot demand is excluded from human-intent measurement. Every search response carries its searchObservationId (null when none was recorded). Bot classification is a bounded user-agent heuristic, not attestation. accepted: true on a signal proves first-party intake, not delivery to any analytics destination.

Simulator

createBookingSimulator() (from @brightwild/booking-contracts/simulator) is an in-memory implementation of the contract for tests and local development; pass its fetch to createBookingClient. It serves the sandbox's fictional listings and prices, its guest-email outcomes (SIMULATOR_SCENARIOS; see the partner guide's sandbox section), the API's quotas, party rules and check order, and a token endpoint that issues 15-minute tokens with the API's claims. Its concurrency and replay checks prove client and HTTP behavior, not distributed idempotency or payment readiness.

Payment setup is ready in its own response, and a submitted checkout stays pending until await simulator.settle(checkoutId, outcome?), a test-driver action rather than an API endpoint: it settles as outcome (confirmed, failed, uncertain or requires-action) or as the guest's email chose. It returns a promise. GET requests cannot advance a checkout. Options configure the rest: applications (each with a profile of settlement: "card" or "partner"), timing (setup and settlement after delays, as the hosted sandbox runs), bookedBlocks (the sandbox's fixed booked nights, isBookedNight), imageBaseUrl (where a host serves the listings' photos; without it listings have none), store (any async key-value store; memory by default), origin and now.

Simulated reservations produce the API's partner webhook events (partnerWebhookEventSchema; see the partner guide's Webhooks section): pass webhooks: ({ applicationId, events }) => … to receive them in process, once the change each reports is stored; reservation.event.list replays them from then on. settle() sends checkout.resolved (and reservation.confirmed when confirmed), and two more test-driver actions act as Brightwild's reservations team: await simulator.cancelReservation(reference) sends reservation.cancelled, and await simulator.changeReservation(reference, { checkIn, checkOut, party }) sends reservation.changed. reservation.reservation.get reads the reservation as it then is. The +cancelled and +changed guest emails make the same change 5 seconds after confirmation, applied on the next read.

Documentation and versions

The release includes dist/docs/, the developer site: the partner guide (index.html, guide.md), the API reference (reference.html, rendered from reference.json by a pinned Scalar bundle), the changelog, this SDK guide (sdk.html, sdk.md), the operation and error reference (reference.md), openapi.json, llms.txt, the agent skill and a release manifest. Serve it with any static HTTP host. The reference page never executes API requests and has no credential entry or AI chat; the guides need no JavaScript.

Each release manifest records the SDK version, wire version and a schema digest. Within a wire version the API changes only additively; a breaking change ships as a new wire version with a new SDK version. Keep installed artifacts immutable, update the SDK for tooling changes, and follow the changelog when the wire contract changes.