Skip to content

Developers

REST API v1

A read-only API over your organization's reservations, listings and payments — the same data your dashboard shows. Every request is scoped to the property-manager organization that owns the API key; there is no way to read another organization's data.

Getting started

Create an API key in the admin app under Settings → API (property-manager account required — keys always act for the whole organization, so team members cannot create them). The base URL for every endpoint is:

https://<your-domain>/api/v1

All endpoints are read-only GET requests returning JSON. A first call to try:

curl "https://<your-domain>/api/v1/listings" \
  -H "Authorization: Bearer ck_live_…"

Authentication

Send your key as a bearer token on every request:

Authorization: Bearer ck_live_…
  • The full key is shown once, at creation. Store it in a secret manager; the settings page only ever shows the last 4 characters again.
  • A missing, unknown or revoked key gets 401 invalid_api_key.
  • If a key leaks, revoke it in Settings → API — revocation is immediate — and create a new one.

Rate limits

Each key may make 60 requests per minute, with a short burst allowance. Every response carries the current state:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1753500000   # unix seconds

Past the limit you get 429 rate_limited with a Retry-After header (seconds). Well-behaved clients should pace themselves on X-RateLimit-Remaining instead of retrying on 429.

Pagination

List endpoints accept limit and offset and return:

{
  "data": [ … ],
  "totalCount": 137,
  "nextOffset": 25   // null on the last page
}

To page through results, repeat the request with offset=nextOffset until nextOffset is null. The sort order is stable, so pages never skip or repeat rows.

Errors

Every error is JSON with a stable machine-readable code:

{ "error": { "code": "invalid_params", "message": "limit: Too big …" } }
StatusCodeMeaning
401invalid_api_keyMissing, unknown, or revoked key.
404not_foundNo resource matches (e.g. unknown check-in code).
422invalid_paramsA query param failed validation — including unknown param names, so typos surface instead of being ignored.
429rate_limitedRate limit exceeded; see Retry-After.
500internal_errorUnexpected server error; safe to retry with backoff.

Conventions

  • Dates are YYYY-MM-DD calendar days in the property's local calendar (Europe/Rome); timestamps are ISO 8601 UTC.
  • All monetary amounts are EUR numbers (e.g. 486.5).
  • Hours (checkinFromHour, guestDeclaredArrivalHour, …) are 0–23 in the listing's local time.
  • Related data is expanded only when asked for via include= — responses stay lean by default.
  • The API is versioned in the path (/api/v1); fields may be added without notice, but existing fields will not change meaning or disappear within v1.

Webhooks

Instead of polling, register an HTTPS endpoint under Settings → Webhooks and pick the events you care about. Each occurrence is delivered as a POST with a JSON body:

{
  "id": "9b2e6a1c-…",              // unique per event — dedupe on this
  "type": "checkin.completed",
  "createdAt": "2026-08-14T15:04:05.000Z",
  "data": {
    "reservation": { …same object as GET /api/v1/reservations/{checkinCode},
                     incl. pmsReservationId / pmsListingId (Hostaway ids)… },
    "payment": { … }              // extra keys on some event types, see below
  }
}
EventSent when
reservation.createdA reservation was created (PMS sync or manual import).
reservation.updatedReservation core data changed (dates, guests, price, status…). Not sent for no-op sync replays.
reservation.cancelledA reservation transitioned to cancelled.
checkin.completedThe guest flow reached its terminal complete state (documents, contract and payments all done).
checkin.awaiting_confirmationThe guest finished their part but the check-in needs host confirmation (e.g. ID documents to review).
checkin.submittedThe guest finished their part of the check-in (documents, contract, extras and payment step all done). Sent once per reservation, whether the check-in then goes straight to complete or waits for host confirmation — the one to use for a "check-in done, thank you" message. Not sent when a host marks the check-in complete from the admin.
payment.succeededA check-in payment (fees and/or security deposit) was captured via Stripe.
upsell.purchasedThe guest bought one or more optional extras: ticked on the Extras step and paid with the check-in payment, or added later from the finished screen / extras link. Mandatory extras are never reported.
security_hold.placedA security hold is now in place: a pre-authorization was authorized (any lane — scheduled, immediate, re-auth, guest link) or a deposit was charged.
security_hold.failedA pre-authorization attempt failed (e.g. card declined) and may need action before arrival.
security_hold.releasedThe hold was given back: pre-authorization cancelled (including from the Stripe dashboard or by expiry) or deposit refunded.
security_hold.capturedThe pre-authorized amount (or part of it) was captured as a charge.
contract.signedThe guest signed the rental contract.
document.review_requiredGuest ID documents were flagged for host review (e.g. second upload or scanner mismatch).
portale_alloggiati.sentGuest data was accepted by the Portale Alloggiati police portal.
portale_alloggiati.failedThe Portale Alloggiati send failed and needs review; the payload carries the per-guest error details.
  • payment.succeeded adds a payment object (stripePaymentIntentId, amount, feesAmount, depositAmount, paidAt); upsell.purchased adds an upsellPurchase object (stripePaymentIntentId, amount, paidAt, and items, each with upsellId, name, amount); security_hold.* add a securityHold object (source, amount, stripePaymentIntent, detail, and the failure message on security_hold.failed); portale_alloggiati.* add a portaleAlloggiati object with the error details on failure.
  • Test events: Settings → Webhooks → Send test event delivers a signed sample of any event type with a fake guest (Test Guest, [email protected], PMS ids "0") and "test": true at the top level of the body — real events never carry it, so filter on it before messaging a guest.
  • Respond with any 2xx within 10 seconds. Redirects are not followed; anything else counts as a failure.
  • Delivery is at-least-once and ordering is not guaranteed — dedupe on the event id and rely on the embedded resource state, not on event order.
  • Failed deliveries are retried with backoff (roughly 1 min → 24 h, 8 attempts over ~2 days). An endpoint that keeps failing is disabled automatically; re-enable it from Settings → Webhooks, where you can also inspect recent deliveries and redeliver one manually.

Verifying signatures

Every delivery is signed with the endpoint's secret (shown once at creation). The X-Checkin-Signature header contains a unix timestamp and an HMAC-SHA256 of `${timestamp}.${rawBody}`:

X-Checkin-Signature: t=1753600000,v1=5257a869e7…

// Node.js
const crypto = require("node:crypto");
function verify(header, rawBody, secret) {
  const { t, v1 } = Object.fromEntries(
    header.split(",").map((p) => p.split("="))
  );
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay guard
  const mac = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v1));
}

Compute the HMAC over the raw request body exactly as received — don't re-serialize the parsed JSON. The X-Checkin-Event and X-Checkin-Delivery headers carry the event type and delivery id for logging.

For no-code platforms whose built-in HMAC check only signs the body (Activepieces, n8n, Make, …), every delivery also carries X-Checkin-Signature-256: a plain hex HMAC-SHA256 of the raw body with the same secret, no timestamp and no prefix. Configure the platform with header name X-Checkin-Signature-256, algorithm SHA-256, hex encoding and an empty prefix. Prefer X-Checkin-Signature when you write your own receiver — the timestamp is what protects you from replays.

List listings

GET/api/v1/listings

Your properties: id, guest-facing name, your internal name, city, status, capacity and the standard check-in-from / checkout-by hours (0-23, listing local time). Use the returned listingId to filter reservations. Pass availableFrom/availableTo to keep only listings with no reservation on those nights.

  • Listings deleted on the PMS side are never returned; includeInactive=true re-adds manually deactivated ones.
  • Availability is based on reservations only — manual calendar blocks made in the PMS are not visible here.

Query parameters

NameTypeDefaultDescription
includeInactivestring—Also return manually deactivated listings (PMS-deleted ones stay hidden).
availableFromdate (YYYY-MM-DD)—With availableTo, only listings with no reservation on any night in the range.
availableTodate (YYYY-MM-DD)—Last night of the availability window; defaults to availableFrom.
citystring—Case-insensitive substring match on the listing's city.

Example response

{
  "data": [
    {
      "listingId": "a3f1c2e4-5b6d-4f7e-8a9b-0c1d2e3f4a5b",
      "name": "Uptown Apartment with Balcony",
      "internalName": "Uptown 2B",
      "city": "Milan",
      "status": "active",
      "maxGuests": 4,
      "checkinFromHour": 15,
      "standardCheckoutHour": 10
    }
  ]
}

Get a listing

GET/api/v1/listings/{listingId}

One listing by its listingId — the same object the list returns and every reservation embeds.

  • Deactivated listings are returned too (a direct id lookup); listings deleted on the PMS side 404.

No query parameters.

Example response

{
  "data": {
    "listingId": "a3f1c2e4-5b6d-4f7e-8a9b-0c1d2e3f4a5b",
    "name": "Uptown Apartment with Balcony",
    "internalName": "Uptown 2B",
    "city": "Milan",
    "status": "active",
    "maxGuests": 4,
    "checkinFromHour": 15,
    "standardCheckoutHour": 10
  }
}

List reservations

GET/api/v1/reservations

Paginated reservations, oldest arrival first. All filters are optional and combine with AND: independent arrival and departure date ranges, cancellation status, listing, and a free-text query (guest-name substring, guest check-in code, PMS reservation id, guest email, or guest phone). Expand related data with include=payments,guests.

  • Every reservation embeds its full listing object — the same shape GET /api/v1/listings returns.
  • guestEmail / phone are the PMS copies; guestDeclaredEmail / guestDeclaredPhone are what the guest entered at online check-in (null if not given).
  • query matches emails and phones both as the PMS sent them and as the guest entered them in online check-in. A full email address matches that mailbox ignoring case and +tags ([email protected] finds [email protected]; Gmail also ignores dots and googlemail.com); a fragment (3+ characters) matches as a substring. Phones compare digits only, with or without the country code (+39, 0039), from 6 digits up, so a partial number works.
  • guestFlowCompletion is 'complete' when the guest finished online check-in, 'awaiting_confirmation' when it needs host review (see idDocumentsNeedReview and securityHoldStatus), 'incomplete' otherwise.
  • securityHoldStatus: 'none' (no deposit/pre-auth due or not reached), 'awaiting_scheduled_preauth' (pre-auth scheduled, placed ~2 days before arrival), 'held' (hold in place), 'failed' (attempt failed, needs retry — securityHoldFailureReason then carries the card/bank decline or error text, null in every other state), 'released' (hold returned in full after checkout), 'captured' (damage money was taken).
  • registeredGuests (include=guests) carries identity basics from submitted ID documents only — never document numbers.
  • Amounts are EUR. totalPrice is the PMS booking total; payment rows are what was actually collected or refunded.

Query parameters

NameTypeDefaultDescription
arrivalStartDatedate (YYYY-MM-DD)—Earliest arrival date (YYYY-MM-DD), inclusive.
arrivalEndDatedate (YYYY-MM-DD)—Latest arrival date (YYYY-MM-DD), inclusive.
departureStartDatedate (YYYY-MM-DD)—Earliest departure date (YYYY-MM-DD), inclusive.
departureEndDatedate (YYYY-MM-DD)—Latest departure date (YYYY-MM-DD), inclusive.
statusactive | cancelled | allallFilter on cancellation state.
listingIduuid—Only reservations for this listing (see GET /v1/listings).
querystring—Guest-name substring, guest check-in code, PMS reservation id, guest email (full address or fragment), or guest phone (6+ digits).
includestring—Comma-separated expansions: payments, guests.
limitinteger (1–100)25Rows per page (max 100).
offsetinteger (0–10000)0Rows to skip; pass a previous response's nextOffset.

Example response

{
  "data": [
    {
      "checkinCode": "8GY96S3N",
      "pmsProvider": "hostaway",
      "pmsReservationId": "41287653",
      "pmsListingId": "205117",
      "guestName": "Maria Rossi",
      "guestEmail": "[email protected]",
      "phone": "+39 333 1234567",
      "listing": {
        "listingId": "a3f1c2e4-5b6d-4f7e-8a9b-0c1d2e3f4a5b",
        "name": "Uptown Apartment with Balcony",
        "internalName": "Uptown 2B",
        "city": "Milan",
        "status": "active",
        "maxGuests": 4,
        "checkinFromHour": 15,
        "standardCheckoutHour": 10
      },
      "arrivalDate": "2026-08-14",
      "departureDate": "2026-08-17",
      "nights": 3,
      "numberOfGuests": 2,
      "channelName": "Booking.com",
      "totalPrice": 486.5,
      "cancelled": false,
      "guestFlowCompletion": "complete",
      "idDocumentsNeedReview": false,
      "securityHoldStatus": "held",
      "securityHoldFailureReason": null,
      "guestDeclaredArrivalHour": 15,
      "guestDeclaredBeds": 2,
      "guestDeclaredEmail": "[email protected]",
      "guestDeclaredPhone": "+39 333 7654321",
      "registeredGuests": [
        {
          "name": "Maria Rossi",
          "dateOfBirth": "1990-04-02",
          "nationality": "Italy",
          "mainGuest": true
        },
        {
          "name": "Luca Rossi",
          "dateOfBirth": "1988-11-19",
          "nationality": "Italy",
          "mainGuest": false
        }
      ],
      "payments": [
        {
          "kind": "checkin",
          "purpose": "payment",
          "amount": 86.5,
          "paidAt": "2026-08-01T09:12:44.000Z",
          "stripePaymentIntent": "pi_3PqXk2Abc123def456",
          "stripeRefundId": null,
          "lineItems": [
            {
              "description": "Cleaning fee",
              "amount": 60,
              "vatRate": "22",
              "exemptionTag": null
            },
            {
              "description": "City tax",
              "amount": 26.5,
              "vatRate": "0",
              "exemptionTag": "city_tax_fuori_campo"
            }
          ]
        }
      ]
    }
  ],
  "totalCount": 137,
  "nextOffset": 25
}

Get a reservation

GET/api/v1/reservations/{checkinCode}

One reservation by its guest check-in code (the code guests receive in their check-in link). Registered guests and payments are always included.

No query parameters.

Example response

{
  "data": {
    "checkinCode": "8GY96S3N",
    "pmsProvider": "hostaway",
    "pmsReservationId": "41287653",
    "pmsListingId": "205117",
    "guestName": "Maria Rossi",
    "guestEmail": "[email protected]",
    "phone": "+39 333 1234567",
    "listing": {
      "listingId": "a3f1c2e4-5b6d-4f7e-8a9b-0c1d2e3f4a5b",
      "name": "Uptown Apartment with Balcony",
      "internalName": "Uptown 2B",
      "city": "Milan",
      "status": "active",
      "maxGuests": 4,
      "checkinFromHour": 15,
      "standardCheckoutHour": 10
    },
    "arrivalDate": "2026-08-14",
    "departureDate": "2026-08-17",
    "nights": 3,
    "numberOfGuests": 2,
    "channelName": "Booking.com",
    "totalPrice": 486.5,
    "cancelled": false,
    "guestFlowCompletion": "complete",
    "idDocumentsNeedReview": false,
    "securityHoldStatus": "held",
    "securityHoldFailureReason": null,
    "guestDeclaredArrivalHour": 15,
    "guestDeclaredBeds": 2,
    "guestDeclaredEmail": "[email protected]",
    "guestDeclaredPhone": "+39 333 7654321",
    "registeredGuests": [
      {
        "name": "Maria Rossi",
        "dateOfBirth": "1990-04-02",
        "nationality": "Italy",
        "mainGuest": true
      },
      {
        "name": "Luca Rossi",
        "dateOfBirth": "1988-11-19",
        "nationality": "Italy",
        "mainGuest": false
      }
    ],
    "payments": [
      {
        "kind": "checkin",
        "purpose": "payment",
        "amount": 86.5,
        "paidAt": "2026-08-01T09:12:44.000Z",
        "stripePaymentIntent": "pi_3PqXk2Abc123def456",
        "stripeRefundId": null,
        "lineItems": [
          {
            "description": "Cleaning fee",
            "amount": 60,
            "vatRate": "22",
            "exemptionTag": null
          },
          {
            "description": "City tax",
            "amount": 26.5,
            "vatRate": "0",
            "exemptionTag": "city_tax_fuori_campo"
          }
        ]
      }
    ]
  }
}