For restaurants: 2 months free on Starter. No card needed.Claim 2 months free

RevoTab

Revotab for developers

One API for every table, every app.

Everything in Revotab, as JSON over HTTPS: bookings, the floor plan, guests, waitlist, loyalty, vouchers, orders, menus, reviews and campaigns, for restaurant teams, for guests and for your own systems, with signed webhooks and a free sandbox.

Endpoints
312
Webhook events
8
Languages
en · el

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

1 · Sign in
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"}'
Answer
{
  "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", "…"] } }
    ]
  }
}
2 · Today's bookings
curl https://revotab.com/api/v1/restaurants/3/reservations?date=2026-11-20 \
  -H "Accept: application/json" \
  -H "Authorization: Bearer 12|8yJx…"
3 · Book a table
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"}'
Answer
{
  "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.

Accounts with two-factor
// 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

Sign in with an emailed code
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) and page.
  • Actions answer with a message for people, sometimes data (a new booking's code, a key shown once) and location when 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_cursor until it is null.
A screen
GET /restaurants/3/reservations?date=2026-11-20
{
  "page": "app/Reservations",
  "data": { "date": "2026-11-20", "reservations": [ … ], "shifts": [ … ], "tables": [ … ] }
}
Actions
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.

Example
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."] }
}
StatuscodeMeaning
401unauthenticatedNo token, or it was revoked. Sign in again.
402upgrade_requiredThe restaurant’s plan does not include this module (header X-Feature names it), or the account has no active plan.
403forbiddenSigned in, but this role, key or account cannot do this.
404not_foundIt does not exist, or it belongs to a restaurant you are not part of.
409conflictThe action does not fit the current state (for example two-factor is already on).
422validation_failedA field is missing or wrong. See errors.
422rejectedThe action was refused for a business reason; message says why.
423password_requiredA sensitive action: send the account password in X-Confirm-Password.
429rate_limitedToo many requests. Wait for the seconds in Retry-After.
500server_errorOur side. It is logged; try again.

Plans, roles and limits

  • Plans. Each restaurant's account.features (in GET /me) lists what its plan includes. Show only those screens; anything else answers 402 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:

Changes since
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:

Push registration
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.

reservation.createdreservation.updatedreservation.cancelledwaitlist.createdwaitlist.updatedorder.createdorder.updatedreview.submitted
What arrives
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.

Node.js
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));
}
PHP
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.

Create a developer 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 Dart client
# 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_api
Or call it directly
final 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']);

Building the Revotab apps? Everything above is what they use.

For restaurants, cafés, bistros and tavernas

Your tables. Your guests. Your money.