Integrating

Sandbox

A separate host with its own keys, where every call is free, nothing reaches a supplier, and nothing is ever billed. Build here first.

Getting on it

Base URL$PARTNER_API_SANDBOX_URL
Keysvcb_test_…
CostFree, at any volume
Search quotaNever counted
SuppliersNever contacted

Create a test key in the portal. Test keys are refused on the live host and live keys on the sandbox host, so the two environments cannot be crossed by accident.

What the data is

The sandbox is not a set of fixtures somebody wrote once and forgot. It is deliberately close to production in the places where being wrong would cost you a rewrite:

  • Hotels. Searches return a fixed catalogue of properties from our own data, priced from a snapshot of a real test property that we refresh every hour. Real prices, real currencies, real cancellation policy text — no supplier call. Bookings land on a supplier test property that is never actually reserved.
  • Flights. The supplier's own test mode, with test credentials. Offer availability and stability follow that supplier's rules rather than ours.
  • eSIM. A fixed plan inventory. Orders complete and produce a profile, but nothing is provisioned on a real network.

Close, not identical

Sandbox results are stable by design, so do not use them to test how your UI behaves when a property sells out, and do not use the sandbox for load testing. Both are best done in a conversation with us rather than against the environment.

Forcing failures

The paths that break in production are the ones a happy-path integration never exercises. Set X-Partner-Customer-Ref to one of six reserved values and the sandbox will produce that failure on demand, deterministically.

Shell
curl -sS "$PARTNER_API_SANDBOX_URL/v1/hotels/bookings" \
  -H "Authorization: Bearer $VACABEE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "X-Partner-Customer-Ref: sbx_price_changed" \
  -H "Content-Type: application/json" \
  -d '{ "rateId": "rate_…", "settlement": "partner" }'
TriggerWhat happensWhat you must handle
sbx_offer_expiredThe offer or rate handle is treated as expired.Re-run the search and re-price rather than showing a dead offer.
sbx_payment_failedThe payment step fails.Mode B: return the traveller to checkout. Mode A: your own payment retry.
sbx_supplier_errorThe supplier returns an error.Fail cleanly, release your own reservation, do not retry blindly.
sbx_price_changed409 price_changed with the new price.Show the new price and ask, or accept within your own tolerance.
sbx_insufficient_funds402 before the supplier is contacted.Mode A only: alert, top up, and keep the traveller informed.
sbx_slowThe response is deliberately slow.Your timeout and retry path — with the same idempotency key.

sbx_slow together with idempotency is the pairing worth spending real time on: it is the exact situation — a timed-out booking that may or may not have succeeded — that produces duplicate reservations in integrations that skipped it.

Before you switch to live

  • All six triggers produce a defined outcome in your system, not an unhandled exception.
  • A booking retried after a timeout, with the same idempotency key, results in exactly one booking.
  • Your webhook endpoint verifies signatures, rejects stale timestamps and survives the same event twice.
  • You store X-Request-Id for every call you make.
  • Supplier cancellation policy text is shown to the traveller verbatim.
  • Your keys live in a secret manager, never in the repository or a client bundle.

Then swap the host and the key. Nothing else in your integration changes.

NextErrors and versioningThe error envelope, retry rules and how /v1 evolves.