3RPMS General API Integration Knowledge Base

Welcome to the 3RPMS GraphQL API documentation. This API allows you to interact with the 3RPMS Property Management System.

3RPMS GraphQL API is based on GraphQL. Visit https://graphql.org/ to learn more about the protocol.

API Endpoints
Production server:
https://www.3rpms.de/graphql
​
Sandbox server:
https://demo.3rpms.de/graphql

Overview

Prerequisites

Important

Please, read the 3RPMS® GENERAL API INTEGRATION GUIDE first!

GraphQL basics and tips

Although there is a lot more about GraphQL, we hereby list the very basics you should bear in mind when starting the integration.

Bear in mind!

All HTTP calls to the server must use POST method.

Access

Endpoint

Production server endpoint: https://www.3rpms.de/graphql (please make sure you use www.)

Sandbox server endpoint: https://demo.3rpms.de/graphql

Authentication

All requests require an API key in the header Authorization: Bearer <myapikey> where

API key

Create an API key in 3RPMS® under Settings -> Integrations access or ask the administrator of the hotel to do it if you do not have access.

Each hotel can have multiple API keys, each of which can have the following restrictions:

  • API Key has read-only access
  • API Key has write access
  • API Key has been disabled (when you want to restrict access temporary)
Important

Contact support if you are not sure if an integration should have write or read-only access!

Changelog

Show changelog

This changelog lists integration-relevant changes, in chronological order.

2026-01-26

Improvements

  • Add Reservation.paymentTerms and Query.paymentTerms fields.
query Reservations {
  reservations {
    edges {
      node {
        code
        paymentTerms {
          name
        }
      }
    }
  }
}

2026-01-06

Improvements

  • Add Reservation.billingClient and Reservation.billingContact fields. When present, these client details will be used for invoices instead of reservation owner.
  • Add UpdateReservationInput.clientId, UpdateReservationInput.contactId, UpdateReservationInput.billingClientId and UpdateReservationInput.billingContactId fields to updateReservation mutation
  • Add simplified room availability API to query for category availability for each date. This API is similar to how rates and restrictions are already queried.
query Availability($dateRange:DateRangeInput!) {
  settings {
    categories {
      edges {
        node {
          id
          name
          availability(filter:{date:$dateRange}) {
            date
            availability
          }
        }
      }
    }
  }
}

2025-12-01

Improvements

  • Add updateReservation mutation
    • Only Reservation.groupName can be updated

2025-09-15

Improvements

  • Add Client.newsletterSubscriptionDoubleOptInAt field. A non-null value represents that the email has been verified and is known to be valid.

2025-07-24

Documentation

2025-07-23

Improvements

  • Add updateCategoryRestrictions mutation

2025-06-11

Improvements

  • Add RoomSetup.countTowardsPerformance field.
    • Some rooms may be used for purposes other than lodgings, for example, conference rooms or car parking spaces. In such cases, when this field is false, the room should not be considered towards Key Performance Metrics.

2025-05-16

Improvements

2025-05-06

Improvements

  • Add description field to Category type
{
                    settings {
                      categories {
                        edges {
                          node {
                            # By default, uses language set in `Accept-Language` header (by default, german)
                            description
                            
                            # Or, can override to select a specific language. argument must be a 2-letter country code
                            enDescription: description(language: "en")
                          }
                        }
                      }
                    }
                  }
                  

2025-02-04

Improvements

  • Added cancelledAt field to Reservation type.

2025-02-03

Improvements

  • Added daily rates breakdown fields to RoomRate type:
    • packPriceGross
    • packPriceNet
    • lodgingGross
    • lodgingNet

Deprecations

  • Deprecated RoomRate.amount field in favour of RoomRate.packPriceGross

2025-01-20

Improvements

  • Added bookingChannelCode to ReservationFilter
  • Added logical or clause to all filters. See documentation

2024-10-14

Improvements

  • Added Person.stayPreferences and Person.mealPreferences as a replacement for Person.preferences
  • Added Company.stayPreferences as a replacement for Company.preferences

Deprecations

  • Deprecated Person.preferences , Company.preferences, and Client.preferences.

2024-09-11

Improvements

  • Mutation updateRoomSetup added.
    • At the moment, the only field that can be updated is cleaningStatus

2024-09-05

Improvements

  • Add pagination arguments for RoomStay.dailyRates connection

2024-07-17

Improvements

2024-03-25

Improvements

2023-09-26

Improvements

2023-05-16

Deprecations

  • Deprecate RoomSetup.bedLinenChange field

2023-03-23

Breaking Changes

  • graphql query errors no longer include extensions.category=graphql
Before After
{
"errors": [
{
"message": "Syntax Error",
"extensions": {
"category": "graphql"
},
}
]
}
{
"errors": [
{
"message": "Syntax Error"
}
]
}

There is no change for other category errors. There is no change for queries that dont result in an error.

2023-02-13

Improvements

  • Adds External Sales API:
    • Type ExternalSalesProduct added
    • Mutation createExternalSalesProduct added
    • Mutation updateExternalSalesProduct added
    • Mutation createExternalSale added
    • Query externalSalesProducts added

Documentation

2023-02-06

Improvements

  • Added check_in and check_out to RoomStayFilter. Allows to, for example, retrieve all checked in rooms that have not yet checked out.

2023-02-01

Breaking Changes

  • Union RoomAccessKey replaced with an interface of the same name. No queries are expected to break, but tools inspecting the schema might be affected.

Improvements

  • Query roomAccessKeys added to retrieve all room access keys your integration is allowed to grant/revoke to rooms
  • MutationcreateRoomAccessKey added to create a room access key - room pin or key storage (pin,compartment) pair
  • Mutation addRoomAccessKey added to assign a room access key to a room
  • Mutation removeRoomAccessKey added to remove room access key from a room
  • Mutation deleteRoomAccessKey added to delete a room access key
  • Type RoomAccessKeyStorage added for reading (pin, compartment) pairs. A sibling type to RoomAccessPin
  • Added configuration error category, for errors that must not be visible to guests, but should be shown for hotel staff.

Documentation

2023-01-23

Improvements

  • Field Reservation.selfcheckinStatus added
    • Returns an enum with values DISABLED, ENABLED, AVAILABLE, COMPLETED

Deprecations

  • Field Reservation.selfcheckinEnabled deprecated in favour of Reservation.selfcheckinStatus

2022-12-19

Improvements

  • Field RoomStay.roomAccessKey added to allow retrieving room pin code for hotels with salto integration set up.

2022-11-14

Improvements

  • Field Reservation.totalAmount added
  • Field Reservation.openAmount added

2022-10-27

Breaking Changes

  • Client type was split into Person and Company types. Most existing queries are expected to continue working, this will affect your integration if __typename is used.

Improvements

  • Query clientTitles added - a list of all active client titles
  • Query communicationLanguages added - a list of available communication languages, ordered by hotel's preference.
  • Mutation addRoomStayGuest added - allows adding an existing client as a guest to a room
  • Mutation removeRoomStayGuest added - allows removing a client as a guest to a room
  • Mutation createClient added - allows creating new Person clients.
  • Mutation updateClient added - allows updating any existing client
  • New fields to Person type added - clientTitle, street, zipcode, city, country, fax, preferences, nationality, passportNumber, idCardNumber, issuingAuthority
  • New fields to Company type added - street, zipcode, city, country, fax, preferences
  • Add a @chain directive to allow using a result from one operation as input for the next operation, within one request.

Deprecations

  • Deprecate Person.title field in favour of Person.clientTitle
  • Deprecate Person.company field - value is always empty
  • Deprecate Company.firstname field - value is always empty
  • DeprecateCompany.lastname field - value is always empty
  • Deprecate Company.title field - value is always empty
  • Deprecate Company.carPlateNumber field - value is always empty
  • Deprecate Company.birthday field - value is always null
  • Deprecate Company.birthdayGreetingsEnabled field - value is always false
  • Deprecate Client.title, Client.firstname, Client.lastname, Client.carPlateNumber, Client.birthday, Client.birthdayGreetingsEnabled - use the implementation Person type
  • Deprecate Client.company - use the implementation Company type.

< 2022-10-27

Changes before 2022-10-27 are not tracked.

GraphQL API

Why GraphQL?

Advantages of GraphQL

Here are several advantages of GraphQL protocol relevant to API clients integrating with a GraphQL endpoint.

1. Speed

GraphQL is faster than other communication APIs because it facilitates you to cut down your request query by choosing only the specific fields you want to query.

2. No over-fetching and under-fetching problems

The main advantage of GraphQl over REST is that REST responses contain too much data or sometimes not enough data, which creates the need for another request. GraphQL solves this problem by fetching only the exact and specific data in a single request.

3. Hierarchical Structure

GraphQL follows a hierarchical structure where relationships between objects are defined in a graphical structure. Here, every object type represents a component, and every related field from an object type to another object type represents a component wrapping another component.

4. Defines a data shape

When we request GraphQL queries to the server, the server returns the response in a simple, secure, and predictable shape. So, it facilitates you to write a specific query according to your requirement. This makes GraphQL really easy to learn and use.

Examples

Let's say, you want to get room stays with arrival, departure dates, and a list of arriving guest names

GraphQL

With GraphQL there would be just one request and it would look like this:

query ArrivingPrimaryGuests($date: Date!) {
  room_stays(
    filter: { reservation_from: { gt: $date } }
  ) {
    edges {
      node {
        id
        arrival: reservation_from
        departure: reservation_to
        first_guest {
          firstname
          lastname
        }
      }
    }
  }
}

In response you would get a structured resultset with the information you requested only:

{
  "data": {
    "room_stays": {
      "edges": [
        {
          "node": {
            "id": 1,
            "arrival": "2022-02-21",
            "departure": "2022-02-28",
            "first_guest": {
              "firstname": "Matt",
              "lastname": "Luis"
            }
          }
        },
        {
          "node": {
            "id": 2,
            "arrival": "2022-02-22",
            "departure": "2022-02-26",
            "guests": {
              "firstname": "Linda",
              "lastname": "Webber"
            }
          }
        }
      ]
    }
  }
}

Rate Limiting

Rate Limits

There's a limit of 200 requests per minute per IP. Exceeding this limit will block further connections for a period of 20 minutes.

Optimisation

Reducing queries

When querying for multiple records by id at the same time, often the queries can be combined:

Bad Example Good Example
{
 room_stays(filter: {id: {eq: "<room stay id 1>"}}) {
  ...
 }
}

{
 room_stays(filter: {id: {eq: "<room stay id 2>"}}) {
  ...
 }
}
{
 room_stays(filter: {id: {in: ["<room stay id 1>", "<room stay id 2>"]}}) {
  ...
 }
}

Batching queries

See Query Batches

Filters

Fields with a filter argument allow, as the name implies, filtering the result-set items.

Operator reference

Some fields may support only a subset of operators.

Operator Description Example
lt Larger than reservation_from: {lt: "2022-02-18"}
le Larger or equal reservation_from: {le: "2022-02-18"}
eq Equal reservation_from: {eq: "2022-02-18"}
in Equals to any of the values reservation_from: {in: ["2022-02-18", "2022-02-20"]}
ge Greater or equal reservation_from: {ge: "2022-02-18"}
gt Greater than reservation_from: {gt: "2022-02-18"}

Logical Operators

In addition to comparisons, the following logical operators are also supported:

Operator Description Example
not Negates nested criteria not: {reservation_from: {eq: "2022-02-18"}}
or At least one of the nested criteria must match or: [{reservation_from: {eq: "2022-02-18"}}}, {reservation_from: {eq: "2022-02-25"}}}]

Examples

In these example we'll be using root room_stays field:

room_stays(filter: RoomStayFilter): RoomStayConnection!

Filter one field

{
    room_stays(
        filter: {
            id: { in: ["nQZA3P1U1", "J0lZ3H4Ml"] }
        }
    ) {
        ...
    }
}

Here we look up specific RoomStay items by id

Filter multiple fields

When multiple fields are used, they all must match.

{
    room_stays(
        filter: {
            reservation_from: { lt: "2022-02-28" }
            reservation_to: { ge: "2022-02-18" }
        }
    ) {
        ...
    }
}

Here we look up RoomStay items that are staying between Feb 18th and Feb 28th.

Filter using multiple operators

Filters are not limited to using just one operator - any subset of the allowed operators can be used at the same time.

{
    room_stays(
        filter: {
            reservation_from: {
                le: "2022-02-27"
                ge: "2022-02-21"                
            }
        }
    ) {
        ...
    }
}

Here we look up RoomStay items that arrived between Feb 21st and Feb 27th.

Negation

Negation is done by using the reserved not field.

{
    room_stays(
        filter: {
            reservation_from: { gt: "2022-02-18" }
            not: {
                reservationStatus: { eq: CANCELLED }
            }
        }
    ) {
        ...
    }
}

Here we find all RoomStay with arrival after Feb 18th that have not been cancelled.

Disjunction

Logical Disjunction can be done by using the reserved or field:

{
    room_stays(
        filter: {
            or: [
                {reservation_from: { eq: "2022-02-04" }},
                {reservation_from: { eq: "2022-02-11" }},
                {reservation_from: { eq: "2022-02-18" }},
                {reservation_from: { eq: "2022-02-25" }},
            ]
        }
    ) {
        ...
    }
}

Here we find all RoomStay items that arrive on any friday in February 2022

Pagination

Specification

3RPMS implements relay-style connections for pagination. For detailed information see the GraphQL Cursor Connections Specification .

GraphQL allows you to fetch only the fields you need, with no unnecessary overhead. This helps keep network responses small and fast.

However, GraphQL doesn't automatically guarantee small responses. This is especially apparent when a field contains a list. To avoid enormous response from a seemingly small query, lists, containing unbounded maximum amount of items, use pagination:

query RoomStays($cursor: String = null) {
    reservations(first:10 after:$cursor) {
        edges {
            node {
                # reservation fields
            }
        }
        pageInfo {
            hasNextPage
            endCursor
        }
    }
}

Here, when no $cursor is provided, only the first 10 reservations are returned. To retrieve the next 10 reservations, the same query can be sent with $cursor set to value from endCursor field; and so on for the next 10 reservations. When there are no more results, hasNextPage will be set to false.

While the example uses reservations, it works the same for all connections.

The response to the previous example would look like this:

{
  "data": {
    "reservations": {
      "edges": [
        {"node": {...}},
        {"node": {...}},
        ... 8 more results       
      ],
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "bWFkZSB5b3UgbG9vaw=="
      }
    }
  }
}
Best practice

Load only as many records you need. If you're using only the first result, then specify first:1; or any other value depending on how you use the result set.

Important

Cursors are opaque strings, and must be passed back to the server as-is. Cursors should be stored only long enough to query the next page, and should not be stored long-term. Cursors may be different for each query depending on provided arguments or other scenarios.

Defaults

When no cursor arguments are provided, by default first:20 will be used, that is, only the first 20 items will be returned.

Unless otherwise specified on the field, maximum number of items a connection can return is 100.

Backward pagination

In addition to previously described forward pagination, 3RPMS also supports backwards pagination using last and before arguments, and startCursor and hasPreviousPage fields.

query RoomStays($cursor: String = null) {
    reservations(last:10 before:$cursor) {
        edges {
            node {
                # reservation fields
            }
        }
        pageInfo {
            hasPreviousPage
            startCursor
        }
    }
}

Here the last 10 reservations are returned. Similarly to before, to retrieve the previous 10 reservations, same query can be sent with $cursor set to value from $startCursor. When there are no previous reservations, hasPreviousPage will be false

Errors

GraphQL requests cannot be fully executed in some cases - malformed query, insufficient access, or validation errors. For schema and query errors, refer to GraphQL's specification https://spec.graphql.org/June2018/#sec-Errors. This section will cover only errors not part of the graphql specification.

Atomic mutations

All mutations succeed, or none succeed. When sending multiple mutations in one request, and any one of them results in an error, all other mutations should be considered failed as well. In such cases, retrieved data is only informative and should be discarded.

Error Structure

In case of an error, a top-level error field will be returned by 3RPMS and will contain one or more errors. Each error will always have, at minimum, a message field.

In addition to graphql errors, 3RPMS will return the following errors:

  • Configuration Errors - Errors that require hotel staff, or 3RPMS support intervention to resolve and will fail with any provided input. For example, some functionality has not been enabled for the hotel.
  • Validation Errors - Errors that user is expected to be able to recover from by inputting different data. For example, value outside of expected range or some record not found.
  • Auth Errors - Api token is not provided or is incorrect

Configuration

Configuration errors will have the extension.category field set to "configuration".

Configuration errors must not be shown to hotel guests, but may be shown to hotel staff and can be logged by your integration. These errors can be resolved only by hotel staff in 3RPMS system, or by contacting 3RPMS support. For example, some functionality has not been enabled for the hotel.

{
  "errors": [
    {
      "message": "This hotel has not beet set up to support Room Access Key api",
      "extensions": {
        "category": "configuration"
      },
    }
  ]
}

Validation

Validation errors always will have the extensions.category field set to "validation".

Validation errors are meant to be resolvable by end-users, for example, by modifying the form in your application, and attempting again. These errors are safe to show to hotel guests, and usually can be recovered from.

Forwards Compatibility

Validation messages can change, and additional validations may be added at any time. These messages must not be used in comparisons.

Argument errors

For errors related to input arguments, extensions.argumentPath field will contain a path to the field that caused an error (similar to path in GraphQL schema errors).

Request

query AvailableRooms {
    inventory(filter: {
        period: {
            start:"2022-09-08"
            end:"2022-09-08"
        }
        categories:["a", "b", "c"]
    }) {
        # ...
    }
}

In this example, filter has invalid data - a requested category ("c") does not exist.

Response

{
  "errors": [
    {
      "message": "Sorry, Property Category not found",
      "extensions": {
        "argumentPath": [
          "filter",
          "categories",
          2
        ],
        "category": "validation"
      },
      "path": [
        "inventory"
      ],
    }
  ]
}

extensions.argumentPath can be followed to find the field that caused an error.

Non-argument errors

For validation errors that cannot be attributed to any specific input input argument, extensions.argumentPath will not be added

Request

In this example, provided data is correct, but room cannot be updated for other reasons.

mutation CheckIn {
    updateRoomStay(input:{id:"119157" check_in:"2022-09-08T16:00:00+00:00"}) {
        roomStay {
            check_in
            check_out
        }
    }
}

Response

{
  "errors": [
    {
      "message": "This reservation was cancelled",
      "extensions": {
        "category": "validation"
      },
      "path": [
        "updateRoomStay"
      ]
    }
  ]
}
Validation message language

By default, validation error messages are in German. To see errors in English, add a Accept-Language header to each graphql request:

Accept-Language: en

Besides german de and english en no other language is supported at the moment.

Auth

"category": "auth" errors will be returned in the following cases:

  • Missing api token (Authorization: Bearer api_... header)
  • Api token incorrect
    • Token might have been disabled, or rotated
  • Running a mutation with read-only permissions.

This error must not be shown to end-guests, but might be relevant for hotel staff.

Response

{
  "errors": [
    {
      "message": "Operation \"query\" not allowed",
      "extensions": {
        "category": "auth"
      }
    }
  ]
}

GraphQL Error

When a malformed query, non-existent field or other graphql-specific error is returned, there will be no extensions.category field:

{
  “errors”: [
    {
      “message”: “Syntax Error”
    }
  ]
}

GraphQL errors must not be shown to end-guests or hotel staff. These errors may require a change in your integration.

Extra features

Query Fragments

You can encapsulate common data structures in Fragments and reuse them.

Without Fragments

In the following example, you will immediately notice that the data structure of guests and first_guest are identical due to the fact they both return objects of type Person. Scroll to the With Fragments section to see how this can be optimized.

query RoomStays {
  room_stays(filter: { date: "2022-10-27" }, first: 2) {
    edges {
      node {
        id
        room_setup {
          name
        }
        guests {
          clientTitle {
            name
          }
          firstname
          lastname
          language
          email
          telephone
          mobile
          carPlateNumber
        }
        first_guest {
          clientTitle {
            name
          }
          firstname
          lastname
          language
          email
          telephone
          mobile
          carPlateNumber
        }
      }
    }
  }
}

With Fragments

The previously redundant guests and first_guest data structure blocks now have been encapsulated in a reusable Fragment called Guest, so instead of repeating the complete list of attributes, it is enough to reuse the fragment by specifying it as ...Guest

query RoomStays {
                    room_stays(filter: { date: "2022-10-27" }, first: 2) {
                      edges {
                        node {
                          id
                          room_setup {
                            name
                          }
                          guests {
                            ...Guest
                          }
                          first_guest {
                            ...Guest
                          }
                        }
                      }
                    }
                  }
                  
                  fragment Guest on Person {
                    clientTitle {
                      name
                    }
                    firstname
                    lastname
                    language
                    email
                    telephone
                    mobile
                    carPlateNumber
                  }
                  

Query variables

A query without variables

In the following example, you will notice that a static value "2022-02-18" is provided for the date attribute:

query RoomStays {
    room_stays(
        filter: {
            date: "2022-02-18"
        }
    ) {
        edges {
            cursor
            node {
                id
                reservation {
                    code
                }
                room_setup {
                    expectedCleaningStatus(date: "2022-02-18")
                }
            }
        }
    }
}

A query with variables

This example demonstrates how the previously static value can be turned into a $date variable:

query RoomStays($date: Date!) {
    room_stays(
        filter: {
            date: $date
        }
    ) {
        edges {
            cursor
            node {
                id
                reservation {
                    code
                }
                room_setup {
                    expectedCleaningStatus(date: $date)
                }
            }
        }
    }
}

Now you can pass the value via $date variable and reuse it in the query if necessary.

Multiple queries within one request

Using aliases

You can perform multiple queries within one request using GrapQL aliases. In the following example, we will call performanceStatistics query twice but with different input arguments which very much makes sense if you want to get data for today and tomorrow, for example

Variables

{
    "today": "2022-03-01",
    "tomorrow": "2022-03-02"
}

Query

query PerformanceStatistics($today: Date!, $tomorrow: Date!) {
                    today: performanceStatistics(filter: { date: $today }) {
                      ...PerformanceStatistics
                    }
                    tomorrow: performanceStatistics(filter: { date: $tomorrow }) {
                      ...PerformanceStatistics
                    }
                  }
                  
                  fragment PerformanceStatistics on PerformanceStatistics {
                    occupancy
                    adr
                    revPAR
                  }
                  

Using batches

You can execute multiple GraphQL queries within one GraphQL request, like this:

Query

[
    {
        "query":"query PerformanceStatistics($date: Date!) { performanceStatistics(filter: { date: $date }) { occupancy } }",
        "variables": {
            "date": "2022-02-28"
        }
    }, 
    {
        "query":"query PerformanceStatistics($date: Date!) { performanceStatistics(filter: { date: $date }) { occupancy } }",
        "variables": {
            "date": "2022-02-27"
        }
    }
]

Chained Operations

The @chain(variable: [const]String!) directive enables passing data from one operation to the next, when sending batch operations. In the subsequent operation, the value is provided as a variable with the name specified in the directive.

This helps in cases, when a mutation depends on data returned by a previous mutation - using a @chain directive both mutations can be sent in one request instead of two

Example

An example, to create a guest and immediately reference it in the next mutation to add it to a room:

POST https://www.3rpms.de/graphql
                  Content-Type: application/json
                  Authorization: <your api token>
                  
                  [
                    {
                      "query": "mutation CreateGuest($createGuest: CreatePersonInput!) { createClient(input: {person: $createGuest}) { client { id @chain(variable:\"clientId\") } } }",
                      "variables": {
                        "createGuest": {
                          "lastname": "Přemyslid",
                          "country": "de",
                          "language": "de"
                        }
                      }
                    },
                    {
                      "query": "mutation AddGuestToRoom($roomStayId:ID! $clientId:ID!) { addRoomStayGuest(input:{roomStayId:$roomStayId clientId:$clientId}) { roomStay { guests { id } } } }",
                      "variables": {
                        "roomStayId": "<room id>"
                      }
                    }
                  ]
                  

Notice how the 2nd mutation AddGuestToRoom uses a variable $clientId that it does not provide - the variable comes from the previous mutation. However, if there were more operations, $clientId variable would not be available in the 3rd (or subsequent) operations - @chain directive provides variables only for the one subsequent operation.

According to GraphQL specification, queries and mutation selections can be executed in any order. To avoid determinism issues, there's the following restrictions on using @chain:

  • @chain cannot be used on a field that is a descendant of a list [Type].
  • Same variable name cannot be declared more than once.

In addition, to reduce accidental variable overwriting, there's also the following restrictions:

  • @chain variable is not allowed to override a variable already sent in the request

Queries

room_stays

Description

Get a list of reserved rooms. By default, rooms from cancelled reservation are not returned.

If a previously requested room's id is no longer returned, assume it has been moved to a different day, deleted or reservation cancelled altogether.

If present, returns rooms after $after cursor and at most $first entries.

Similarly, returns rooms before $before cursor, and at most $last entries.

Response

Returns a RoomStayConnection!

Arguments
Name Description
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String

Example

Query
query Room_stays(
  $filter: RoomStayFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  room_stays(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...RoomStayEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": RoomStayFilter,
  "first": 987,
  "after": "xyz789",
  "last": 123,
  "before": "xyz789"
}
Response
{
  "data": {
    "room_stays": {
      "edges": [RoomStayEdge],
      "pageInfo": PageInfo,
      "totalCount": 987
    }
  }
}

reservations

Description

A list of all reservations.

If present, returns reservation after $after cursor and at most $first entries.

Similarly, returns reservation before $before cursor, and at most $last entries.

Response

Returns a ReservationConnection!

Arguments
Name Description
filter - ReservationFilter
first - Int
after - String
last - Int
before - String

Example

Query
query Reservations(
  $filter: ReservationFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  reservations(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...ReservationEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": ReservationFilter,
  "first": 987,
  "after": "xyz789",
  "last": 123,
  "before": "abc123"
}
Response
{
  "data": {
    "reservations": {
      "edges": [ReservationEdge],
      "pageInfo": PageInfo,
      "totalCount": 987
    }
  }
}

clients

Description

A list of all clients.

If present, returns clients after $after cursor and at most $first entries.

Similarly, returns clients before $before cursor, and at most $last entries.

Response

Returns a ClientConnection!

Arguments
Name Description
filter - ClientFilter
first - Int
after - String
last - Int
before - String

Example

Query
query Clients(
  $filter: ClientFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  clients(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...ClientEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": ClientFilter,
  "first": 123,
  "after": "xyz789",
  "last": 123,
  "before": "abc123"
}
Response
{
  "data": {
    "clients": {
      "edges": [ClientEdge],
      "pageInfo": PageInfo,
      "totalCount": 987
    }
  }
}

clientTitles

Description

A list of all client titles.

If present, returns client titles after $after cursor and at most $first entries.

Similarly, returns client titles before $before cursor, and at most $last entries.

Response

Returns a ClientTitleConnection!

Arguments
Name Description
first - Int
after - String
last - Int
before - String

Example

Query
query ClientTitles(
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  clientTitles(
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...ClientTitleEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "first": 987,
  "after": "xyz789",
  "last": 987,
  "before": "abc123"
}
Response
{
  "data": {
    "clientTitles": {
      "edges": [ClientTitleEdge],
      "pageInfo": PageInfo,
      "totalCount": 987
    }
  }
}

communicationLanguages

Description

A list of supported client communication languages, ordered by preference. Emails, invoices, etc., content created by 3RPMS can be only in these languages.

If present, returns languages after $after cursor and at most $first entries.

Similarly, returns languages before $before cursor, and at most $last entries.

Response

Returns a LanguageConnection!

Arguments
Name Description
first - Int
after - String
last - Int
before - String

Example

Query
query CommunicationLanguages(
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  communicationLanguages(
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...LanguageEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "first": 123,
  "after": "xyz789",
  "last": 987,
  "before": "abc123"
}
Response
{
  "data": {
    "communicationLanguages": {
      "edges": [LanguageEdge],
      "pageInfo": PageInfo,
      "totalCount": 123
    }
  }
}

webhookEndpoints

Description

A list of all webhook endpoints for requesting integration.

See https://www.3rpms.de/graphql/docs#webhooks-overview for documentation

If present, returns webhook endpoints after $after cursor and at most $first entries.

Similarly, returns webhook endpoints before $before cursor, and at most $last entries.

Response

Returns a WebhookEndpointConnection!

Arguments
Name Description
filter - WebhookEndpointFilter
first - Int
after - String
last - Int
before - String

Example

Query
query WebhookEndpoints(
  $filter: WebhookEndpointFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  webhookEndpoints(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...WebhookEndpointEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": WebhookEndpointFilter,
  "first": 123,
  "after": "abc123",
  "last": 987,
  "before": "abc123"
}
Response
{
  "data": {
    "webhookEndpoints": {
      "edges": [WebhookEndpointEdge],
      "pageInfo": PageInfo,
      "totalCount": 123
    }
  }
}

paymentMethods

Description

Payment methods that are available for requesting integration to create deposits with.

If present, returns payment methods after $after cursor and at most $first entries.

Similarly, returns payment methods before $before cursor, and at most $last entries.

Response

Returns a PaymentMethodConnection!

Arguments
Name Description
filter - PaymentMethodFilter
first - Int
after - String
last - Int
before - String

Example

Query
query PaymentMethods(
  $filter: PaymentMethodFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  paymentMethods(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...PaymentMethodEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": PaymentMethodFilter,
  "first": 123,
  "after": "abc123",
  "last": 123,
  "before": "xyz789"
}
Response
{
  "data": {
    "paymentMethods": {
      "edges": [PaymentMethodEdge],
      "pageInfo": PageInfo,
      "totalCount": 123
    }
  }
}

roomAccessKeys

Description

Room access keys your integration has control over - for deleting, assigning or revoking.

If present, returns room access keys after $after cursor and at most $first entries.

Similarly, returns room access keys before $before cursor, and at most $last entries.

Response

Returns a RoomAccessKeyConnection!

Arguments
Name Description
filter - RoomAccessKeyFilter
first - Int
after - String
last - Int
before - String

Example

Query
query RoomAccessKeys(
  $filter: RoomAccessKeyFilter,
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  roomAccessKeys(
    filter: $filter,
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...RoomAccessKeyEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "filter": RoomAccessKeyFilter,
  "first": 987,
  "after": "xyz789",
  "last": 123,
  "before": "abc123"
}
Response
{
  "data": {
    "roomAccessKeys": {
      "edges": [RoomAccessKeyEdge],
      "pageInfo": PageInfo,
      "totalCount": 987
    }
  }
}

externalSalesProducts

Description

Products your integration can use to add external sales

If present, returns products after $after cursor and at most $first entries.

Similarly, returns products before $before cursor, and at most $last entries.

Response

Returns an ExternalSalesProductConnection!

Arguments
Name Description
first - Int
after - String
last - Int
before - String

Example

Query
query ExternalSalesProducts(
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  externalSalesProducts(
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...ExternalSalesProductEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "first": 987,
  "after": "xyz789",
  "last": 123,
  "before": "abc123"
}
Response
{
  "data": {
    "externalSalesProducts": {
      "edges": [ExternalSalesProductEdge],
      "pageInfo": PageInfo,
      "totalCount": 123
    }
  }
}

paymentTerms

Description

A list of all payment terms.

If present, returns payment terms after $after cursor and at most $first entries.

Similarly, returns payment terms before $before cursor, and at most $last entries.

Response

Returns a PaymentTermsConnection!

Arguments
Name Description
first - Int
after - String
last - Int
before - String

Example

Query
query PaymentTerms(
  $first: Int,
  $after: String,
  $last: Int,
  $before: String
) {
  paymentTerms(
    first: $first,
    after: $after,
    last: $last,
    before: $before
  ) {
    edges {
      ...PaymentTermsEdgeFragment
    }
    pageInfo {
      ...PageInfoFragment
    }
    totalCount
  }
}
Variables
{
  "first": 987,
  "after": "xyz789",
  "last": 987,
  "before": "xyz789"
}
Response
{
  "data": {
    "paymentTerms": {
      "edges": [PaymentTermsEdge],
      "pageInfo": PageInfo,
      "totalCount": 123
    }
  }
}

settings

Response

Returns a Settings!

Example

Query
query Settings {
  settings {
    categories {
      ...CategoryConnectionFragment
    }
    roomSetups {
      ...RoomSetupConnectionFragment
    }
  }
}
Response
{
  "data": {
    "settings": {
      "categories": CategoryConnection,
      "roomSetups": RoomSetupConnection
    }
  }
}

performanceStatistics

Response

Returns a PerformanceStatistics!

Arguments
Name Description
filter - StatisticsFilter!

Example

Query
query PerformanceStatistics($filter: StatisticsFilter!) {
  performanceStatistics(filter: $filter) {
    occupancy
    adr
    revPAR
  }
}
Variables
{"filter": StatisticsFilter}
Response
{
  "data": {
    "performanceStatistics": {
      "occupancy": 123.45,
      "adr": Decimal,
      "revPAR": Decimal
    }
  }
}

inventory

Response

Returns an Inventory!

Arguments
Name Description
filter - InventoryFilter!

Example

Query
query Inventory($filter: InventoryFilter!) {
  inventory(filter: $filter) {
    available {
      ...InventoryRoomConnectionFragment
    }
    occupied {
      ...InventoryRoomConnectionFragment
    }
    booked {
      ...InventoryRoomConnectionFragment
    }
  }
}
Variables
{"filter": InventoryFilter}
Response
{
  "data": {
    "inventory": {
      "available": InventoryRoomConnection,
      "occupied": InventoryRoomConnection,
      "booked": InventoryRoomConnection
    }
  }
}

Mutations

updateCategoryPrices

Description

Update default rates for categories. Changes are pushed out to booking portals immediately, if they're connected.

Avoid sending multiple updates for the same (date,category,occupancy) pair in one request. We provide no guarantee on the order in which they're processed.

Response

Returns an UpdateCategoryPricesPayload!

Arguments
Name Description
input - UpdateCategoryPricesInput!

Example

Query
mutation UpdateCategoryPrices($input: UpdateCategoryPricesInput!) {
  updateCategoryPrices(input: $input) {
    updatedCategoryPrices {
      ...CategoryPriceConnectionFragment
    }
  }
}
Variables
{"input": UpdateCategoryPricesInput}
Response
{
  "data": {
    "updateCategoryPrices": {
      "updatedCategoryPrices": CategoryPriceConnection
    }
  }
}

updateCategoryRestrictions

Description

Update restrictions for categories. Changes are pushed out to booking portals immediately, if they're connected.

Avoid sending multiple updates for the same (date,category) pair in one request. We provide no guarantee on the order in which they're processed.

Arguments
Name Description
input - UpdateCategoryRestrictionsInput!

Example

Query
mutation UpdateCategoryRestrictions($input: UpdateCategoryRestrictionsInput!) {
  updateCategoryRestrictions(input: $input) {
    updatedCategoryRestrictions {
      ...CategoryRestrictionConnectionFragment
    }
  }
}
Variables
{"input": UpdateCategoryRestrictionsInput}
Response
{
  "data": {
    "updateCategoryRestrictions": {
      "updatedCategoryRestrictions": CategoryRestrictionConnection
    }
  }
}

updateReservation

Response

Returns an UpdateReservationPayload!

Arguments
Name Description
input - UpdateReservationInput!

Example

Query
mutation UpdateReservation($input: UpdateReservationInput!) {
  updateReservation(input: $input) {
    reservation {
      ...ReservationFragment
    }
  }
}
Variables
{"input": UpdateReservationInput}
Response
{
  "data": {
    "updateReservation": {"reservation": Reservation}
  }
}

updateRoomStay

Response

Returns an UpdateRoomStayPayload!

Arguments
Name Description
input - UpdateRoomStayInput!

Example

Query
mutation UpdateRoomStay($input: UpdateRoomStayInput!) {
  updateRoomStay(input: $input) {
    roomStay {
      ...RoomStayFragment
    }
    errors {
      ...ValidationErrorFragment
    }
  }
}
Variables
{"input": UpdateRoomStayInput}
Response
{
  "data": {
    "updateRoomStay": {
      "roomStay": RoomStay,
      "errors": [ValidationError]
    }
  }
}

updateRoomSetup

Response

Returns an UpdateRoomSetupPayload!

Arguments
Name Description
input - UpdateRoomSetupInput!

Example

Query
mutation UpdateRoomSetup($input: UpdateRoomSetupInput!) {
  updateRoomSetup(input: $input) {
    roomSetup {
      ...RoomSetupFragment
    }
  }
}
Variables
{"input": UpdateRoomSetupInput}
Response
{"data": {"updateRoomSetup": {"roomSetup": RoomSetup}}}

createWebhookEndpoint

Response

Returns a CreateWebhookEndpointPayload!

Arguments
Name Description
input - CreateWebhookEndpointInput!

Example

Query
mutation CreateWebhookEndpoint($input: CreateWebhookEndpointInput!) {
  createWebhookEndpoint(input: $input) {
    webhookEndpoint {
      ...WebhookEndpointFragment
    }
    secret
    errors {
      ...ValidationErrorFragment
    }
  }
}
Variables
{"input": CreateWebhookEndpointInput}
Response
{
  "data": {
    "createWebhookEndpoint": {
      "webhookEndpoint": WebhookEndpoint,
      "secret": "xyz789",
      "errors": [ValidationError]
    }
  }
}

updateWebhookEndpoint

Response

Returns an UpdateWebhookEndpointPayload!

Arguments
Name Description
input - UpdateWebhookEndpointInput!

Example

Query
mutation UpdateWebhookEndpoint($input: UpdateWebhookEndpointInput!) {
  updateWebhookEndpoint(input: $input) {
    webhookEndpoint {
      ...WebhookEndpointFragment
    }
    errors {
      ...ValidationErrorFragment
    }
  }
}
Variables
{"input": UpdateWebhookEndpointInput}
Response
{
  "data": {
    "updateWebhookEndpoint": {
      "webhookEndpoint": WebhookEndpoint,
      "errors": [ValidationError]
    }
  }
}

deleteWebhookEndpoint

Response

Returns a DeleteWebhookEndpointPayload!

Arguments
Name Description
input - DeleteWebhookEndpointInput!

Example

Query
mutation DeleteWebhookEndpoint($input: DeleteWebhookEndpointInput!) {
  deleteWebhookEndpoint(input: $input) {
    deleted
    errors {
      ...ValidationErrorFragment
    }
  }
}
Variables
{"input": DeleteWebhookEndpointInput}
Response
{
  "data": {
    "deleteWebhookEndpoint": {
      "deleted": true,
      "errors": [ValidationError]
    }
  }
}

createPaymentMethod

Description

At the moment, there is a limit of only one payment method per integration per hotel

Response

Returns a CreatePaymentMethodPayload!

Arguments
Name Description
input - CreatePaymentMethodInput!

Example

Query
mutation CreatePaymentMethod($input: CreatePaymentMethodInput!) {
  createPaymentMethod(input: $input) {
    paymentMethod {
      ...PaymentMethodFragment
    }
  }
}
Variables
{"input": CreatePaymentMethodInput}
Response
{
  "data": {
    "createPaymentMethod": {
      "paymentMethod": PaymentMethod
    }
  }
}

updatePaymentMethod

Response

Returns an UpdatePaymentMethodPayload!

Arguments
Name Description
input - UpdatePaymentMethodInput!

Example

Query
mutation UpdatePaymentMethod($input: UpdatePaymentMethodInput!) {
  updatePaymentMethod(input: $input) {
    paymentMethod {
      ...PaymentMethodFragment
    }
  }
}
Variables
{"input": UpdatePaymentMethodInput}
Response
{
  "data": {
    "updatePaymentMethod": {
      "paymentMethod": PaymentMethod
    }
  }
}

deletePaymentMethod

Response

Returns a DeletePaymentMethodPayload!

Arguments
Name Description
input - DeletePaymentMethodInput!

Example

Query
mutation DeletePaymentMethod($input: DeletePaymentMethodInput!) {
  deletePaymentMethod(input: $input) {
    deleted
  }
}
Variables
{"input": DeletePaymentMethodInput}
Response
{"data": {"deletePaymentMethod": {"deleted": false}}}

addRoomStayGuest

Description

Adds a guest to a room stay. Guest will be added at the end of existing guest list

Response

Returns an AddRoomStayGuestPayload!

Arguments
Name Description
input - AddRoomStayGuestInput!

Example

Query
mutation AddRoomStayGuest($input: AddRoomStayGuestInput!) {
  addRoomStayGuest(input: $input) {
    roomStay {
      ...RoomStayFragment
    }
  }
}
Variables
{"input": AddRoomStayGuestInput}
Response
{"data": {"addRoomStayGuest": {"roomStay": RoomStay}}}

removeRoomStayGuest

Response

Returns a RemoveRoomStayGuestPayload!

Arguments
Name Description
input - RemoveRoomStayGuestInput!

Example

Query
mutation RemoveRoomStayGuest($input: RemoveRoomStayGuestInput!) {
  removeRoomStayGuest(input: $input) {
    roomStay {
      ...RoomStayFragment
    }
  }
}
Variables
{"input": RemoveRoomStayGuestInput}
Response
{"data": {"removeRoomStayGuest": {"roomStay": RoomStay}}}

createClient

Response

Returns a CreateClientPayload!

Arguments
Name Description
input - CreateClientInput!

Example

Query
mutation CreateClient($input: CreateClientInput!) {
  createClient(input: $input) {
    client {
      ...ClientFragment
    }
  }
}
Variables
{"input": CreateClientInput}
Response
{"data": {"createClient": {"client": Client}}}

updateClient

Response

Returns an UpdateClientPayload!

Arguments
Name Description
input - UpdateClientInput!

Example

Query
mutation UpdateClient($input: UpdateClientInput!) {
  updateClient(input: $input) {
    client {
      ...ClientFragment
    }
  }
}
Variables
{"input": UpdateClientInput}
Response
{"data": {"updateClient": {"client": Client}}}

createDeposit

Response

Returns a CreateDepositPayload!

Arguments
Name Description
input - CreateDepositInput!

Example

Query
mutation CreateDeposit($input: CreateDepositInput!) {
  createDeposit(input: $input) {
    incomingPayment {
      ...IncomingPaymentFragment
    }
  }
}
Variables
{"input": CreateDepositInput}
Response
{
  "data": {
    "createDeposit": {"incomingPayment": IncomingPayment}
  }
}

createRoomAccessKey

Response

Returns a CreateRoomAccessKeyPayload!

Arguments
Name Description
input - CreateRoomAccessKeyInput!

Example

Query
mutation CreateRoomAccessKey($input: CreateRoomAccessKeyInput!) {
  createRoomAccessKey(input: $input) {
    roomAccessKey {
      ...RoomAccessKeyFragment
    }
  }
}
Variables
{"input": CreateRoomAccessKeyInput}
Response
{
  "data": {
    "createRoomAccessKey": {
      "roomAccessKey": RoomAccessKey
    }
  }
}

deleteRoomAccessKey

Response

Returns a DeleteRoomAccessKeyPayload!

Arguments
Name Description
input - DeleteRoomAccessKeyInput!

Example

Query
mutation DeleteRoomAccessKey($input: DeleteRoomAccessKeyInput!) {
  deleteRoomAccessKey(input: $input) {
    deleted
  }
}
Variables
{"input": DeleteRoomAccessKeyInput}
Response
{"data": {"deleteRoomAccessKey": {"deleted": false}}}

addRoomAccessKey

Response

Returns an AddRoomAccessKeyPayload!

Arguments
Name Description
input - AddRoomAccessKeyInput!

Example

Query
mutation AddRoomAccessKey($input: AddRoomAccessKeyInput!) {
  addRoomAccessKey(input: $input) {
    roomStay {
      ...RoomStayFragment
    }
    roomAccessKey {
      ...RoomAccessKeyFragment
    }
  }
}
Variables
{"input": AddRoomAccessKeyInput}
Response
{
  "data": {
    "addRoomAccessKey": {
      "roomStay": RoomStay,
      "roomAccessKey": RoomAccessKey
    }
  }
}

removeRoomAccessKey

Response

Returns a RemoveRoomAccessKeyPayload!

Arguments
Name Description
input - RemoveRoomAccessKeyInput!

Example

Query
mutation RemoveRoomAccessKey($input: RemoveRoomAccessKeyInput!) {
  removeRoomAccessKey(input: $input) {
    roomStay {
      ...RoomStayFragment
    }
    roomAccessKey {
      ...RoomAccessKeyFragment
    }
  }
}
Variables
{"input": RemoveRoomAccessKeyInput}
Response
{
  "data": {
    "removeRoomAccessKey": {
      "roomStay": RoomStay,
      "roomAccessKey": RoomAccessKey
    }
  }
}

createExternalSalesProduct

Description

Each integration can create only one external sales product

Arguments
Name Description
input - CreateExternalSalesProductInput!

Example

Query
mutation CreateExternalSalesProduct($input: CreateExternalSalesProductInput!) {
  createExternalSalesProduct(input: $input) {
    product {
      ...ExternalSalesProductFragment
    }
  }
}
Variables
{"input": CreateExternalSalesProductInput}
Response
{
  "data": {
    "createExternalSalesProduct": {
      "product": ExternalSalesProduct
    }
  }
}

updateExternalSalesProduct

Arguments
Name Description
input - UpdateExternalSalesProductInput!

Example

Query
mutation UpdateExternalSalesProduct($input: UpdateExternalSalesProductInput!) {
  updateExternalSalesProduct(input: $input) {
    product {
      ...ExternalSalesProductFragment
    }
  }
}
Variables
{"input": UpdateExternalSalesProductInput}
Response
{
  "data": {
    "updateExternalSalesProduct": {
      "product": ExternalSalesProduct
    }
  }
}

createExternalSale

Response

Returns a CreateExternalSalePayload!

Arguments
Name Description
input - CreateExternalSaleInput!

Example

Query
mutation CreateExternalSale($input: CreateExternalSaleInput!) {
  createExternalSale(input: $input) {
    created
  }
}
Variables
{"input": CreateExternalSaleInput}
Response
{"data": {"createExternalSale": {"created": true}}}

Objects

UpdateCategoryPricesPayload

Fields
Field Name Description
updatedCategoryPrices - CategoryPriceConnection!

A list of updated rates.

If present, returns rates after $after cursor and at most $first entries.

Similarly, returns rates before $before cursor, and at most $last entries.

Arguments
first - Int
after - String
last - Int
before - String
Example
{"updatedCategoryPrices": CategoryPriceConnection}

UpdateCategoryRestrictionsPayload

Fields
Field Name Description
updatedCategoryRestrictions - CategoryRestrictionConnection!

A list of updated restrictions.

If present, returns rates after $after cursor and at most $first entries.

Similarly, returns rates before $before cursor, and at most $last entries.

Arguments
first - Int
after - String
last - Int
before - String
Example
{
  "updatedCategoryRestrictions": CategoryRestrictionConnection
}

UpdateRoomStayPayload

Fields
Field Name Description
roomStay - RoomStay
Example
{"roomStay": RoomStay}

UpdateRoomSetupPayload

Fields
Field Name Description
roomSetup - RoomSetup
Example
{"roomSetup": RoomSetup}

PageInfo

Fields
Field Name Description
startCursor - String

Corresponds to the first node's cursors in edges

For empty lists value will be NULL

endCursor - String

Corresponds to the last node's cursors in edges

For empty lists value will be NULL

hasNextPage - Boolean! If first/after is used, then then server returns true if further edges exist, otherwise false
hasPreviousPage - Boolean! If last/before is used, then then server returns true if prior edges exist, otherwise false
Example
{
  "startCursor": "xyz789",
  "endCursor": "abc123",
  "hasNextPage": false,
  "hasPreviousPage": true
}

RoomStayConnection

Description

Paginated reserved rooms.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [RoomStayEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [RoomStayEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

RoomStayEdge

Description

Pagination entry wrapping RoomStay

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - RoomStay! Pagination item
Example
{
  "cursor": "xyz789",
  "node": RoomStay
}

RoomStay

Fields
Field Name Description
id - ID! Uniquely identifies the room stay. Doesnt mean anything to guests
reservation - Reservation! Reservation details
room_setup - RoomSetup Physical room details
roomName - String Physical room's name or number. This can be different from room_setup.name if the room has been renamed. Value is null when no physical room has been assigned.
category - Category Category from which rate and other ammenities are taken from. This is not the physical category the guest is staying in, not to be confused with room_setup.category
guests - [Person!]! List of all guests staying in the room
first_guest - Person First guest is the person that room is booked on, the other guests are co-travellers. For most integrations we recommend using first_guest
gross - Decimal! Pack price
dailyRates - RoomRateConnection!

Contains daily rates for current room stay, with dates in interval [reservation_from,reservation_to). Not guaranteed to be ordered by date.

If present, returns daily rates after $after cursor and at most $first entries.

Similarly, returns daily rates before $before cursor, and at most $last entries.

Arguments
first - Int
after - String
last - Int
before - String
lodgingsGross - Decimal! Gross without included products
additionalSales - Decimal! Additional products or services charged to this room. Not included in the price
reservation_from - Date! Arrival date
reservation_to - Date! Departure date
check_in - Datetime Exact date and time of when guests arrived
check_out - Datetime Exact date and time when guests departed - can assume they no longer have access to the room if field is present not null
selfcheckout_enabled - Boolean If room has selfcheckout enabled. If true, you may direct the guest to selfcheckout_url
selfcheckout_url - String Absolute url to where the guest can pay for the room. May be present even if selfcheckout_enabled is false
mealNotes - String! Notes for allergies, food preferences and such
maidNotes - String! Notes for arrival that should be follow when preparing the room, for example, allergic free pillows
guestMessage - String! A message that should be delivered to arriving guests in a particular room of a reservation.
ageGroups - AgeGroups
createdAt - Datetime!
updatedAt - Datetime!
roomAccessKey - RoomAccessKey
Example
{
  "id": "4",
  "reservation": Reservation,
  "room_setup": RoomSetup,
  "roomName": "xyz789",
  "category": Category,
  "guests": [Person],
  "first_guest": Person,
  "gross": Decimal,
  "dailyRates": RoomRateConnection,
  "lodgingsGross": Decimal,
  "additionalSales": Decimal,
  "reservation_from": "2007-12-03",
  "reservation_to": "2007-12-03",
  "check_in": Datetime,
  "check_out": Datetime,
  "selfcheckout_enabled": true,
  "selfcheckout_url": "abc123",
  "mealNotes": "xyz789",
  "maidNotes": "xyz789",
  "guestMessage": "abc123",
  "ageGroups": AgeGroups,
  "createdAt": Datetime,
  "updatedAt": Datetime,
  "roomAccessKey": RoomAccessKey
}

RoomAccessKeyConnection

Description

Paginated room access keys.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [RoomAccessKeyEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [RoomAccessKeyEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

RoomAccessKeyEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - RoomAccessKey! Pagination item
Example
{
  "cursor": "abc123",
  "node": RoomAccessKey
}

RoomAccessPin

Description

Intended use is for when the room has a pin code instead of a physical key

Fields
Field Name Description
id - ID!
pin - String! PIN intended for guests. Up to 10 characters.
roomStays - RoomStayConnection!
Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
Example
{
  "id": 4,
  "pin": "abc123",
  "roomStays": RoomStayConnection
}

RoomAccessKeyStorage

Description

Intended use is for when a physical key has to be retrieved from pin-protected storage outside of the room

Fields
Field Name Description
id - ID!
pin - String! PIN intended for guests. Up to 10 characters.
compartment - String! Relevant only for hotel staff to know where to place the key. Must not be shown to guests.
roomStays - RoomStayConnection!
Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
Example
{
  "id": 4,
  "pin": "xyz789",
  "compartment": "xyz789",
  "roomStays": RoomStayConnection
}

RoomAccessQR

Fields
Field Name Description
id - ID!
qrData - String! The data being used to generate QR code
roomStays - RoomStayConnection!
Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
Example
{
  "id": "4",
  "qrData": "xyz789",
  "roomStays": RoomStayConnection
}

CreateRoomAccessKeyPayload

Fields
Field Name Description
roomAccessKey - RoomAccessKey
Example
{"roomAccessKey": RoomAccessKey}

DeleteRoomAccessKeyPayload

Fields
Field Name Description
deleted - Boolean! Always true
Example
{"deleted": true}

AddRoomAccessKeyPayload

Fields
Field Name Description
roomStay - RoomStay
roomAccessKey - RoomAccessKey
Example
{
  "roomStay": RoomStay,
  "roomAccessKey": RoomAccessKey
}

RemoveRoomAccessKeyPayload

Fields
Field Name Description
roomStay - RoomStay
roomAccessKey - RoomAccessKey
Example
{
  "roomStay": RoomStay,
  "roomAccessKey": RoomAccessKey
}

AgeGroups

Fields
Field Name Description
adults - Int!
child1 - Int!
child2 - Int!
free - Int!
Example
{"adults": 123, "child1": 987, "child2": 987, "free": 987}

RoomRateConnection

Description

Paginated room rates.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [RoomRateEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [RoomRateEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

RoomRateEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - RoomRate! Pagination item
Example
{
  "cursor": "abc123",
  "node": RoomRate
}

RoomRate

Fields
Field Name Description
date - Date!
packPriceGross - Decimal! Amount for the lodging, included services and their VATs
packPriceNet - Decimal! Amount for the lodging and included services
lodgingGross - Decimal! Amount for the lodging and VAT
lodgingNet - Decimal! Amount for the lodging
Example
{
  "date": "2007-12-03",
  "packPriceGross": Decimal,
  "packPriceNet": Decimal,
  "lodgingGross": Decimal,
  "lodgingNet": Decimal
}

ClientConnection

Description

Paginated clients.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [ClientEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [ClientEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

ClientEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - Client! Pagination item
Example
{
  "cursor": "abc123",
  "node": Client
}

Person

Fields
Field Name Description
id - ID!
clientTitle - ClientTitle
firstname - String Will always have at least firstname or lastname
lastname - String Will always have at least firstname or lastname
street - String
zipcode - String
city - String
country - String! ISO 3166-1 alpha-2 country code
language - String

Preferred communication language. ISO 639-1 2 character Code.

See communicationLanguages query for a list of allowed language codes.

email - String
telephone - String May be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
mobile - String Despite the name, may be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
fax - String
stayPreferences - String
mealPreferences - String
nationality - String
passportNumber - String
idCardNumber - String
issuingAuthority - String
carPlateNumber - String
birthday - Date
newsletterSubscriptionEnabled - Boolean! true when client agreed to recieve martketing newsletter
newsletterSubscriptionDoubleOptInAt - Datetime Contains the date and time of when client provided consent to receive the newsletter via double opt-in.
birthdayGreetingsEnabled - Boolean! true when client agreed to receive birthday greeting
Example
{
  "id": "4",
  "clientTitle": ClientTitle,
  "firstname": "abc123",
  "lastname": "abc123",
  "street": "abc123",
  "zipcode": "abc123",
  "city": "abc123",
  "country": "abc123",
  "language": "abc123",
  "email": "abc123",
  "telephone": "xyz789",
  "mobile": "abc123",
  "fax": "xyz789",
  "stayPreferences": "xyz789",
  "mealPreferences": "abc123",
  "nationality": "abc123",
  "passportNumber": "abc123",
  "idCardNumber": "abc123",
  "issuingAuthority": "xyz789",
  "carPlateNumber": "abc123",
  "birthday": "2007-12-03",
  "newsletterSubscriptionEnabled": false,
  "newsletterSubscriptionDoubleOptInAt": Datetime,
  "birthdayGreetingsEnabled": true
}

Company

Fields
Field Name Description
id - ID!
company - String!
companyAdditionalInformation - String
street - String
zipcode - String
city - String
country - String! ISO 3166-1 alpha-2 country code
language - String

Preferred communication language. ISO 639-1 2 character Code.

See communicationLanguages query for a list of allowed language codes.

email - String
telephone - String May be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
mobile - String Despite the name, may be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
fax - String
stayPreferences - String
newsletterSubscriptionEnabled - Boolean! true when client agreed to recieve martketing newsletter
newsletterSubscriptionDoubleOptInAt - Datetime Contains the date and time of when client provided consent to receive the newsletter via double opt-in.
Example
{
  "id": 4,
  "company": "abc123",
  "companyAdditionalInformation": "xyz789",
  "street": "xyz789",
  "zipcode": "xyz789",
  "city": "xyz789",
  "country": "xyz789",
  "language": "xyz789",
  "email": "abc123",
  "telephone": "abc123",
  "mobile": "xyz789",
  "fax": "abc123",
  "stayPreferences": "abc123",
  "newsletterSubscriptionEnabled": false,
  "newsletterSubscriptionDoubleOptInAt": Datetime
}

ClientTitleConnection

Description

Paginated client titles.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [ClientTitleEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [ClientTitleEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

ClientTitleEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - ClientTitle! Pagination item
Example
{
  "cursor": "xyz789",
  "node": ClientTitle
}

ClientTitle

Fields
Field Name Description
id - ID!
name - String!
Example
{
  "id": "4",
  "name": "xyz789"
}

BookingSource

Fields
Field Name Description
name - String!
Example
{"name": "xyz789"}

BookingType

Fields
Field Name Description
name - String!
Example
{"name": "abc123"}

StayType

Fields
Field Name Description
name - String!
Example
{"name": "abc123"}

PaymentTermsConnection

Description

Paginated payment terms.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [PaymentTermsEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of payment terms
Example
{
  "edges": [PaymentTermsEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

PaymentTermsEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries.
node - PaymentTerms! Pagination item
Example
{
  "cursor": "abc123",
  "node": PaymentTerms
}

PaymentTerms

Fields
Field Name Description
id - ID!
name - String!
Example
{
  "id": "4",
  "name": "xyz789"
}

RoomSetupConnection

Description

Paginated physical rooms.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [RoomSetupEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [RoomSetupEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

RoomSetupEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - RoomSetup! Pagination item
Example
{
  "cursor": "xyz789",
  "node": RoomSetup
}

RoomSetup

Description

Physical room details

Fields
Field Name Description
id - ID!
name - String! Room number
areaName - String!
cleaningStatus - CleaningStatus!
expectedCleaningStatus - CleaningStatus! Rooms dont have to be cleaned everyday. Used to check initial cleaning status on $date. Only future dates can be passed.
Arguments
date - Date!
category - Category! Default category from which rate and other ammenities are taken from
countTowardsPerformance - Boolean! Some rooms may be used for purposes other than lodgings, for example, conference rooms or car parking spaces. In such cases, when this value is false, the room should not be considered towards Key Performance Metrics.
roomStays - RoomStayConnection!

Get a list of reserved room stays for current room. By default, cancelled rooms are not returned.

If a previously requested room's id is no longer returned, assume it has been moved to a different day, deleted or reservation cancelled altogether.

If present, returns rooms after $after cursor and at most $first entries.

Similarly, returns rooms before $before cursor, and at most $last entries.

Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
Example
{
  "id": 4,
  "name": "abc123",
  "areaName": "xyz789",
  "cleaningStatus": "CLEAN",
  "expectedCleaningStatus": "CLEAN",
  "category": Category,
  "countTowardsPerformance": false,
  "roomStays": RoomStayConnection
}

CategoryConnection

Description

Paginated categories.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [CategoryEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [CategoryEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

CategoryEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - Category! Pagination item
Example
{
  "cursor": "abc123",
  "node": Category
}

Category

Description

Accommodations are divided into categories (e.g. double room superior) and rooms (e.g. room 312).

The properties of a room are determined by the category it belongs to, so each room must be assigned to a category.

The availability is determined via the rooms available at a certain point in time for each category.

Fields
Field Name Description
id - ID!
name - String! Name of category, e.g., double room or single room. Usually meant only for staff. If you're looking for a value to display to guests, consider using description
description - String!

Category description meant for guests. This description is on invoices and emails sent by 3RPMS.

language must be a ISO 3166-1 alpha-2 code. When language is not provided, language used in the request's Accept-Language header will be used.

Arguments
language - String
standardOccupancy - Int! Usually, this is the number of beds the category has
minOccupancy - Int

Minimum occupancy a non-standard rate has been defined for. When null, no occupancy based pricing is used.

Occupancy specific rates are defined for interval [minOccupancy, maxOccupancy]

maxOccupancy - Int

Maximum occupancy a non-standard rate has been defined for. When null, no occupancy based pricing is used.

Occupancy specific rates are defined for interval [minOccupancy, maxOccupancy]

minimumRate - Decimal!

Minimum allowed rate for any date.

Rate may still be 0.00 regardless of this value if no rate for the day has been sate.

roomSetups - RoomSetupConnection!

Active physical rooms that use this category by default. If a room is no longer in the list, assume it has been moved to a different category, deleted or deactivated.

If present, returns rooms after $after cursor and at most first $first entries.

Similarly, returns rooms before $before cursor, and at most last $last entries.

Arguments
first - Int
after - String
last - Int
before - String
defaultPrices - CategoryPriceConnection!

Rates used for this category

If present, returns rates after $after cursor and at most first $first entries.

Similarly, returns rates before $before cursor, and at most last $last entries.

Arguments
first - Int
after - String
last - Int
before - String
restrictions - CategoryRestrictionConnection!

Restrictions set up for this category

If present, returns restrictions after $after cursor and at most first $first entries.

Similarly, returns restrictions before $before cursor, and at most last $last entries.

Arguments
first - Int
after - String
last - Int
before - String
availability - [CategoryAvailability!]! Room availability count, grouped by date. Even with no reservations, this might not be the same as total number of rooms, if a room is disabled for maintenance or other reasons.
Arguments
Example
{
  "id": "4",
  "name": "xyz789",
  "description": "xyz789",
  "standardOccupancy": 123,
  "minOccupancy": 123,
  "maxOccupancy": 987,
  "minimumRate": Decimal,
  "roomSetups": RoomSetupConnection,
  "defaultPrices": CategoryPriceConnection,
  "restrictions": CategoryRestrictionConnection,
  "availability": [CategoryAvailability]
}

CategoryPriceConnection

Description

Paginated category rates.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [CategoryPriceEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [CategoryPriceEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

CategoryPriceEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - CategoryPrice! Pagination item
Example
{
  "cursor": "abc123",
  "node": CategoryPrice
}

CategoryPrice

Fields
Field Name Description
category - Category!
date - Date!
amount - Decimal! Rate for standard occupancy
occupancies - [CategoryOccupancyPrice!]!
Example
{
  "category": Category,
  "date": "2007-12-03",
  "amount": Decimal,
  "occupancies": [CategoryOccupancyPrice]
}

CategoryOccupancyPrice

Fields
Field Name Description
occupancy - Int!
amount - Decimal!
Example
{"occupancy": 987, "amount": Decimal}

CategoryAvailability

Fields
Field Name Description
date - Date!
availability - Int! Number of available rooms on this date
Example
{"date": "2007-12-03", "availability": 987}

UpdateReservationPayload

Fields
Field Name Description
reservation - Reservation
Example
{"reservation": Reservation}

ReservationConnection

Description

Paginated reservations.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [ReservationEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [ReservationEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

ReservationEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - Reservation! Pagination item
Example
{
  "cursor": "abc123",
  "node": Reservation
}

Reservation

Fields
Field Name Description
id - ID!
code - String!

Uniquely identifies the reservation for this hotel. Guaranteed to never change.

Guest may or may not know the reservation code.

bookingChannelCode - String

External booking channel/portal provided reservation code. Not present for reservations created directly in pms.

Usually known to guests.

pmsUrl - String! Full url to open the reservation in 3RPMS. Value may change at any point.
status - ReservationStatus!
cancelledAt - Datetime Contains the date and time of cancellation, when reservation's status is CANCELLED.
groupName - String!
client - Client! Client details on who made the reservation. Do not confuse this with room guests - they may or may not be the same
contact - Person If present, prefer communicating must be done through contact details
selfcheckinStatus - SelfcheckinStatus! When AVAILABLE, you may direct the guest to selfcheckinUrl
selfcheckinUrl - String Absolute url to where the guest can complete selfcheckin. May be present even if selfcheckinEnabled is false
bookingSource - BookingSource
bookingType - BookingType
stayType - StayType
paymentTerms - PaymentTerms
rooms - RoomStayConnection!

Get a list of reserved rooms.

If present, returns rooms after $after cursor and at most first $first entries.

Similarly, returns rooms before $before cursor, and at most last $last entries.

Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
createdAt - Datetime!
updatedAt - Datetime!
notes - String! Internal notes about a reservation. Must be visible only to hotel staff. Must not be visible to guests.
deposits - IncomingPaymentConnection!

A list of non-cancelled deposits for this reservation.

If present, returns deposits after $after cursor and at most first $first entries.

Similarly, returns deposits before $before cursor, and at most last $last entries.

Arguments
first - Int
after - String
last - Int
before - String
totalAmount - Decimal! Rooms' and products' price summed up. Invoiced items are summed as well.
openAmount - Decimal! Total amount, with already made deposits and invoiced items subtracted. If you intend to ask guests for a deposit, this is the amount to use to cover the entire reservation.
billingClient - Client This information will appear on the invoice.
billingContact - Person This information will be used as contact details for the invoice.
Example
{
  "id": 4,
  "code": "abc123",
  "bookingChannelCode": "abc123",
  "pmsUrl": "abc123",
  "status": "CANCELLED",
  "cancelledAt": Datetime,
  "groupName": "xyz789",
  "client": Client,
  "contact": Person,
  "selfcheckinStatus": "DISABLED",
  "selfcheckinUrl": "xyz789",
  "bookingSource": BookingSource,
  "bookingType": BookingType,
  "stayType": StayType,
  "paymentTerms": PaymentTerms,
  "rooms": RoomStayConnection,
  "createdAt": Datetime,
  "updatedAt": Datetime,
  "notes": "abc123",
  "deposits": IncomingPaymentConnection,
  "totalAmount": Decimal,
  "openAmount": Decimal,
  "billingClient": Client,
  "billingContact": Person
}

Settings

Fields
Field Name Description
categories - CategoryConnection!

If present, returns categories after $after cursor and at most first $first entries.

Similarly, returns categories before $before cursor, and at most last $last entries.

Arguments
filter - CategoryFilter
first - Int
after - String
last - Int
before - String
roomSetups - RoomSetupConnection!

If present, returns rooms after $after cursor and at most first $first entries.

Similarly, returns rooms before $before cursor, and at most last $last entries.

Arguments
filter - RoomSetupFilter
first - Int
after - String
last - Int
before - String
Example
{
  "categories": CategoryConnection,
  "roomSetups": RoomSetupConnection
}

PerformanceStatistics

Description

Performance statistics for the selected date

Fields
Field Name Description
occupancy - Float! Occupancy in range [0, 1] where 0 is empty and 1 is fully occupied
adr - Decimal! Average daily Rate
revPAR - Decimal! Revenue Per Available Room
Example
{
  "occupancy": 987.65,
  "adr": Decimal,
  "revPAR": Decimal
}

Inventory

Fields
Field Name Description
available - InventoryRoomConnection!

List of available rooms for sale for the given period. Even with no reservations, this might not be the same as total number of rooms, if a rooms is disabled for maintence or other reasons.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Arguments
first - Int
after - String
last - Int
before - String
occupied - InventoryRoomConnection!

List of already booked or blocked rooms in the given period. Reservations placed in sandbox are not counted here.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Arguments
first - Int
after - String
last - Int
before - String
booked - InventoryRoomConnection!

List of rooms with one or more booking on it (in case of overbookings) in the given period. Blocked rooms do not appear in this list.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Arguments
first - Int
after - String
last - Int
before - String
Example
{
  "available": InventoryRoomConnection,
  "occupied": InventoryRoomConnection,
  "booked": InventoryRoomConnection
}

InventoryRoomConnection

Description

Paginated inventory rooms

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [InventoryRoomEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [InventoryRoomEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

InventoryRoomEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - RoomSetup! Pagination item
Example
{
  "cursor": "xyz789",
  "node": RoomSetup
}

CategoryRestrictionConnection

Description

Paginated category restrictions.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [CategoryRestrictionEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [CategoryRestrictionEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

CategoryRestrictionEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - CategoryRestriction! Pagination item
Example
{
  "cursor": "abc123",
  "node": CategoryRestriction
}

CategoryRestriction

Fields
Field Name Description
date - Date!
stopSell - Boolean! When active, category is not bookable
minStay - Int! Minimum Length of Stay
maxStay - Int Maximum Length of Stay. NULL represents a no limit on maximum stay
closedToArrival - Boolean!
closedToDeparture - Boolean!
guarantee - Boolean! Is room bookableable without the need of a guarantee
cancellation - Int The amount of days whilst possible to cancel prior to arrival without penalties. NULL is non cancellable, 0 is a standard policy as defined in booking portals.
breakfastIncluded - Boolean!
Example
{
  "date": "2007-12-03",
  "stopSell": true,
  "minStay": 987,
  "maxStay": 123,
  "closedToArrival": false,
  "closedToDeparture": true,
  "guarantee": false,
  "cancellation": 123,
  "breakfastIncluded": false
}

LanguageConnection

Description

Paginated communication languages

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [LanguageEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of products
Example
{
  "edges": [LanguageEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

LanguageEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - Language! Pagination item
Example
{
  "cursor": "abc123",
  "node": Language
}

Language

Fields
Field Name Description
code - String! ISO 639-1 2 character Code
Example
{"code": "abc123"}

WebhookEndpointConnection

Description

Paginated webhook endpoints

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [WebhookEndpointEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of products
Example
{
  "edges": [WebhookEndpointEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

WebhookEndpointEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - WebhookEndpoint! Pagination item
Example
{
  "cursor": "xyz789",
  "node": WebhookEndpoint
}

WebhookEndpoint

Fields
Field Name Description
id - ID!
url - String!
status - WebhookEndpointStatus!
events - [String!]!
createdAt - Datetime!
updatedAt - Datetime!
Example
{
  "id": 4,
  "url": "abc123",
  "status": "ENABLED",
  "events": ["xyz789"],
  "createdAt": Datetime,
  "updatedAt": Datetime
}

CreateWebhookEndpointPayload

Fields
Field Name Description
webhookEndpoint - WebhookEndpoint
secret - String Used for signing webhook payload. Secret can be retrieved only once on creation
Example
{
  "webhookEndpoint": WebhookEndpoint,
  "secret": "abc123"
}

UpdateWebhookEndpointPayload

Fields
Field Name Description
webhookEndpoint - WebhookEndpoint
Example
{"webhookEndpoint": WebhookEndpoint}

DeleteWebhookEndpointPayload

Fields
Field Name Description
deleted - Boolean! Always true
Example
{"deleted": false}

CreatePaymentMethodPayload

Fields
Field Name Description
paymentMethod - PaymentMethod
Example
{"paymentMethod": PaymentMethod}

UpdatePaymentMethodPayload

Fields
Field Name Description
paymentMethod - PaymentMethod
Example
{"paymentMethod": PaymentMethod}

DeletePaymentMethodPayload

Fields
Field Name Description
deleted - Boolean! Always true
Example
{"deleted": true}

PaymentMethodConnection

Description

Paginated payment methods.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [PaymentMethodEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [PaymentMethodEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

PaymentMethodEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - PaymentMethod! Pagination item
Example
{
  "cursor": "xyz789",
  "node": PaymentMethod
}

PaymentMethod

Fields
Field Name Description
id - ID!
name - String!
Example
{
  "id": "4",
  "name": "abc123"
}

CreateDepositPayload

Fields
Field Name Description
incomingPayment - IncomingPayment!
Example
{"incomingPayment": IncomingPayment}

IncomingPaymentConnection

Description

Paginated incoming payments.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [IncomingPaymentEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [IncomingPaymentEdge],
  "pageInfo": PageInfo,
  "totalCount": 987
}

IncomingPaymentEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - IncomingPayment! Pagination item
Example
{
  "cursor": "xyz789",
  "node": IncomingPayment
}

IncomingPayment

Fields
Field Name Description
id - ID!
paymentMethodName - String! Payment name at the time the payment was created. Might be different from paymentMethod.paymentName if it has been changed.
datetime - Datetime!
amount - Decimal!
paymentMethod - PaymentMethod!
receiptUrl - String

Value may be null in some cases:

  • Some old payments do not have receipts.

  • Receipts for payments that require a signature wont be immediately available, and usually take a few seconds for a receipt to be available.

Arguments
options - ReceiptOptions!
Example
{
  "id": "4",
  "paymentMethodName": "xyz789",
  "datetime": Datetime,
  "amount": Decimal,
  "paymentMethod": PaymentMethod,
  "receiptUrl": "abc123"
}

AddRoomStayGuestPayload

Fields
Field Name Description
roomStay - RoomStay
Example
{"roomStay": RoomStay}

RemoveRoomStayGuestPayload

Fields
Field Name Description
roomStay - RoomStay
Example
{"roomStay": RoomStay}

CreateClientPayload

Fields
Field Name Description
client - Client
Example
{"client": Client}

UpdateClientPayload

Fields
Field Name Description
client - Client
Example
{"client": Client}

CreateExternalSalesProductPayload

Fields
Field Name Description
product - ExternalSalesProduct
Example
{"product": ExternalSalesProduct}

UpdateExternalSalesProductPayload

Fields
Field Name Description
product - ExternalSalesProduct
Example
{"product": ExternalSalesProduct}

CreateExternalSalePayload

Fields
Field Name Description
created - Boolean! Always true
Example
{"created": false}

ExternalSalesProductConnection

Description

Paginated products.

Default page size is 20, if not provided. Maximum page size is 100.

Pagination implements Relay Connection spec. For detailed information follow https://relay.dev/graphql/connections.htm

Fields
Field Name Description
edges - [ExternalSalesProductEdge!]! List of paginated items
pageInfo - PageInfo!
totalCount - Int! Total number of items matching $filter
Example
{
  "edges": [ExternalSalesProductEdge],
  "pageInfo": PageInfo,
  "totalCount": 123
}

ExternalSalesProductEdge

Fields
Field Name Description
cursor - String! Unique id of node. Use it for pagination to get surrounding entries
node - ExternalSalesProduct! Pagination item
Example
{
  "cursor": "abc123",
  "node": ExternalSalesProduct
}

ExternalSalesProduct

Fields
Field Name Description
id - ID!
name - String!
Example
{"id": 4, "name": "abc123"}

Input objects

UpdateCategoryPricesInput

Fields
Input Field Description
prices - [CategoryPriceInput!]! Up to 1000 updates are allowed at a time
Example
{"prices": [CategoryPriceInput]}

CategoryPriceInput

Fields
Input Field Description
category - ID!
dateRange - DateRangeInput!
occupancy - Int NULL for default occupancy
amount - Decimal For occupancy rates NULL can be used to reset to default derived rate.
Example
{
  "category": 4,
  "dateRange": DateRangeInput,
  "occupancy": 987,
  "amount": Decimal
}

UpdateCategoryRestrictionsInput

Fields
Input Field Description
restrictions - [CategoryRestrictionInput!]! Up to 1000 updates are allowed at a time
Example
{"restrictions": [CategoryRestrictionInput]}

CategoryRestrictionInput

Fields
Input Field Description
category - ID!
dateRange - DateRangeInput!
stopSell - Boolean
minStay - Int Value must be between 1 and 99
maxStay - Int NULL for no limit on maximum stay. When non-null, value must be larger or equal to minStay.
closedToArrival - Boolean
closedToDeparture - Boolean
guarantee - Boolean
cancellation - Int Allowed values: NULL (non cancellable) , 0 (standard policy), 1, 2, 3, 7, 15, 30, 45, 90,
breakfastIncluded - Boolean
Example
{
  "category": "4",
  "dateRange": DateRangeInput,
  "stopSell": false,
  "minStay": 123,
  "maxStay": 123,
  "closedToArrival": false,
  "closedToDeparture": true,
  "guarantee": false,
  "cancellation": 123,
  "breakfastIncluded": true
}

UpdateRoomStayInput

Fields
Input Field Description
id - ID!
check_in - Datetime

check_in date must be before check_out.

Set to null, to undo check-in. Room must not be checked-out for undo to be allowed

check_out - Datetime

Room has to be checked-in already, for check-out to be allowed.

Set to null, to undo check-out"

Example
{
  "id": 4,
  "check_in": Datetime,
  "check_out": Datetime
}

UpdateRoomSetupInput

Fields
Input Field Description
id - ID!
cleaningStatus - CleaningStatus
Example
{"id": "4", "cleaningStatus": "CLEAN"}

RoomAccessKeyFilter

Fields
Input Field Description
id - IdOperators
not - RoomAccessKeyFilter
or - [RoomAccessKeyFilter!]
Example
{
  "id": IdOperators,
  "not": RoomAccessKeyFilter,
  "or": [RoomAccessKeyFilter]
}

CreateRoomAccessKeyInput

Description

Not all hotels support Room Access Api. Requires one and only one to be set: roomAccessPin, roomAccessKeyStorage, roomAccessQR.

Fields
Input Field Description
roomAccessPin - CreateRoomAccessPinInput When provided, payload's roomAccessKey field will be of type RoomAccessPin
roomAccessKeyStorage - CreateRoomAccessKeyStorageInput When provided, payload's roomAccessKey field will be of type RoomAccessKeyStorage
roomAccessQR - CreateRoomAccessQRInput When provided, payload's roomAccessKey field will be of type RoomAccessQR
Example
{
  "roomAccessPin": CreateRoomAccessPinInput,
  "roomAccessKeyStorage": CreateRoomAccessKeyStorageInput,
  "roomAccessQR": CreateRoomAccessQRInput
}

CreateRoomAccessPinInput

Fields
Input Field Description
pin - String! Must match against ([A-Z0-9]|[a-z0-9]){1,10} (both lowercase and uppercase are allowed, but they should not be mixed) Your integration is responsible for ensuring the same pin is not used for multiple reservation rooms at the same time
Example
{"pin": "abc123"}

CreateRoomAccessKeyStorageInput

Fields
Input Field Description
pin - String! ([A-Z0-9]|[a-z0-9]){1,10} Your integration is responsible for ensuring the pins are unique
compartment - String! ([A-Z0-9]|[a-z0-9]){1,10}
Example
{
  "pin": "xyz789",
  "compartment": "abc123"
}

CreateRoomAccessQRInput

Fields
Input Field Description
qrData - String! The data being used to generate QR code. At most 512 characters are allowed
Example
{"qrData": "abc123"}

DeleteRoomAccessKeyInput

Fields
Input Field Description
roomAccessKeyId - ID!

Restrictions when deleting an access key:

  • Only access keys created by your integration can be deleted.
  • Room Access cannot be assigned to any reservation room.
Example
{"roomAccessKeyId": 4}

AddRoomAccessKeyInput

Fields
Input Field Description
roomStayId - ID! When a room already has an access key, the previous one must be removed first
roomAccessKeyId - ID! Only access keys created by your integration can be assigned
Example
{"roomStayId": "4", "roomAccessKeyId": 4}

RemoveRoomAccessKeyInput

Fields
Input Field Description
roomStayId - ID!
roomAccessKeyId - ID! Only access keys created by your integration can be revoked
Example
{"roomStayId": 4, "roomAccessKeyId": "4"}

RoomStayFilter

Fields
Input Field Description
id - IdOperators
date - Date

All reservations that arrive or stayover on $date

Alias for {reservation_from: {le: $date}, reservation_to: {gt: $date}}

reservation_from - DateOperators
reservation_to - DateOperators
reservationStatus - ReservationStatusOperators
updatedAt - DatetimeOperators
check_in - DatetimeOperators
check_out - DatetimeOperators
not - RoomStayFilter
or - [RoomStayFilter!]
Example
{
  "id": IdOperators,
  "date": "2007-12-03",
  "reservation_from": DateOperators,
  "reservation_to": DateOperators,
  "reservationStatus": ReservationStatusOperators,
  "updatedAt": DatetimeOperators,
  "check_in": DatetimeOperators,
  "check_out": DatetimeOperators,
  "not": RoomStayFilter,
  "or": [RoomStayFilter]
}

ClientFilter

Fields
Input Field Description
id - IdOperators
birthday - BirthdayOperators
not - ClientFilter
or - [ClientFilter!]
Example
{
  "id": IdOperators,
  "birthday": BirthdayOperators,
  "not": ClientFilter,
  "or": [ClientFilter]
}

CategoryPriceFilter

Fields
Input Field Description
date - DateRangeInput!
Example
{"date": DateRangeInput}

CategoryRestrictionFilter

Fields
Input Field Description
date - DateRangeInput!
Example
{"date": DateRangeInput}

CategoryAvailabilityFilter

Fields
Input Field Description
date - DateRangeInput! Date range can contain at most 1000 days
Example
{"date": DateRangeInput}

UpdateReservationInput

Fields
Input Field Description
id - ID!
groupName - String
clientId - ID
contactId - ID
billingClientId - ID
billingContactId - ID
Example
{
  "id": 4,
  "groupName": "abc123",
  "clientId": "4",
  "contactId": "4",
  "billingClientId": "4",
  "billingContactId": 4
}

ReservationFilter

Fields
Input Field Description
id - IdOperators
code - StringOperators
bookingChannelCode - StringOperators
status - ReservationStatusOperators
updatedAt - DatetimeOperators
not - ReservationFilter
or - [ReservationFilter!]
Example
{
  "id": IdOperators,
  "code": StringOperators,
  "bookingChannelCode": StringOperators,
  "status": ReservationStatusOperators,
  "updatedAt": DatetimeOperators,
  "not": ReservationFilter,
  "or": [ReservationFilter]
}

CategoryFilter

Fields
Input Field Description
id - IdOperators
not - CategoryFilter
or - [CategoryFilter!]
Example
{
  "id": IdOperators,
  "not": CategoryFilter,
  "or": [CategoryFilter]
}

RoomSetupFilter

Fields
Input Field Description
id - IdOperators
not - RoomSetupFilter
or - [RoomSetupFilter!]
Example
{
  "id": IdOperators,
  "not": RoomSetupFilter,
  "or": [RoomSetupFilter]
}

StatisticsFilter

Fields
Input Field Description
date - Date! Statistics for selected date
Example
{"date": "2007-12-03"}

DateRangeInput

Description

Inclusive [start, end] date range

Fields
Input Field Description
start - Date!
end - Date!
Example
{
  "start": "2007-12-03",
  "end": "2007-12-03"
}

InventoryFilter

Fields
Input Field Description
period - DateRangeInput!
categories - [String!] List of category ids. If not provided, all categories will be used
Example
{
  "period": DateRangeInput,
  "categories": ["abc123"]
}

ReservationStatusOperators

Fields
Input Field Description
eq - ReservationStatus field = x
in - [ReservationStatus!] field = x OR field = y OR ...
Example
{"eq": "CANCELLED", "in": ["CANCELLED"]}

WebhookEndpointFilter

Fields
Input Field Description
id - IdOperators
not - WebhookEndpointFilter
or - [WebhookEndpointFilter!]
Example
{
  "id": IdOperators,
  "not": WebhookEndpointFilter,
  "or": [WebhookEndpointFilter]
}

CreateWebhookEndpointInput

Fields
Input Field Description
url - String!
status - WebhookEndpointStatus!
events - [String!]!
Example
{
  "url": "abc123",
  "status": "ENABLED",
  "events": ["abc123"]
}

UpdateWebhookEndpointInput

Fields
Input Field Description
id - ID!
url - String
status - WebhookEndpointStatus When disabling an endpoint, any currently pending webhooks may still be sent
events - [String!] When removing an event, any currently pending webhooks may still be sent
Example
{
  "id": "4",
  "url": "abc123",
  "status": "ENABLED",
  "events": ["abc123"]
}

DeleteWebhookEndpointInput

Fields
Input Field Description
id - ID!
Example
{"id": 4}

CreatePaymentMethodInput

Fields
Input Field Description
name - String!
Example
{"name": "abc123"}

UpdatePaymentMethodInput

Fields
Input Field Description
id - ID!
name - String
Example
{"id": 4, "name": "xyz789"}

DeletePaymentMethodInput

Fields
Input Field Description
id - ID!
Example
{"id": 4}

PaymentMethodFilter

Fields
Input Field Description
id - IdOperators
not - PaymentMethodFilter
or - [PaymentMethodFilter!]
Example
{
  "id": IdOperators,
  "not": PaymentMethodFilter,
  "or": [PaymentMethodFilter]
}

CreateDepositInput

Fields
Input Field Description
paymentMethod - ID!
reservation - ID!
amount - Decimal!
datetime - Datetime
postingText - String
Example
{
  "paymentMethod": 4,
  "reservation": 4,
  "amount": Decimal,
  "datetime": Datetime,
  "postingText": "abc123"
}

ReceiptOptions

Fields
Input Field Description
languageCode - String! See communicationLanguages query for a list of allowed language codes. For invalid language codes, a hotel-defined fallback will be used
Example
{"languageCode": "abc123"}

AddRoomStayGuestInput

Fields
Input Field Description
roomStayId - ID!
clientId - ID!
beforeId - ID The ID of the client to position this client before. If omitted or set to null the client will be added at the end.
Example
{
  "roomStayId": 4,
  "clientId": "4",
  "beforeId": 4
}

RemoveRoomStayGuestInput

Fields
Input Field Description
roomStayId - ID!
clientId - ID!
Example
{
  "roomStayId": "4",
  "clientId": "4"
}

CreateClientInput

Fields
Input Field Description
person - CreatePersonInput!
Example
{"person": CreatePersonInput}

CreatePersonInput

Fields
Input Field Description
clientTitleId - ID
firstname - String At least one of firstname or lastname is required.
lastname - String At least one of firstname or lastname is required.
street - String
zipcode - String
city - String
country - String!
language - String!
email - String
telephone - String
mobile - String
fax - String
preferences - String preferences cannot be used together with stayPreferences
stayPreferences - String
mealPreferences - String
nationality - String
passportNumber - String
idCardNumber - String
issuingAuthority - String
birthday - Date
carPlateNumber - String
Example
{
  "clientTitleId": 4,
  "firstname": "abc123",
  "lastname": "xyz789",
  "street": "xyz789",
  "zipcode": "abc123",
  "city": "xyz789",
  "country": "abc123",
  "language": "abc123",
  "email": "abc123",
  "telephone": "xyz789",
  "mobile": "xyz789",
  "fax": "abc123",
  "preferences": "xyz789",
  "stayPreferences": "abc123",
  "mealPreferences": "xyz789",
  "nationality": "abc123",
  "passportNumber": "xyz789",
  "idCardNumber": "abc123",
  "issuingAuthority": "xyz789",
  "birthday": "2007-12-03",
  "carPlateNumber": "abc123"
}

UpdateClientInput

Fields
Input Field Description
id - ID!
person - UpdatePersonInput One of person or company is required, but not both at the same time.
company - UpdateCompanyInput One of person or company is required, but not both at the same time.
Example
{
  "id": "4",
  "person": UpdatePersonInput,
  "company": UpdateCompanyInput
}

UpdatePersonInput

Fields
Input Field Description
clientTitleId - ID
street - String
zipcode - String
city - String
country - String
language - String
email - String
telephone - String
mobile - String
fax - String
preferences - String preferences cannot be used together with stayPreferences
stayPreferences - String
mealPreferences - String
nationality - String
passportNumber - String
idCardNumber - String
issuingAuthority - String
birthday - Date
carPlateNumber - String
Example
{
  "clientTitleId": 4,
  "street": "xyz789",
  "zipcode": "xyz789",
  "city": "abc123",
  "country": "abc123",
  "language": "abc123",
  "email": "xyz789",
  "telephone": "xyz789",
  "mobile": "xyz789",
  "fax": "abc123",
  "preferences": "abc123",
  "stayPreferences": "abc123",
  "mealPreferences": "abc123",
  "nationality": "xyz789",
  "passportNumber": "xyz789",
  "idCardNumber": "xyz789",
  "issuingAuthority": "xyz789",
  "birthday": "2007-12-03",
  "carPlateNumber": "xyz789"
}

UpdateCompanyInput

Fields
Input Field Description
street - String
zipcode - String
city - String
country - String
language - String
email - String
telephone - String
mobile - String
fax - String
preferences - String preferences cannot be used together with stayPreferences
stayPreferences - String
Example
{
  "street": "abc123",
  "zipcode": "abc123",
  "city": "xyz789",
  "country": "xyz789",
  "language": "abc123",
  "email": "xyz789",
  "telephone": "xyz789",
  "mobile": "xyz789",
  "fax": "xyz789",
  "preferences": "abc123",
  "stayPreferences": "abc123"
}

CreateExternalSalesProductInput

Fields
Input Field Description
name - String! Name can be up to 20 characters
Example
{"name": "xyz789"}

UpdateExternalSalesProductInput

Fields
Input Field Description
id - ID!
name - String Name can be up to 20 characters
Example
{
  "id": "4",
  "name": "xyz789"
}

CreateExternalSaleInput

Fields
Input Field Description
productId - ID! External Sale product to use, id of one created in createExternalSaleProduct mutation
roomStayId - ID! Room stay to which the product will be attached to. Reservation must not be cancelled
amount - Decimal!
saleCreatedAt - Datetime!
receiptNumber - String!
receiptPdfUrl - String Url to receipt pdf. Up to 200 characters.
waiterName - String
tableName - String
Example
{
  "productId": "4",
  "roomStayId": 4,
  "amount": Decimal,
  "saleCreatedAt": Datetime,
  "receiptNumber": "abc123",
  "receiptPdfUrl": "xyz789",
  "waiterName": "xyz789",
  "tableName": "xyz789"
}

IdOperators

Fields
Input Field Description
eq - ID field = x
in - [ID] field = x OR field = y OR ...
Example
{"eq": 4, "in": ["4"]}

DateOperators

Fields
Input Field Description
lt - Date field < x
le - Date field <= x
eq - Date field = x
in - [Date!] field = x OR field = y OR ...
ge - Date field >= x
gt - Date field > x
Example
{
  "lt": "2007-12-03",
  "le": "2007-12-03",
  "eq": "2007-12-03",
  "in": ["2007-12-03"],
  "ge": "2007-12-03",
  "gt": "2007-12-03"
}

DatetimeOperators

Fields
Input Field Description
lt - Datetime field < x
le - Datetime field <= x
eq - Datetime field = x
in - [Datetime!] field = x OR field = y OR ...
ge - Datetime field >= x
gt - Datetime field > x
Example
{
  "lt": Datetime,
  "le": Datetime,
  "eq": Datetime,
  "in": [Datetime],
  "ge": Datetime,
  "gt": Datetime
}

StringOperators

Fields
Input Field Description
eq - String field = x
in - [String!] field = x OR field = y OR ...
Example
{
  "eq": "abc123",
  "in": ["xyz789"]
}

BirthdayOperators

Description

Year is ignored for all operators, for example, {eq:"2022-01-01"} will also return results for year 2021.

Range constraints wrap around when end date is before start date, for example, {ge:"2021-11-01", le:"2021-01-31"} will return results for November, December and January; while {gt:"2021-01-31", lt:"2021-11-01"} will return results for the other 9 months.

Fields
Input Field Description
lt - Date birthday < x. Cannot be used together with le
le - Date birthday <= x. Cannot be used together with lt
eq - Date birthday = x
in - [Date!] birthday = x OR birthday = y OR ...
ge - Date birthday >= x. Cannot be used together with gt
gt - Date birthday > x. Cannot be used together with ge
Example
{
  "lt": "2007-12-03",
  "le": "2007-12-03",
  "eq": "2007-12-03",
  "in": ["2007-12-03"],
  "ge": "2007-12-03",
  "gt": "2007-12-03"
}

Interfaces

RoomAccessKey

Description

Additional types could be added at any time, make sure your integration can safely continue when an unrecognised access key type is returned

Fields
Field Name Description
id - ID!
roomStays - RoomStayConnection! Reservation rooms that have this room access key assigned to. Even when your integration always assigns an access key to only one reservation room, in some cases the same access key can be assigned on other reservation rooms by 3RPMS, for example, when splitting a reservation room in two
Arguments
filter - RoomStayFilter
first - Int
after - String
last - Int
before - String
Possible Types
RoomAccessKey Types

RoomAccessPin

RoomAccessKeyStorage

RoomAccessQR

Example
{"id": 4, "roomStays": RoomStayConnection}

Client

Description

Each client is either a Person or Company, after creation this type never changes.

see concrete type definitions for more fields.

Fields
Field Name Description
id - ID!
title - String Client's preferred salutation Use clientTitle from the concrete Person type instead
firstname - String Use firstname from the concrete Person type instead
lastname - String Use lastname from the concrete Person type instead
company - String Use company from the concrete Company type instead
street - String
zipcode - String
city - String
country - String! ISO 3166-1 alpha-2 country code
language - String Preferred communication language. ISO 639-1 2 character Code
email - String
telephone - String May be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
mobile - String Despite the name, may be either a mobile phone or a landline. Some features might not be supported, like sms or whatsapp
fax - String
preferences - String Use stayPreferences
stayPreferences - String
carPlateNumber - String Use carPlateNumber from the concrete Person type instead
birthday - Date Use birthday from the concrete Person type instead
newsletterSubscriptionEnabled - Boolean! true when client agreed to recieve martketing newsletter
newsletterSubscriptionDoubleOptInAt - Datetime Contains the date and time of when client provided consent to receive the newsletter via double opt-in.
birthdayGreetingsEnabled - Boolean! Use birthdayGreetingsEnabled from the concrete Person type instead
Possible Types
Client Types

Person

Company

Example
{
  "id": 4,
  "title": "xyz789",
  "firstname": "abc123",
  "lastname": "abc123",
  "company": "xyz789",
  "street": "abc123",
  "zipcode": "abc123",
  "city": "abc123",
  "country": "abc123",
  "language": "abc123",
  "email": "abc123",
  "telephone": "abc123",
  "mobile": "xyz789",
  "fax": "abc123",
  "preferences": "abc123",
  "stayPreferences": "abc123",
  "carPlateNumber": "xyz789",
  "birthday": "2007-12-03",
  "newsletterSubscriptionEnabled": false,
  "newsletterSubscriptionDoubleOptInAt": Datetime,
  "birthdayGreetingsEnabled": true
}

Enums

CleaningStatus

Values
Enum Value Description

CLEAN

DIRTY

Example
"CLEAN"

SelfcheckinStatus

Values
Enum Value Description

DISABLED

Self checkin not enabled for this reservation, and likely wont be.

ENABLED

Self checkin is enabled, but not yet available. Too early or too late to be completed.

AVAILABLE

Self checkin available for completion. With this state the guest may be redirected to self checkin page.

COMPLETED

Already been completed. Final State.
Example
"DISABLED"

ReservationStatus

Values
Enum Value Description

CANCELLED

No more actions with a reservation are expected. Status can change to Active.

ACTIVE

Nothing invoiced, or partially invoiced. Status might change to Invoiced or Cancelled.

INVOICED

Fully invoiced. Status can change back to Active.
Example
"CANCELLED"

WebhookEndpointStatus

Values
Enum Value Description

ENABLED

DISABLED

Example
"ENABLED"

Scalars

Date

Description

Format YYYY-MM-DD

Example
"2007-12-03"

Datetime

Description

Format ISO8601

Example
Datetime

Decimal

Description

A decimal number separated by a dot. Arbitrary scale and precision. For example, "9999.99".

Decimals are always sent as strings, and, for input values, must be sent as strings.

Example
Decimal

Int

Description

The Int scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.

Example
123

ID

Description

The ID scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.

Example
4

Boolean

Description

The Boolean scalar type represents true or false.

Example
true

Float

Description

The Float scalar type represents signed double-precision fractional values as specified by IEEE 754.

Example
123.45

String

Description

The String scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.

Example
"abc123"

Directives

chain

Description

Allows the value of this field to be injected into a variable for use in subsequent operation within the same request.

This enables dependent queries to be executed in a single round-trip.

Locations: FIELD

Fields
Field Name Description
variable - String!
Example
{"variable": "xyz789"}

include

Description

Directs the executor to include this field or fragment only when the if argument is true.

Locations: FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENT

Fields
Field Name Description
if - Boolean! Included when true.
Example
{"if": true}

skip

Description

Directs the executor to skip this field or fragment when the if argument is true.

Locations: FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENT

Fields
Field Name Description
if - Boolean! Skipped when true.
Example
{"if": false}

Webhooks

Overview

3RPMS uses webhooks to notify your application when an event happens in a hotel. Webhooks enable 3RPMS to push real-time notifications to your integration. Webhooks are sent as a HTTPS POST request with a JSON payload. You can then use these notifications to execute actions in your integration.

Terminology

  • Webhook - an event notification
  • Webhook endpoint - your server that receives event notifications (webhooks)

Reference

For all webhook types the payload will be a json object with the following top-level structure:

{
  "id": "[A-Za-z0-9]{64}",
  "type": "room_stay.updated",
  "created_at": 2147483647,
  "webhook_endpoint_id": "[A-Za-z0-9]{64}",
  "data": ...
}
Field Type Description
id string Unique id of this event. Multiple endpoints receiving the same event will have the same id.
type string name of the webhook
created_at int timestamp of when the event happened. May not be the same as when your integration receives this.
webhook_endpoint_id string id of the webhook endpoint that was created via graphql api.
Can be used to identify what hotel this webhook belongs to.
data any Main body of the webhook. See specific type's reference for more details.
Best practice

For forwards compatibility, allow unknown fields at any nesting level in the payload

If you want to get notifications about any additional event types not listed here, contact 3RPMS support at office@3rpms.de describing your use case.

reservation.created

Sent when a new Reservation is created.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated reservation's id. Use this to query for more data from the API

reservation.updated

Sent when an existing Reservation is updated.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
},
"updated_fields": [...]
Field Type Description
object.id string Associated reservation's id. Use this to query for more data from the API
updated_fields string[]|null list of Reservation field names (from graphql API) that have changed. When updated_fields is not present, assume that any field could have changed.
* This will not include *Connection type fields.
* Fields that contain their own ID (for example, client or contact) will only be included when a different record is referenced, for example, a change to client's first name will not trigger a reservation.updated webhook (use client.updated event for that), but changing reservation's referenced client to someone else will trigger a reservation.updated webhook.

room_stay.created

Sent when a new RoomStay is created.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated room stay's id. Use this to query for more data from the API
updated_fields string[]|null list of RoomStay field names (from graphql API) that have changed. When updated_fields is not present, assume that any field could have changed. This will not include *Connection type fields.

room_stay.updated

Sent when an existing RoomStay is updated.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
},
"updated_fields": [...]
Field Type Description
object.id string Associated room stay's id. Use this to query for more data from the API
updated_fields string[]|null list of RoomStay field names (from graphql API) that have changed. When updated_fields is not present, assume that any field could have changed.
* This will not include *Connection type fields.
* Fields that contain their own ID (for example, reservation or guests) will only be included when a different record is referenced; for example, a change to guest's first name will not trigger a room_stay.updated webhook (use client.updated event for that), but changing adding a new guest will trigger a room_stay.updated webhook.

room_stay.deleted

Sent when an existing RoomStay is deleted.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated room stay's id.

client.updated

Sent when an existing Client is updated.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated client's id. Use this to query for more data from the API

client.deleted

Sent when an existing Client is deleted.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated client's id

room_access_key.deleted

Sent when an existing RoomAccessKey is deleted.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
}
Field Type Description
object.id string Associated room access key's id

category.availability.updated

Sent when room availability for a Category changes.

Body shape

"object": {
  "id": "[A-Za-z0-9-]{1,64}"
},
"period": {
  "start": "YYYY-MM-DD",
  "end": "YYYY-MM-DD"
}
Field Type Description
object.id string Associated category's id
period object|null Affected period. Will be null when all dates are affected.
period.start date-string Start (inclusive) of the affected period.
period.end date-string End (inclusive) of the affected period.

Creating a webhook

  1. Identify the webhooks to receive
  2. Handle requests from 3RPMS by parsing each event object and returning 2xx response status codes.
  3. Register your endpoint with 3RPMS
Webhook endpoint limit

There is a limit of 5 active webhook endpoints per hotel per integration

Step 1: Identify the webhooks to receive

See Supported events section. Each webhook endpoint can listen to multiple events.

Best practice

Create endpoints with only events you need for better performance.

Step 2: Handle requests from 3RPMS

Webhooks will be sent as a POST request, with payload in the request's body

const express = require('express');
const crypto = require('crypto');
const scmp = require('scmp');
const app = express();
// If the same endpoint is used for multiple hotels, each one will have a different endpoint secret
const webhookEndpointSecret = '[your_webhook_endpoint_secret]';
app.post('/3rpms', express.raw({type: 'application/json'}), (request, response) => {
const payloadRaw = request.body;
const signatureHeader = request.header('3rpms-signature');
const payload = JSON.parse(payloadRaw);
if (!verifyHeader(signatureHeader, payloadRaw, webhookEndpointSecret)) {
    // See Security section below for `verifySignature` definition
    response.sendStatus(400);
    return;
}
if (payload.type === 'room_stay.created') {
    const roomStayId = payload.data.object.id;
    // Execute your actions for `room_stay.created`
} else if (payload.type === 'room_stay.updated') {
    const roomStayId = payload.data.object.id;
    // Execute your actions for `room_stay.updated`
    //} else if ... { }
} else {
    console.log(`Unhandled event type ${payload.type}`);
}
// Return a response to acknowledge receipt of the event
response.sendStatus(204);
});
app.listen(8000, () => console.log('Running on port 8000'));
$payloadRaw = file_get_contents('php://input');
$signatureHeader = $_SERVER['HTTP_3RPMS_SIGNATURE'];
if (!verifySignature($signatureHeader, $payloadRaw, '[your_webhook_endpoint_secret]')) {
// See Security section below for `verifySignature` definition
// If the same endpoint is used for multiple hotels, each one will have a different endpoint secret
http_response_code(400);
exit();
}
$payload = json_decode($payloadRaw, true, 4, JSON_THROW_ON_ERROR);
if ($payload['type'] === 'room_stay.created') {
$roomStayId = $payload['data']['object']['id'];
// Execute your actions for `room_stay.created`
} elseif ($payload['type'] === 'room_stay.updated') {
$roomStayId = $payload['data']['object']['id'];
// Execute your actions for `room_stay.updated`
//} eseif (...)
} else {
echo 'Received unknown event type ' . $payload['type'];
}
http_response_code(200);
Secure connections

All endpoints are required to use https with a validate certificate

Duplicate events

Webhook endpoints might occasionally receive the same webhook more than once. We advise you to guard against duplicated webhooks by making your webhook processing idempotent. One way of doing this is logging the webhooks you've processed, and then not processing already-logged events.

3RPMS guarantees at least once delivery for webhooks

Order of events

3RPMS does not guarantee delivery of webhooks in the order in which they are generated. For example, creating a room stay and immediately deleting it might generate the following webhooks:

  1. room_stay.created
  2. room_stay.deleted

It's possible you'll receive a roomstay.deleted webhook first, for a room stay your integration doesn't know about. Your endpoint must not expect delivery of these webhooks in this order and should handle this accordingly. Use the API to fetch any missing objects (for example, you can fetch the reservation when receiving room_stay.created webhook).

Security

3RPMS signs all webhooks by including a signature in each request's 3rpms-signature header. This allows you to verify that the webhook was sent by 3RPMS, not by a third party.

To verify signatures, you'll need to save your endpoint's secret when registering your webhooks endpoint in step 3. It can be retrieved only once when first registering the endpoint.

3RPMS generates a unique secret key for each endpoint. Additionally, if you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on.

Preventing replay attacks

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, 3RPMS includes a timestamp in the 3rpms-signature header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your application reject the payload. We recommend a tolerance of no more than five minutes between the timestamp and the current time.

3RPMS generates the timestamp and signature each time we send a webhook to your endpoint. If 3RPMS retries an webhook (for example, your endpoint previously replied with a non-2xx status code), then we generate a new signature and timestamp for the next delivery attempt.

Verify signature

The 3rpms-signature header included in each signed webhook contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by a signature=.

3rpms-signature:
t=1658997155,
signature=e889ddfe87e55422a1a6493b2db846548ff2a7f22268501b519f9f4f5f70e2ff

3RPMS generates signatures using a hash-based message authentication code (HMAC) with SHA-256. For forwards compatibility, your integration must support handling multiple signatures prefixed with signature=.

Step 1: Extract the timestamp and signatures from the header

Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a (prefix, value) pair.

The value for the prefix t corresponds to the timestamp, and signature corresponds to the signature (or signatures). You can discard all other elements.

Step 2: Prepare the signed payload string

The signed_payload string is created by concatenating:

  • The timestamp (as a string) from header
  • The character .
  • The full request POST body
Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the endpoint's signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature (or signatures) in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures.

Sample code
function verifyHeader(header, body, secret, tolerance = 5 * 60) {
// Header contains two parts, timestamp and signature, in format `t=123,signature=abc`
const {timestamp, signatures} = header.split(',').reduce((acc, pair) => {
    const [key, value] = pair.split('=');
    if (key === 't') acc.timestamp = value;
    else if (key === 'signature') acc.signatures.push(value);
    return acc;
}, {
    timestamp: -1,
    // For forwards compatibility, allow multiple `signatures` to be present `signature=agfdgfdgd,signature=gkdsgde`
    signatures: [],
});
if (timestamp === -1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {
    // Reject to prevent replay attacks.
    return false;
}
const signedPayload = timestamp + '.' + body;
const expectedSignature = crypto.createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');
for(const signature of signatures) {
    if (scmp(Buffer.from(expectedSignature), Buffer.from(signature))) {
        return true;
    }
}
return false;
}
function verifySignature(string $header, string $payload, string $secret, int $tolerance = 5 * 60): bool
{
// Header contains two parts, timestamp and signatures, in format `t=123,signature=abc,signature=def`
$timestamp = null;
$signatures = [];
foreach (explode(',', $header) as $item) {
    [$key, $value] = explode('=', $item, 2);
    if ($key === 't') {
        $timestamp = $value;
    } elseif ($key === 'signature') {
        $signatures[] = $value;
    }
}
if (abs(time() - $timestamp) > $tolerance) {
    // Difference between current time and sent time is too high.
    // Reject to prevent replay attacks.
    return false;
}
$signedPayload = $timestamp . '.' . $payload;
$expectedSignature = hash_hmac('sha256', $signedPayload, $secret);
// For forwards compatibility, support multiple `signatures` in the header. Any succeeding counts as verified
foreach ($signatures as $signature) {
    if (hash_equals($expectedSignature, $signature)) {
        return true;
    }
}
return false;
}
Timeouts

Webhooks that take too long to deliver a response status code will be treated as failed and delivery will be attempted again.

Step 3: Register your endpoint with 3RPMS

See graphQL docs on how to set use our api

# Variables
# {
#   "input": {
#     "url":"https://example.com/3rpms-webhook",
#     "status":"DISABLED",
#     "events":["room_stay.created"]
#   }
# }
mutation CreateWebhookEndpoint($input:CreateWebhookEndpointInput!) {
  createWebhookEndpoint(input:$input) {
    webhookEndpoint { id }
    secret
  }
}

Make sure to save response's webhookEndpoint.id as it may be needed to identify the hotel when receiving a webhook.

Best practice

Create new webhook endpoints in a DISABLED state, and update it to ENABLED after persisting the signing secret. This will avoid signing errors for webhooks immediately after registering the webhook endpoint

Additional Data

A webhook notifies your integration that something happened and includes only the bare minimum data to identify the affected record, 3RPMS will not know what fields your integrations needs.

After receiving a webhook, its expected that your integration will query the API for the data it needs. For example, for a room_stay.updated webhook, can use the data.object.id value to query for the room's current stay period:

# Variables
# {
#   "id": "<value from data.object.id>"
# }
query RoomStay($id:ID!) {
  room_stays(filter:{id:{eq:$id}} first:1) {
    edges {
      node {
        id
        arrival:reservation_from
        departure:reservation_to
        # other fields needed
      }
    }
  }
}

Retries

3RPMS webhooks have built-in retry methods for 3xx, 4xx, or 5xx response status codes. Any webhooks with a non-2xx respones status code will be attempted again for a few days or until a 2xx response status code is received

Redirects

Webhook endpoint redirects will not be followed for security reasons. 3xx response status code is treated as failing and will be re-sent.

Failing endpoints

Endpoints will be disabled when all sent webhooks have been failing for a few days

Troubleshooting

Lost signing secret

Signing secret is retrievable only when first creating a webhook endpoint with 3RPMS. If you had not saved it or its compromised, a new webhook endpoint has to be created and the old one deleted.

Lost api key

If you want to disable a webhook endpoint, but have lost the api key, return any non-2xx response status code. After a few days, the endpoint will be disabled and 3RPMS will stop sending notifications

Stopped receiving webhooks

Endpoints that have been failing for a few days are disabled. You can check the endpoint's status via api:

query WebhookEndpointStatus($id:ID!) {
  webhookEndpoints(filter:{id:{eq:$id}}) {
    edges {
      node { status }
    }
  }  
}

If its DISABLED, and you've resolved the issues that caused the endpoint to be disabled in the first place, it can be enabled back via api:

# Variables:
# {
#   "input": {
#     "id": "your_webhook_endpoint_id",
#     "status": "ENABLED"
#   }
# }
mutation EnableWebhookEndpoint($input:UpdateWebhookEndpointInput!) {
  updateWebhookEndpoint(input:$input) {
    webhookEndpoint { status }
  }
}

Guides

Using Room Access Keys

Room Access Key API is used to read or write room door pin numbers, key storage pin, or other type of room access types. Room Access Key is a generic name for anything that gives guest access to the room - pin, physical key or other types of access in the future. This API allows to:

  • Reading the PIN to, for example, send the guest an sms with their room's number and PIN. We expect most integrations to care only about reading Room Access Keys.
  • For door lock or key storage integrators, inform 3RPMS of what Room Access Key is being used for a reservation, for other integrations to use.

Reading Room Access Key

For most integrations only reading PIN will be relevant.

Currently two types of PINs can be present on a room:

  • RoomAccessPin is used for pins with direct access to the room and used for the entire duration of the stay, e.g., door locks
  • RoomAccessKeyStorage is used for key storage that, after entering pin, will give the guest a physical key to use. This pin is meant to be used only once on arrival, and may not function after.
  • RoomAccessQR is used for locks with a QR code scanner. This key type contains data to generate a QR code.

Example query

Forwards compatability

New Room Access Key types may be added at any time. Your integration must handle cases when roomAccessKey returns a type not queried for.

Given a reservation room's id, to retrieve a Room Access Key for a specific room:

# Variables:
# {
#   "roomId": "<room id>"
# }
query RoomAccessKey($roomId:ID!) {
  room_stays(filter:{id:{eq:$roomId}} first:1) {
    edges {
      node {
        room_setup { name }
        roomAccessKey {
          ... on RoomAccessPin { pin }
          ... on RoomAccessKeyStorage { pin }
          ... on RoomAccessQR { qrData }
        }
      }
    }
  }
}

A reservation may contain multiple rooms, and each room may have a different Room Access Key. To retrieve keys for all rooms inside a reservation, when you have the reservation's code (not to be confused with reservation's id):

# Variables:
# {
#   "reservationCode": "<reservation code>",
#   "roomCursor": null
# }
query ReservationRoomAccessKeys($reservationCode:String! $roomsCursor:String = null) {
  reservations(filter:{code:{eq:$reservationCode}}) {
    edges {
      node {
        rooms(first:20 after:$roomsCursor) {
          edges {
            node {
              room_setup { name }
              roomAccessKey {
                ... on RoomAccessPin { pin }
                ... on RoomAccessKeyStorage { pin }
                ... on RoomAccessQR { qrData }
              }
            }
          }
          pageInfo { hasNextPage endCursor }
        }
      }
    }
  }
}

In the response, roomAccessKey.pin can be an alpha-numerical string with length up to 10 characters, or qrData in case of a QR code Room Access Key type. RoomAccessKeyStorage will also contain a compartment field, but that must not be shown to end guests.

Pagination

A reservation can contain more rooms than can be returned with one request. Make sure to use pageInfo.hasNextPage and pageInfo.endCursor to read subsequent pages.

Room Access Key Changes

Room access key can change at any time before arrival, or during stay. See webhooks guide, if your integration requires the latest room access keys.

Writing Room Access Key

Intended Use

This section is relevant only for door lock or key storage integrations. Writing room access keys only informs 3RPMS of how to access the room. If your integration does not control any keys, these writes wont do anything.

Basic Flow

                flowchart TD
                    A["room_stays()"] --> B["createRoomAccessKey()"]
                    B --> C["addRoomAccessKey()"]
                    C --> D["removeRoomAccessKey()"]
                    D --> E["deleteRoomAccessKey()"]
                
  1. room_stays() reads relevant Room Stay entries.
  2. createRoomAccessKey() creates a room access key. Until added to a room stay, this will not be visible to users or other integrations.
  3. addRoomAccessKey() assigns a room access key to a room stay. Recommended to do this at least a day before arrival, so that arrival instructions can be sent to guest beforehand.
    • Integrations can add only keys it has created with createRoomAccessKey()
    • Same key can be assigned to multiple rooms at the same time
    • Each room stay can have only one key assigned to it.
  4. removeRoomAccessKey() removes room access key from a room stay.
    • Integrations can remove only its own keys
  5. deleteRoomAccessKey() deletes a room access key; it has to be removed from any room stays first. 3RPMS will delete keys 2 days after departure (or 2 days after not being assigned to any room).
    • Integrations can delete only keys its own keys

1. Retrieve Room Stays

Bookings could be made months or years in advance, yet only bookings a few days in advance are relevant. 3RPMS recommends to store data only for the upcoming 7 days, and:

  1. Do one initial data poll when integration is connected
  2. Nightly synchronization for new dates

For example, to query for room stays from January 30th + next 6 days:

# Variables
                # {
                #   "start": "2026-01-30",
                #   "end": "2026-02-05"
                # }
                query RoomStays($start:Date! $end:Date! $cursor:String=null) {
                  room_stays(filter:{reservation_from:{le:$end} reservation_to:{gt:$start}} first:20 after:$cursor) {
                    edges {
                      node {
                          ...RoomStaySubset
                      }
                    }
                    pageInfo {
                      endCursor
                      hasNextPage
                    }
                  }
                }
                
                fragment RoomStaySubset on RoomStay {
                  id
                  room_setup { id }
                  arrival: reservation_from
                  departure: reservation_to
                
                  # In some cases, a newly created room stay may use an already existing room access key,
                  # for example, when an existing room stay is split in two separate ones.
                  roomAccessKey { id }
                }
                

New bookings could be created just before arrival. In addition Room Stay arrival, departure and physical room can change at any time. How to respond to these changes is described in Listen for Room Stay Changes section.

2. Create a Room Access Key

There are three types of room access keys - RoomAccessPin, RoomAccessKeyStorage and RoomAccessQR. Any one of them can be created with the same mutation, by providing different inputs:

mutation CreateRoomAccessKey($input:CreateRoomAccessKeyInput!) {
    createRoomAccessKey(input:$input) {
        roomAccessKey { id }
    }
}

In response, you'll get an id that you need to save, to assign to a room stay and keep track of.

RoomAccessPin type's use case is when a pin unlocks a door directly. This type has one required field:

  • pin that guest will use to open the door. Up to 10 alpha-numeric characters. Both uppercase and lowercase is allowed, but not both at the same time.

Variable structure to use for this type, with the previously mentioned mutation:

{
  "input": {
    "roomAccessPin": {
      "pin": "1234567890"
    }
  }
}

RoomAccessKeyStorage use case is when a pin is used to on arrival get a physical key that is then used for the duration of the stay. This type has two required fields :

  • pin that guest will use to access key storage. Up to 10 alpha-numeric characters. Both uppercase and lowercase is allowed, but not both at the same time.
  • compartment where hotel staff will place the key. Usually not relevant for guests. Up to 10 alphanumeric characters.

Variable structure to use for this type, with the previously mentioned mutation:

{
  "input": {
    "roomAccessKeyStorage": {
      "pin": "1234567890",
      "compartment": "3R"
    }
  }
}

RoomAccessQR use case is when a room's door can be opened by scanning a QR code. This type has one required field:

  • qrData Data being used to generate QR code. Up to 512 characters are allowed.

Variable structure to use for this type, with the previously mentioned mutation:

{
  "input": {
    "roomAccessQR": {
      "qrData": "abcdefgh1234567890"
    }
  }
}

3. Assign a Room Access Key

A newly created room access key will not be used for anything. For hotel staff and other systems to be aware of a Room Access Key, it has to be assigned to a Room Stay. Assignment is done the same way for all Room Access Key types:

# Variables:
# {
#   "addRoomAccessKey": {
#     "roomStayId": "<room stay id>",
#     "roomAccessKeyId": "<room access key id>"
#   }
# }
mutation AddRoomAccessKey($addRoomAccessKey: AddRoomAccessKeyInput!) {
  addRoomAccessKey(input:$addRoomAccessKey) {
    roomStay {
      room_setup { id name }
      arrival: reservation_from
      departure: reservation_to
      roomAccessKey { id }
    }
  }
}
Recommendation

Room Access Key should be assigned to the room at least a day before arrival, to allow sending out arrival instructions.

4. Listen for Room Stay Changes

New bookings could be created just before arrival, or a reservation could be cancelled last minute. In addition Room Stay arrival, departure and physical room can change at any time. Your integration must respond to these changes to ensure the Room Access Key works when, for example, a Room Stay is moved up by a week.

Scenarios to handle:

  • Arrival/Departure change
  • Room Stay moved to a different physical room
  • Room Stay deleted
  • Reservation cancelled or re-activated
  • Room Stay created

Most scenarios are applicable only when using door pin, but some are relevant for key storage or other key types as well. 3RPMS recommends setting up a webhook to react to these changes in realtime:

  • room_stay.created - Add Room Access Keys to last-minute bookings.
  • room_stay.updated - Changes to arrival, departure or physical room may be of interest.
  • room_stay.deleted - When a room stay that had your integration's key assigned, is deleted, your integration may need to remove some access on your side.
  • reservation.updated - When a reservation that had your integration's key assigned, is cancelled, your integration may need to remove some access on your side. Similarly, when a reservation is activated.
  • room_access_key.deleted

See webhooks guide on how to use them.

Upon receiving a webhook, retrieve the data your integration needs:

# Variables
                # {
                #   "ids": ["<room stay id 1>", "<room stay id 2>"]
                # }
                query RoomStay($ids:[ID!]!) {
                    room_stays(filter:{id:{in:$ids}}) {
                        edges {
                            node {
                                ...RoomStaySubset
                            }
                        }
                    }
                }
                
                fragment RoomStaySubset on RoomStay {
                    id
                    room_setup { id }
                    arrival: reservation_from
                    departure: reservation_to
                
                    # In some cases, a newly created room stay may use an already existing room access key,
                    # for example, when an existing room stay is split in two separate ones.
                    roomAccessKey { id }
                }
                

5. Revoke a Room Access Key

A Room Stay can have only on Room Access Key assigned. To change pin, or assign a different Room Access Key, the previous one has to be revoked. This is the same for all Room Access Key types:

# Variables
# {
#   "removeRoomAccessKey": {
#     "roomStayId": "<room stay id>",
#     "roomAccessKeyId": "<room access key id >"
#   }
# }
mutation RemoveRoomAccessKey($removeRoomAccessKey:RemoveRoomAccessKeyInput!) {
  removeRoomAccessKey(input:$removeRoomAccessKey) {
    roomStay {
      id
      roomAccessKey {
        id
      }
    }
  }
}

6. Delete a Room Access Key

After departure, or when your integration knows the key is no longer needed, its recommended to delete the room access key. Only Room Access Keys that have not been assigned to any rooms can be deleted.

# Variables
# { 
#   "roomAccessKeyId": "abc"
# }
mutation DeleteRoomAccessKey($roomAccessKeyId:ID!) {
  deleteRoomAccessKey(input:{roomAccessKeyId: $roomAccessKeyId}) {
    deleted
  }
}
Cleanup

3RPMS will revoke and delete Room Access Keys 2 days after room stay departure, or 2 days of not being assigned to any room. Such Room Access Keys are assumed to be no longer used.

Rate Limits

Handling Room Access Keys updates for many Room Stays at the same time can cause exceeding rate limits. To prevent this, batch your queries in as few requests as possible. For example:

  1. Request: Query for rooms within the stay period
  2. Request: Create all Room Access Keys
  3. Request: Assigning new Room Access keys, Revoking and Deleting old Room Access Keys

To reduce requests even further, 2nd and 3rd request can be combined by interlieving the queries and using Chained Operations: Create, Assign, Create, Assign, Revoke, Revoke, Delete, Delete

External Sales API

The External Sales API provides the ability to integrate your POS system with 3RPMS enabling sending sales' bills to the guest's room. This helps achieve a seamless experience for guests and improves the overall efficiency for sales performed through 3rd party systems.

API Access

External Sales API requires read/write permissions

Terminology:

  • External Sale - A single sale attached to a room stay.
  • External Sales Product - A sale's template. Each External Sale will reference an External Sales Product.

Set up external sales product

Before any External Sale can be added, an External Sales Product must be set up. This will need to be referenced when adding an External Sale. The product's name can be anything, but its recommended to be your integration's name - this name will be visible in reservation's item listing.

# Query variables:
# {
#   "createExternalSalesProduct": {
#     "name": "Retro-rant"
#   }
# }
mutation CreateExternalSalesProduct($createExternalSalesProduct: CreateExternalSalesProductInput!) {
  createExternalSalesProduct(input:$createExternalSalesProduct) {
    product { id name }
  }
}

Your integration should save the response's id to later use in when adding. an External Sale

Limitation

There is a limit of 1 External Sales Product per integration

Update External Sales Product

The name of an External Sales Product can be changed at any time. This name will be used only for new External Sales created, any existing External Sales will remain unchanged.

# Query variables:
# {
#   "updateExternalSalesProduct": {
#     "id": "<id>",
#     "name": "Retro-raunt"
#   }
# }
mutation UpdateExternalSalesProduct($updateExternalSalesProduct: UpdateExternalSalesProductInput!) {
  updateExternalSalesProduct(input:$updateExternalSalesProduct) {
    product { id name }
  }
}

List External Sales Products

In case state from createExternalSalesProduct was not saved or was lost, it can be retrieved with externalSalesProducts query:

query ExternalSalesProducts {
  externalSalesProducts {
    edges {
      node { id name }
    }
  }
}

Retrieve active room list

Every new External Sale must be attached to a room stay. An example query to retrieve room stays that: (1) have checked in, and (2) have not checked out, and (3) reservation is not cancelled.

query CheckedInRoomStays($cursor: String = null) {
  room_stays(first:20 after:$cursor filter:{ not:{ check_in:{ eq:null } } check_out:{ eq:null } }) {
    edges {
      node {
        id
        reservation { code }
        roomName
        first_guest { firstname lastname }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}

External Sales can be attached to any non-cancelled rooms, your integration may use a different filter if needed.

Add External Sale

In addition to external sales product and room stay id, there are 3 requires values - amount, saleCreatedAt, receiptNumber. Quantity or discount cannot be specified - amount is the final total.

# Query variables:
# {
#   "createExternalSale": {
#     "productId": "<external sales product id>",
#     "roomStayId": "<room stay id>",
#     "amount": "12.34",
#     "saleCreatedAt": "2023-02-20T11:13:00+01:00",
#     "receiptNumber": "INVOICE-123"
#   }
# }
mutation CreateExternalSale($createExternalSale:CreateExternalSaleInput!) {
  createExternalSale(input: $createExternalSale) {
    created
  }
}

In addition, your integration can also provide optional receiptPdfUrl, waiterName and tableName values.

Cancel External Sale

External Sales cannot be updated or deleted by your integration, nor by hotel staff. Cancellation can be achieved by sending negative amount using the same mutation as above.

Rates & Restrictions API

This guide explains how to read and update category-level rates and restrictions.

Reading Rates and Restrictions

Rates and Restrictions are scoped to a category and a specific date. While category filter can be skipped, a date range for rates and restrictions is mandatory. Rates can be ready by using the Category.defaultPrices field, for example to read rates for all of July 2025:

# Variables
                # {
                #   "dateRange": {
                #     "start": "2025-07-01",
                #     "end": "2025-07-31"
                #   }
                # }
                query RatesAndRestrictions($dateRange:DateRangeInput!) {
                  settings {
                    categories {
                      edges {
                        node {
                          ...MyCategoryData
                        }
                      }
                    }
                  }
                }
                
                fragment MyCategoryData on Category {
                  id
                  defaultPrices(filter:{date:$dateRange} first:31) {
                    edges {
                      node {
                        date
                        amount
                      }
                    }
                  }
                }
                

Similarly, restrictions can be read by using Category.restrictions field:

fragment MyCategoryData on Category {
  id
  restrictions(filter:{date:$dateRange} first:31) {
    edges {
      node {
        date
        stopSell
        minStay
        maxStay
        closedToArrival
        closedToDeparture
        guarantee
        cancellation
        breakfastIncluded
      }
    }
  }
}
Best Practice

defaultPrices and restrictions fields are *Connection types. Like all *Connection types, they support cursor pagination and are limited to at most 100 results per page. However, as each date appears only once, its more convenient to use multiple date ranges to read more data. instead of cursor arguments.

Field Description
stopSell When active, category is not bookable
minStay Minimum Length of Stay. Value is between 1 and 99 (inclusive)
maxStay Maximum Length of Stay. NULL represents a no limit on maximum stay
closedToArrival When active, arriving on this day is not allowed
closedToDeparture When active, departing on this day is not allowed
guarantee When active, a guarantee is required to stay on this date
cancellation The amount of days whilst possible to cancel prior to arrival without penalties. NULL is non cancellable, 0 is a standard policy as defined in booking portals.
breakfastIncluded Whether breakfast is included in price or not.
Channel Manager Limitations

Not all restrictions are supported by all Channel Managers. Some hotels might chose to not use all restrictions because of this.

Occupancy-Based Pricing

Some categories might have different rates based on how many people are staying in the room. For example, a standard rate might be set up for 2 people, with a cheaper rate if room is booked by 1 person. If your integration also wants to query occupancy based prices, the query can be augmented to include this data as well:

fragment MyCategoryData on Category {
  id
  standardOccupancy
  defaultPrices(filter:{date:$dateRange} first:31) {
    edges {
      node {
        date
        amount
        occupancies {
          occupancy
          amount
        }
      }
    }
  }
}

top-level amount refers to rate used by standard occupancy (standardOccupancy field), while occupancies.occupancy field refers to rate used by each occupancy other than standard occupancy.

occupancies field will be empty when a category does not use occupancy based pricing, so if your integration needs these rates, it's safe to include the fields for all categories.

Updating Rates and Restrictions

API Access

Updating rates and restrictions requires read/write permissions

Rates can be updated by using the updateCategoryPrices mutation. It requires:

  • Category ID
  • Date Range (inclusive)
  • Amount
# Variables:
# { 
#   "updates": [
#     {category: "<category id>", dateRange: {start: "2025-07-07", end: "2025-07-11"}, amount: "123.45"},
#     {category: "<category id>", dateRange: {start: "2025-07-14", end: "2025-07-18"}, amount: "111.99"},
#   ],
# }
#
mutation UpdatePrice($updates:[CategoryPriceInput!]!) {
    updateCategoryPrices(input:{prices: $updates}) {
        __typename
    }
}

Similarly, restrictions can be updated by using updateCategoryRestrictions mutation. It requires:

  • Category ID
  • Date Range (inclusive)
  • Changeset - any subset of restriction fields. Non-provided fields will not be updated.
# Variables:
# {
#   "restrictions": [
#     {
#       "category": "<category id>",
#       "dateRange": {
#         "start": "2025-07-01",
#         "end": "2025-07-31"
#       },
#       "stopSell": true,
#       "minStay": 4,
#       "maxStay": 5,
#       "closedToArrival": true,
#       "closedToDeparture": true,
#       "guarantee": true,
#       "cancellation": 2,
#       "breakfastIncluded": true
#     }
#   ]
# }
mutation UpdateRestrictions($restrictions: [CategoryRestrictionInput!]!) {
    updateCategoryRestrictions(input:{restrictions:$restrictions}) { __typename }
}
Update Limit

At most 1000 updates can be sent in one mutation. 1 update is a category and date pair. For example, updating 1 category for July is 31 updates. To update more than 1000 days, multiple requests must be sent.

Occupancy-Based Pricing

Occupancy-based pricing by default is derived from the standard rate. Updating these rates is optional. However, if the default behaviour is not suitable, your integration may still provide a different rate for each occupancy by providing an additional occupancy value:

{ 
  "updates": [
    {category: "<category id>", dateRange: {start: "2023-09-13", end: "2023-09-13"}, occupancy: 2, amount: "123.45"},
  ],
}

To reset back to using a derived rate for an occupancy, the amount value can be NULL:

{ 
  "updates": [
    {category: "<category id>", dateRange: {start: "2023-09-13", end: "2023-09-13"}, occupancy: 2, amount: null},
  ],
}
No Duplicates

Avoid sending multiple updates for the same (date,category,?occupancy) tuple in one request. We provide no guarantee on the order in which updates processed.