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.
- Call Stripe's hosted confirmation flow, then send only
setupReference, the verifiedpaymentMethodId(pm_…) andconsentAccepted: truetoreservation.checkout.verifyPayment. Consent is required; payment capture is not a marketing opt-in. Card number, CVC, expiry and billing fields never pass through the bridge or the API. See Stripe card setup. - Read the existing checkout before confirming the card; proceed only for the
same quote in
needs_payment. Reconcile the exact SetupIntent before retries or after reload, reusing its succeeded method instead of confirming it again (starter/public/stripe-payment.mjs). reservation.checkout.setupPaymentis asynchronous: read the checkout untilpaymentSetup.stateleavesrequested, asrequestPaymentSetupinstarter/public/checkout-payment.mjsdoes.- Present payment authorization and current terms before one Confirm
reservation action. In that action, verify payment and then submit the
same accepted quote; render pending until confirmation arrives. If a
response is lost, read the same checkout:
readycan submit without repeating the card step, while pending, uncertain or confirmed must recover without a new checkout. - A quote or terms change requires fresh acceptance. Expiry removes stale
payment and acceptance controls (
starter/public/checkout-expiry.mjs). A completed card setup alone does not confirm a reservation.
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.