CreateBooking
const url = 'https://example.com/wink.partner.v1.Booking/CreateBooking';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"idempotencyKey":"example","bookingOriginUrl":"example","rooms":[{"propertyId":"example","checkIn":"example","checkOut":"example","roomRateId":"example","bedroomConfigurationId":"example","occupancy":{"adults":1,"children":[{"age":1,"quantity":1}],"quantity":1},"promotionalCodes":["example"],"ancillaries":[{"inventoryId":"example","channelInventoryId":"example","offerId":"example","type":"INVENTORY_SCOPE_UNSPECIFIED","quantity":1,"requestedStart":"example","requestedEnd":"example"}],"specialRequests":"example","guest":{"firstName":"example","lastName":"example","email":"example","phone":"example"},"externalBookingCode":"example","externalTransactionId":"example","externalCustomerId":"example","externalFees":[{"feeId":"example","amount":"example","type":"example","description":"example"}]}],"currencyCode":"example","languageCode":"example","traceId":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/wink.partner.v1.Booking/CreateBooking \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "idempotencyKey": "example", "bookingOriginUrl": "example", "rooms": [ { "propertyId": "example", "checkIn": "example", "checkOut": "example", "roomRateId": "example", "bedroomConfigurationId": "example", "occupancy": { "adults": 1, "children": [ { "age": 1, "quantity": 1 } ], "quantity": 1 }, "promotionalCodes": [ "example" ], "ancillaries": [ { "inventoryId": "example", "channelInventoryId": "example", "offerId": "example", "type": "INVENTORY_SCOPE_UNSPECIFIED", "quantity": 1, "requestedStart": "example", "requestedEnd": "example" } ], "specialRequests": "example", "guest": { "firstName": "example", "lastName": "example", "email": "example", "phone": "example" }, "externalBookingCode": "example", "externalTransactionId": "example", "externalCustomerId": "example", "externalFees": [ { "feeId": "example", "amount": "example", "type": "example", "description": "example" } ] } ], "currencyCode": "example", "languageCode": "example", "traceId": "example" }'Create a booking on behalf of the agency
Books inventory against the agency’s balance and returns the resulting confirmations. This is the one rpc that MOVES MONEY on the way in.
IDEMPOTENT ON idempotency_key. gRPC clients retry on UNAVAILABLE by default and a retried create is
a double booking and a double debit, so the key is REQUIRED rather than optional. Replaying a key
returns the ORIGINAL confirmations without booking again. Reuse a key only to retry the same intent —
a new booking needs a new key, and sending a used key with a different payload is FAILED_PRECONDITION
rather than a silent no-op, because the alternative is an agent believing they booked something they
did not.
A multi-room create produces SEVERAL bookings sharing a group_id, which is what makes them
refundable together via RefundBookings.
Returns INVALID_ARGUMENT when rooms is empty, the request is malformed or idempotency_key is
blank, FAILED_PRECONDITION
when the key was used for a different request, and PERMISSION_DENIED when the account is not a
travel agent or lacks BOOKING:WRITE.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”Request for CreateBooking.
Shaped from the domain’s CreateAgentBookingRequest rather than from what a single-room booking looks
like. rooms is REPEATED because the domain takes a list and because that is how a group booking is
made — the group_id that RefundBookings operates on exists precisely because one create can produce
several bookings. A one-room booking is a one-element list.
object
Idempotency key. REQUIRED — see the rpc comment for why this is not optional on a money-moving call. Any opaque string up to 128 characters that is unique per booking intent; a UUID is the obvious choice.
Which of the agency’s sites or apps the booking originated on, as an absolute URL.
REQUIRED, and an explicit field rather than something sniffed from a header. Over REST this was read off the Origin/Host of the inbound request, which gRPC has no equivalent of — and which quietly recorded whatever proxy happened to terminate the call. Stating it makes the value mean what it always claimed to.
What to book. At least one room; each entry may name a different property, so a multi-property itinerary is one call.
One room to book.
Field-for-field from AgentBookingRequest, minus nights (derivable from the dates, and a second source
of truth for the same fact) and vendor_specific (a vendor-keyed metadata map, excluded for the same
reason its outbound twin is).
object
The property to book at. From a Search or Inventory result.
Local dates at the property, ISO-8601 (YYYY-MM-DD).
The rate to book. From a pricing result’s RoomRate.
Which bedroom layout, when the rate offers more than one.
Who is in the room, and how many identical rooms of it. Child AGES are required rather than a count: properties price children in age bands, and an unpriceable age produces a rate that cannot be booked.
object
Adults in this room. Defaults to 1.
Children in this room, by age. Ages are REQUIRED, not optional detail: properties price children in age bands, and an unpriceable age produces a rate the guest cannot actually book.
A number of children of the same age.
object
Age at check-in, in years. Zero is valid — infants are priced differently from older children, not ignored.
How many children of this age. Defaults to 1.
How many identical rooms of this configuration. Defaults to 1.
Promotional codes to apply. Silently ignored when they do not apply to this rate, which is the same behaviour the booking engine has.
Extras to add to this room — spa treatments, restaurant sittings, activities, add-ons, and the in-room ancillaries sold with the rate itself.
One extra to add to a room.
Every field is quoted back from something the Inventory surface already handed you — there is nothing to construct and nothing to look up. Where you read them from depends on which kind of extra it is:
PROPERTY-LEVEL (restaurant, spa, meeting room, activity, attraction, place, add-on) inventory_id AncillaryInventory.inventory_id — the restaurant or spa itself channel_inventory_id AncillaryInventory.channel_inventory_identifier offer_id AncillaryOffer.id — the sitting, the 1-hour massage type AncillaryInventory.type
IN-ROOM (INVENTORY_SCOPE_ROOM_TYPE_ANCILLARY) inventory_id RoomRate.room_rate_id — the RATE, not a room type channel_inventory_id leave blank — this is the common case and not an error offer_id RoomRate.room_type_ancillaries[].id type INVENTORY_SCOPE_ROOM_TYPE_ANCILLARY
The in-room case is the one to read twice: its inventory_id is a RATE because the gate for these is
keyed by the master rate rather than by an entity. Sending a room type id there matches nothing, and it
does so SILENTLY rather than failing.
object
The thing being sold from — see the table above. Never a room type.
Your channel’s gate onto it. Empty when the item reached you through Wink Network rather than one of your own sales channels, which is the common case and not an error.
The priced row you are buying.
Which kind of extra this is. INVENTORY_SCOPE_ANNOUNCEMENT is informational only and is rejected here.
How many. Bounded by the offer’s min_pax and max_pax when it sets them; defaults to 1.
OPTIONAL, and only meaningful for appointment-style extras — a spa slot, a restaurant sitting, an
activity departure — or where the offer’s pricing_type is per hour or per day and the span is what
is being priced.
In-room extras and add-ons OMIT them: they are bundled with the stay, and sending times makes the platform treat them as appointments and issue calendar invites nobody asked for.
These are REQUESTED, never confirmed. The property is not committing to them by accepting the booking. ISO-8601 local date-time at the property.
Free text for the property.
Who is staying in this room.
object
The agency’s own references, carried onto the booking and echoed back on reporting. These are the reason an agent booking is its own request type: an agency reconciling Wink against its own system needs its identifiers to survive the round trip.
Fees the agency is adding on top of the rate. Amounts are in MINOR UNITS of display_currency.
A fee the agency is adding on top of the rate.
object
MINOR UNITS of the enclosing request’s display_currency.
ISO 4217 currency to quote totals back in, and ISO 639-1 language for anything human-readable. BOTH ARE REQUIRED — the domain rejects a blank either way, so omitting one is INVALID_ARGUMENT.
CAREFUL: this is NOT the same currency_code as AgentBooking’s, despite the shared name. This one is
what the agency wants to SEE; that one is what the property SETTLES in, and the two differ whenever a
booking is sold in one currency and paid out in another. Every monetary field on the response is in
the settlement currency regardless of what is asked for here — see AgentBooking.currency_code.
The agency’s own correlation id for this attempt, carried into the platform’s logs. Distinct from
idempotency_key: this one identifies the REQUEST for tracing, that one identifies the INTENT for
deduplication, and reusing a trace id is harmless where reusing a key is not.
Responses
Section titled “Responses”The RPC completed with gRPC status OK.
Response for CreateBooking.
object
One booking per room requested, in the order requested. A multi-room create produces several bookings
sharing a group_id — that is what makes them refundable together.
Repeated rather than singular because the domain returns BookingConfirmations, which is a LIST. A single-booking response would have quietly dropped every room after the first.
One booking, as the agency that sold it needs to see it.
A deliberate projection, not a mirror of the internal model — see the note at the top of this file for what is excluded and why.
object
Wink’s identifier for this booking.
The human-facing reference shown to the traveller, e.g. on the confirmation email.
Present when this booking was made as part of a group; RefundBookings operates on this value.
Where the booking is in its lifecycle.
LIFECYCLE ONLY — this does NOT report refunds. BOOKING_STATUS_PARTIALLY_REFUNDED and
BOOKING_STATUS_FULLY_REFUNDED are retained for bookings written before the lifecycle and refund
axes were separated, and are never emitted for anything refunded after that. They were always
lossy: a refund settling used to overwrite a cancellation here, so only one of the two facts
survived. Read refund (field 14) for whether money came back and how much; a refunded booking
that was not cancelled now correctly reports BOOKING_STATUS_PAYMENT_SUCCEEDED here.
How the booking came to exist.
When the booking was created. ISO-8601 instant, UTC.
ISO 4217 currency every monetary field on this message is denominated in. This is the SOURCE currency
— the one the property settles in — which is not necessarily the one the traveller was shown. See the
note on net_total_sales_amount.
The property and stay.
object
A postal address.
object
Street line 1.
Street line 2, when the property supplies one.
State, province or region.
Postal or ZIP code.
City name.
ISO 3166-1 alpha-2 country code, e.g. “TH”.
Country name in the requested language.
The full address pre-formatted for display, newline-free.
Direct contact for the property, for when the agency needs to call about a booking.
IANA zone, e.g. “Asia/Bangkok”. Check-in and check-out below are LOCAL to the property, so without this an agency cannot tell whether a same-day cancellation is still same-day.
The stay itself.
object
Local dates at the property, ISO-8601 (YYYY-MM-DD).
Nights between them, precomputed so nobody has to reimplement the date arithmetic.
What was booked.
How many rooms of the above, and who is in each.
One entry per child, carrying that child’s age at check-in. Ages drive pricing, so an empty list means no children rather than unknown ages.
Who booked it, and who is staying. Frequently the same person, and deliberately still two fields: an agency books on behalf of a traveller far more often than not.
object
A person on the booking. Deliberately not the platform’s user model: an agency needs to contact somebody about a stay, not to know their account.
object
What the agency earns and what the sale was worth.
object
Net sale value in currency_code, summed across SALE-type beneficiaries.
The SOURCE currency, meaning the one the property settles in. A booking sold in one currency and settled in another carries an exchange spread, and that spread is intentional platform behaviour — do not reconcile this against what the traveller was charged and expect equality.
Net amount attributable to the booking after fees, in currency_code.
The agency’s commission on this booking, as a percentage at booking time. Percentages rather than amounts because that is how they are agreed and how they survive a partial refund.
The agency’s own agency-fee percentage at booking time.
Commission percentage restated after any refund. Equal to commission_percent until something is
refunded, and the value to reconcile against once something is.
Present once cancelled.
object
ISO-8601 instant, UTC.
Free text supplied by whoever cancelled.
Who cancelled. An agency cancelling on the traveller’s behalf and a property cancelling on the agency are different conversations.
The classification the cancellation was filed under, which is not the same thing as reason: this is
the closed list the platform reports on, that is what a human typed.
Present once any refund exists, whether from a cancellation or an immediate refund.
object
Amount returned, in currency_code minor units. This is the SOURCE amount — what the property
settles — matching every other monetary field on this message.
When the refund was requested, and when it actually settled. processed_at is empty while the status
is PENDING or REQUIRES_ACTION, which is the honest answer: it has not been processed yet.
Where a refund has got to. Mirrors the domain’s BookingRefundStatus.
REFUND_STATUS_PENDING: Requested and settling. NOT a failure, and NOT to be retried — retrying a PENDING refund is how you refund twice.REFUND_STATUS_CANCELED: Spelled with one L, matching the payment processor and the domain enum.REFUND_STATUS_REQUIRES_ACTION: Needs intervention before it can settle; not terminal.
Why it was issued, from the closed list, plus whatever free text accompanied it.
Processor-hosted receipt for the traveller, when one exists. Published because an agency fielding “where is my money” needs something to forward; the processor’s refund id and fee do not appear here for the same reason no other payment plumbing does.
Free text captured at booking time.
Charges that apply outside the room rate — early check-in, late check-out and anything the property
adds. has_additional_charges is the cheap check; additional_charges_summary is the human-readable
detail when it is true.
How the traveller paid.
The white-label configuration this booking was made through. The id only — the configuration itself is the agency’s own setup and is managed elsewhere, not published here.
Status changes in order, oldest first. Enough to answer “when was this cancelled” without a support ticket.
One lifecycle transition.
object
Where a booking is in its lifecycle.
Mirrors the domain enum exactly. PAYMENT_SUCCEEDED is the “booked and paid” state — there is no
separate CONFIRMED, and no COMPLETED: a stay that has happened is still PAYMENT_SUCCEEDED.
BOOKING_STATUS_INITIALIZED: In flight. Never returned by this API — see GetBooking.BOOKING_STATUS_TEST_PAYMENT_SUCCEEDED: Paid against a test payment method. Returned so an agency can tell its own test bookings apart from real ones rather than discovering the difference in a reconciliation.BOOKING_STATUS_PAYMENT_SUCCEEDED: Booked and paid. The normal terminal state for a stay that goes ahead.BOOKING_STATUS_FAILED: Never returned: failed bookings are excluded from every response on this service.
True when this response replayed an earlier call rather than creating anything. Nothing was charged.
Example
{ "bookings": [ { "status": "BOOKING_STATUS_UNSPECIFIED", "creation": "BOOKING_CREATION_TYPE_UNSPECIFIED", "cancellation": { "source": "CANCELLATION_SOURCE_UNSPECIFIED", "type": "CANCELLATION_TYPE_UNSPECIFIED" }, "refund": { "status": "REFUND_STATUS_UNSPECIFIED", "reason": "REFUND_REASON_UNSPECIFIED" }, "paymentType": "PAYMENT_TYPE_UNSPECIFIED", "statusHistory": [ { "status": "BOOKING_STATUS_UNSPECIFIED" } ] } ]}