Documentation

Vacabee Partner API

One REST API for hotels, flights, airport transfers and eSIM data plans. You search, price and book from your own product; we handle suppliers and fulfilment. Today you are merchant of record and take the traveller's payment yourself, and each booking is charged against a prepaid account we keep for you.

Before you write any code

  1. 1Your Vacabee contact enables the API channel on your partner account and agrees your terms: which products you may sell, your net margins, your search allowance, and your deposit and credit limit. Your settlement mode is fixed per key at that point, not per booking.
  2. 2Sign in to the partner portal and create a test key. The secret is shown once, at creation, and never again.
  3. 3Build against the sandbox. It is free, never counts against your search quota and never reaches a real supplier — but its data is generated rather than real, so read sandbox before you treat a green run as a rehearsal.
  4. 4Verify your error handling against the error envelope and register a webhook endpoint.
  5. 5Pass certification, then create the live key yourself and swap it in. Until a certification has passed, creating a vcb_live_ key fails with 400 — and most of the catalogue is answered from sandbox traffic you were going to produce anyway, so read it before you build rather than after.

Base URLs

EnvironmentBase URLKey prefix
Livehttps://api.vacabee.comvcb_live_…
Sandboxhttps://sandbox-api.vacabee.comvcb_test_…

One host, both key kinds — for now

This page used to say that pinning a key to a host was not implemented. It is: the gateway refuses a key of the wrong kind with wrong_environment, and the refusal names the base URL to use instead of telling you to rotate a key that was never the problem. What is not in place yet is the separation it needs — today a single deployment accepts both kinds, so the check has nothing to refuse. Separate live and sandbox hosts are planned, and the check begins to bite the moment they are apart. Until then, keep your live and test credentials in separate configuration: a deployment wired with the wrong one will not fail on its first call.

Conventions

These hold for every endpoint, so they are worth reading once instead of rediscovering per resource.

  • Versioned path. Every partner route lives under /v1; the unauthenticated probes /health, /health/ready and /health/full sit at the root and are in the same OpenAPI document, so a gateway rule of “everything under /v1” locks them out. Additive changes land in /v1 without notice; breaking ones belong in a new major version. See errors and versioning.
  • Money. There is no money object. Every amount is a flat integer in the minor unit, in a field with a Cents suffix, alongside a sibling currency on the same object — "totalCents": 12950, "currency": "USD" is 129.50 USD. No floats anywhere, ever. Two exceptions worth handling: chargeCents on a cancellation policy and maxTotalCents on a flight order carry no currency of their own — they are in the currency of the rate or offer they belong to. And eSIM amounts arrive with a lower-case code ("usd") where the ledger uses upper case, so compare currencies case-insensitively.
  • Time. Timestamps are ISO 8601 in UTC, with one exception you must not parse as UTC: hotel cancellation deadlines — freeCancellationUntil and the from / until of a cancellation policy — are local time at the property and carry no offset. Travel dates are plain calendar dates.
  • Tracing. Every response carries X-Request-Id. Log it. It is the first thing support will ask for and the only way we can find your call in ours.
  • Pagination. Not uniform, so do not write one generic loop. GET /v1/esim/orders, GET /v1/account/ledger/entries and GET /v1/webhooks/deliveries are cursor-based — follow nextCursor until it is null, never construct one. GET /v1/esim/plans is page-based (page, pageSize, totalPages). The eSIM countries and regions and the webhook endpoint list return everything at once.
  • Writes. Every POST requires an Idempotency-Key — including the search endpoints, which create nothing. This is enforced, not advisory.
  • Cancellation policies. What you get is our structured reading of the supplier terms under our own field names: an array of { from, until, chargeCents } windows plus a derived refundable flag on a hotel rate, a single cancellationPolicy string on a confirmed hotel booking, and a conditions object on a flight offer. It is not the supplier's verbatim wording, so do not present it as the contractual text — and expect three shapes, not one.

Your first call

GET /v1/ping is public and takes no key at all. It answers one question — can I reach you — and answers it the same way whether your credentials are right, wrong or absent. Use it to separate a network problem from a key problem, never to test a key.

Shell — reachability
export VACABEE_API_URL="https://sandbox-api.vacabee.com"

curl -sS "$VACABEE_API_URL/v1/ping" -D -

GET /v1/account is the call that proves a key. It needs a valid one, and it answers in a single round trip who you are, which scopes the key carries, which settlement modes your account is enabled for and what your limits are.

Shell — key, scopes and limits
export VACABEE_API_KEY="vcb_test_…"

curl -sS "$VACABEE_API_URL/v1/account" \
  -H "Authorization: Bearer $VACABEE_API_KEY" \
  -D -

Read the response headers, not just the body. X-Request-Id is on everything, including /v1/ping. The quota headers — X-Search-Quota-Limit, -Remaining, -Reset and X-Search-Billable, which is spelled without the Quota — only appear on authenticated routes that got past authentication, so they are absent from /v1/ping and from a 401, 403 or 429 raised by a guard. That absence is normal, not a broken proxy.

Server side only

An API key is a bearer credential for your entire partner account. Never ship one in a browser bundle, a mobile app, or anything else a traveller can open. Calls originate from your backend.

How a search works

There is one shape per vertical, and there is no mode to choose. Every search call holds the connection until the supplier has answered, so size your client timeout in seconds and do not run one inside a handler that dies at ten.

VerticalThe callAfterwards
HotelsPOST /v1/hotels/searches blocks until rates are in and returns a searchId with hotels.Free: GET /v1/hotels/searches/{id} re-reads it. PATCH refines it and POST …/load-more extends it — both can be billable.
FlightsPOST /v1/flights/searches answers immediately with the complete set of offers.No session and nothing to poll. Re-search to refresh.
TransfersPOST /v1/transfers/searches answers immediately with the quotes.No session and nothing to poll. Re-search to refresh.

A hotel search can come back status: "partial" and a hit can carry a rateStatus that is not yet available; show what you have and re-read rather than blocking. Rate, offer and quote handles are valid for about twenty minutes — after that, booking one is 410 offer_expired and you search again.

Earlier versions of this page described a blocking mode selected with an await_rates field on the search body, and a streaming mode over a WebSocket at /v1/stream. Neither is real. There is no WebSocket endpoint, and await_rates is not a field you can send — the gateway sets it on the upstream call itself, and the request validator rejects unknown properties, so putting it in your body returns 400.

What is not built yet

Everything on these pages describes what the gateway does today. The following appear in contracts, schemas or earlier drafts of this documentation and do not exist — do not plan an integration around them:

  • WebSocket price streaming. /v1/stream does not exist and there is no WebSocket server. The maxWebsocketConnections and maxWebsocketSubscriptions values in GET /v1/account come from your terms and enforce nothing, and the stream:subscribe scope guards no route. Poll instead.
  • Vacabee as merchant of record. There is no per-booking settlement field, and checkoutUrl does not arrive on any booking response; every booking settles against your prepaid account, including on a key contracted for it. See settlement.
  • Most webhook event types. Four are delivered; the rest are declared and silent, including the low-balance and quota warnings. See webhooks.
  • Traveller records on our side. X-Partner-Customer-Ref is allowed through CORS and read by nothing, and there is no /v1/customers resource or deletion endpoint. Correlate on your own externalReference.
  • Invoices for bookings and searches. Bookings are drawn from your prepaid balance as they confirm; searches are metered but not billed. There is no settlement run and no invoice line for either today.
  • Deprecation signalling. No Deprecation or Sunset headers, no /v2, and no automated notification. Keep your parsing tolerant and watch the changelog.

Everything else

NextAuthenticationAPI keys, environments, scopes and IP allow lists.