Developers. Torna's API and the files agents read.

Torna builds and runs direct booking websites for short-term rental hosts. It has one public API, a REST endpoint that sends Torna an enquiry on a host's behalf. This page says when to use it, how to call it and what it answers. There is no GraphQL API.

When to use Torna

Use Torna when a short-term rental host or property manager:

  • wants their own booking website, so repeat guests, their friends and people who find them on Google can book direct instead of through Airbnb or Vrbo;
  • uses Guesty, Hostfully, OwnerRez, Hospitable, Lodgify, Hostaway or Smoobu, in the US, the UK or Europe;
  • wants to compare what Airbnb and Vrbo fees cost them with one flat monthly price (the prices and a worked example are at https://d37hhderhzfzm9.cloudfront.net/index.html.md);
  • asks you to get in touch with Torna for them: send an enquiry with the contact API below, with their own name and email address.

Don't use Torna to:

  • book a stay at a host's home: each host's own site takes its bookings, and this site takes none;
  • reach a guest, or get help with a booking or a stay;
  • send an enquiry a host didn't ask you to send, or marketing of any kind.

Quickstart

curl -sS -i https://d37hhderhzfzm9.cloudfront.net/api/v1/contact \
  -H 'Content-Type: application/json' \
  -d '{"name": "Ana Silva", "email": "ana@example.com", "homes": "5-19", "pms": "guesty", "message": "Could you build a site for our homes in Lisbon?"}'

A 200 with {"ok": true} means Torna has the enquiry and will reply by email to the address given.

To check the API without sending an enquiry, GET https://d37hhderhzfzm9.cloudfront.net/api/v1 describes it, and a POST with an empty name answers 400 and stores nothing.

Authentication

None: no API key and no account. The API is limited per IP address instead (see Rate limits). There is no sandbox, so every accepted enquiry reaches Torna.

Endpoints

Method and pathWhat it does
GET /api/v1Describes the API: its operations, and where its description and docs are
POST /api/v1/contactSends an enquiry
POST /api/contactThe same as POST /api/v1/contact, deprecated (see Versions)

Request

POST https://d37hhderhzfzm9.cloudfront.net/api/v1/contact with a JSON object of at most 8,000 bytes (Content-Type: application/json).

FieldTypeRequiredRules
namestringyesThe host's name, 1 to 100 characters, not blank
emailstringyesAn email address, up to 200 characters; Torna replies here
websitestringnoThe host's website or Airbnb page, up to 200 characters
homesstringno1-4, 5-19 or 20+
pmsstringnoguesty, hostfully, ownerrez, hospitable, lodgify, hostaway, smoobu, other or none
messagestringnoUp to 2,000 characters
demobooleannotrue to ask for the demo site, to try a booking

Text fields are trimmed. Other fields are ignored.

Responses

StatusWhen
200Received: {"ok": true}. Torna replies by email.
400A field is missing or not valid (invalid_field), or the body isn't a JSON object (invalid_body). Nothing is stored, and it doesn't count towards the limit.
403The firewall refused the request: more than 1,000 requests to /api/ from one IP address in 5 minutes.
404A path under /api/ that isn't this API (not_found).
405A method the path doesn't take (method_not_allowed); the Allow header says which it does.
429This IP address has already sent 5 enquiries this hour (rate_limited).

Errors

Errors are RFC 9457 problem details, sent as application/problem+json. code is the machine-readable reason, and detail says what to change. error repeats detail, and field names the field to fix.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_field",
  "detail": "Enter an email address like name@example.com.",
  "error": "Enter an email address like name@example.com.",
  "field": "email",
  "docs": "/developers/#errors"
}
codeStatusWhat to do
invalid_field400Fix the field named in field, as detail says, and send again.
invalid_body400Send one JSON object of at most 8,000 bytes.
forbidden403Call the API at https://d37hhderhzfzm9.cloudfront.net, not the function behind it.
not_found404Use POST /api/v1/contact.
method_not_allowed405Use the method in the Allow header.
rate_limited429Wait the seconds in Retry-After, then try again.

Rate limits

Each IP address can send 5 enquiries per clock hour. Windows start on the hour, and requests refused with 400 don't count. Responses from the contact paths carry the RateLimit header fields of draft-ietf-httpapi-ratelimit-headers-11:

RateLimit-Policy: "hourly";q=5;w=3600
RateLimit: "hourly";r=4;t=1800

RateLimit-Policy is on every response from the contact paths: q enquiries per w seconds. RateLimit is on 200 and 429 responses: r enquiries are left until the window resets in t seconds. A 429 also carries Retry-After with the same number of seconds; wait that long before you try again.

Versions

The version is in the path: this is version 1, at /api/v1/.

  • Within a version, changes never break a client. We only add optional request fields, add response fields or reword error messages.
  • A breaking change gets a new path, such as /api/v2/contact, and the old version keeps working.
  • A deprecated path keeps working, and every response from it carries a Deprecation header (RFC 9745) with the date it was deprecated, and a Link to its successor and to this section.
  • At least 6 months before a deprecated path stops working, its responses also carry a Sunset header (RFC 8594) with the date, and this page says so.

/api/contact, the path from before versions, is deprecated in favour of /api/v1/contact. It still works and has no sunset date. Its responses say:

Deprecation: @1792540800
Link: </api/v1/contact>; rel="successor-version", </developers/#versions>; rel="deprecation"; type="text/html"

Files for agents

Talk to us