Restado for developers
Connect your own systems to Restado: read your tickets and calls, open a ticket from another tool, and receive an event the moment something happens. Everything is per company — a key or a webhook only ever sees its own company's data.
Base address: https://api.restado.ai/api/v1
API keys
An owner or admin creates a key in Settings → API & webhooks and chooses what it may do:
- read — list tickets and calls;
- write — also open tickets.
The key is shown once, when it is created (it starts with rst_). Keep it on your server — never in a website or an app. Revoke it in the same place and it stops working at once. Send it in either header:
Authorization: Bearer rst_…
# or
X-Restado-Api-Key: rst_…Tickets
List tickets
GET /tickets — the latest 200, newest first. Optional ?status= open, escalating, awaiting_human, resolved or closed.
curl https://api.restado.ai/api/v1/tickets?status=open \
-H "Authorization: Bearer rst_…"{
"tickets": [
{
"id": "7d1c…",
"title": "No hot water",
"priority": "P2",
"status": "open",
"property_id": "1a2b…",
"properties": { "name": "Casa Mar" },
"created_at": "2026-10-19T08:41:00Z",
"resolved_at": null
}
]
}Open a ticket
POST /tickets (a write key). title is required; details, priority (P2–P4, default P3 — an emergency P1 is never opened from outside; the assistant handles those on the call) and propertyId are optional. The same issue for the same home is never opened twice: you get the open ticket back, with created: false.
curl -X POST https://api.restado.ai/api/v1/tickets \
-H "Authorization: Bearer rst_…" \
-H "Content-Type: application/json" \
-d '{ "title": "Dishwasher leaking", "details": "Reported by the cleaner", "priority": "P2", "propertyId": "1a2b…" }'{ "ticketId": "9f3e…", "created": true }Calls
GET /calls?since=2026-10-01T00:00:00Z — completed guest calls since a moment (default: the last 7 days), up to 200, each with the summary the assistant wrote.
{
"calls": [
{
"id": "c0ff…",
"conversation_id": "5e6f…",
"started_at": "2026-10-19T07:58:12Z",
"ended_at": "2026-10-19T08:01:16Z",
"status": "completed",
"summary": "The guest at Casa Mar had no hot water; a P2 ticket was opened and the plumber was called."
}
]
}Errors and limits
- 401 — no key, or the key is unknown or revoked (
{ "error": "…", "code": "BAD_API_KEY" }). - 403 — the key may not do this (a read key opening a ticket).
- 400 — the body is not valid;
errorsays what to fix. - 429 — more than 120 requests a minute from one key; wait a minute.
Webhooks
Add an https address in Settings → API & webhooks and choose the events. Restado sends each event as a POST with a JSON body, signed with the endpoint's secret (shown once, when you add it). Answer with any 2xx within 10 seconds. A failure is retried with growing pauses; after repeated failures the endpoint is switched off — Settings shows it, with the last error, and you switch it back on there.
| Event | When |
|---|---|
| ticket.created | A new ticket — opened by the assistant after a call, by your team, or through the API. |
| ticket.resolved | A ticket was resolved. |
| call.completed | A guest call ended and its summary is ready. |
| escalation.accepted | Someone on your on-call list accepted an urgent issue. |
| escalation.exhausted | Nobody on the on-call list answered an urgent issue. |
POST https://your-server.example/restado
Content-Type: application/json
X-Restado-Event: ticket.created
X-Restado-Delivery: 7d1c… (the event id — the same on every retry)
X-Restado-Signature: t=1792340000,v1=5f2b…
{ "id": "7d1c…", "type": "ticket.created", "created": "2026-10-19T08:41:00Z", "data": { … } }{ "ticketId": "…", "title": "No hot water", "priority": "P2", "propertyId": "…", "reservationId": "…", "createdBy": "ai" }{ "ticketId": "…", "title": "No hot water", "resolvedAt": "2026-10-19T09:12:00Z" }{ "callId": "…", "conversationId": "…", "durationSeconds": 184, "endedReason": "customer-ended-call" }{ "ticketId": "…", "priority": "P1", "attempts": 2, "accepted": true }{ "ticketId": "…", "priority": "P1", "attempts": 6, "accepted": false }The same event can arrive twice (a retry after a timeout): use X-Restado-Delivery to ignore a repeat.
Verifying a webhook
The signature is an HMAC-SHA256 of <t>.<raw body> with your endpoint's secret, in hex. Check it on the raw body, before parsing, and refuse a t older than five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRestado(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}import hmac, hashlib, time
def verify_restado(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))What the API never shares
Guests' phone numbers and email addresses, PINs, door and lock codes, recordings and transcripts never leave Restado through the API or a webhook. Nothing here can change a booking, a payment or your settings.
Questions: hello@restado.ai.