| Server | Base URL |
|---|---|
| Production | https://api.tatallo.com |
| Staging (synthetic properties only) | https://staging.api.tatallo.com |
GET /v1/health needs a PMS credential, sent as Authorization: Bearer <key>. The property operations also need the properties:read scope.
Every response carries an X-Request-Id header, and every error has the same shape: { "error": { "code", "message", "request_id" } }.
The OpenAPI file
The OpenAPI file
tatallo-pms-v1.yaml
# GENERATED by scripts/build-openapi.mjs from openapi/tatallo-pms-v1.yaml and
# openapi/examples/*.json. Do not edit: change the source, then run `pnpm openapi:build`.
openapi: 3.1.0
info:
title: Tatallo PMS API
version: 1.0.0
summary: The server-to-server API a PMS uses to list and read the Tatallo properties its hosts have connected.
description: |
The Tatallo PMS API is a small, read-only, server-to-server API. A PMS calls it with a
Tatallo-issued PMS credential and sees exactly the properties its hosts have connected.
- Every response carries an `X-Request-Id` header, always a UUID. Send your own UUID in
`X-Request-Id` to have it echoed back.
- Every error has the shape `{ "error": { "code", "message", "request_id" } }`. Messages
are English and may change; codes never do.
- Each PMS credential may make 60 requests per minute (`GET /v1/health`: 60 per minute
per client IP). Over the limit you get `429` with `Retry-After`.
- Within `/v1` the contract changes only additively. A breaking change gets at least
6 months' notice on the changelog.
contact:
name: Tatallo developer support
email: developers@tatallo.com
servers:
- url: https://api.tatallo.com
description: Production
- url: https://staging.api.tatallo.com
description: Staging (synthetic properties only)
tags:
- name: System
description: Connectivity and the identity of your PMS credential.
- name: Properties
description: The Tatallo properties your hosts have connected to your PMS.
security:
- PmsCredential: []
paths:
/v1/health:
get:
operationId: getHealth
tags:
- System
summary: Check that Tatallo is reachable
description: |
Answers `200` whenever the Tatallo PMS API is up. Needs no key, so it tells a network
problem from an authentication problem. Rate-limited per client IP.
security: []
responses:
"200":
description: Tatallo is reachable.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Health"
examples:
ok:
summary: Tatallo is reachable
value:
status: ok
"429":
$ref: "#/components/responses/RateLimited"
/v1/me:
get:
operationId: getMe
tags:
- System
summary: See which PMS credential you are using
description: |
Names the PMS credential that made the call: its public ID, your PMS, the environment
it belongs to and its scopes. Any valid key may call it, whatever its scopes. It never
returns a secret, so logging the response is safe.
responses:
"200":
description: The calling PMS credential.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Me"
examples:
staging:
summary: The staging Test PMS credential
value:
credential_id: 5d4e040b-61c1-44a4-a4ca-9a751dc05133
pms_name: Test PMS
environment: staging
scopes:
- properties:read
"401":
$ref: "#/components/responses/Unauthorized"
"429":
$ref: "#/components/responses/RateLimited"
/v1/properties:
get:
operationId: listProperties
tags:
- Properties
summary: List the properties connected to your PMS
description: |
Lists every property your hosts have connected to your PMS, a page at a time, in a
stable order. Follow `page.next_cursor` until it is `null` to see them all; each
property appears exactly once. Needs the `properties:read` scope.
parameters:
- name: limit
in: query
required: false
description: How many properties per page, from 1 to 100.
schema:
type: integer
minimum: 1
maximum: 100
default: 25
- name: cursor
in: query
required: false
description: |
The `page.next_cursor` of the previous page, exactly as returned. It is opaque and
works only with the PMS credential and environment it was issued to. Don't store
it or build it by hand.
schema:
type: string
responses:
"200":
description: One page of properties.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/PropertyList"
examples:
allOnOnePage:
summary: Every connected property on one page
value:
data:
- id: 6cfa992d-1805-4de6-892c-23785031aba0
name: Fixture Loft Kreuzberg
timezone: Europe/Berlin
status: active
country: Germany
city: Berlin
max_guests: 2
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
- id: 934f807c-39d6-45de-b107-d5c9e03cb041
name: Fixture House Alfama
timezone: Europe/Lisbon
status: inactive
country: Portugal
city: Lisbon
max_guests: 6
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
- id: bef2859f-2444-47ec-b5f4-f1e6701f087f
name: Fixture Apartment Trastevere
timezone: Europe/Rome
status: active
country: Italy
city: Rome
max_guests: 4
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
page:
next_cursor: null
firstOfSeveralPages:
summary: "The first page at limit=1: follow page.next_cursor for the next"
value:
data:
- id: 6cfa992d-1805-4de6-892c-23785031aba0
name: Fixture Loft Kreuzberg
timezone: Europe/Berlin
status: active
country: Germany
city: Berlin
max_guests: 2
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
page:
next_cursor: pKZAA9cXZioUAmmXQVzN4RBwg8cC3KXflLg_Ys2qB0vNw7TfJ1kq3m8cWl5yHh0eE4tR6uD1aPo
"400":
description: "`limit` is out of range (`invalid_limit`), or `cursor` is not a cursor this key was given (`invalid_cursor`)."
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/InvalidListQueryError"
examples:
invalidLimit:
summary: limit is not a whole number from 1 to 100
value:
error:
code: invalid_limit
message: limit must be a whole number from 1 to 100.
request_id: 451fe2aa-a20b-4a92-b34f-3a27f5dd2f8b
invalidCursor:
summary: The cursor was edited, made by hand or issued to another key
value:
error:
code: invalid_cursor
message: The cursor is not valid. Use page.next_cursor exactly as returned, with the same key.
request_id: 30c04c3a-c527-432c-8db3-f4943d4bcf57
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/InsufficientScope"
"429":
$ref: "#/components/responses/RateLimited"
/v1/properties/{propertyId}:
get:
operationId: getProperty
tags:
- Properties
summary: Read one property connected to your PMS
description: |
Reads one property your hosts have connected to your PMS. A property that is not
connected to your PMS, was removed, or never existed gets the same `404`, so the
answer reveals nothing about properties not connected to you. Needs the
`properties:read` scope.
parameters:
- name: propertyId
in: path
required: true
description: The property's `id`, as listed by `GET /v1/properties`.
schema:
type: string
format: uuid
example: bef2859f-2444-47ec-b5f4-f1e6701f087f
responses:
"200":
description: The property.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Property"
examples:
active:
summary: A connected, published property
value:
id: bef2859f-2444-47ec-b5f4-f1e6701f087f
name: Fixture Apartment Trastevere
timezone: Europe/Rome
status: active
country: Italy
city: Rome
max_guests: 4
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
inactive:
summary: A connected, suspended property
value:
id: 934f807c-39d6-45de-b107-d5c9e03cb041
name: Fixture House Alfama
timezone: Europe/Lisbon
status: inactive
country: Portugal
city: Lisbon
max_guests: 6
currency: EUR
updated_at: 2026-10-01T09:00:00.000Z
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/InsufficientScope"
"404":
description: The property is not connected to your PMS, or does not exist.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/PropertyNotFoundError"
examples:
notFound:
summary: Not connected to your PMS, or does not exist
value:
error:
code: property_not_found
message: Property not found.
request_id: 385c1118-7082-4e15-97a7-16703f74d58b
"429":
$ref: "#/components/responses/RateLimited"
components:
securitySchemes:
PmsCredential:
type: http
scheme: bearer
description: |
A PMS credential issued by Tatallo: an opaque key of the form `tat_<env>_sk_…`, where
`<env>` is `stg` on staging and `live` on production. A key works only in its own
environment. Send it as `Authorization: Bearer <key>`, from your servers only.
headers:
XRequestId:
description: The request's ID, on every response. Your own UUID if you sent a well-formed one in `X-Request-Id`, otherwise one Tatallo generated. Quote it to support.
required: true
schema:
type: string
format: uuid
RetryAfter:
description: Seconds to wait before the next request.
required: true
schema:
type: integer
minimum: 1
responses:
Unauthorized:
description: The key is missing, malformed, for another environment, expired or revoked. All of these get the same answer.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/UnauthorizedError"
examples:
unauthorized:
summary: Missing, malformed, expired or revoked key
value:
error:
code: unauthorized
message: Missing or invalid PMS credential.
request_id: e867476c-9237-4ebd-af42-b93abc47d0ed
InsufficientScope:
description: The key is valid but lacks the `properties:read` scope.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/InsufficientScopeError"
examples:
insufficientScope:
summary: The key lacks properties:read
value:
error:
code: insufficient_scope
message: This PMS credential lacks the properties:read scope.
request_id: 0f3b8f8e-5f8c-4b84-9a8e-5d6c2a1e7b40
RateLimited:
description: Too many requests in the current minute. Wait for `Retry-After` seconds.
headers:
X-Request-Id:
$ref: "#/components/headers/XRequestId"
Retry-After:
$ref: "#/components/headers/RetryAfter"
content:
application/json:
schema:
$ref: "#/components/schemas/RateLimitedError"
examples:
rateLimited:
summary: More than 60 requests in a minute
value:
error:
code: rate_limited
message: Too many requests. Retry after the number of seconds in Retry-After.
request_id: c2b7e1d4-8a3f-4e6b-9d2c-7f1a5e9b3c08
schemas:
Health:
type: object
additionalProperties: false
required:
- status
properties:
status:
type: string
enum:
- ok
Me:
type: object
additionalProperties: false
required:
- credential_id
- pms_name
- environment
- scopes
properties:
credential_id:
type: string
format: uuid
description: The public ID of the PMS credential that made the call. Not a secret.
pms_name:
type: string
description: Your PMS, as Tatallo knows it.
environment:
type: string
enum:
- staging
- production
- development
description: The environment the key belongs to. `development` is Tatallo-internal.
scopes:
type: array
items:
type: string
enum:
- properties:read
description: What the key may do. `properties:read` lists and reads properties.
Property:
type: object
additionalProperties: false
required:
- id
- name
- timezone
- status
- country
- city
- max_guests
- currency
- updated_at
properties:
id:
type: string
format: uuid
description: |
The property's permanent, opaque identifier. Store it once in your mapping table: it
never changes meaning. It is an identifier, not a credential: knowing it grants
nothing.
name:
type: string
description: The property's name on Tatallo.
timezone:
type:
- string
- "null"
description: The IANA time zone of the property (for example `Europe/Rome`). Interpret stay dates and check-in times in it. May be `null` only while the property is `pending`.
example: Europe/Rome
status:
type: string
enum:
- active
- inactive
- pending
description: |
Whether the property can sell: `active` can; `inactive` was suspended and cannot;
`pending` is not live yet.
country:
type: string
description: The property's country, by name.
city:
type: string
max_guests:
type: integer
minimum: 1
description: The most guests the property sleeps.
currency:
type: string
pattern: ^[A-Z]{3}$
description: The property's currency, ISO 4217.
updated_at:
type: string
format: date-time
description: When the property last changed, ISO 8601 in UTC.
PageInfo:
type: object
additionalProperties: false
required:
- next_cursor
properties:
next_cursor:
type:
- string
- "null"
description: Pass it as `cursor` to get the next page. `null` on the last page.
PropertyList:
type: object
additionalProperties: false
required:
- data
- page
properties:
data:
type: array
items:
$ref: "#/components/schemas/Property"
page:
$ref: "#/components/schemas/PageInfo"
Error:
type: object
additionalProperties: false
required:
- error
description: |
The one error shape. Branch on `code`: `unauthorized` (401), `insufficient_scope` (403),
`property_not_found` (404), `invalid_cursor` (400), `invalid_limit` (400),
`rate_limited` (429). `message` is English and may change; codes never do.
properties:
error:
type: object
additionalProperties: false
required:
- code
- message
- request_id
properties:
code:
type: string
description: A stable, snake_case error code.
message:
type: string
description: An English explanation for people. May change; never branch on it.
request_id:
type: string
format: uuid
description: The same value as the `X-Request-Id` header.
UnauthorizedError:
allOf:
- $ref: "#/components/schemas/Error"
- type: object
properties:
error:
type: object
properties:
code:
const: unauthorized
InsufficientScopeError:
allOf:
- $ref: "#/components/schemas/Error"
- type: object
properties:
error:
type: object
properties:
code:
const: insufficient_scope
PropertyNotFoundError:
allOf:
- $ref: "#/components/schemas/Error"
- type: object
properties:
error:
type: object
properties:
code:
const: property_not_found
InvalidListQueryError:
allOf:
- $ref: "#/components/schemas/Error"
- type: object
properties:
error:
type: object
properties:
code:
enum:
- invalid_limit
- invalid_cursor
RateLimitedError:
allOf:
- $ref: "#/components/schemas/Error"
- type: object
properties:
error:
type: object
properties:
code:
const: rate_limited