# Build a custom integration

**Connect your application to Booking.com's dedicated endpoint, discover the tools available to your account, and call them through the Model Context Protocol (MCP).**

## Implementation overview

Follow these steps:

1. [Configure the endpoint and authentication headers](#authentication).
2. [Initialise an MCP client and negotiate the protocol version](#connect-and-initialise).
3. [Discover the available tools and inspect their schemas](#discovering-available-tools).
4. [Call a tool with validated arguments](#calling-tools).
5. [Handle results and errors](#understand-the-response).


## Server fundamentals

The server uses [MCP over Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) with JSON-RPC 2.0 messages.

The endpoint is stateless. Requests do not require a persistent server-side MCP session, but your client must still complete the MCP initialisation and protocol version negotiation sequence before calling tools.

The tools and other MCP capabilities available to your application depend on your affiliate configuration.

## Authentication

Every request to the endpoint must include your affiliate ID and Demand API key.

* The MCP application validates the key without checking an IP allowlist for the requests.
* Network access restrictions outside the application are separate; confirm the endpoint access during onboarding.


### Credentials

Obtain or confirm these credentials with your Account Manager during the onboarding process:

* **Affiliate ID** – Identifies your Booking.com partner account and attributes requests to it.
* **Demand API key** – Authenticates your requests. Keep it out of version control, logs, screenshots and support messages.


## The MCP dedicated endpoint

Send MCP requests to:

```text
POST https://demandapi-mcp.booking.com/v1/sp/mcp/
```

* Keep the trailing slash.
* Send your affiliate ID in a header; do not append it to the URL.
* Use `POST` for MCP operations. `GET` and `DELETE` are not supported by this stateless endpoint and return `405 Method Not Allowed`.


The separate `/v1/mcp/:affiliateId` endpoint is for AI Platform Partner integrations. Use the MCP dedicated endpoint for the instructions in this guide.

### Required headers

Send exactly one value for each partner header with every request, including initialisation and tool discovery:

```http
x-booking-affiliate-id: <your-affiliate-id>
x-booking-authorization: <your-demand-api-key>
```

* `x-booking-affiliate-id` must contain only digits. Keep it as a string, including any leading zeroes.
* `x-booking-authorization` must contain your issued Demand API key unchanged. Do not add a `Bearer ` prefix.
* An `Authorization: Bearer ...` header is not a substitute for these headers.


For JSON-RPC `POST` requests, also send these transport headers. MCP clients normally set them for you:

```http
Content-Type: application/json
Accept: application/json, text/event-stream
```

#### Unauthorised access error

Missing or invalid partner headers and failed API key validation return HTTP `401 Unauthorized` with this JSON-RPC error:

```json
{
  "jsonrpc": "2.0",
  "error": { "code": -32001, "message": "Unauthorized" },
  "id": null
}
```

If authentication continues to fail after you have checked both headers, contact your Booking.com support representative. Do not include your Demand API key in the support request.

## Connect and initialise

Use an [MCP SDK](https://modelcontextprotocol.io/docs/sdk) to manage the MCP transport, initialisation and protocol version negotiation.

Before calling `tools/list` or `tools/call`, your client must:

1. Send an `initialize` request.
2. Negotiate a supported MCP protocol version.
3. Send the `notifications/initialized` notification.
4. Include the negotiated protocol version in subsequent HTTP requests, as required by the MCP transport.


MCP SDKs and compatible clients normally handle this sequence automatically.

A simplified `initialize` request has the following structure:

```
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "<supported-protocol-version>",
    "capabilities": {},
    "clientInfo": {
      "name": "example-client",
      "version": "1.0.0"
    }
  }
}
```

The request must be sent as an HTTP POST to the MCP endpoint with the required authentication and transport headers.

### MCP Inspector

For development and debugging, [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) lets you connect, inspect tool schemas, and test calls without building a client first. Its user interface and CLI require Node.js, npm, and `npx`.

To open the Inspector user interface:

```bash
npx @modelcontextprotocol/inspector
```

Select **Streamable HTTP**, enter:

`https://demandapi-mcp.booking.com/v1/sp/mcp/`

Add both required [partner headers](#required-headers) before connecting. Inspector shows the tools and schemas exposed to your account.

The CLI examples below use environment variables. Set:

* `BOOKING_AFFILIATE_ID`
* `BOOKING_DEMAND_API_KEY`


using your approved credential-management method.

Protect credentials during testing
Do not paste real keys into shared scripts or shell history. These CLI commands expand the key into a process argument. Run them on a trusted machine and do not share process listings containing the key.

## Discovering available tools

Call `tools/list` to retrieve the tool names and input schemas available to your account. Always use this response as the source of truth for tool names, arguments and validation rules.

### List available tools

```bash
npx @modelcontextprotocol/inspector --cli \
  https://demandapi-mcp.booking.com/v1/sp/mcp/ \
  --transport http \
  --method tools/list \
  --header "x-booking-affiliate-id: ${BOOKING_AFFILIATE_ID}" \
  --header "x-booking-authorization: ${BOOKING_DEMAND_API_KEY}"
```

The following tool names are examples only. The tools available to your account may differ and can change over time. The response from tools/list is authoritative:

* `accommodations_search`
* `accommodations_room_search`
* `attractions_search`
* `cars_search`
* `flights_search`
* `answer_property_qa_by_ids_v2`.


If the response includes `nextCursor`, call tools/list again with that cursor until no cursor is returned. Do not assume that one response contains every available tool.

If the list is empty or an expected tool is missing, confirm the capabilities enabled for your affiliate account with your Account Manager.

### Understanding tool schemas

Each tool's `inputSchema` describes its arguments, types, required fields, and constraints.

When a tool provides an `outputSchema`, use it to validate and process its `structuredContent`. If `structuredContent` is not present, handle the returned `content` blocks instead.

Inspect these schemas directly from `tools/list`. There is no separate schema-retrieval step.

## Calling tools

Choose a tool from the discovery response and invoke it with `tools/call`.

A tool call has the following JSON-RPC structure:

```
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "<tool-name>",
    "arguments": {
      "<argument-name>": "<value>"
    }
  }
}
```

* Validate the `arguments` against the tool's current `inputSchema` before sending the request.
* In Inspector CLI, pass the exact tool name with `--tool-name` and its arguments with `--tool-arg`. Start with a minimal valid request, then add optional parameters.


### Example: accommodation search

Use this example if your account exposes `accommodations_search` with the fields shown below.

Otherwise, adapt the name and arguments to the current schema returned by `tools/list`.

Set `CHECKIN_DATE` and `CHECKOUT_DATE` to future dates in `YYYY-MM-DD` format, with checkout after check-in.

This request searches for two adults and one room in Paris, using `gb` as the user country code and `GBP` as the currency.

```bash
npx @modelcontextprotocol/inspector --cli \
  https://demandapi-mcp.booking.com/v1/sp/mcp/ \
  --transport http \
  --method tools/call \
  --tool-name accommodations_search \
  --header "x-booking-affiliate-id: ${BOOKING_AFFILIATE_ID}" \
  --header "x-booking-authorization: ${BOOKING_DEMAND_API_KEY}" \
  --tool-arg 'destination=Paris' \
  --tool-arg "checkin_date=${CHECKIN_DATE}" \
  --tool-arg "checkout_date=${CHECKOUT_DATE}" \
  --tool-arg 'number_of_adults=2' \
  --tool-arg 'number_of_rooms=1' \
  --tool-arg 'user_country_code=gb' \
  --tool-arg 'user_locale=en-GB' \
  --tool-arg 'currency=GBP'
```

#### Understand the response

Check both the HTTP response and the MCP message. An HTTP success status alone does not mean that a tool call succeeded.

| Check | Meaning | What to do |
|  --- | --- | --- |
| HTTP error status | The HTTP request was rejected or could not be processed, for example because of authentication, rate limiting or a server error. | Read the response body and use the [troubleshooting table](#common-integration-issues). |
| Top-level JSON-RPC `error` | The MCP request failed instead of returning a `result`. | Inspect `error.code` and `error.message`. Correct the request or handle the reported failure. |
| `result.isError: true` | The tool call reported a failure. | Read the `content` blocks and follow the tool's recovery guidance. Do not assume that `structuredContent` is present. |
| Successful tool result | The tool call returned successfully. | Process `structuredContent` when present, using the tool's `outputSchema`. Otherwise, process the returned `content` blocks. |


* `content` contains MCP content blocks, such as text, images, resource links or embedded resources. It can be empty, and text blocks are not always JSON.
* `_meta` may contain additional metadata. Do not rely on fields in `_meta` unless they are documented for the specific tool.


### Example: accommodation search with no results

For the current `accommodations_search` implementation, a search with no matching accommodations is returned as a tool execution error.

Its text content contains a JSON error payload rather than a successful result with an empty accommodations array.

The following shows the text payload decoded for readability, not the outer JSON-RPC response:

```json
{
  "type": "not_found",
  "message": "No accommodations found for the given search parameters in Booking.com inventory",
  "details": [],
  "suggestedAction": "Ask the user to change the search parameters",
  "retryGuidance": "Suggest possible changes users can apply: different check-in and check-out dates, nearby regions to the destination, relaxing specific facility requirements, etc. Do not change any parameters yourself, only change based on user feedback. If user gives the same exact parameters like the previous request, do not send the same request again. In that case, refer the user to the previous search results."
}
```

* Surface the suggested action and retry guidance to the calling application or assistant.
* Do not silently relax the traveller's criteria or repeat the same request without a change.


## Common integration issues

| Status | Cause | Resolution |
|  --- | --- | --- |
| `400 Bad Request` | The server rejected the HTTP or JSON-RPC request format, for example because of malformed JSON or invalid transport headers. | Read the response error and check the JSON-RPC message and transport headers. |
| `401 Unauthorized` | Partner headers are missing or invalid, the Demand API key failed validation, or the authentication service is unavailable. | Check both `x-booking-*` headers, the digit-only affiliate ID and the unchanged Demand API key. If valid credentials still fail, contact support. |
| `403 Forbidden` | The credentials are valid, but the affiliate account is not enabled for the Demand partner endpoint. | Confirm that the affiliate account has been enabled during onboarding. |
| `404 Not Found` | The endpoint path is incorrect. | Use `/v1/sp/mcp/`, including the trailing slash and without an affiliate ID suffix. |
| `405 Method Not Allowed` | An unsupported HTTP method was used. | Use `POST` for MCP operations. |
| `429 Too Many Requests` | The rate limit was exceeded. | Retry according to the guidance in [Rate limiting](#rate-limiting). |
| `5xx Server Error` | A temporary server or dependency failure occurred. | Retry transient failures with bounded backoff. If the problem persists, contact support and include the request ID if one is available. Do not include credentials. |


### Rate limiting

After a `429` response, retry with exponential backoff and jitter. Respect any Retry-After value and apply a maximum retry count.

Do not retry `400`, `401`, `403`, `404` or `405` responses unless you correct the request.

A search with no results is not a rate-limit failure. Surface the returned guidance and ask the traveller or calling application whether the search criteria should change.

## Best practices

✅ Validate tool arguments against the current `inputSchema` returned by `tools/list`.

✅ Validate `structuredContent` against the tool's `outputSchema` when an output schema is provided.

✅ Handle content-only responses and tool execution errors.

✅ Implement timeouts for MCP requests.

✅ Handle `429` responses with bounded exponential backoff, jitter and `Retry-After` support.

✅ Test authentication, disabled affiliates, missing tools, invalid arguments, tool errors and rate limiting as separate cases.

✅ Log enough request context to diagnose failures, but exclude API keys, authentication headers and other credentials.

✅ If your application uses an LLM, show users when tools are available and when they are invoked. Keep the user in control of sensitive operations.

✅ Monitor schema changes and revalidate your integration when the available tools or their schemas change.