{
    "openapi": "3.1.0",
    "info": {
        "title": "HOTTEVIA Supplier API",
        "version": "1.0.0",
        "description": "The Supplier API is how a partner sends HOTTEVIA its hotels, rooms, rates\nand availability, and how it tells us what happened to a reservation we\ndelivered.\n\nEvery request is signed. Authentication is by credential and signature,\nnever by a session, and a credential belongs to one connection in one\nenvironment — a TEST credential cannot reach LIVE and vice versa.\n\nA new integration works entirely against TEST. LIVE access is granted\nafter certification and a commercial go-live approval, not by holding a\ncredential.",
        "contact": {
            "name": "HOTTEVIA Supplier Support",
            "email": "suppliers@hottevia.com"
        }
    },
    "servers": [
        {
            "url": "https://travel.cairocool.com/supplier-api",
            "description": "TEST — the only environment open to a new integration"
        }
    ],
    "security": [
        {
            "SupplierSignature": []
        }
    ],
    "components": {
        "securitySchemes": {
            "SupplierSignature": {
                "type": "apiKey",
                "in": "header",
                "name": "X-HOTTEVIA-Client-Id",
                "description": "Every request carries four headers and an HMAC-SHA256 signature\nover a canonical string. The signature covers the method, the\npath, the timestamp, the message id and a hash of the body, so\nchanging any of them invalidates it.\n\nCanonical string, joined by newlines, in this exact order:\n\n    METHOD\n    /path\n    timestamp\n    message-id\n    sha256(body)\n\nThe body hash is the hex SHA-256 of the raw request body, and of\nthe empty string when there is no body. Sign that canonical\nstring with your client secret and send the hex digest.\n\nA timestamp more than the allowed window from our clock is\nrejected, and a message id that has been seen before is rejected\nas a replay — so retry a failed call with a *new* message id,\nand repeat the *same* one only when you intend to prove\nidempotency."
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "description": "Every failure uses this envelope, whatever went wrong.",
                "properties": {
                    "request_id": {
                        "type": "string"
                    },
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "AUTH_MISSING",
                                    "AUTH_INVALID_CLIENT",
                                    "AUTH_INVALID_SIGNATURE",
                                    "AUTH_TIMESTAMP_EXPIRED",
                                    "AUTH_REPLAY_DETECTED",
                                    "CONNECTION_INACTIVE",
                                    "ENVIRONMENT_MISMATCH",
                                    "RATE_LIMITED",
                                    "VALIDATION_ERROR",
                                    "FORBIDDEN",
                                    "INTERNAL_ERROR",
                                    "HOTEL_NOT_FOUND",
                                    "ROOM_NOT_FOUND",
                                    "HOTEL_NOT_MAPPED",
                                    "ROOM_MAPPING_INVALID",
                                    "MAPPING_CONFLICT",
                                    "IDEMPOTENCY_CONFLICT",
                                    "ROOM_NOT_MAPPED",
                                    "PRODUCT_INACTIVE",
                                    "OWNERSHIP_CONFLICT",
                                    "INVALID_CURRENCY",
                                    "INVALID_DATE_RANGE",
                                    "INVALID_OCCUPANCY",
                                    "RATE_NOT_FOUND",
                                    "STALE_UPDATE",
                                    "COMMERCIAL_MODEL_UNKNOWN",
                                    "ARI_PARTIAL_FAILURE",
                                    "CERTIFICATION_REQUIRED"
                                ]
                            },
                            "message": {
                                "type": "string"
                            }
                        }
                    }
                },
                "example": {
                    "request_id": "req_a1b2c3d4",
                    "error": {
                        "code": "AUTH_INVALID_SIGNATURE",
                        "message": "signature mismatch"
                    }
                }
            },
            "Hotel": {
                "type": "object",
                "required": [
                    "external_hotel_id",
                    "name"
                ],
                "properties": {
                    "external_hotel_id": {
                        "type": "string",
                        "description": "Your own stable identifier. Never a name."
                    },
                    "name": {
                        "type": "string"
                    },
                    "city": {
                        "type": "string"
                    },
                    "country": {
                        "type": "string",
                        "maxLength": 2
                    },
                    "address": {
                        "type": "string"
                    },
                    "stars": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5
                    },
                    "latitude": {
                        "type": "number"
                    },
                    "longitude": {
                        "type": "number"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "inactive"
                        ]
                    }
                },
                "example": {
                    "external_hotel_id": "H-1001",
                    "name": "Nile View Hotel",
                    "city": "Cairo",
                    "country": "EG",
                    "stars": 4
                }
            },
            "Room": {
                "type": "object",
                "required": [
                    "external_room_id",
                    "name"
                ],
                "properties": {
                    "external_room_id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "max_occupancy": {
                        "type": "integer"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "inactive"
                        ]
                    }
                },
                "example": {
                    "external_room_id": "R-DBL",
                    "name": "Double Room",
                    "max_occupancy": 2
                }
            },
            "Rate": {
                "type": "object",
                "required": [
                    "external_rate_id",
                    "external_hotel_id",
                    "external_room_id",
                    "currency",
                    "amount"
                ],
                "properties": {
                    "external_rate_id": {
                        "type": "string"
                    },
                    "external_hotel_id": {
                        "type": "string"
                    },
                    "external_room_id": {
                        "type": "string"
                    },
                    "rate_plan_code": {
                        "type": "string"
                    },
                    "currency": {
                        "type": "string",
                        "maxLength": 3
                    },
                    "amount": {
                        "type": "number"
                    },
                    "valid_from": {
                        "type": "string",
                        "format": "date"
                    },
                    "valid_to": {
                        "type": "string",
                        "format": "date"
                    },
                    "meal_plan": {
                        "type": "string"
                    },
                    "refundable": {
                        "type": "boolean"
                    },
                    "min_stay": {
                        "type": "integer"
                    },
                    "commercial_model": {
                        "type": "string",
                        "enum": [
                            "net_rate",
                            "commission",
                            "net_invoicing"
                        ]
                    }
                },
                "example": {
                    "external_rate_id": "RT-1",
                    "external_hotel_id": "H-1001",
                    "external_room_id": "R-DBL",
                    "currency": "EGP",
                    "amount": 1200,
                    "valid_from": "2026-10-01",
                    "valid_to": "2026-10-31",
                    "commercial_model": "net_rate"
                }
            },
            "Availability": {
                "type": "object",
                "description": "Allocation and restrictions for one room on one date. Only the restrictions listed here are supported; anything else is rejected rather than silently ignored.",
                "required": [
                    "external_hotel_id",
                    "external_room_id",
                    "date"
                ],
                "properties": {
                    "external_hotel_id": {
                        "type": "string"
                    },
                    "external_room_id": {
                        "type": "string"
                    },
                    "date": {
                        "type": "string",
                        "format": "date"
                    },
                    "allocation": {
                        "type": "integer",
                        "description": "Null means free sale; zero means nothing left."
                    },
                    "stop_sell": {
                        "type": "boolean"
                    },
                    "min_stay": {
                        "type": "integer"
                    },
                    "closed_to_arrival": {
                        "type": "boolean"
                    },
                    "closed_to_departure": {
                        "type": "boolean"
                    }
                },
                "example": {
                    "external_hotel_id": "H-1001",
                    "external_room_id": "R-DBL",
                    "date": "2026-10-05",
                    "allocation": 3,
                    "stop_sell": false
                }
            },
            "BookingEvent": {
                "type": "object",
                "description": "What happened to a reservation we delivered. ACK only means you received it — confirmation is a separate, later event.",
                "required": [
                    "event_id",
                    "booking_reference",
                    "type"
                ],
                "properties": {
                    "event_id": {
                        "type": "string",
                        "description": "Your id for this event. Repeating it is safe and changes nothing."
                    },
                    "booking_reference": {
                        "type": "string"
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "ACK",
                            "ACCEPTED",
                            "PMS_CONFIRMED",
                            "REJECTED",
                            "CANCEL_CONFIRMED",
                            "CANCEL_REJECTED"
                        ]
                    },
                    "provider_reference": {
                        "type": "string"
                    },
                    "reason": {
                        "type": "string"
                    }
                },
                "example": {
                    "event_id": "ev-77",
                    "booking_reference": "HTV-12345",
                    "type": "PMS_CONFIRMED",
                    "provider_reference": "PMS-99"
                }
            }
        },
        "parameters": {
            "Timestamp": {
                "name": "X-HOTTEVIA-Timestamp",
                "in": "header",
                "required": true,
                "schema": {
                    "type": "string"
                },
                "description": "Unix seconds when the request was signed."
            },
            "MessageId": {
                "name": "X-HOTTEVIA-Message-Id",
                "in": "header",
                "required": true,
                "schema": {
                    "type": "string",
                    "format": "uuid"
                },
                "description": "Unique per request. Reusing one is treated as a replay."
            },
            "Signature": {
                "name": "X-HOTTEVIA-Signature",
                "in": "header",
                "required": true,
                "schema": {
                    "type": "string"
                },
                "description": "Hex HMAC-SHA256 of the canonical string."
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Credential, signature, timestamp or replay check failed.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "Forbidden": {
                "description": "Authenticated, but not permitted — wrong environment, inactive connection, or a certification the LIVE gate has not granted.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "Conflict": {
                "description": "Mapping, ownership or idempotency conflict.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "ValidationError": {
                "description": "The request was understood and refused.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "RateLimited": {
                "description": "Too many requests. Back off and retry with a new message id.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            }
        }
    },
    "tags": [
        {
            "name": "Sandbox",
            "description": "Prove the signature works before sending anything real."
        },
        {
            "name": "Catalog",
            "description": "Hotels and rooms, keyed on your own identifiers."
        },
        {
            "name": "Rates & Availability",
            "description": "Prices, allocation and restrictions."
        },
        {
            "name": "Booking",
            "description": "Events you send us about a reservation we delivered."
        }
    ],
    "paths": {
        "/v1/ari": {
            "post": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Send rates and availability together in one call.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Availability"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/availability": {
            "get": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Read stored availability.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Send allocation and restrictions.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Availability"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/booking-events": {
            "post": {
                "tags": [
                    "Booking"
                ],
                "summary": "Tell us what happened to a reservation we delivered.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/BookingEvent"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/hotels": {
            "get": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Read the hotels we hold for you, with their mapping status.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Create or update one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Hotel"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/hotels/{externalHotelId}": {
            "get": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Read the hotels we hold for you, with their mapping status.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Create or update one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Hotel"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/hotels/{externalHotelId}/rooms": {
            "post": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Create or update a room on one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Room"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            },
            "get": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Read the rooms we hold for one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/v1/hotels/{externalHotelId}/rooms/{externalRoomId}": {
            "put": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Create or update a room on one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Room"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            },
            "get": {
                "tags": [
                    "Catalog"
                ],
                "summary": "Read the rooms we hold for one of your hotels.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/v1/ping": {
            "get": {
                "tags": [
                    "Sandbox"
                ],
                "summary": "Check that your signature is accepted.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/v1/rates": {
            "get": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Read stored rates.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Send rates.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Rate"
                            }
                        }
                    }
                },
                "x-idempotent": true,
                "description": "Writes are idempotent on your own identifier: sending the same object again updates it rather than creating a second one."
            }
        },
        "/v1/rates/{externalRateId}": {
            "get": {
                "tags": [
                    "Rates & Availability"
                ],
                "summary": "Read stored rates.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/v1/test/echo": {
            "post": {
                "tags": [
                    "Sandbox"
                ],
                "summary": "Echo a signed body back, to verify body hashing.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/v1/whoami": {
            "get": {
                "tags": [
                    "Sandbox"
                ],
                "summary": "See which connection and environment your credential resolves to.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Timestamp"
                    },
                    {
                        "$ref": "#/components/parameters/MessageId"
                    },
                    {
                        "$ref": "#/components/parameters/Signature"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        }
    },
    "x-error-codes": [
        {
            "code": "AUTH_MISSING",
            "status": 401,
            "action": "Send the client id, timestamp, message id and signature headers."
        },
        {
            "code": "AUTH_INVALID_CLIENT",
            "status": 401,
            "action": "The credential is unknown or revoked. Create a new TEST credential in the portal."
        },
        {
            "code": "AUTH_INVALID_SIGNATURE",
            "status": 401,
            "action": "Rebuild the canonical string exactly, including the body hash, and re-sign."
        },
        {
            "code": "AUTH_TIMESTAMP_EXPIRED",
            "status": 401,
            "action": "Your clock has drifted. Sync it and retry with a fresh timestamp."
        },
        {
            "code": "AUTH_REPLAY_DETECTED",
            "status": 401,
            "action": "This message id was used before. Use a new one for a genuine retry."
        },
        {
            "code": "CONNECTION_INACTIVE",
            "status": 403,
            "action": "The connection is not in a state that accepts traffic. Check the portal."
        },
        {
            "code": "ENVIRONMENT_MISMATCH",
            "status": 403,
            "action": "A TEST credential cannot call LIVE, or the reverse."
        },
        {
            "code": "RATE_LIMITED",
            "status": 429,
            "action": "Slow down and retry with a new message id."
        },
        {
            "code": "VALIDATION_ERROR",
            "status": 422,
            "action": "Read the message: a field is missing, malformed, or a restriction is unsupported."
        },
        {
            "code": "FORBIDDEN",
            "status": 403,
            "action": "This connection may not act on that resource."
        },
        {
            "code": "INTERNAL_ERROR",
            "status": 500,
            "action": "Our fault. Retry with a new message id; if it persists, quote the request id."
        },
        {
            "code": "HOTEL_NOT_FOUND",
            "status": 404,
            "action": "We hold no hotel under that identifier for you."
        },
        {
            "code": "ROOM_NOT_FOUND",
            "status": 404,
            "action": "We hold no room under that identifier on that hotel."
        },
        {
            "code": "HOTEL_NOT_MAPPED",
            "status": 409,
            "action": "We have your hotel but have not mapped it to a canonical property yet. Nothing for you to do."
        },
        {
            "code": "ROOM_MAPPING_INVALID",
            "status": 422,
            "action": "The room does not belong to that hotel."
        },
        {
            "code": "MAPPING_CONFLICT",
            "status": 409,
            "action": "That identifier is already mapped elsewhere. Contact support."
        },
        {
            "code": "IDEMPOTENCY_CONFLICT",
            "status": 409,
            "action": "The same idempotency key was reused with a different body."
        },
        {
            "code": "ROOM_NOT_MAPPED",
            "status": 409,
            "action": "The room is not mapped yet. Rates for it are held until it is."
        },
        {
            "code": "PRODUCT_INACTIVE",
            "status": 409,
            "action": "The product is inactive; reactivate it before sending rates."
        },
        {
            "code": "OWNERSHIP_CONFLICT",
            "status": 409,
            "action": "Another source currently owns that inventory, so this one may not write it."
        },
        {
            "code": "INVALID_CURRENCY",
            "status": 422,
            "action": "Use a three-letter ISO currency the contract allows."
        },
        {
            "code": "INVALID_DATE_RANGE",
            "status": 422,
            "action": "The range is empty or inverted."
        },
        {
            "code": "INVALID_OCCUPANCY",
            "status": 422,
            "action": "Occupancy is outside what the room allows."
        },
        {
            "code": "RATE_NOT_FOUND",
            "status": 404,
            "action": "No rate under that identifier."
        },
        {
            "code": "STALE_UPDATE",
            "status": 409,
            "action": "A newer update already landed; this one is older and was not applied."
        },
        {
            "code": "COMMERCIAL_MODEL_UNKNOWN",
            "status": 409,
            "action": "State the commercial model on the rate."
        },
        {
            "code": "ARI_PARTIAL_FAILURE",
            "status": 207,
            "action": "Some rows applied and some did not. The response lists which."
        },
        {
            "code": "CERTIFICATION_REQUIRED",
            "status": 403,
            "action": "Live traffic needs a valid certification. It may have expired or been revoked."
        }
    ]
}