Enquiries Channel manager Bookings Your website Booking engine Pricing
v2026-09

Create a booking

Write a booking your system took into Doorloom. This is the only endpoint that requires an idempotency key, and the only one that can refuse for reasons of inventory.

POST https://api.doorloom.com/api/integrations/v1/bookings Idempotency-Key required

Headers

Field Type Required Description
Authorization string Required Bearer <your API key>.
Content-Type string Required application/json.
Idempotency-Key string Required 8 to 128 characters, unique per create attempt. A UUID is ideal.

The idempotency contract

Retrying with the same key and the same body replays the original response byte-for-byte and never creates a second booking. The same key with a different body is refused with IDEMPOTENCY_KEY_REUSED. Keys are remembered for 24 hours.

Refusals are stored too. If a create fails with 409 UNAVAILABLE, retrying the same key replays that refusal rather than racing for the room again — so a naive retry loop cannot turn a conflict into a double booking.

Request body

Body

Field Type Required Description
external_booking_id string Required Your id for this booking. Max 128 characters. Unique within your integration — sending one twice returns the existing booking rather than creating another.
property object Required Which property is being booked.
property.doorloom_id integer Optional Doorloom's property id.
property.external_id string Optional Your alias for the property. Max 128 characters.
units array Required One entry per unit being booked. A two-unit stay is two entries. 1 to 20 entries.
units[].inventory_id integer Optional Pin a specific Doorloom unit.
units[].external_unit_id string Optional Pin a specific unit by your alias. Max 128 characters.
units[].adults integer Required 1 to 50.
units[].children integer Optional 0 to 50. Defaults to 0.
units[].meal_plan string Optional EP, CP, MP or AP.
units[].stay_price number Optional Room money for this unit across the whole stay. See pricing below.
check_in string Required YYYY-MM-DD. May not be before yesterday.
check_out string Required YYYY-MM-DD. Must be after check_in. Exclusive — the guest leaves this morning.
guest object Required The lead guest.
guest.name string Required Max 255 characters.
guest.phone_code string Optional Dialling code, max 8 characters. Defaults to +91.
guest.mobile string Required Max 20 characters.
guest.email string Optional Valid email, max 255 characters.
guest.country string Optional Max 64 characters.
pricing object Required What the guest is being charged.
pricing.total number Required The booking total. Doorloom stores it as given and does not re-price it.
pricing.currency string Optional Must be INR if sent.
pricing.includes_gst boolean Optional Whether total already includes GST.
pricing.breakdown object Optional Free-form. Stored verbatim and handed back to you unchanged; Doorloom never interprets it.
payment object Optional What you have collected so far.
payment.status string Optional paid, partial or unpaid.
payment.amount_collected number Optional Becomes the advance recorded on the booking.
payment.collected_by string Optional partner or host.
payment.reference string Optional Your payment reference. Max 128 characters.
source object Optional Where the booking came from.
source.channel string Optional e.g. airbnb. Max 64 characters. Set once at create — it cannot be patched later.
source.reference string Optional Your reference on that channel. Max 128 characters.
status string Optional confirmed or pending. Defaults to confirmed. Both hold inventory.
notes string Optional Max 2000 characters.

Give the property as either doorloom_id or external_id. Sending neither is a 422. Stay length is capped; a stay longer than the platform maximum is refused on check_out.

Pricing: you set it

Doorloom stores pricing.total as the booking total and does not re-derive it. Your system is the source of truth for the price of a booking it took.

Per-unit stay_price values are used when every unit has one. If any is missing, the total is split evenly across units instead. Send all of them or none.

payment.amount_collected becomes the advance, and the balance follows from the total.

Units: pinning versus allocating

Each entry in units[] is one unit of the property for the whole stay.

  • Give inventory_id or external_unit_id to pin a specific unit.
  • Give neither to have Doorloom allocate any free unit.

A pinned unit that is taken is refused, never swapped

Doorloom will not quietly move the guest to a different room. If you pinned it and it is not free, you get 409 UNAVAILABLE and nothing is written. Omit the unit reference if you are happy with any room.

Example

Request json
{
    "external_booking_id": "PMS-100234",
    "property": {
        "external_id": "VILLA-9"
    },
    "units": [
        {
            "external_unit_id": "ROOM-A",
            "adults": 2,
            "children": 1,
            "meal_plan": "CP",
            "stay_price": 8000
        },
        {
            "adults": 2,
            "meal_plan": "EP",
            "stay_price": 8000
        }
    ],
    "check_in": "2026-10-12",
    "check_out": "2026-10-14",
    "guest": {
        "name": "Asha Rao",
        "phone_code": "+91",
        "mobile": "9876543210",
        "email": "[email protected]",
        "country": "IN"
    },
    "pricing": {
        "total": 18600,
        "currency": "INR",
        "includes_gst": false,
        "breakdown": {
            "room": 16000,
            "meals": 2600
        }
    },
    "payment": {
        "status": "partial",
        "amount_collected": 4000,
        "collected_by": "partner",
        "reference": "pay_123"
    },
    "source": {
        "channel": "airbnb",
        "reference": "HM-77"
    },
    "status": "confirmed",
    "notes": "Late arrival, around 11pm"
}

The first unit is pinned to ROOM-A; the second is left for Doorloom to allocate.

Responses

Field Type Description
201 created A new booking. data is the booking object.
200 duplicate A booking with this external_booking_id already exists. It is returned unchanged, with data.duplicate: true added. Treat this as success.
Response 201 (abridged) json
{
    "success": true,
    "message": "Booking created",
    "data": {
        "id": 55231,
        "external_booking_id": "PMS-100234",
        "revision": 1,
        "origin": "partner",
        "status": "confirmed",
        "property": {
            "doorloom_id": 902,
            "external_id": "VILLA-9"
        },
        "check_in": "2026-10-12",
        "check_out": "2026-10-14",
        "guests": {
            "adults": 4,
            "children": 1
        },
        "units": [
            "…"
        ],
        "pricing": {
            "currency": "INR",
            "total": 18600,
            "advance": 4000,
            "balance": 14600
        },
        "…": "…"
    }
}

When the stay is not available

A 409 UNAVAILABLE means no free unit for the whole stay, or a pinned unit that is taken. Nothing is written. The response carries the per-night picture so you can correct your calendar without a second call.

Response 409 json
{
    "success": false,
    "code": "UNAVAILABLE",
    "message": "Amber Villa, Alibaug is not available for these dates.",
    "errors": {
        "availability": {
            "property": {
                "doorloom_id": 902
            },
            "window": {
                "from": "2026-10-12",
                "to": "2026-10-13"
            },
            "total_units": 2,
            "dates": [
                {
                    "date": "2026-10-12",
                    "available_units": 1,
                    "total_units": 2,
                    "is_blocked": false,
                    "block_scope": null,
                    "units_cap": null
                },
                {
                    "date": "2026-10-13",
                    "available_units": 0,
                    "total_units": 2,
                    "is_blocked": false,
                    "block_scope": null,
                    "units_cap": null
                }
            ]
        }
    }
}

The conflict window ends on the last night

errors.availability.window.to is check_out minus one day — the last night of the stay, not the departure date. It lines up with dates[], which lists nights.

The dates[] array is exactly an availability.changed payload minus units[], so you can feed it through the same code path.

Errors

Errors

Status Code Meaning
422 VALIDATION_FAILED The request body failed validation. errors is Laravel's field bag: an object keyed by field path, each holding an array of messages.
422 IDEMPOTENCY_KEY_REQUIRED POST /bookings was sent without a usable Idempotency-Key header.
422 IDEMPOTENCY_KEY_REUSED The key has already been used, with a different request body. Doorloom fingerprints the body (SHA-256 over key-sorted JSON), so key order does not matter but any value change does.
409 IN_PROGRESS An identical request is still being processed. Two requests raced; the first one is still inside its transaction.
422 PROPERTY_NOT_MAPPED The property reference is missing, unknown, or outside your integration's coverage. Coverage is set by Doorloom staff and only ever includes properties the brand owner owns outright, never cohosted ones.
422 PROPERTY_ARCHIVED The property exists and is covered, but the host has archived it. Archived properties are off-market and cannot take bookings.
422 UNIT_NOT_MAPPED A units[] line names a unit alias Doorloom does not know, or one that belongs to a different property.
422 UNIT_INACTIVE The unit is mapped but no longer sellable — deactivated or deleted by the host.
409 UNAVAILABLE There is no free unit for the whole stay, or a unit you pinned is taken. Nothing was written. Doorloom never silently swaps a pinned unit for another.
429 RATE_LIMITED You exceeded one of the rate limits. The standard Retry-After and X-RateLimit-* headers are set on the response.

What happens afterwards

A successful create emits two things: an availability.changed event for the property, because inventory moved, and a booking.changed event with origin: "partner" — your own write echoed back. Neither requires any action from you.

Something unclear or wrong on this page? Write to [email protected] and tell us which page — we would rather fix the doc than answer the ticket twice.