Getting started
The base address is https://revotab.com/api/v1. Send and receive JSON, always with Accept: application/json. Sign in once to get a token, then send it as Authorization: Bearer <token>.
curl -X POST https://revotab.com/api/v1/auth/login \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"your-password","device_name":"Host stand iPad"}'{
"token": "12|8yJx…",
"token_type": "Bearer",
"account": {
"id": 7, "kind": "user", "name": "Eleni", "email": "[email protected]",
"restaurants": [
{ "id": 3, "name": "Taverna Thalassa", "role": "owner",
"account": { "plan": "pro", "status": "active", "features": ["reservations", "floor_plan", "waitlist", "…"] } }
]
}
}curl https://revotab.com/api/v1/restaurants/3/reservations?date=2026-11-20 \
-H "Accept: application/json" \
-H "Authorization: Bearer 12|8yJx…"curl -X POST https://revotab.com/api/v1/restaurants/3/reservations \
-H "Accept: application/json" -H "Content-Type: application/json" \
-H "Authorization: Bearer 12|8yJx…" \
-H "Idempotency-Key: 6f1c2a4e-booking-1" \
-d '{"date":"2026-11-20","time":"20:30","party_size":4,"first_name":"Maria","source":"phone"}'{
"message": "Booking K7Q2MZ4P created.",
"data": { "created": { "id": 812, "code": "K7Q2MZ4P" } }
}Three kinds of access
Restaurant teams
Owners and staff sign in with their Revotab account. Same roles and plan as the dashboard, on every plan.
Guests
No password: an emailed 6-digit code. Their bookings, vouchers and cards at every restaurant.
Integrations
API keys for one restaurant, read-only or read and write. Premium plan, or free in a sandbox.
Most guest actions also work without signing in (/public/…), exactly like the booking pages on the website: a restaurant is found by its slug, a booking by its code.
Signing in
Team accounts
POST /auth/login with email, password and a device_name. Each device gets its own token; people see their devices with GET /me/tokens and sign one out with DELETE /me/tokens/{id}. POST /auth/logout ends the token you send.
// 1. POST /auth/login answers, for an account with two-factor:
{ "two_factor_required": true, "challenge": "eyJpdiI6…" }
// 2. Send the challenge with the 6-digit code from the authenticator app:
POST /auth/two-factor
{ "challenge": "eyJpdiI6…", "code": "123456", "device_name": "Host stand iPad" }Guests
POST /auth/guest/code { "email": "[email protected]" }
POST /auth/guest/verify { "email": "[email protected]", "code": "482913", "device_name": "Maria's iPhone" }
→ { "token": "41|…", "account": { "kind": "guest", … } }A code works once, for 10 minutes, and five wrong tries end it. The first sign-in creates the guest's account.
API keys
Made in the dashboard under Settings › API (or POST /restaurants/{id}/settings/api-keys). A key works only on its own restaurant's /restaurants/{id}/… endpoints, like a manager, and can never reach billing, the team or the key and webhook settings. Read-only keys can only send GET.
Responses
The API runs the same code as the Revotab dashboard and website, so every rule and email behaves the same. Answers come in a few shapes:
- Screens answer with
data(everything that screen shows) andpage. - Actions answer with a
messagefor people, sometimesdata(a new booking's code, a key shown once) andlocationwhen the website would open another page. - Payments and Stripe pages answer with
redirect_url: open it in the browser or an in-app browser. - Guest-account lists are paged: follow
next_cursoruntil it isnull.
GET /restaurants/3/reservations?date=2026-11-20
{
"page": "app/Reservations",
"data": { "date": "2026-11-20", "reservations": [ … ], "shifts": [ … ], "tables": [ … ] }
}POST /restaurants/3/guests/55/notes → { "message": "Saved." }
POST /restaurants/3/billing/portal → { "redirect_url": "https://billing.stripe.com/…" }
POST /public/restaurants/thalassa/reservations (with a deposit)
→ { "redirect_url": "https://checkout.stripe.com/…", "data": { "created": { "code": "K7Q2MZ4P", "deposit_amount": 40 } } } Messages follow Accept-Language (en or el), then the account's own language. Send an Idempotency-Key header with bookings, orders and payments: a retry with the same key gets the first answer again (marked Idempotent-Replayed: true) instead of booking twice.
Errors
Every error has the same shape. code is stable, for your code; message is for people.
HTTP/1.1 422 Unprocessable Content
{
"message": "The party size field must be at least 1.",
"code": "validation_failed",
"errors": { "party_size": ["The party size field must be at least 1."] }
}| Status | code | Meaning |
|---|---|---|
| 401 | unauthenticated | No token, or it was revoked. Sign in again. |
| 402 | upgrade_required | The restaurant’s plan does not include this module (header X-Feature names it), or the account has no active plan. |
| 403 | forbidden | Signed in, but this role, key or account cannot do this. |
| 404 | not_found | It does not exist, or it belongs to a restaurant you are not part of. |
| 409 | conflict | The action does not fit the current state (for example two-factor is already on). |
| 422 | validation_failed | A field is missing or wrong. See errors. |
| 422 | rejected | The action was refused for a business reason; message says why. |
| 423 | password_required | A sensitive action: send the account password in X-Confirm-Password. |
| 429 | rate_limited | Too many requests. Wait for the seconds in Retry-After. |
| 500 | server_error | Our side. It is logged; try again. |
Plans, roles and limits
- Plans. Each restaurant's
account.features(inGET /me) lists what its plan includes. Show only those screens; anything else answers402 upgrade_required. - Roles. Owners, managers and staff see what they see in the dashboard. Billing is for owners. Revotab HQ endpoints are for platform admins with two-factor on.
- Sensitive actions (such as deleting the account) need the password again in
X-Confirm-Password. - Rate limits. 240 requests a minute per account, 60 a minute before signing in.
- Subscriptions are started on the website. Apps show the plan and invoices but should not sell plans inside the app.
Live updates and push
For live screens (today's list, the floor plan, the waitlist), ask what changed since the last answer, using its server_time:
GET https://revotab.com/api/v1/restaurants/3/changes?since=2026-11-20T19:42:10+00:00
{
"data": {
"server_time": "2026-11-20T19:42:25+00:00",
"reservations": [ … ], "waitlist": [ … ], "orders": [ … ], "reviews": [ … ]
}
}Register each app install for push notifications after signing in, and again when the push token changes:
POST /me/devices (POST /guest/devices for guest apps)
{ "platform": "ios", "push_token": "f3c1…", "name": "Host stand iPad", "app_version": "1.0.0" }Webhooks
Add a public https:// address under Settings › API and pick the events. Revotab sends a signed POST, waits up to 10 seconds for a 2xx, retries for about a day, and switches the webhook off after 20 failures in a row.
POST https://your-system.example.com/revotab
Revotab-Event: reservation.created
Revotab-Delivery: 2b0c0e0a-5a1f-4a5e-9a39-3b6d7f1d2c11
Revotab-Signature: t=1795721530,v1=5f2b…
{ "event": "reservation.created", "created_at": "2026-11-20T19:42:10+00:00",
"restaurant_id": 3, "data": { "code": "K7Q2MZ4P", "date": "2026-11-20", "time": "20:30", … } } Check every delivery: compute an HMAC-SHA256 of t + "." + raw body with the webhook's signing secret and compare it with v1.
import crypto from 'node:crypto';
// rawBody: the request body exactly as received (a string or Buffer)
export function isFromRevotab(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // reject replays
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}function is_from_revotab(string $rawBody, string $header, string $secret): bool
{
parse_str(str_replace(',', '&', $header), $p);
$expected = hash_hmac('sha256', $p['t'].'.'.$rawBody, $secret);
return abs(time() - (int) $p['t']) < 300 && hash_equals($expected, $p['v1'] ?? '');
}Sandbox
A developer account gets a free sandbox: a demo restaurant with every module and test data. It is never billed, sends no real emails or text messages, stays out of the directory, and its API keys can only reach its own data.
Your sandbox restaurant
Ready in a second. One per account.
Apps can do the same with POST /me/sandbox. For live data, a restaurant on the Premium plan makes keys for you in its own Settings › API.
Flutter and code generation
The whole API is described in OpenAPI 3.1, generated from the code on every release, so it is always current.
# Generate a typed Dart client from the OpenAPI document
dart pub global activate openapi_generator_cli # or use openapi-generator
openapi-generator generate -i https://revotab.com/developers/openapi.json \
-g dart-dio -o packages/revotab_apifinal res = await http.get(
Uri.parse('https://revotab.com/api/v1/me'),
headers: {
'Accept': 'application/json',
'Accept-Language': 'el', // messages in Greek
'Authorization': 'Bearer $token',
},
);
final body = jsonDecode(res.body);
if (res.statusCode >= 400) throw ApiError(body['code'], body['message'], body['errors']);Team accounts sign in with email and password (and two-factor); guests with an emailed code. Every sign-in returns a bearer token for one device.
The signed-in team member: profile, password, language, two-factor, devices, push notifications, new restaurants and the developer sandbox.
Everything in the Revotab dashboard, for one restaurant: today, bookings, the floor plan, guests, waitlist, loyalty, vouchers, orders, menu, reviews, campaigns, emails, insights, team, settings and billing. Team tokens and API keys.
What guests can do without an account: find restaurants, read menus, check availability, book, order, buy vouchers, join loyalty, leave reviews, and manage a booking by its code.
A signed-in guest's bookings, vouchers, loyalty cards, orders and preferences at every restaurant.
Platform administration for Revotab staff: accounts, finance, support, content, SEO, emails, jobs, system and security. Admins with two-factor only.
The first partner endpoints, kept for existing integrations. New integrations should use API keys with the restaurant endpoints.
Building the Revotab apps? Everything above is what they use.
