EDistribution API

Book an order on credit#

POST/v1/bookings

Re-prices the order and, only if its current total still matches acceptedTotal, runs the synchronous credit gate (402 on a denial, with the shortfall and available credit) before starting the booking. externalBookingId is the idempotency key: a replay with identical contents returns the same booking, charged once; a replay with different contents is 409, never a silent second booking. An order belonging to another agent group is 404, identically to one that doesn't exist.

202 is a real, expected outcome, not an edge case. The booking runs through a durable, external-first workflow; if it hasn't resolved by the time this call returns (e.g. the workflow started but hasn't finished), the response is 202 with status: "PROCESSING" — the credit charge IS authorized (chargedAmount reflects it), but bookingId is null until the booking actually lands. Poll GET /v1/bookings?externalBookingIds= with the same externalBookingId to discover the outcome once it resolves — this is the same idempotency key, so a repeat POST also converges on the resolved booking (same externalBookingId, same contents) rather than charging or booking a second time.

A replay never edits guests that are already on the order. Guests are matched on phone or email; anyone already present is left exactly as first submitted and only genuinely new guests are added. Re-POSTing to correct a typo in a guest's name, salutation or date of birth therefore has no effect, and the response still shows the original details — by replay time the booking may already have snapshotted them, and patching only one side would leave the order and the booking disagreeing. To change guest details, use a new order and a new externalBookingId. See the externalBookingId field docs.

Optional push callbacks. Supply callbackUrl and we POST the terminal outcome (booking.confirmed / booking.cancelled / booking.failed) to it instead of making you poll — signed with X-Elivaas-Signature: t=<unix-seconds>,v1=<hex> (HMAC-SHA256 over t + "." + rawBody, using your agent group's callback signing secret) and carrying a stable X-Elivaas-Event-Id because delivery is at-least-once. See the callbackUrl field docs for the full verification recipe, the payload shape, and the URL restrictions (https only, no private/metadata addresses, no redirects) that are enforced here with a 400. Polling is unaffected and stays the fallback: a callback that cannot be delivered is retried with backoff and then given up on.

Request

cURL
curl -X POST 'https://partner-api.elivaas.com/v1/bookings' \
  -u "$ELIVAAS_API_KEY:" \
  -H 'Content-Type: application/json' \
  -d &#39;{"orderId":"3fa85f64-5717-4562-b3fc-2c963f66afa6","externalBookingId":"po-48213-booking-1","acceptedTotal":0,"primaryGuest":{"guestId":"string","salutation":"string","firstName":"string","lastName":"string","email":"string","phone":"string","countryCode":"string","city":"string","dob":"2026-08-01","anniversary":"2026-08-01","position":0},"additionalGuests":[{"guestId":"string","salutation":"string","firstName":"string","lastName":"string","email":"string","phone":"string","countryCode":"string","city":"string","dob":"2026-08-01","anniversary":"2026-08-01","position":0}],"specialRequests":"string","callbackUrl":"https://example-pms.com/webhooks/bard-bookings"}'
Response 200
{
  "bookingId": "string",
  "externalBookingId": "string",
  "status": "CREATED",
  "chargedAmount": 0,
  "availableCreditAfter": 0
}

Request body#

orderIdstring<uuid>required

The order to book — from a prior POST /v1/orders call.

externalBookingIdstringrequired

Caller-supplied id for this booking attempt, unique per agent group. The idempotency key: replaying the same value with identical contents returns the same booking rather than creating a second one. Max 64 characters.

A replay will not edit an existing booking's guests. On a replay, any guest already on the order — matched on phone or email — is left exactly as first submitted; only guests not yet present are added. So re-POSTing with a corrected spelling, salutation or date of birth for a guest you already sent does NOT update them, and the response will still show the original details. This is deliberate: by the time you are replaying, the booking may already have snapshotted those guests, and silently patching one side would leave the order and the booking disagreeing with nothing to tell you which is right. Use a NEW externalBookingId against a new order if the guest details themselves need to change, and contact support to amend a booking that has already confirmed.

acceptedTotalnumberrequired

The order's grand total the caller is accepting, as last quoted. Compared against a fresh re-price at booking time; a mismatch is rejected rather than booking at a different price than the caller saw.

primaryGuestobjectrequired

A guest captured on the booking (primary or additional).

additionalGuestsobject[]

Additional guests on the booking, if any.

specialRequestsstring

Free-text special requests, passed through to the booking.

callbackUrlstring

Optional. An https URL we POST the booking's TERMINAL outcome to, so you do not have to poll a 202 PROCESSING booking. Supplying it changes nothing else: polling GET /v1/bookings?externalBookingIds= keeps working exactly as before and remains the system of record. Omit it and nothing is ever sent.

When you get called. Once, per terminal outcome: booking.confirmed, booking.cancelled, or booking.failed (the booking never materialised and the credit authorization was released). Intermediate PROCESSING is never pushed. A booking that is confirmed and later cancelled produces two callbacks.

Body. { eventId, eventType, occurredAt, status, externalBookingId, bookingId, reasonCode, reason }. status is CONFIRMED | CANCELLED | FAILED; reasonCode is machine-readable and non-null only on a failure (BOOKING_NOT_COMPLETED).

A later outcome supersedes an earlier one — order by occurredAt, do not assume the first one you receive is final. Two cases are expected and neither is a bug. (1) A booking we could not confirm within its window is reported booking.failed; if its workflow then lands late, you also receive booking.confirmed for the same externalBookingId. The later CONFIRMED is the truth. (2) A booking that is confirmed and later cancelled sends booking.confirmed then booking.cancelled. Dedupe is per eventId only — it does NOT collapse different event types for one booking, by design.

Delivery is at-least-once — you MUST dedupe. Every request carries X-Elivaas-Event-Id: <uuid>, stable across all retries of the same outcome, and the same id is in the body as eventId. Treat a repeat of an id you have already processed as a no-op.

Verifying the signature. Each request carries X-Elivaas-Signature: t=<unix-seconds>,v1=<hex>. To verify: take the RAW request body exactly as received (do not parse and re-serialise — that changes key order and whitespace and breaks the MAC), build t + "." + rawBody, compute HMAC-SHA256(secret, that) hex-encoded lowercase, and compare to v1 in constant time. Then reject anything where t is more than a few minutes old — that is what makes a captured request non-replayable. The secret is your agent group's callback signing secret, shown once when your API credential was issued; it is not your API key, and it is never retrievable from any read endpoint. The timestamp differs between retries of the same event, so verify each request on its own.

Requirements, enforced at request time with 400. Scheme must be https. The host must resolve exclusively to public addresses — loopback, RFC1918, link-local, unique-local, carrier-grade NAT, multicast, broadcast and cloud instance-metadata addresses are all rejected. Redirects are never followed: a 3xx counts as a failed delivery. Respond 2xx to acknowledge; anything else is retried with exponential backoff (6 attempts over roughly four hours) and then recorded as permanently failed, at which point polling is your fallback.

Responses#

StatusMeaning
200The booking resolved synchronously — CREATED on first conversion, ALREADY_EXISTS on an idempotent replay.
202The charge is authorized but the booking has not resolved yet (status: "PROCESSING", bookingId: null). Re-POST the identical request later to check again.
400Validation failure. The message names the offending parameter. Limits are rejected, never silently clamped: an over-length id list, a page size above 100 or a date window longer than 365 days all land here rather than coming back quietly truncated.
401Missing, malformed, revoked or unknown API key, or an agent group whose distribution access is disabled. Pass the key as the HTTP Basic username with an empty password.
402Credit check failed: reasonCode is CREDIT_LIMIT_EXCEEDED (with shortfall) or ACCOUNT_ON_HOLD. availableCredit reflects the group's headroom at the moment of the decision. No booking is made and no charge is posted to the ledger.
404No such resource on your channel. A resource that exists but belongs to another tenant is reported identically — the two are never distinguished, so this response can't be used to enumerate ids.
409Either the order's price moved since it was quoted (expectedTotal/breakup populated — re-price and resubmit with the new total) or externalBookingId was already used for a booking with different contents (those two fields absent). No booking is made either way.
429Rate limit exhausted for this credential. Wait Retry-After seconds before retrying; the budget is per credential, not per IP.

See Errors for the error body shape and which statuses are worth retrying.