API reference
Schemas
The 83 objects the requests and responses of the API are built from. Every type named on an operation page resolves to an entry here.
All schemas
AccountDtoAccountKeyDtoAccountLimitsDtoAccountPartnerDtoApiErrorDetailDtoApiErrorDtoCancelBookingDtoConfirmFlightChangeDtoContactDtoCreatedWebhookEndpointDtoCreateEsimOrderDtoCreateFlightOrderDtoCreateHotelBookingDtoCreateTransferOrderDtoCreateWebhookEndpointDtoDependencyStatusDtoEsimCheckoutDtoEsimCountryDtoEsimCustomerDtoEsimOrderDtoEsimOrderPageDtoEsimPlanDtoEsimPlanPageDtoEsimRegionDtoEsimUsageDtoFlightCancellationDtoFlightCancellationQuoteDtoFlightChangeDtoFlightChangeOptionDtoFlightChangeOptionsDtoFlightChangeOptionsRequestDtoFlightOfferDtoFlightOrderDtoFlightOrderPageDtoFlightOrderSummaryDtoFlightSearchDtoFlightSearchRequestDtoFlightSliceDtoFullHealthDtoGuestDtoHotelBookingDtoHotelBookingPageDtoHotelCancellationPolicyDtoHotelDestinationDtoHotelHitDtoHotelPriceFromDtoHotelRateDetailDtoHotelRatesDtoHotelSearchDtoHotelSearchPatchDtoHotelSearchRequestDtoLedgerBalanceDtoLedgerEntryDtoLedgerEntryPageDtoLoadMoreDtoPatchWebhookEndpointDtoPingDtoRotateWebhookSecretDtoSearchQuotaDtoSearchTransfersDtoTerseHealthDtoTransferCancellationQuoteDtoTransferHolderDtoTransferOfferDtoTransferOrderDtoTransferOrderPageDtoTransferPlaceDtoTransferSearchResultDtoTransferSettlementDtoTravellerDtoUpstreamStatusDtoWebhookAttemptDtoWebhookBookingDataDtoWebhookCheckoutDataDtoWebhookDeliveryDetailDtoWebhookDeliveryDtoWebhookDeliveryHeadersDtoWebhookDeliveryListDtoWebhookEndpointDtoWebhookEsimDataDtoWebhookEventDtoWebhookRetryResultDtoWebhookTestResultDtoA field whose own shape is another named object stops at that name and links here rather than nesting further — that is what keeps a table readable instead of unrolling a whole booking into one column of dotted paths.
AccountDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
partner | AccountPartnerDto | Required | — | |
partner.id | string | Required | Our identifier for the partner. Stable. | 1e4861d9-af96-4821-b264-c1670dbd4b2b |
partner.slug | string | Required | Short name of the partner, as assigned in the portal. | whitelabeltravel |
partner.name | string | Optional | Display name of the partner. | Whitelabel Travel |
key | AccountKeyDto | Required | — | |
key.prefix | string | Required | The visible part of the key this request came in with. It is shown in the portal as well — so you can tell which of several keys is currently talking. | vcb_test_A8xWTLai |
key.environment | string | Required | Environment of this key. The gateway compares it against the kind of key the deployment is configured to accept and refuses a mismatch with wrong_environment — but today a single deployment accepts both kinds, so that refusal does not fire yet. It starts to refuse once the two hosts are separate deployments; until then, matching the key prefix against the base URL is on the caller (the SDK derives the host from the prefix by itself).One of: LIVE TEST | TEST |
key.sandbox | boolean | Required | Whether this call lands on the sandbox: no suppliers, no costs, no search quota. | true |
key.settlement | string | Required | How THIS key settles — who takes the traveller's money for anything bought with it.
partner: you are merchant of record. Prices you see are net, your account is charged, your traveller pays you.
vacabee: we are merchant of record. Prices you see are end-customer prices, your traveller pays us, and you earn a commission.
The mode follows the key, not the request: a key does one of the two and never both. If you run both modes you hold two keys — which is also what makes it unambiguous which price a search result carries. Sending settlement in a booking body is allowed and is checked against this value; it cannot change it.One of: partner vacabee | partner |
settlementModes | string[] | Required | Settlement modes your ACCOUNT is set up for — the menu, not the choice. What this particular key does is key.settlement, and it is exactly one of these.
A mode listed here that is not your key's is reachable by issuing a key for it, not by asking for it in a request body: a body field that decides who pays would be a permission you write for yourself, and the price it applies to was already formed during the search. | ["partner","vacabee"] |
scopes | string[] | Required | What this key may do. A call outside this list is refused with insufficient_scope and names the missing scope.One of: account:read hotels:search hotels:book flights:search flights:book transfers:search transfers:book esim:read esim:order ledger:read webhooks:manage customers:manage | ["account:read","hotels:search","hotels:book"] |
limits | AccountLimitsDto | Required | — | |
limits.requestsPerMinute | number | Required | Requests per minute across all classes, unless limited more tightly. | 600 |
limits.rateLimitClasses | map<string, number> | Required | Upper bound per class and minute. A search counts differently from a read, because it costs money at the supplier. | {"READ":1200,"DEFAULT":600,"SEARCH":120,"BOOKING":60} |
limits.freeSearchesPerDay | number | Required | Billable searches per day that cost nothing. Only calls that actually reach a supplier are counted. | 1000 |
limits.searchHardCapPerDay | number | null | Required | Hard upper bound on searches per day, or null for none. Do NOT confuse it with the free quota: beyond that one you are billed, here you are refused. | null |
limits.maxWebsocketConnections | number | Required | Concurrent WebSocket connections for the streaming channel. | 20 |
limits.maxWebsocketSubscriptions | number | Required | Concurrent subscriptions across all WebSocket connections. | 200 |
ipAllowlist | string[] | Required | IP addresses and networks this key may be used from. Empty means no restriction. | [] |
AccountKeyDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
prefix | string | Required | The visible part of the key this request came in with. It is shown in the portal as well — so you can tell which of several keys is currently talking. | vcb_test_A8xWTLai |
environment | string | Required | Environment of this key. The gateway compares it against the kind of key the deployment is configured to accept and refuses a mismatch with wrong_environment — but today a single deployment accepts both kinds, so that refusal does not fire yet. It starts to refuse once the two hosts are separate deployments; until then, matching the key prefix against the base URL is on the caller (the SDK derives the host from the prefix by itself).One of: LIVE TEST | TEST |
sandbox | boolean | Required | Whether this call lands on the sandbox: no suppliers, no costs, no search quota. | true |
settlement | string | Required | How THIS key settles — who takes the traveller's money for anything bought with it.
partner: you are merchant of record. Prices you see are net, your account is charged, your traveller pays you.
vacabee: we are merchant of record. Prices you see are end-customer prices, your traveller pays us, and you earn a commission.
The mode follows the key, not the request: a key does one of the two and never both. If you run both modes you hold two keys — which is also what makes it unambiguous which price a search result carries. Sending settlement in a booking body is allowed and is checked against this value; it cannot change it.One of: partner vacabee | partner |
AccountLimitsDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
requestsPerMinute | number | Required | Requests per minute across all classes, unless limited more tightly. | 600 |
rateLimitClasses | map<string, number> | Required | Upper bound per class and minute. A search counts differently from a read, because it costs money at the supplier. | {"READ":1200,"DEFAULT":600,"SEARCH":120,"BOOKING":60} |
freeSearchesPerDay | number | Required | Billable searches per day that cost nothing. Only calls that actually reach a supplier are counted. | 1000 |
searchHardCapPerDay | number | null | Required | Hard upper bound on searches per day, or null for none. Do NOT confuse it with the free quota: beyond that one you are billed, here you are refused. | null |
maxWebsocketConnections | number | Required | Concurrent WebSocket connections for the streaming channel. | 20 |
maxWebsocketSubscriptions | number | Required | Concurrent subscriptions across all WebSocket connections. | 200 |
AccountPartnerDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | Our identifier for the partner. Stable. | 1e4861d9-af96-4821-b264-c1670dbd4b2b |
slug | string | Required | Short name of the partner, as assigned in the portal. | whitelabeltravel |
name | string | Optional | Display name of the partner. | Whitelabel Travel |
ApiErrorDetailDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
type | string | Required | For branching. Stable and machine-readable — a caller may switch on this. New types can be added at any time, so an unknown type must be treated like a generic error.One of: authentication_required invalid_api_key api_key_revoked api_key_expired insufficient_scope ip_not_allowed wrong_environment invalid_request not_found method_not_allowed unsupported_media_type idempotency_key_required idempotency_key_reuse idempotency_key_in_progress offer_expired price_changed insufficient_funds search_quota_exceeded account_not_active not_implemented settlement_not_allowed settlement_not_available rate_limit_exceeded upstream_unavailable upstream_error internal_error | insufficient_scope |
message | string | Required | For humans: log lines and support requests. The wording can change at any time — NEVER compare against it. | The key lacks the `hotels:book` scope. |
requestId | string | Required | Identifier of this exact request, also in the X-Request-Id header. Quote it in every support request: it is how we find the request in our logs. | a4f1c358-6b2e-5acb-8ef5-0c3d0bcc35f5 |
docUrl | string | Optional | The documentation page that explains this error. Points at the topic, not at one page per error type. | https://developers.vacabee.com/docs/authentication |
missingScope | string | Optional | On insufficient_scope: the scope that is missing. Saves a caller from having to read it out of the message. | hotels:book |
retryAfter | number | Optional | On rate_limit_exceeded: seconds until the next permitted attempt. Also present in the Retry-After header. | 12 |
ApiErrorDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
error | ApiErrorDetailDto | Required | — | |
error.type | string | Required | For branching. Stable and machine-readable — a caller may switch on this. New types can be added at any time, so an unknown type must be treated like a generic error.One of: authentication_required invalid_api_key api_key_revoked api_key_expired insufficient_scope ip_not_allowed wrong_environment invalid_request not_found method_not_allowed unsupported_media_type idempotency_key_required idempotency_key_reuse idempotency_key_in_progress offer_expired price_changed insufficient_funds search_quota_exceeded account_not_active not_implemented settlement_not_allowed settlement_not_available rate_limit_exceeded upstream_unavailable upstream_error internal_error | insufficient_scope |
error.message | string | Required | For humans: log lines and support requests. The wording can change at any time — NEVER compare against it. | The key lacks the `hotels:book` scope. |
error.requestId | string | Required | Identifier of this exact request, also in the X-Request-Id header. Quote it in every support request: it is how we find the request in our logs. | a4f1c358-6b2e-5acb-8ef5-0c3d0bcc35f5 |
error.docUrl | string | Optional | The documentation page that explains this error. Points at the topic, not at one page per error type. | https://developers.vacabee.com/docs/authentication |
error.missingScope | string | Optional | On insufficient_scope: the scope that is missing. Saves a caller from having to read it out of the message. | hotels:book |
error.retryAfter | number | Optional | On rate_limit_exceeded: seconds until the next permitted attempt. Also present in the Retry-After header. | 12 |
CancelBookingDto
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | Optional |
ConfirmFlightChangeDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
changeId | string | Required | chg_9f2c… |
ContactDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
email | string | Required | ada@example.com | |
phone | string | Optional | +4915112345678 |
CreatedWebhookEndpointDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
endpoint | WebhookEndpointDto | Required | — | |
endpoint.id | string | Required | ep_01J8… | |
endpoint.url | string | Required | https://api.example.com/hooks/vacabee | |
endpoint.events | string[] | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked | — |
endpoint.secretPrefix | string | Required | Only the beginning of the signing secret, so that you can tell two endpoints apart. The full value appears exactly once: on creation and on rotation. | whsec_LL2z9E |
endpoint.status | string | Required | One of: ACTIVE DISABLED | — |
endpoint.description | string | null | Required | — | |
endpoint.lastDeliveryAt | string | null | Required | 2026-09-05T09:12:44.000Z | |
endpoint.createdAt | string | Required | 2026-08-30T11:02:00.000Z | |
endpoint.canDeliver | boolean | Required | False for endpoints predating delivery: for those we only hold a hash of the secret, and you cannot sign with a hash. Such endpoints receive nothing until the secret has been rotated once. | — |
signingSecret | string | Required | THE ONLY TIME you get to see this value. It is the only thing that tells a genuine delivery apart from any other POST to your public endpoint. Lost it? Then rotate — do not ask us for it. | whsec_LL2z9ESy… |
signingSecretShownOnce | boolean | Required | true | |
previousSecretValidUntil | string | null | Optional | On rotation only: up to this point we ALSO sign with the previous secret, so you can switch over without losing an event. | — |
CreateEsimOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
planId | string | Required | plan_01J8… | |
quantity | number | Optional | 1 | |
externalReference | string | Required | YOUR reference for this order. It is the idempotency across the whole operation: the same value twice yields ONE order, not two — even when the first response never reached you. | order-2026-0915-abc |
customer | EsimCustomerDto | Required | — | |
customer.firstName | string | Required | Ada | |
customer.lastName | string | Required | Lovelace | |
customer.email | string | Required | YOUR customer's address. We send him nothing — it goes to the supplier, who issues the eSIM against it. | ada@example.com |
customer.phone | string | Optional | +4915112345678 | |
settlement | string | Optional | Optional. Who takes the traveller’s money for this order. The mode follows your API KEY — sending it here is a check, not a choice: a value that contradicts your key is refused rather than obeyed.One of: partner vacabee | — |
CreateFlightOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
offerId | string | Required | off_8f2c… | |
externalReference | string | Required | order-2026-0915-abc | |
passengers | TravellerDto[] | Required | One entry per traveller, as the airline needs them. | — |
passengers[].firstName | string | Required | Ada | |
passengers[].lastName | string | Required | Lovelace | |
passengers[].dateOfBirth | string | Optional | ISO date. Most airlines require it; without it the order can be rejected. | 1990-05-17 |
passengers[].gender | string | Optional | As the airline records it. Pass through what your customer stated. | f |
passengers[].title | string | Optional | Salutation as the airline records it — mr, ms, mrs, miss, dr. Optional here and required by most carriers: leaving it out answers "Each passenger must include 'title'" from the airline. | mr |
contact | ContactDto | Required | — | |
contact.email | string | Required | ada@example.com | |
contact.phone | string | Optional | +4915112345678 | |
maxTotalCents | number | Optional | Upper limit. Airlines REPRICE an offer before booking — that is the rule, not the exception. If the new price is above it, NOTHING is booked and nothing is charged; you get 409 price_changed with both amounts and decide yourself. Without this value we book at the new price. | 45000 |
settlement | string | Optional | Optional. Who takes the traveller’s money for this order. The mode follows your API KEY — sending it here is a check, not a choice.One of: partner vacabee | — |
CreateHotelBookingDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
rateId | string | Required | From a search result. Valid for 20 minutes — after that 410 offer_expired, and you search again. The window is how long the offer is valid at the provider, not a limit we set. | rate_8f2c… |
externalReference | string | Required | YOUR reference. Idempotency across the whole operation: the same value twice yields ONE booking — even when the first response never reached you. | booking-2026-0915-abc |
guests | GuestDto[] | Required | One entry per guest — as many as the rate is for. A rate for two adults and a child needs three names; naming fewer is refused rather than padded, because an invented name ends up on an invoice and at a hotel desk. Pass age for a child. With several rooms the guests are placed in the order given. | — |
guests[].firstName | string | Required | Ada | |
guests[].lastName | string | Required | Lovelace | |
guests[].age | number | Optional | A child's age. Suppliers price children by age, so leave it off for adults. | 8 |
contact | ContactDto | Required | — | |
contact.email | string | Required | ada@example.com | |
contact.phone | string | Optional | +4915112345678 | |
specialRequests | string | Optional | — | |
settlement | string | Optional | Optional. Who takes the traveller’s money for this booking. The mode follows your API KEY — sending it here is a check, not a choice: a value that contradicts your key is refused rather than obeyed.One of: partner vacabee | — |
CreateTransferOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
quoteId | string | Required | From a search result. Valid for about 20 minutes; after that the answer is 410 offer_expired and you search again. | qte_9dJ2… |
externalReference | string | Required | YOUR reference for this order, and the idempotency key. Repeating a request with the same value returns the same order instead of buying a second transfer — which is what makes a retry after a timeout safe. | — |
holder | TransferHolderDto | Required | — | |
holder.firstName | string | Required | — | |
holder.lastName | string | Required | — | |
holder.email | string | Required | — | |
holder.phone | string | Required | E.164. The driver calls this number when the pickup goes wrong. | +351912345678 |
remark | string | Optional | Free text for the driver, e.g. "two large suitcases, child seat needed". | — |
language | string | Optional | en | |
settlement | string | Optional | Optional. Who takes the traveller’s money for this order. The mode follows your API KEY — sending it here is a check, not a choice: a value that contradicts your key is refused rather than obeyed.One of: partner vacabee | — |
flightNumber | string | Optional | The traveller flight this transfer meets. Required by the supplier when either end is an airport (IATA), so the driver can follow the arrival. Max 7 characters. | IB3100 |
CreateWebhookEndpointDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
url | string | Required | https, and the host must resolve publicly. A target inside a private network would make us query internal services from within our own cluster and deliver the answer to you. | https://api.example.com/hooks/vacabee |
events | string[] | Required | An unknown event is refused rather than accepted: a subscription to something nobody publishes looks like coverage and is silence.One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked | ["booking.confirmed","payment.refunded"] |
description | string | Optional | Production consumer |
DependencyStatusDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
status | string | Required | One of: ok degraded error | ok |
responseTime | number | Optional | Response time of the check, in milliseconds. | 7 |
EsimCheckoutDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
url | string | null | Required | Send your traveller here. The link is issued ONCE and is never stored, by us or by anyone: repeat the same externalReference and you get the order back with url: null and the deadline of the link you already have. Keep it when you receive it. | https://mrbee.ai/pay/8Kx… |
expiresAt | string | Required | When the link stops being payable. Running out is the ORDINARY outcome of an unpaid order, not a fault: nothing is reserved for your traveller, the price simply expires. You then get esim.failed with reason checkout_expired and can order again at the price of the day. | 2026-09-10T12:15:00.000Z |
amountCents | number | Required | What YOUR TRAVELLER pays, in minor units — the end-customer price. Not a net price: in this mode you buy nothing from us. | 4200 |
currency | string | Required | usd |
EsimCountryDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
code | string | Required | ISO 3166-1 alpha-2. | DE |
name | string | Required | Germany | |
flag | string | null | Required | Flag emoji, when we have one. | 🇩🇪 |
EsimCustomerDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
firstName | string | Required | Ada | |
lastName | string | Required | Lovelace | |
email | string | Required | YOUR customer's address. We send him nothing — it goes to the supplier, who issues the eSIM against it. | ada@example.com |
phone | string | Optional | +4915112345678 |
EsimOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
purchaseId | string | Required | pur_01J8… | |
netAmountCents | number | Required | What YOU owe us for this order — your purchase price. What you charge your own customer is your business and we do not see it.
0 when this order settles as vacabee: there your traveller pays us directly and you owe us nothing at all. What HE pays is in checkout.amountCents. | 1080 |
currency | string | Required | usd | |
status | string | Required | ACTIVE means: the eSIM is provisioned at the supplier.
AWAITING_PAYMENT appears only in vacabee settlement and means exactly what it says — the order is written down, NOTHING has been bought, and nothing will be until your traveller has paid through checkout.url.One of: ACTIVE PENDING AWAITING_PAYMENT | — |
provisioningStatus | string | Required | provisioned | |
iccid | string | null | Required | 89310… | |
activationCode | string | null | Required | LPA:1$smdp.example$ABC | |
lpaCode | string | null | Required | — | |
smdpAddress | string | null | Required | — | |
qrCodeUrl | string | null | Required | https://…/qr.png | |
settlement | string | Required | How this order settles — echoed back so you never have to infer it. It follows your API KEY; see /docs/settlement.One of: partner vacabee | — |
checkout | object | null | Required | Where your traveller pays. null in partner settlement, where the order is already paid for out of your account. | — |
checkout.url | string | null | Required | Send your traveller here. The link is issued ONCE and is never stored, by us or by anyone: repeat the same externalReference and you get the order back with url: null and the deadline of the link you already have. Keep it when you receive it. | https://mrbee.ai/pay/8Kx… |
checkout.expiresAt | string | Required | When the link stops being payable. Running out is the ORDINARY outcome of an unpaid order, not a fault: nothing is reserved for your traveller, the price simply expires. You then get esim.failed with reason checkout_expired and can order again at the price of the day. | 2026-09-10T12:15:00.000Z |
checkout.amountCents | number | Required | What YOUR TRAVELLER pays, in minor units — the end-customer price. Not a net price: in this mode you buy nothing from us. | 4200 |
checkout.currency | string | Required | usd |
EsimOrderPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | EsimOrderDto[] | Required | — | |
items[].purchaseId | string | Required | pur_01J8… | |
items[].netAmountCents | number | Required | What YOU owe us for this order — your purchase price. What you charge your own customer is your business and we do not see it.
0 when this order settles as vacabee: there your traveller pays us directly and you owe us nothing at all. What HE pays is in checkout.amountCents. | 1080 |
items[].currency | string | Required | usd | |
items[].status | string | Required | ACTIVE means: the eSIM is provisioned at the supplier.
AWAITING_PAYMENT appears only in vacabee settlement and means exactly what it says — the order is written down, NOTHING has been bought, and nothing will be until your traveller has paid through checkout.url.One of: ACTIVE PENDING AWAITING_PAYMENT | — |
items[].provisioningStatus | string | Required | provisioned | |
items[].iccid | string | null | Required | 89310… | |
items[].activationCode | string | null | Required | LPA:1$smdp.example$ABC | |
items[].lpaCode | string | null | Required | — | |
items[].smdpAddress | string | null | Required | — | |
items[].qrCodeUrl | string | null | Required | https://…/qr.png | |
items[].settlement | string | Required | How this order settles — echoed back so you never have to infer it. It follows your API KEY; see /docs/settlement.One of: partner vacabee | — |
items[].checkout | object | null | Required | Where your traveller pays. null in partner settlement, where the order is already paid for out of your account. | — |
items[].checkout.url | string | null | Required | Send your traveller here. The link is issued ONCE and is never stored, by us or by anyone: repeat the same externalReference and you get the order back with url: null and the deadline of the link you already have. Keep it when you receive it. | https://mrbee.ai/pay/8Kx… |
items[].checkout.expiresAt | string | Required | When the link stops being payable. Running out is the ORDINARY outcome of an unpaid order, not a fault: nothing is reserved for your traveller, the price simply expires. You then get esim.failed with reason checkout_expired and can order again at the price of the day. | 2026-09-10T12:15:00.000Z |
items[].checkout.amountCents | number | Required | What YOUR TRAVELLER pays, in minor units — the end-customer price. Not a net price: in this mode you buy nothing from us. | 4200 |
items[].checkout.currency | string | Required | usd | |
nextCursor | string | null | Required | Pass as cursor to get the next page. null means this was the last one. | null |
EsimPlanDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | plan_7f3a2b | |
providerPlanId | string | Required | The provider's own identifier. | plan_7f3a2b |
name | string | Required | Europe 10 GB / 30 days | |
planType | string | Required | One of: data unlimited voice_sms | data |
dataAmountMb | number | Required | Included data in megabytes. | 10240 |
validityDays | number | Required | 30 | |
voiceSeconds | number | null | Required | null | |
smsCount | number | null | Required | null | |
isGlobal | boolean | Required | false | |
countries | EsimCountryDto[] | Required | — | |
countries[].code | string | Required | ISO 3166-1 alpha-2. | DE |
countries[].name | string | Required | Germany | |
countries[].flag | string | null | Required | Flag emoji, when we have one. | 🇩🇪 |
regions | EsimRegionDto[] | Required | — | |
regions[].id | string | Required | reg_eu | |
regions[].code | string | Required | EU | |
regions[].name | string | Required | Europe | |
retailCents | number | Required | YOUR price in minor units — the net price after your margin, not the consumer price. This is what we charge your account when you order. | 4200 |
currency | string | Required | usd |
EsimPlanPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | EsimPlanDto[] | Required | — | |
items[].id | string | Required | plan_7f3a2b | |
items[].providerPlanId | string | Required | The provider's own identifier. | plan_7f3a2b |
items[].name | string | Required | Europe 10 GB / 30 days | |
items[].planType | string | Required | One of: data unlimited voice_sms | data |
items[].dataAmountMb | number | Required | Included data in megabytes. | 10240 |
items[].validityDays | number | Required | 30 | |
items[].voiceSeconds | number | null | Required | null | |
items[].smsCount | number | null | Required | null | |
items[].isGlobal | boolean | Required | false | |
items[].countries | EsimCountryDto[] | Required | — | |
items[].countries[].code | string | Required | ISO 3166-1 alpha-2. | DE |
items[].countries[].name | string | Required | Germany | |
items[].countries[].flag | string | null | Required | Flag emoji, when we have one. | 🇩🇪 |
items[].regions | EsimRegionDto[] | Required | — | |
items[].regions[].id | string | Required | reg_eu | |
items[].regions[].code | string | Required | EU | |
items[].regions[].name | string | Required | Europe | |
items[].retailCents | number | Required | YOUR price in minor units — the net price after your margin, not the consumer price. This is what we charge your account when you order. | 4200 |
items[].currency | string | Required | usd | |
total | number | Required | 42 | |
page | number | Required | 1 | |
pageSize | number | Required | 25 | |
totalPages | number | Required | 2 |
EsimRegionDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | reg_eu | |
code | string | Required | EU | |
name | string | Required | Europe |
EsimUsageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
dataUsedMb | number | null | Required | 1024 | |
dataRemainingMb | number | null | Required | 9216 | |
dataTotalMb | number | null | Required | 10240 | |
status | string | null | Required | Null means: the supplier was briefly unreachable and you are seeing the last known state. | — |
expiresAt | string | null | Required | — | |
expired | boolean | Required | — |
FlightCancellationDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
orderId | string | null | Required | ord_5a11 | |
status | string | Required | CANCELLED | |
refundedCents | number | Required | What was credited. 0 on a non-refundable fare. | 19260 |
currency | string | Required | USD | |
refundCredited | boolean | Required | Whether the credit reached your account. FALSE means the flight IS cancelled and we owe you — we can see it and so can you; do not treat it as "nothing happened". | true |
message | string | null | Required | Present only when refundCredited is false. | — |
FlightCancellationQuoteDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
penaltyCents | number | Required | The airline penalty. Stays with us; it is the difference between what you paid and what comes back. | 2500 |
refundCents | number | Required | What is credited to your account if you cancel. Your NET figure — the consumer markup was never in your price and is not in your refund. | 19260 |
currency | string | Required | USD | |
freeCancellation | boolean | Required | True when the whole amount comes back. | false |
cancellable | boolean | Required | False when the airline will not take this order back at all — then cancelling is refused rather than credited at zero. | true |
FlightChangeDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
orderId | string | Required | ord_5a11 | |
status | string | Required | CHANGED | |
differenceCents | number | Required | Signed, as in the option you took. | 4200 |
currency | string | Required | USD | |
settled | boolean | Required | Whether the money moved. FALSE means the TICKET IS REISSUED and the difference has not settled — a debt in one direction or the other, never a reason to retry. | true |
message | string | null | Required | — |
FlightChangeOptionDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
changeId | string | Required | Pass this to the change endpoint. Nothing else identifies it. | chg_9f2c… |
differenceCents | number | Required | SIGNED. Positive means you pay the difference; NEGATIVE means the new itinerary is cheaper and the difference is credited to your account. There is no separate direction field — read the sign. | 4200 |
currency | string | Required | USD | |
newTotalCents | number | null | Required | 46200 | |
expiresAt | string | null | Required | — | |
slices | FlightSliceDto[] | Required | — | |
slices[].origin | string | Required | LHR | |
slices[].destination | string | Required | JFK | |
slices[].departure | string | Required | 2026-11-15T08:00:00Z | |
slices[].arrival | string | Required | 2026-11-15T16:30:00Z | |
slices[].durationMinutes | number | Required | 510 | |
slices[].stops | number | Required | 0 |
FlightChangeOptionsDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
orderId | string | Required | ord_5a11 | |
options | FlightChangeOptionDto[] | Required | — | |
options[].changeId | string | Required | Pass this to the change endpoint. Nothing else identifies it. | chg_9f2c… |
options[].differenceCents | number | Required | SIGNED. Positive means you pay the difference; NEGATIVE means the new itinerary is cheaper and the difference is credited to your account. There is no separate direction field — read the sign. | 4200 |
options[].currency | string | Required | USD | |
options[].newTotalCents | number | null | Required | 46200 | |
options[].expiresAt | string | null | Required | — | |
options[].slices | FlightSliceDto[] | Required | — | |
options[].slices[].origin | string | Required | LHR | |
options[].slices[].destination | string | Required | JFK | |
options[].slices[].departure | string | Required | 2026-11-15T08:00:00Z | |
options[].slices[].arrival | string | Required | 2026-11-15T16:30:00Z | |
options[].slices[].durationMinutes | number | Required | 510 | |
options[].slices[].stops | number | Required | 0 |
FlightChangeOptionsRequestDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
removeSliceId | string | Required | The slice of the existing order you want to replace. | sli_0000A |
add | object | Required | The replacement leg: {origin, destination, departureDate}, same vocabulary as a search. | {"origin":"BER","destination":"LIS","departureDate":"2026-10-04"} |
FlightOfferDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
offerId | string | Required | Stable: the same flight carries the same id across two searches, so you can tell it is the same offer. Pass it to POST /v1/flights/orders. | off_2c7d |
totalCents | number | null | Required | YOUR net price. | 42000 |
currency | string | null | Required | EUR | |
slices | FlightSliceDto[] | Required | — | |
slices[].origin | string | Required | LHR | |
slices[].destination | string | Required | JFK | |
slices[].departure | string | Required | 2026-11-15T08:00:00Z | |
slices[].arrival | string | Required | 2026-11-15T16:30:00Z | |
slices[].durationMinutes | number | Required | 510 | |
slices[].stops | number | Required | 0 | |
airline | object | null | Required | {"iataCode":"BA","name":"British Airways"} | |
cabinClass | string | null | Required | economy | |
conditions | object | null | Required | Change and refund rules, VERBATIM. A legal consequence, not a display string. | {"changeBeforeDeparture":{"allowed":true,"penaltyCents":5000},"refundBeforeDeparture":{"allowed":false,"penaltyCents":null}} |
baggage | object | null | Required | {"carryOn":1,"checked":0} |
FlightOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
orderId | string | null | Required | ord_5a11 | |
externalReference | string | null | Required | The externalReference you sent when creating this booking. | my-order-4711 |
status | string | null | Required | CONFIRMED | |
pnr | string | null | Required | The airline record locator. | X7QK2P |
totalAmountCents | number | null | Required | 42000 | |
currency | string | null | Required | EUR | |
slices | FlightSliceDto[] | Required | — | |
slices[].origin | string | Required | LHR | |
slices[].destination | string | Required | JFK | |
slices[].departure | string | Required | 2026-11-15T08:00:00Z | |
slices[].arrival | string | Required | 2026-11-15T16:30:00Z | |
slices[].durationMinutes | number | Required | 510 | |
slices[].stops | number | Required | 0 | |
passengers | object[] | Required | — | |
conditions | object | null | Required | Verbatim, as in the offer. | — |
FlightOrderPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | FlightOrderSummaryDto[] | Required | — | |
items[].orderId | string | null | Required | ord_5a11 | |
items[].status | string | null | Required | CONFIRMED | |
items[].pnr | string | null | Required | X7QK2P | |
items[].totalAmountCents | number | null | Required | 42000 | |
items[].currency | string | null | Required | EUR | |
items[].createdAt | string | null | Required | — | |
nextCursor | string | null | Required | Pass as cursor to get the next page. null means this was the last one. The value is opaque — read it, do not build it. | null |
FlightOrderSummaryDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
orderId | string | null | Required | ord_5a11 | |
status | string | null | Required | CONFIRMED | |
pnr | string | null | Required | X7QK2P | |
totalAmountCents | number | null | Required | 42000 | |
currency | string | null | Required | EUR | |
createdAt | string | null | Required | — |
FlightSearchDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
offers | FlightOfferDto[] | Required | — | |
offers[].offerId | string | Required | Stable: the same flight carries the same id across two searches, so you can tell it is the same offer. Pass it to POST /v1/flights/orders. | off_2c7d |
offers[].totalCents | number | null | Required | YOUR net price. | 42000 |
offers[].currency | string | null | Required | EUR | |
offers[].slices | FlightSliceDto[] | Required | — | |
offers[].slices[].origin | string | Required | LHR | |
offers[].slices[].destination | string | Required | JFK | |
offers[].slices[].departure | string | Required | 2026-11-15T08:00:00Z | |
offers[].slices[].arrival | string | Required | 2026-11-15T16:30:00Z | |
offers[].slices[].durationMinutes | number | Required | 510 | |
offers[].slices[].stops | number | Required | 0 | |
offers[].airline | object | null | Required | {"iataCode":"BA","name":"British Airways"} | |
offers[].cabinClass | string | null | Required | economy | |
offers[].conditions | object | null | Required | Change and refund rules, VERBATIM. A legal consequence, not a display string. | {"changeBeforeDeparture":{"allowed":true,"penaltyCents":5000},"refundBeforeDeparture":{"allowed":false,"penaltyCents":null}} |
offers[].baggage | object | null | Required | {"carryOn":1,"checked":0} |
FlightSearchRequestDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
origin | string | Required | IATA code of the departure airport. | LHR |
destination | string | Required | JFK | |
departureDate | string | Required | ISO date. | 2026-11-15 |
returnDate | string | Optional | Omit for a one-way. | 2026-11-22 |
adults | number | Optional | 1 | |
children | number | Optional | 0 | |
infants | number | Optional | Infants without their own seat. | 0 |
cabinClass | string | Optional | One of: economy premium_economy business first | — |
maxConnections | number | Optional | 0 means direct flights only. | 1 |
airlines | string | Optional | Comma-separated IATA airline codes. | BA,LH |
sort | string | Optional | price |
FlightSliceDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
origin | string | Required | LHR | |
destination | string | Required | JFK | |
departure | string | Required | 2026-11-15T08:00:00Z | |
arrival | string | Required | 2026-11-15T16:30:00Z | |
durationMinutes | number | Required | 510 | |
stops | number | Required | 0 |
FullHealthDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
status | string | Required | One of: ok degraded error | ok |
service | string | Required | vacabee-partner-api | |
timestamp | string | Required | 2026-09-05T04:12:33.019Z | |
database | DependencyStatusDto | Required | — | |
database.status | string | Required | One of: ok degraded error | ok |
database.responseTime | number | Optional | Response time of the check, in milliseconds. | 7 |
redis | DependencyStatusDto | Required | — | |
redis.status | string | Required | One of: ok degraded error | ok |
redis.responseTime | number | Optional | Response time of the check, in milliseconds. | 7 |
upstreams | map<string, UpstreamStatusDto> | Required | State per downstream service, keyed by the service name. | {"profile":{"status":"ok","service":"vacabee-profile","responseTime":21}} |
GuestDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
firstName | string | Required | Ada | |
lastName | string | Required | Lovelace | |
age | number | Optional | A child's age. Suppliers price children by age, so leave it off for adults. | 8 |
HotelBookingDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
bookingId | string | null | Required | bkg_31f0 | |
externalReference | string | null | Required | The externalReference you sent when creating this booking. | my-order-4711 |
status | string | null | Required | CONFIRMED | |
hotelName | string | null | Required | Hotel Lisboa Centro | |
checkIn | string | null | Required | 2026-11-02 | |
checkOut | string | null | Required | 2026-11-06 | |
totalAmountCents | number | null | Required | 12400 | |
currency | string | null | Required | EUR | |
confirmationNumber | string | null | Required | conf_88ab | |
cancellationPolicy | string | null | Required | Verbatim, as in the rate. | — |
guests | object[] | Required | — |
HotelBookingPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | HotelBookingDto[] | Required | — | |
items[].bookingId | string | null | Required | bkg_31f0 | |
items[].externalReference | string | null | Required | The externalReference you sent when creating this booking. | my-order-4711 |
items[].status | string | null | Required | CONFIRMED | |
items[].hotelName | string | null | Required | Hotel Lisboa Centro | |
items[].checkIn | string | null | Required | 2026-11-02 | |
items[].checkOut | string | null | Required | 2026-11-06 | |
items[].totalAmountCents | number | null | Required | 12400 | |
items[].currency | string | null | Required | EUR | |
items[].confirmationNumber | string | null | Required | conf_88ab | |
items[].cancellationPolicy | string | null | Required | Verbatim, as in the rate. | — |
items[].guests | object[] | Required | — | |
nextCursor | string | null | Required | null |
HotelCancellationPolicyDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
from | string | null | Required | From when this window applies. | 2026-11-01T18:00:00 |
until | string | null | Required | Until when. | 2026-11-02T14:00:00 |
chargeCents | number | null | Required | What cancelling in this window costs, in minor units — at YOUR net basis. | 11340 |
HotelDestinationDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | dst_lisbon | |
name | string | Required | Lisbon | |
type | string | Required | One of: city region poi hotel | city |
country | string | Required | ISO 3166-1 alpha-2. | PT |
HotelHitDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
hotelId | string | null | Required | htl_4b21 | |
name | string | null | Required | Altis Avenida Hotel | |
starRating | number | null | Required | Star category, 0–5. Never a review score. | 5 |
address | string | null | Required | One line, as the supplier states it. We do not split it into city and country — that would be guessing, and a guessed address is worse than an undivided one. | Rua 1º De Dezembro, 120, Lisbon |
latitude | number | null | Required | 38.72 | |
longitude | number | null | Required | -9.14 | |
images | string[] | Required | — | |
amenities | string[] | Required | ["Free Wi-Fi","Parking"] | |
priceFrom | object | null | Required | An indicative "from" price. NOT bookable — call GET /v1/hotels/{hotelId}/rates for rates you can book. | — |
priceFrom.totalCents | number | null | Required | For the whole stay. | 49600 |
priceFrom.perNightCents | number | null | Required | 12400 | |
priceFrom.currency | string | null | Required | EUR | |
rateStatus | string | null | Required | available — there are bookable rates, go fetch them. checking — the supplier's price query is still running. none — nothing to be had for these dates.One of: available checking none | available |
reviewScore | number | null | Required | Guest review score, 0–10. Absent means no reviews — never substituted. | 8.4 |
reviewCount | number | null | Required | How many reviews the score is made of. A score without it is not a verdict. | 214 |
HotelPriceFromDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
totalCents | number | null | Required | For the whole stay. | 49600 |
perNightCents | number | null | Required | 12400 | |
currency | string | null | Required | EUR |
HotelRateDetailDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
rateId | string | Required | Pass to POST /v1/hotels/bookings. Expires — a stale rate answers 410 offer_expired rather than booking at a price that no longer holds. | rate_9f2c1a |
roomName | string | null | Required | Double Room | |
boardType | string | null | Required | breakfast | |
totalCents | number | null | Required | YOUR net price for the whole stay. | 12400 |
currency | string | null | Required | EUR | |
refundable | boolean | null | Required | null means we do not know — not "no". "Non-refundable" is a statement with money behind it, and guessing it is the wrong way to be helpful. | true |
freeCancellationUntil | string | null | Required | Free cancellation up to this moment, when the supplier states one. Local time at the property, as they state it. | 2026-11-01T18:00:00 |
cancellationPolicies | HotelCancellationPolicyDto[] | Required | Every window the supplier states, in full. We do NOT condense them into one sentence: money hangs on them, and a summary would be our reading rather than the supplier's. | — |
cancellationPolicies[].from | string | null | Required | From when this window applies. | 2026-11-01T18:00:00 |
cancellationPolicies[].until | string | null | Required | Until when. | 2026-11-02T14:00:00 |
cancellationPolicies[].chargeCents | number | null | Required | What cancelling in this window costs, in minor units — at YOUR net basis. | 11340 |
HotelRatesDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
hotelId | string | Required | htl_4b21 | |
rates | HotelRateDetailDto[] | Required | — | |
rates[].rateId | string | Required | Pass to POST /v1/hotels/bookings. Expires — a stale rate answers 410 offer_expired rather than booking at a price that no longer holds. | rate_9f2c1a |
rates[].roomName | string | null | Required | Double Room | |
rates[].boardType | string | null | Required | breakfast | |
rates[].totalCents | number | null | Required | YOUR net price for the whole stay. | 12400 |
rates[].currency | string | null | Required | EUR | |
rates[].refundable | boolean | null | Required | null means we do not know — not "no". "Non-refundable" is a statement with money behind it, and guessing it is the wrong way to be helpful. | true |
rates[].freeCancellationUntil | string | null | Required | Free cancellation up to this moment, when the supplier states one. Local time at the property, as they state it. | 2026-11-01T18:00:00 |
rates[].cancellationPolicies | HotelCancellationPolicyDto[] | Required | Every window the supplier states, in full. We do NOT condense them into one sentence: money hangs on them, and a summary would be our reading rather than the supplier's. | — |
rates[].cancellationPolicies[].from | string | null | Required | From when this window applies. | 2026-11-01T18:00:00 |
rates[].cancellationPolicies[].until | string | null | Required | Until when. | 2026-11-02T14:00:00 |
rates[].cancellationPolicies[].chargeCents | number | null | Required | What cancelling in this window costs, in minor units — at YOUR net basis. | 11340 |
priceNotice | object | null | Required | Present ONLY when the price here differs materially from the one the search advertised. Show both numbers rather than quietly using the higher one. | — |
HotelSearchDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
searchId | string | null | Required | Pass to GET/PATCH /v1/hotels/searches/{id} to refine or page. | ses_7a1f |
status | string | Required | complete means every supplier answered. A partial result still carries hotels — show them rather than waiting. | complete |
hotels | HotelHitDto[] | Required | — | |
hotels[].hotelId | string | null | Required | htl_4b21 | |
hotels[].name | string | null | Required | Altis Avenida Hotel | |
hotels[].starRating | number | null | Required | Star category, 0–5. Never a review score. | 5 |
hotels[].address | string | null | Required | One line, as the supplier states it. We do not split it into city and country — that would be guessing, and a guessed address is worse than an undivided one. | Rua 1º De Dezembro, 120, Lisbon |
hotels[].latitude | number | null | Required | 38.72 | |
hotels[].longitude | number | null | Required | -9.14 | |
hotels[].images | string[] | Required | — | |
hotels[].amenities | string[] | Required | ["Free Wi-Fi","Parking"] | |
hotels[].priceFrom | object | null | Required | An indicative "from" price. NOT bookable — call GET /v1/hotels/{hotelId}/rates for rates you can book. | — |
hotels[].priceFrom.totalCents | number | null | Required | For the whole stay. | 49600 |
hotels[].priceFrom.perNightCents | number | null | Required | 12400 | |
hotels[].priceFrom.currency | string | null | Required | EUR | |
hotels[].rateStatus | string | null | Required | available — there are bookable rates, go fetch them. checking — the supplier's price query is still running. none — nothing to be had for these dates.One of: available checking none | available |
hotels[].reviewScore | number | null | Required | Guest review score, 0–10. Absent means no reviews — never substituted. | 8.4 |
hotels[].reviewCount | number | null | Required | How many reviews the score is made of. A score without it is not a verdict. | 214 |
HotelSearchPatchDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
destination | string | Optional | Where to search. Either this, nearPoi, or a latitude/longitude pair — without one of the three there is nothing to anchor the search on. | Lisbon |
nearPoi | string | Optional | A landmark to search around. Useful for small places a city name would miss. | Torre de Belém |
areaHint | string | Optional | A district within the destination. | Alfama |
latitude | number | Optional | 38.7223 | |
longitude | number | Optional | -9.1393 | |
radiusKm | number | Optional | Radius around the coordinates, in kilometres. | 5 |
checkIn | string | Optional | ISO date. Without dates you get availability without prices. | 2026-11-02 |
checkOut | string | Optional | 2026-11-06 | |
adults | number | Optional | 2 | |
children | number | Optional | 0 | |
childrenAges | string[] | Optional | One age per child. Required by most suppliers to price a room correctly. | [7,11] |
rooms | number | Optional | 1 | |
board | string | Optional | One of: any room_only breakfast half_board full_board all_inclusive | — |
budgetMaxPerNight | number | Optional | Upper bound per night, in minor units. | 20000 |
currency | string | Optional | EUR | |
starMin | number | Optional | Minimum star rating. | 4 |
amenities | string[] | Optional | ["wifi","parking"] | |
sort | string | Optional | price_asc | |
language | string | Optional | en | |
limit | number | Optional | At most 100. | 20 |
HotelSearchRequestDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
destination | string | Optional | Where to search. Either this, nearPoi, or a latitude/longitude pair — without one of the three there is nothing to anchor the search on. | Lisbon |
nearPoi | string | Optional | A landmark to search around. Useful for small places a city name would miss. | Torre de Belém |
areaHint | string | Optional | A district within the destination. | Alfama |
latitude | number | Optional | 38.7223 | |
longitude | number | Optional | -9.1393 | |
radiusKm | number | Optional | Radius around the coordinates, in kilometres. | 5 |
checkIn | string | Optional | ISO date. Without dates you get availability without prices. | 2026-11-02 |
checkOut | string | Optional | 2026-11-06 | |
adults | number | Optional | 2 | |
children | number | Optional | 0 | |
childrenAges | string[] | Optional | One age per child. Required by most suppliers to price a room correctly. | [7,11] |
rooms | number | Optional | 1 | |
board | string | Optional | One of: any room_only breakfast half_board full_board all_inclusive | — |
budgetMaxPerNight | number | Optional | Upper bound per night, in minor units. | 20000 |
currency | string | Optional | EUR | |
starMin | number | Optional | Minimum star rating. | 4 |
amenities | string[] | Optional | ["wifi","parking"] | |
sort | string | Optional | price_asc | |
language | string | Optional | en | |
limit | number | Optional | At most 100. | 20 |
LedgerBalanceDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
accountId | string | Required | acc_4f21 | |
accountRef | string | Required | Quote this in a bank transfer so we can match the payment to your account. | VCB-ACC-9K2M |
currency | string | Required | USD | |
balanceCents | number | Required | The sum of every entry on your account. Holds are stored as negative entries, so money already reserved for a booking in flight is ALREADY deducted here. | 500000 |
creditLimitCents | number | Required | Agreed overdraft, if you have one. | 0 |
heldCents | number | Required | How much of the balance is reserved for bookings that have started but not finished. Shown so you can explain the number — NOT to be subtracted again: it is already out of balanceCents. | 12400 |
availableCents | number | Required | balance + creditLimit. THE number to check before booking. Holds are not subtracted a second time — they are already inside balanceCents. | 500000 |
status | string | Required | Anything but ACTIVE and bookings answer 402 regardless of the balance.One of: ACTIVE SUSPENDED CLOSED | ACTIVE |
LedgerEntryDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | led_8a31 | |
kind | string | Required | One of: DEPOSIT HOLD RELEASE CHARGE REFUND ADJUSTMENT | CHARGE |
amountCents | number | Required | Signed, in minor units. Negative takes money out. The balance is their sum. | -12400 |
currency | string | Required | USD | |
sourceReference | string | Required | Your reference for the thing that caused this. Use it to reconcile. | hotel:e2e-1788707669 |
description | string | null | Required | Hotel htl_4b21 2026-11-02 | |
createdAt | string | Required | 2026-09-06T14:31:09.272Z |
LedgerEntryPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | LedgerEntryDto[] | Required | — | |
items[].id | string | Required | led_8a31 | |
items[].kind | string | Required | One of: DEPOSIT HOLD RELEASE CHARGE REFUND ADJUSTMENT | CHARGE |
items[].amountCents | number | Required | Signed, in minor units. Negative takes money out. The balance is their sum. | -12400 |
items[].currency | string | Required | USD | |
items[].sourceReference | string | Required | Your reference for the thing that caused this. Use it to reconcile. | hotel:e2e-1788707669 |
items[].description | string | null | Required | Hotel htl_4b21 2026-11-02 | |
items[].createdAt | string | Required | 2026-09-06T14:31:09.272Z | |
nextCursor | string | null | Required | null |
LoadMoreDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
limit | number | Optional | How many more results to add. | 20 |
PatchWebhookEndpointDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
url | string | Optional | https://api.example.com/hooks/v2 | |
events | string[] | Optional | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked | — |
description | string | Optional | — | |
status | string | Optional | DISABLED stops delivery without deleting the endpoint and its history — for a planned maintenance window.One of: ACTIVE DISABLED | — |
PingDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
status | string | Required | Always ok if the response arrives. | ok |
service | string | Required | Which service answered. | vacabee-partner-api |
timestamp | string | Required | Server time in UTC, per ISO 8601. Useful for spotting a clock drift on your own side — that is a common cause of rejected webhook signatures. | 2026-09-05T04:12:33.019Z |
RotateWebhookSecretDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
immediate | boolean | Optional | Kill the previous secret NOW instead of keeping it valid for 24 hours. Only for a leak: without the overlap, every delivery between this call and your deployment fails your signature check. Rotating twice within the overlap is refused unless you set this — the second rotation would silently invalidate a secret we promised would stay valid. | false |
SearchQuotaDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
day | string | Required | The calendar day this counter belongs to, in YOUR time zone. The allowance resets by your day, not by UTC. | 2026-09-10 |
timezone | string | Required | Europe/Berlin | |
limit | number | Required | Your free searches per day, from your commercial terms. | 1000 |
used | number | Required | Billable searches counted today — INCLUDING any above the allowance. Only calls that reach a supplier count; polling, reads and everything in the sandbox do not. | 312 |
remaining | number | Required | How much of the free allowance is left. Never negative: past the allowance it is 0 and used keeps climbing. | 688 |
reset | string | Required | When the counter goes back to zero. | 2026-09-11T00:00:00.000Z |
hardCap | number | null | Required | Your own ceiling, if you set one. There is no ceiling from our side — past the free allowance you keep searching and pay. A cap is only ever yours, which is why a search we turned away is counted separately. | null |
rejected | number | Required | Searches YOUR hard cap turned away today. Zero unless you set a cap. | 0 |
SearchTransfersDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
from | TransferPlaceDto | Required | — | |
from.type | string | Required | How code should be read. IATA is an airport code, ATLAS an accommodation identifier, GPS a lat,lng pair.One of: IATA ATLAS GPS | — |
from.code | string | Required | The identifier itself. | LIS |
from.description | string | Optional | What is at code. Required when type is GPS — coordinates alone do not tell the driver where they are going — and ignored otherwise. | Hotel Riu Palace, Palma |
to | TransferPlaceDto | Required | — | |
to.type | string | Required | How code should be read. IATA is an airport code, ATLAS an accommodation identifier, GPS a lat,lng pair.One of: IATA ATLAS GPS | — |
to.code | string | Required | The identifier itself. | LIS |
to.description | string | Optional | What is at code. Required when type is GPS — coordinates alone do not tell the driver where they are going — and ignored otherwise. | Hotel Riu Palace, Palma |
outbound | string | Required | Local pickup time at the origin, without a timezone suffix. A transfer is booked in the time the driver reads off the clock. | 2026-11-02T08:30:00 |
inbound | string | Optional | Return leg. Present = round trip, absent = one way. | — |
adults | number | Required | — | |
children | number | Optional | 0 | |
infants | number | Optional | 0 | |
language | string | Optional | ISO-639-1 language. | en |
TerseHealthDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
status | string | Required | One of: ok degraded error | ok |
service | string | Required | vacabee-partner-api | |
timestamp | string | Required | 2026-09-05T04:12:33.019Z |
TransferCancellationQuoteDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
penaltyCents | number | Required | Stays with us. | 1050 |
refundCents | number | Required | Credited back to your account. | 3150 |
currency | string | Required | USD | |
freeCancellation | boolean | Required | false |
TransferHolderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
firstName | string | Required | — | |
lastName | string | Required | — | |
email | string | Required | — | |
phone | string | Required | E.164. The driver calls this number when the pickup goes wrong. | +351912345678 |
TransferOfferDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
quoteId | string | Required | Book with this. Valid ~20 minutes, then 410 offer_expired. | qte_9dJ2… |
direction | string | null | Required | ARRIVAL | |
transferType | string | null | Required | PRIVATE | |
vehicle | string | null | Required | Car | |
category | string | null | Required | Standard | |
priceCents | number | null | Required | Your PURCHASE price. | 4200 |
currency | string | Required | USD | |
minPax | number | null | Required | 1 | |
maxPax | number | null | Required | 3 | |
luggage | string | null | Required | 2 | |
journeyMinutes | string | null | Required | 35 | |
pickup | object | null | Required | Meeting point and whether the pickup time must be reconfirmed. | — |
from | object | null | Required | — | |
to | object | null | Required | — | |
cancellationPolicies | object[] | Required | VERBATIM from the supplier. Not summarised into a boolean — a cancellation policy is a legal consequence, and "free until 18:00 the day before" does not survive being turned into refundable: true. | — |
TransferOrderDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | null | Required | — | |
externalReference | string | null | Required | The externalReference you sent when creating this booking. | my-order-4711 |
status | string | null | Required | CONFIRMED | |
reference | string | null | Required | The supplier's reference. | — |
amountCents | number | null | Required | 4200 | |
currency | string | Required | USD | |
voucherUrl | string | null | Required | Printable voucher — show it to the traveller. | — |
holder | object | Required | The lead passenger. | — |
legs | object[] | Required | — | |
cancelledAt | string | null | Required | — | |
refundCents | number | null | Required | Credited back on cancellation. | — |
settlement | TransferSettlementDto | Required | — | |
settlement.funds | string | Required | What happened to the money on your account. captured and released are final. still_held means the supplier gave no verdict yet and our reconciliation worker is finishing the job — do NOT invoice your customer on that state; poll the order until it settles.One of: captured released still_held | — |
TransferOrderPageDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
items | TransferOrderDto[] | Required | — | |
items[].id | string | null | Required | — | |
items[].externalReference | string | null | Required | The externalReference you sent when creating this booking. | my-order-4711 |
items[].status | string | null | Required | CONFIRMED | |
items[].reference | string | null | Required | The supplier's reference. | — |
items[].amountCents | number | null | Required | 4200 | |
items[].currency | string | Required | USD | |
items[].voucherUrl | string | null | Required | Printable voucher — show it to the traveller. | — |
items[].holder | object | Required | The lead passenger. | — |
items[].legs | object[] | Required | — | |
items[].cancelledAt | string | null | Required | — | |
items[].refundCents | number | null | Required | Credited back on cancellation. | — |
items[].settlement | TransferSettlementDto | Required | — | |
items[].settlement.funds | string | Required | What happened to the money on your account. captured and released are final. still_held means the supplier gave no verdict yet and our reconciliation worker is finishing the job — do NOT invoice your customer on that state; poll the order until it settles.One of: captured released still_held | — |
nextCursor | string | null | Required | Pass as cursor to get the next page. null means this was the last one. The value is opaque — read it, do not build it. | null |
TransferPlaceDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
type | string | Required | How code should be read. IATA is an airport code, ATLAS an accommodation identifier, GPS a lat,lng pair.One of: IATA ATLAS GPS | — |
code | string | Required | The identifier itself. | LIS |
description | string | Optional | What is at code. Required when type is GPS — coordinates alone do not tell the driver where they are going — and ignored otherwise. | Hotel Riu Palace, Palma |
TransferSearchResultDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
offers | TransferOfferDto[] | Required | — | |
offers[].quoteId | string | Required | Book with this. Valid ~20 minutes, then 410 offer_expired. | qte_9dJ2… |
offers[].direction | string | null | Required | ARRIVAL | |
offers[].transferType | string | null | Required | PRIVATE | |
offers[].vehicle | string | null | Required | Car | |
offers[].category | string | null | Required | Standard | |
offers[].priceCents | number | null | Required | Your PURCHASE price. | 4200 |
offers[].currency | string | Required | USD | |
offers[].minPax | number | null | Required | 1 | |
offers[].maxPax | number | null | Required | 3 | |
offers[].luggage | string | null | Required | 2 | |
offers[].journeyMinutes | string | null | Required | 35 | |
offers[].pickup | object | null | Required | Meeting point and whether the pickup time must be reconfirmed. | — |
offers[].from | object | null | Required | — | |
offers[].to | object | null | Required | — | |
offers[].cancellationPolicies | object[] | Required | VERBATIM from the supplier. Not summarised into a boolean — a cancellation policy is a legal consequence, and "free until 18:00 the day before" does not survive being turned into refundable: true. | — |
TransferSettlementDto
| Field | Type | Required | Description |
|---|---|---|---|
funds | string | Required | What happened to the money on your account. captured and released are final. still_held means the supplier gave no verdict yet and our reconciliation worker is finishing the job — do NOT invoice your customer on that state; poll the order until it settles.One of: captured released still_held |
TravellerDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
firstName | string | Required | Ada | |
lastName | string | Required | Lovelace | |
dateOfBirth | string | Optional | ISO date. Most airlines require it; without it the order can be rejected. | 1990-05-17 |
gender | string | Optional | As the airline records it. Pass through what your customer stated. | f |
title | string | Optional | Salutation as the airline records it — mr, ms, mrs, miss, dr. Optional here and required by most carriers: leaving it out answers "Each passenger must include 'title'" from the airline. | mr |
UpstreamStatusDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
status | string | Required | One of: ok degraded error | ok |
service | string | Required | vacabee-profile | |
responseTime | number | Optional | 21 |
WebhookAttemptDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | — | |
attemptNumber | number | Required | Which attempt this is, counted from 1. | 3 |
startedAt | string | Required | 2026-09-05T09:12:44.000Z | |
durationMs | number | Required | 842 | |
requestHeaders | map<string, string> | null | Required | The headers of THIS attempt, signature included. It differs in every attempt because it is computed over a fresh timestamp — with the old one the retry would fail your replay window. | — |
statusCode | number | null | Required | Null means: no response arrived at all (timeout, DNS, connection). | 500 |
responseBody | string | null | Required | Truncated to 4096 characters — somebody else's response is unbounded in length. | — |
responseHeaders | map<string, string> | null | Required | — | |
error | string | null | Required | — | |
manual | boolean | Required | Was this attempt triggered by hand? | — |
triggeredBy | string | null | Required | — |
WebhookBookingDataDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
bookingType | string | Required | One of: hotel flight transfer esim | — |
bookingReference | string | Required | ABC123 | |
status | string | Optional | CONFIRMED | |
currency | string | Optional | EUR | |
totalAmountCents | number | Optional | What the guest paid, in the smallest unit — not our purchase price. | 48900 |
checkInDate | string | Optional | 2026-11-02 | |
checkOutDate | string | Optional | 2026-11-06 | |
departureDate | string | Optional | 2026-11-02T08:15:00Z | |
returnDate | string | Optional | — | |
hotelName | string | Optional | Hotel Adlon | |
origin | string | Optional | BER | |
destination | string | Optional | LIS | |
passengerCount | number | Optional | 2 | |
guestCount | number | Optional | 2 | |
refunded | boolean | Optional | Only on booking.cancelled: whether money goes back. A cancellation without a refund (non-refundable rate) leaves the margin standing. | — |
reason | string | Optional | Only on booking.failed: a machine-readable cause, so you can branch without parsing prose. checkout_expired means nobody paid the hosted checkout in time — nothing was reserved and nothing has to be undone; quote again if the traveller still wants it. Treat an unknown value the way you treat an unknown event type: as "it failed", not as an error of your own.One of: checkout_expired checkout_failed | checkout_expired |
WebhookCheckoutDataDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
bookingType | string | Required | Which vertical the checkout belongs to.One of: hotel flight transfer esim | — |
bookingReference | string | Required | YOUR externalReference for this booking — the same string you sent, so this needs no lookup table on your side. | ORD-8817 |
amountCents | number | Required | What the traveller is asked to pay, in the smallest unit. | 48900 |
currency | string | Required | EUR | |
expiresAt | string | Required | On checkout.created: when the link stops being payable. On checkout.expired: when it did. | 2026-09-05T10:15:00.000Z |
WebhookDeliveryDetailDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | dl_01J8… | |
eventType | string | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked webhook.test | — |
eventId | string | Required | The business ID of what triggered this — your booking, your refund. | bk_01J8… |
status | string | Required | PENDING is waiting for the next automatic attempt. EXHAUSTED means: the automatic attempts are used up — nothing more happens on its own, but it can still be retried by hand. FAILED means: your endpoint answered 410 Gone, and we stopped instead of calling again.One of: PENDING SUCCEEDED FAILED EXHAUSTED | — |
attemptCount | number | Required | 3 | |
nextAttemptAt | string | null | Required | — | |
lastStatusCode | number | null | Required | 503 | |
lastError | string | null | Required | — | |
lastAttemptAt | string | null | Required | — | |
succeededAt | string | null | Required | — | |
createdAt | string | Required | — | |
endpoint | object | Required | — | |
endpoint.id | string | Optional | — | |
endpoint.url | string | null | Optional | — | |
endpoint.description | string | null | Optional | — | |
retryable | boolean | Required | Sending a successful delivery again would create a second event on your side — which is why it is not retryable. | — |
payload | object | Required | Byte for byte what we sent. A retry sends exactly that again, not a freshly serialized version of it. | — |
attempts | WebhookAttemptDto[] | Required | — | |
attempts[].id | string | Required | — | |
attempts[].attemptNumber | number | Required | Which attempt this is, counted from 1. | 3 |
attempts[].startedAt | string | Required | 2026-09-05T09:12:44.000Z | |
attempts[].durationMs | number | Required | 842 | |
attempts[].requestHeaders | map<string, string> | null | Required | The headers of THIS attempt, signature included. It differs in every attempt because it is computed over a fresh timestamp — with the old one the retry would fail your replay window. | — |
attempts[].statusCode | number | null | Required | Null means: no response arrived at all (timeout, DNS, connection). | 500 |
attempts[].responseBody | string | null | Required | Truncated to 4096 characters — somebody else's response is unbounded in length. | — |
attempts[].responseHeaders | map<string, string> | null | Required | — | |
attempts[].error | string | null | Required | — | |
attempts[].manual | boolean | Required | Was this attempt triggered by hand? | — |
attempts[].triggeredBy | string | null | Required | — |
WebhookDeliveryDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | dl_01J8… | |
eventType | string | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked webhook.test | — |
eventId | string | Required | The business ID of what triggered this — your booking, your refund. | bk_01J8… |
status | string | Required | PENDING is waiting for the next automatic attempt. EXHAUSTED means: the automatic attempts are used up — nothing more happens on its own, but it can still be retried by hand. FAILED means: your endpoint answered 410 Gone, and we stopped instead of calling again.One of: PENDING SUCCEEDED FAILED EXHAUSTED | — |
attemptCount | number | Required | 3 | |
nextAttemptAt | string | null | Required | — | |
lastStatusCode | number | null | Required | 503 | |
lastError | string | null | Required | — | |
lastAttemptAt | string | null | Required | — | |
succeededAt | string | null | Required | — | |
createdAt | string | Required | — | |
endpoint | object | Required | — | |
endpoint.id | string | Optional | — | |
endpoint.url | string | null | Optional | — | |
endpoint.description | string | null | Optional | — | |
retryable | boolean | Required | Sending a successful delivery again would create a second event on your side — which is why it is not retryable. | — |
WebhookDeliveryHeadersDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
X-Vacabee-Signature | string | Required | HMAC-SHA256 over {t}.{raw body}, hex. Compare in constant time and check the age (default 300 s). Verify over the RAW body — parsing and re-serialising yields a different string and therefore a different signature. Recomputed on every attempt, because otherwise every retry would fail your time window. | t=1788570420,v1=8f2c… |
X-Vacabee-Event | string | Required | booking.confirmed | |
X-Vacabee-Delivery | string | Required | The delivery. The same across all attempts. | dl_01J8… |
X-Vacabee-Attempt | string | Required | Which attempt this is, from 1. | 1 |
X-Vacabee-Timestamp | string | Required | 1788570420 |
WebhookDeliveryListDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
deliveries | WebhookDeliveryDto[] | Required | — | |
deliveries[].id | string | Required | dl_01J8… | |
deliveries[].eventType | string | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked webhook.test | — |
deliveries[].eventId | string | Required | The business ID of what triggered this — your booking, your refund. | bk_01J8… |
deliveries[].status | string | Required | PENDING is waiting for the next automatic attempt. EXHAUSTED means: the automatic attempts are used up — nothing more happens on its own, but it can still be retried by hand. FAILED means: your endpoint answered 410 Gone, and we stopped instead of calling again.One of: PENDING SUCCEEDED FAILED EXHAUSTED | — |
deliveries[].attemptCount | number | Required | 3 | |
deliveries[].nextAttemptAt | string | null | Required | — | |
deliveries[].lastStatusCode | number | null | Required | 503 | |
deliveries[].lastError | string | null | Required | — | |
deliveries[].lastAttemptAt | string | null | Required | — | |
deliveries[].succeededAt | string | null | Required | — | |
deliveries[].createdAt | string | Required | — | |
deliveries[].endpoint | object | Required | — | |
deliveries[].endpoint.id | string | Optional | — | |
deliveries[].endpoint.url | string | null | Optional | — | |
deliveries[].endpoint.description | string | null | Optional | — | |
deliveries[].retryable | boolean | Required | Sending a successful delivery again would create a second event on your side — which is why it is not retryable. | — |
nextCursor | string | null | Required | Pass on as cursor to fetch the next page. Null = end. | — |
deliveryEnabled | boolean | Required | False means: nothing is going out on OUR side right now. Events are still being recorded and will go out as soon as it is fixed. This is not a configuration error on your side. | — |
WebhookEndpointDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | ep_01J8… | |
url | string | Required | https://api.example.com/hooks/vacabee | |
events | string[] | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked | — |
secretPrefix | string | Required | Only the beginning of the signing secret, so that you can tell two endpoints apart. The full value appears exactly once: on creation and on rotation. | whsec_LL2z9E |
status | string | Required | One of: ACTIVE DISABLED | — |
description | string | null | Required | — | |
lastDeliveryAt | string | null | Required | 2026-09-05T09:12:44.000Z | |
createdAt | string | Required | 2026-08-30T11:02:00.000Z | |
canDeliver | boolean | Required | False for endpoints predating delivery: for those we only hold a hash of the secret, and you cannot sign with a hash. Such endpoints receive nothing until the secret has been rotated once. | — |
WebhookEsimDataDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
purchaseId | string | Required | pur_01J8… | |
iccid | string | null | Optional | 8931… | |
planName | string | null | Optional | Europe 10 GB / 30 days | |
status | string | Optional | PROVISIONED |
WebhookEventDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
id | string | Required | Stable across ALL retries of the same delivery. The key for deduplication: delivery is at-least-once, not exactly-once. | evt_bk_01J8… |
type | string | Required | A receiver should acknowledge an unknown type with 2xx rather than fail — otherwise a new event can knock over an existing receiver.One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked | booking.confirmed |
createdAt | string | Required | 2026-09-05T10:00:00.000Z | |
data | WebhookBookingDataDto | WebhookEsimDataDto | WebhookCheckoutDataDto | Required | The payload. Shape depends on type. | — |
WebhookRetryResultDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
delivered | boolean | Required | Did your endpoint answer with a 2xx this time? | — |
delivery | WebhookDeliveryDetailDto | Required | — | |
delivery.id | string | Required | dl_01J8… | |
delivery.eventType | string | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked webhook.test | — |
delivery.eventId | string | Required | The business ID of what triggered this — your booking, your refund. | bk_01J8… |
delivery.status | string | Required | PENDING is waiting for the next automatic attempt. EXHAUSTED means: the automatic attempts are used up — nothing more happens on its own, but it can still be retried by hand. FAILED means: your endpoint answered 410 Gone, and we stopped instead of calling again.One of: PENDING SUCCEEDED FAILED EXHAUSTED | — |
delivery.attemptCount | number | Required | 3 | |
delivery.nextAttemptAt | string | null | Required | — | |
delivery.lastStatusCode | number | null | Required | 503 | |
delivery.lastError | string | null | Required | — | |
delivery.lastAttemptAt | string | null | Required | — | |
delivery.succeededAt | string | null | Required | — | |
delivery.createdAt | string | Required | — | |
delivery.endpoint | object | Required | — | |
delivery.endpoint.id | string | Optional | — | |
delivery.endpoint.url | string | null | Optional | — | |
delivery.endpoint.description | string | null | Optional | — | |
delivery.retryable | boolean | Required | Sending a successful delivery again would create a second event on your side — which is why it is not retryable. | — |
delivery.payload | object | Required | Byte for byte what we sent. A retry sends exactly that again, not a freshly serialized version of it. | — |
delivery.attempts | WebhookAttemptDto[] | Required | — | |
delivery.attempts[].id | string | Required | — | |
delivery.attempts[].attemptNumber | number | Required | Which attempt this is, counted from 1. | 3 |
delivery.attempts[].startedAt | string | Required | 2026-09-05T09:12:44.000Z | |
delivery.attempts[].durationMs | number | Required | 842 | |
delivery.attempts[].requestHeaders | map<string, string> | null | Required | The headers of THIS attempt, signature included. It differs in every attempt because it is computed over a fresh timestamp — with the old one the retry would fail your replay window. | — |
delivery.attempts[].statusCode | number | null | Required | Null means: no response arrived at all (timeout, DNS, connection). | 500 |
delivery.attempts[].responseBody | string | null | Required | Truncated to 4096 characters — somebody else's response is unbounded in length. | — |
delivery.attempts[].responseHeaders | map<string, string> | null | Required | — | |
delivery.attempts[].error | string | null | Required | — | |
delivery.attempts[].manual | boolean | Required | Was this attempt triggered by hand? | — |
delivery.attempts[].triggeredBy | string | null | Required | — |
WebhookTestResultDto
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
delivered | boolean | Required | — | |
delivery | WebhookDeliveryDetailDto | Optional | — | |
delivery.id | string | Required | dl_01J8… | |
delivery.eventType | string | Required | One of: booking.confirmed booking.failed booking.cancelled booking.modified checkout.created checkout.expired payment.succeeded payment.failed payment.refunded esim.provisioned esim.usage_threshold ledger.low_balance search_quota.threshold settlement.invoice_issued settlement.payment_failed apikey.revoked webhook.test | — |
delivery.eventId | string | Required | The business ID of what triggered this — your booking, your refund. | bk_01J8… |
delivery.status | string | Required | PENDING is waiting for the next automatic attempt. EXHAUSTED means: the automatic attempts are used up — nothing more happens on its own, but it can still be retried by hand. FAILED means: your endpoint answered 410 Gone, and we stopped instead of calling again.One of: PENDING SUCCEEDED FAILED EXHAUSTED | — |
delivery.attemptCount | number | Required | 3 | |
delivery.nextAttemptAt | string | null | Required | — | |
delivery.lastStatusCode | number | null | Required | 503 | |
delivery.lastError | string | null | Required | — | |
delivery.lastAttemptAt | string | null | Required | — | |
delivery.succeededAt | string | null | Required | — | |
delivery.createdAt | string | Required | — | |
delivery.endpoint | object | Required | — | |
delivery.endpoint.id | string | Optional | — | |
delivery.endpoint.url | string | null | Optional | — | |
delivery.endpoint.description | string | null | Optional | — | |
delivery.retryable | boolean | Required | Sending a successful delivery again would create a second event on your side — which is why it is not retryable. | — |
delivery.payload | object | Required | Byte for byte what we sent. A retry sends exactly that again, not a freshly serialized version of it. | — |
delivery.attempts | WebhookAttemptDto[] | Required | — | |
delivery.attempts[].id | string | Required | — | |
delivery.attempts[].attemptNumber | number | Required | Which attempt this is, counted from 1. | 3 |
delivery.attempts[].startedAt | string | Required | 2026-09-05T09:12:44.000Z | |
delivery.attempts[].durationMs | number | Required | 842 | |
delivery.attempts[].requestHeaders | map<string, string> | null | Required | The headers of THIS attempt, signature included. It differs in every attempt because it is computed over a fresh timestamp — with the old one the retry would fail your replay window. | — |
delivery.attempts[].statusCode | number | null | Required | Null means: no response arrived at all (timeout, DNS, connection). | 500 |
delivery.attempts[].responseBody | string | null | Required | Truncated to 4096 characters — somebody else's response is unbounded in length. | — |
delivery.attempts[].responseHeaders | map<string, string> | null | Required | — | |
delivery.attempts[].error | string | null | Required | — | |
delivery.attempts[].manual | boolean | Required | Was this attempt triggered by hand? | — |
delivery.attempts[].triggeredBy | string | null | Required | — |

