Skip to content
yaReserva

Search the site

Go to

Searches the 78 features, the 36 guides, the documentation and the frequently asked questions.

Book

Documentation

Getting started

The yaReserva API for anyone integrating a system with a hotel: how each request is signed, how to avoid charging twice, and what comes back when something goes wrong.

What this API is

yaReserva is the system a hotel runs and sells with. This API is the door for another program to do part of that work: check what is available and book from the hotel’s own website, charge dinner at the restaurant to the room, or issue the digital key at check-in.

It is not a public catalogue API: every credential belongs to one hotel and to one of its properties, and only sees that property’s data. The hotel creates it from its panel and can switch it off at any time.

IntegrationFor whomWho calls whom
Own booking engineA hotel that already has its own search on its websiteYour server calls yaReserva
Restaurant POSSoft Restaurant, Parrot, Loyverse or another point of saleThe POS calls yaReserva
Door locksSalto, TTLock, Dormakaba, Assa Abloy and the hotel’s bridgeyaReserva calls your bridge

Note: Everything on this page applies to all three

What changes is the header prefix and the key prefix, not the mechanics. Read this page once and then go straight to your integration’s section.

Environments

There are two environments and they are two separate worlds. Credentials from one do not work in the other: a test key in production answers 401, and the other way round too. The hotel asks for the test ones in its panel and gets the base URL to point at along with them.

EnvironmentBase URLWhat happens there
TestThe base URL comes with your test credentialsTest data. No real charges, no real locks. This is where you build.
Productionhttps://api.yareserva.comReal guests and real money. The hotel enables it once the integration already works in test.

Keep the base URL in your configuration, never in code. Going from test to production must be a matter of changing two values: the URL and the credential pair.

Signed authentication

There are no tokens and no sessions. Every request is signed on its own with the connection’s secret, and the secret never travels: what travels is the signature, which is the only thing you can compute if you hold it.

Each integration has its own trio of headers. The name changes, the shape does not: public key, time and signature.

IntegrationKey prefixHeaders
Booking engineyreng_X-YR-ENG-Key, X-YR-ENG-Timestamp, X-YR-ENG-Signature
Restaurant POSyrpos_X-YR-POS-Key, X-YR-POS-Timestamp, X-YR-POS-Signature
Door locksyrlock_X-YR-Lock-Key, X-YR-Lock-Timestamp, X-YR-Lock-Signature
  1. Serialize the body to JSON and keep those exact bytes. From here on do not reformat it: one extra space changes the signature.
  2. Take the current time in Unix seconds, in UTC. That number is the timestamp.
  3. Build the string to sign by joining the timestamp, a dot and the body: “timestamp + "." + body”. On a GET the body is empty, so the string ends at the dot.
  4. Compute HMAC-SHA256 of that string using the secret as the key, and render it as lowercase hexadecimal.
  5. Send the three headers: the public key, the timestamp you used, and the signature prefixed with “sha256=”.
The headers, put together
X-YR-ENG-Key:       yreng_3f9a1c0b7d2e4a6f8b0c1d2e
X-YR-ENG-Timestamp: 1789459391
X-YR-ENG-Signature: sha256=9c1f0a7d4b2e6c8a1f3d5b7e9c0a2d4f6b8e0c2a4d6f8b0c2e4a6d8f0b2c4e6a
bash
KEY=yreng_3f9a1c0b7d2e4a6f8b0c1d2e
SECRET=yreng_sk_...
BODY='{"offers":["eyJ2IjoxLCJyIjoi..."],"guest":{"firstName":"Ana","lastName":"Ruiz","email":"ana@correo.mx"},"acceptedTerms":true,"acceptedPrivacy":true}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST https://api.yareserva.com/api/v1/engine/bookings \
  -H "Content-Type: application/json" \
  -H "X-YR-ENG-Key: $KEY" \
  -H "X-YR-ENG-Timestamp: $TS" \
  -H "X-YR-ENG-Signature: sha256=$SIG" \
  -H "X-Idempotency-Key: 8c2e1f04-5a6b-4c7d-8e9f-0a1b2c3d4e5f" \
  --data-binary "$BODY"
Node.js
const crypto = require('crypto');

function firmar(payload, secret) {
  const body = JSON.stringify(payload);
  const ts = Math.floor(Date.now() / 1000).toString();
  const sig = crypto.createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');
  return { body, ts, sig: `sha256=${sig}` };
}

// Manda `body` tal cual, sin volver a serializar el objeto.
Python
import hashlib, hmac, json, time

def firmar(payload, secret):
    body = json.dumps(payload, separators=(",", ":")).encode()
    ts = str(int(time.time()))
    sig = hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    return body, ts, "sha256=" + sig

Rule: The secret is shown only once

When the connection is created or rotated, the panel shows the secret once and never again: it is stored encrypted and neither the hotel nor we can read it afterwards. Put it in your secret manager right then. If it is lost, the way out is to rotate, and the previous secret stops working immediately.

The timestamp expires

A valid signature stays valid forever if nobody looks at the clock. So we look: the timestamp you send cannot differ from ours by more than five minutes. Outside that window the request is rejected even if the signature is correct.

  • Sync the server clock with NTP. The most common 401 is not a badly computed signature, it is a slow clock.
  • Generate a fresh timestamp and signature on every retry. Resending the whole request as-is fails as soon as five minutes go by.
  • The time goes in seconds, not milliseconds. A value in milliseconds lands thousands of years in the future and is rejected.

Idempotency

A dropped connection does not tell you whether the request arrived. Retrying blind is what duplicates a booking or charges the same dinner twice. That is why everything that creates something carries an idempotency key.

http code
X-Idempotency-Key: 8c2e1f04-5a6b-4c7d-8e9f-0a1b2c3d4e5f
  • Generate the key once, before the first attempt, and reuse it on every retry of that same operation. A UUID v4 will do.
  • The same key returns the same result: the resource that was already created, not a new one and not an error.
  • The key is recorded alongside the resource it created. Reusing it with different data creates nothing new and raises no error: it returns the first one. If the data changes, change the key.
  • The key accepts letters, digits and . _ : - and fits in 100 characters. A UUID v4 will do.
  • The key check and the sale happen inside a lock, so two simultaneous requests with the same key cannot create two resources: they are serialized.

Note: The POS uses the ticket number

For room charges the idempotency key’s job is done by the “externalId” field, the restaurant ticket number. Same idea under another name: retry with the same ticket number and the charge is not duplicated.

Response format

Every response is JSON with the same envelope. When it went well you get “data”; when it did not, you get “error”. Never both. “meta” only shows up on paginated lists.

It went well
{
  "data": {
    "id": "5d0c3f1e-2b7a-4c1d-9e8f-0a1b2c3d4e5f",
    "totalCents": 372000,
    "currency": "MXN",
    "createdAt": "2026-11-20T21:03:11Z"
  },
  "meta": { "page": 1, "limit": 20, "total": 37 }
}
It went wrong
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "checkOut must be after checkIn",
    "details": { "field": "checkOut" }
  }
}
WhatHow it travelsExample
IdentifiersUUID v4 as text. Never integers.5d0c3f1e-2b7a-4c1d-9e8f-0a1b2c3d4e5f
InstantsISO 8601 in UTC, with a trailing Z.2026-11-20T21:03:11Z
DaysYYYY-MM-DD, no time. A check-in is a day, not an instant.2026-11-20
AmountsCents, always an integer and always with its currency.372000 MXN is $3,720.00
ErrorsThe code is stable and in English; the message comes in Spanish, so you can show it as it is.code: OFFER_EXPIRED

Careful: Cents do not become decimals along the way

Divide by one hundred only when you print it on screen. If you store the amount as a decimal number, sooner or later a rounding error leaves one peso of difference between your ticket and the guest’s folio.

Errors

The HTTP status says what kind of problem it is and “error.code” says exactly which one. Program against the code, not against the message text: the message is there for a person to read and may change.

Errors

HTTPCodeWhenWhat to do
400IDEMPOTENCY_KEY_REQUIREDThe idempotency key is missing on something that creates a resource.Send it. It is not invented for you.
401INVALID_SIGNATUREUnknown or revoked key, signature that does not match, or time outside the five-minute window.Check the clock first. Then check that you sign the exact bytes you send.
403ORIGIN_NOT_ALLOWEDThe request came from a browser on a domain the key has not authorized.Do not retry. Either call from your server, or have the hotel declare that domain in its panel.
404NOT_FOUNDThe resource does not exist, or does not belong to this property.Check the identifier. A credential only sees its own property.
409(varios)The operation clashes with the current state: already cancelled, already paid, no longer accepting charges. Each integration has its own codes, listed in its own section.Read the resource to see where it stands before deciding. Retrying as-is will clash again.
413PAYLOAD_TOO_LARGEThe body is over 64 KB.Send fewer lines or less detail.
422VALIDATION_ERRORA field is missing or holds an impossible value.“details.field” says which one. Fix it and send again.
429RATE_LIMIT_EXCEEDEDMore than 300 requests per minute from the same IP.Wait for what “Retry-After” says and retry. It is the only 4xx you retry without changing anything.
500INTERNAL_ERRORSomething broke on our side.Retry with the same idempotency key, backing off between attempts.
503PAYMENTS_NOT_CONFIGUREDThe hotel does not have online payment configured.Do not retry. Everything else keeps working; the only thing you cannot do is charge.

Rule: Never store card data

No endpoint in this API takes a card number, a CVV or an expiry date, and none returns them. Card data is captured by Stripe in the guest’s browser and never passes through your system or ours.

Integrating a hotel?

Credentials come out of the hotel’s panel, and sandbox ones are created the same day. If the hotel does not have an account yet, start by registering it.