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:

Authentication
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.

Request
curl https://api.restado.ai/api/v1/tickets?status=open \
  -H "Authorization: Bearer rst_…"
Response
{
  "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.

Request
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…" }'
Response — 201 when new, 200 when it was already open
{ "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.

Response
{
  "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; error says 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.

EventWhen
ticket.createdA new ticket — opened by the assistant after a call, by your team, or through the API.
ticket.resolvedA ticket was resolved.
call.completedA guest call ended and its summary is ready.
escalation.acceptedSomeone on your on-call list accepted an urgent issue.
escalation.exhaustedNobody on the on-call list answered an urgent issue.
Every delivery
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": { … } }
data — ticket.created
{ "ticketId": "…", "title": "No hot water", "priority": "P2", "propertyId": "…", "reservationId": "…", "createdBy": "ai" }
data — ticket.resolved
{ "ticketId": "…", "title": "No hot water", "resolvedAt": "2026-10-19T09:12:00Z" }
data — call.completed
{ "callId": "…", "conversationId": "…", "durationSeconds": 184, "endedReason": "customer-ended-call" }
data — escalation.accepted
{ "ticketId": "…", "priority": "P1", "attempts": 2, "accepted": true }
data — escalation.exhausted
{ "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.

Node.js
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 ?? ""));
}
Python
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.