Every request needs a company API key (Integrations → Webhooks & API in the panel) sent as Authorization: Bearer tt_live_…. Responses and errors are JSON.
curl "https://api.tapti.me/v1/company/reservations?status=confirmed&updatedSince=2026-09-01T00:00:00Z" \
-H "Authorization: Bearer tt_live_…"
Authentication
Every request carries a company API key as a bearer token. Create one from the panel — Integrations → Webhooks & API → REST API keys — where it is shown once and stored server-side only as a SHA-256 hash; a lost key is revoked and replaced, not recovered. A company can hold up to 10 active keys.
- Read-only keys can call every GET endpoint, and can subscribe and unsubscribe webhooks — a subscription pushes nothing the key could not already read.
- Read-write keys can also POST a booking status change; a read-only key gets 403 Forbidden on those routes.
- A key belongs to one company — every list and lookup is already scoped to it, there is no companyId parameter.
Rate limits
50 requests per minute per key, on a sliding window, shared by every endpoint. Going over returns 429 Too Many Requests; back off and retry rather than polling faster — or subscribe a webhook and stop polling.
Endpoints
/v1/company
Read-only or read-write
Get the current company
The company the key belongs to, and what the key itself can do.
/v1/company/reservations
Read-only or read-write
List reservations
Table reservations for the company, oldest start time first — or, when updatedSince is set, oldest updated first.
| Parameter | Type | Meaning |
|---|---|---|
status |
string | Comma-separated: pending, confirmed, seated, completed, no_show, cancelled. cancelled matches both cancellation reasons. |
branchId |
integer | Restrict to one branch. |
from |
ISO 8601 datetime | Only bookings starting on or after this time (UTC if no offset given). |
to |
ISO 8601 datetime | Only bookings starting before this time. |
updatedSince |
ISO 8601 datetime | Only bookings changed since this time; switches the sort to updatedAt ascending, for syncing. |
limit |
integer | Page size, 1–100. Default 50. |
page |
integer | 1-based page number. Default 1. |
/v1/company/appointments
Read-only or read-write
List appointments
Same shape and filters as reservations, for the Appointments module.
| Parameter | Type | Meaning |
|---|---|---|
status |
string | Comma-separated: pending, confirmed, completed, no_show, cancelled. |
branchId |
integer | Restrict to one branch. |
from |
ISO 8601 datetime | Only bookings starting on or after this time. |
to |
ISO 8601 datetime | Only bookings starting before this time. |
updatedSince |
ISO 8601 datetime | Only bookings changed since this time; sorts by updatedAt ascending. |
limit |
integer | Page size, 1–100. Default 50. |
page |
integer | 1-based page number. Default 1. |
/v1/company/reservations/{id}
Read-only or read-write
Get a reservation
One reservation, in the same shape webhooks carry it, with its attribution. 404 when the id doesn't belong to the key's company.
/v1/company/appointments/{id}
Read-only or read-write
Get an appointment
One appointment, same shape and rules as a reservation lookup.
/v1/company/reservations/{id}/status
Read-write only
Change a reservation's status
Confirm, seat, complete, mark a no-show, cancel or reopen — fires the same webhooks and conversions as changing it on the board. JSON body: {"status": "..."}.
| Parameter | Type | Meaning |
|---|---|---|
status |
string, required | One of: pending, confirmed, seated, completed, no_show, cancelled (canceled and canceled_by_venue also accepted). |
/v1/company/appointments/{id}/status
Read-write only
Change an appointment's status
Same as the reservation endpoint, without the seated state.
| Parameter | Type | Meaning |
|---|---|---|
status |
string, required | One of: pending, confirmed, completed, no_show, cancelled (canceled and canceled_by_venue also accepted). |
/v1/company/events
Read-only or read-write
Poll the event feed
Every booking, order and loyalty event, oldest first — the same feed webhooks are cut from. Pass the last cursor you saw as after to fetch only what’s new, and type to fetch only some kinds.
| Parameter | Type | Meaning |
|---|---|---|
after |
integer cursor | The cursor from the last event you processed. Omit to start from the beginning. |
type |
string | Comma-separated event types or families: booking.confirmed,order.placed, or order, loyalty.*. Omit for every event. |
limit |
integer | Page size, 1–100. Default 50. |
/v1/company/orders
Read-only or read-write
List orders
Guests’ table, takeaway and delivery orders and every sale rung up at the register, oldest first — or, when paidSince is set, in the order they were paid. An order still waiting for its upfront payment is left out.
| Parameter | Type | Meaning |
|---|---|---|
status |
string | Comma-separated: open, paid. |
source |
string | customer (placed by the guest) or staff (rung up by staff). |
fulfilment |
string | Comma-separated: dine_in, takeaway, delivery. |
branchId |
integer | Restrict to one branch. |
from |
ISO 8601 datetime | Only orders created on or after this time. |
to |
ISO 8601 datetime | Only orders created before this time. |
paidSince |
ISO 8601 datetime | Only orders paid since this time; sorts by paidAt ascending, for syncing sales. |
limit |
integer | Page size, 1–100. Default 50. |
page |
integer | 1-based page number. Default 1. |
/v1/company/orders/{id}
Read-only or read-write
Get an order
One order with its lines, totals and attribution, in the shape order webhooks carry it.
/v1/company/loyalty/programs
Read-only or read-write
List loyalty programs
Every stamp-card program: name, status, stamps required, the reward and where it applies.
/v1/company/loyalty/cards
Read-only or read-write
List loyalty cards
Members’ cards, in the loyalty object shape. A member who filled a card has it completed and a fresh active one beside it; customer.id is the member.
| Parameter | Type | Meaning |
|---|---|---|
programId |
integer | Restrict to one program. |
customerId |
integer | Restrict to one member. |
status |
string | Comma-separated: active, completed. |
updatedSince |
ISO 8601 datetime | Only cards stamped or completed since this time; sorts by updatedAt ascending. |
limit |
integer | Page size, 1–100. Default 50. |
page |
integer | 1-based page number. Default 1. |
/v1/company/loyalty/rewards
Read-only or read-write
List loyalty rewards
Rewards earned by completing a card, with their code, expiry and whether they were redeemed.
| Parameter | Type | Meaning |
|---|---|---|
programId |
integer | Restrict to one program. |
customerId |
integer | Restrict to one member. |
status |
string | Comma-separated: issued, redeemed, expired. |
from |
ISO 8601 datetime | Only rewards issued on or after this time. |
to |
ISO 8601 datetime | Only rewards issued before this time. |
limit |
integer | Page size, 1–100. Default 50. |
page |
integer | 1-based page number. Default 1. |
/v1/company/webhooks
Read-only or read-write
List webhook subscriptions
The subscriptions this key made. Webhooks added in the panel are not listed here.
/v1/company/webhooks
Read-only or read-write
Subscribe a webhook
Starts sending events to a URL — what an automation platform’s app calls when a user switches a trigger on. Returns the subscription and its signing secret, once. JSON body: {"url": "…", "events": ["order.placed"]}; Zapier’s {"target_url": "…", "event": "…"} is accepted too.
| Parameter | Type | Meaning |
|---|---|---|
url |
string, required | The https address to send to (target_url also accepted). |
events |
array of strings, required | Event types, families such as order.* or loyalty, or * for everything (event also accepted, for one). |
description |
string | A name shown in the panel. Defaults to the platform and the key’s name. |
/v1/company/webhooks/{id}
Read-only or read-write
Unsubscribe a webhook
Stops sending to a subscription this key made. Returns 204. Revoking the key removes all of its subscriptions too.
The booking object
A reservation and an appointment come out in one shared shape: kind, id, status, source, guestInitiated, startsAt/endsAt/timezone (the branch's own zone), currency, company, branch, guest, attribution, conversionId, createdAt/updatedAt — plus partySize and table for a reservation, service and specialist for an appointment. It's exactly what a webhook delivers, so a receiver that handles one handles both.
{
"data": {
"kind": "reservation",
"id": 4821,
"status": "confirmed",
"source": "online",
"guestInitiated": true,
"startsAt": "2026-09-12T20:00:00+03:00",
"endsAt": "2026-09-12T21:30:00+03:00",
"timezone": "Asia/Baku",
"currency": "AZN",
"company": { "id": 12, "name": "Cafe Aroma", "slug": "cafe-aroma", "country": "AZ" },
"branch": { "id": 3, "name": "Nizami st." },
"partySize": 4,
"table": { "id": 9, "label": "T3" },
"guest": { "name": "Maria K.", "phone": "…", "email": "…" },
"locale": "en",
"note": null,
"conversionId": "tt3f9c…",
"createdAt": "2026-09-10T08:12:04+00:00",
"updatedAt": "2026-09-12T20:00:03+00:00",
"attribution": { "channel": "widget", "utmSource": "instagram", "gclid": null, "fbclid": "IwAR2…" }
}
}
The order object
An order comes out in one shape wherever you meet it — the REST API, the order webhooks, the event feed: kind ("order"), id, number (the ticket staff call out), status (open or paid), source (customer or staff), guestInitiated, fulfilment, table, label, deliveryAddress, note, currency, timezone, company, branch, customer (the loyalty member, when staff identified one), items (name, variant, quantity, unitPriceCents, subtotalCents), itemCount, subtotalCents, discountCents, deliveryFeeCents, tipCents, totalCents, valueCents (what the order is worth without the tip — the number conversions report), paymentMethod, servedBy, conversionId, createdAt, paidAt and attribution. Money is always in minor units.
{
"data": {
"kind": "order",
"id": 9170,
"number": 42,
"status": "paid",
"source": "customer",
"guestInitiated": true,
"fulfilment": "takeaway",
"table": null,
"label": "Ana",
"currency": "EUR",
"branch": { "id": 3, "name": "Vitosha Blvd" },
"customer": { "id": 311, "name": "Ana", "phone": "…" },
"items": [
{ "menuItemId": 88, "name": "Flat white", "variant": "Large",
"quantity": 2, "unitPriceCents": 450, "subtotalCents": 900 }
],
"itemCount": 2,
"subtotalCents": 900,
"discountCents": 0,
"deliveryFeeCents": 0,
"tipCents": 100,
"totalCents": 1000,
"valueCents": 900,
"paymentMethod": "online",
"conversionId": "tt81ad…",
"createdAt": "2026-09-12T12:04:10+03:00",
"paidAt": "2026-09-12T12:05:02+03:00",
"attribution": { "channel": "web", "utmSource": "google", "gclid": "Cj0K…" }
}
}
The loyalty object
Every loyalty event, card and reward shares one shape: kind ("loyalty"), id (the card), timezone, company, branch (where it happened, when there is one), program (id, name, stampsRequired, rewardTitle), customer (id, name, phone, locale), card (id, status, stampsCount, stampsRequired, stampsToReward, createdAt), stamp (for a stamp: source, issuedBy, createdAt) and reward (for a reward: code, title, status, expiresAt, redeemedAt, redeemedBy), plus conversionId, the id a loyalty sign-up is reported under.
{
"kind": "loyalty",
"id": 5120,
"program": { "id": 4, "name": "Coffee card", "stampsRequired": 8, "rewardTitle": "Free coffee" },
"customer": { "id": 311, "name": "Ana", "phone": "…", "locale": "bg" },
"card": { "id": 5120, "status": "active", "stampsCount": 5,
"stampsRequired": 8, "stampsToReward": 3 },
"branch": { "id": 3, "name": "Vitosha Blvd" },
"stamp": { "source": "qr_scan", "issuedBy": "Ivan", "createdAt": "…" },
"reward": null,
"conversionId": "tt5c02…"
}
Event feed
Each row is { id, cursor, type, createdAt, previousStatus, data }. data holds the subject under the key the event’s prefix names: data.booking, data.order or data.loyalty. Store the highest cursor you’ve processed and pass it back as after — it never skips or repeats a row, so it’s safe to resume after a crash. Ignore types you don’t handle: new ones may be added.
| Event type | Meaning |
|---|---|
booking.requested | A guest asked for a booking that waits for your confirmation. |
booking.confirmed | A booking is confirmed — made confirmed, or confirmed later by staff. |
booking.cancelled | Cancelled by the guest or the venue; status says which. |
booking.no_show | The guest did not come. |
booking.completed | The visit is over. |
order.placed | A guest’s order reached the venue — from the page, a table’s QR code, takeaway or delivery; after the payment when the venue takes it upfront. |
order.paid | Any order settled, including sales rung up at the register. |
loyalty.member_joined | A customer got their first stamp on a program. |
loyalty.stamp_added | A stamp was added to a card. |
loyalty.reward_issued | A card was completed and its reward issued. |
loyalty.reward_redeemed | A reward was handed over. |
curl "https://api.tapti.me/v1/company/events?after=998&limit=100" \
-H "Authorization: Bearer tt_live_…"
Webhooks
Create an endpoint under Integrations → Webhooks & API — or connect Zapier, Make, n8n or Pipedream under Integrations → Automation — to have TapTime POST a signed JSON event the moment a booking, an order or a loyalty card changes. You choose the events per endpoint. A company can have up to 50 webhooks, postbacks, chats and automation connections combined.
| Header | Meaning |
|---|---|
TapTime-Event | The event type, e.g. booking.confirmed. |
TapTime-Event-Id | Stable id for the event — use it to deduplicate a retried delivery. |
TapTime-Delivery | Id of this specific delivery attempt. |
TapTime-Signature | t=<unix time>,v1=<hex HMAC-SHA256 of "t.rawBody">, signed with the endpoint's own secret. |
- Any 2xx response is treated as delivered; a non-2xx or a timeout is retried with backoff (1m, 5m, 15m, 1h, 3h, 6h, 12h) up to 8 attempts — a 410 stops retrying.
- Redirects are not followed.
- The URL must be HTTPS in production and may not point at a private or loopback address.
- Delivery is at-least-once: always verify the signature and dedupe on TapTime-Event-Id.
{
"id": "evt_9f3c2a7e1b",
"type": "booking.confirmed",
"createdAt": "2026-09-12T19:02:11+03:00",
"test": false,
"data": { "booking": { "kind": "reservation", "id": 4821, "status": "confirmed", "…": "…" } }
}
const [t, v1] = req.headers["taptime-signature"]
.split(",").map(part => part.split("=")[1]);
const expected = crypto
.createHmac("sha256", process.env.TAPTIME_WEBHOOK_SECRET)
.update(`${t}.${rawBody}`)
.digest("hex");
const valid = crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(v1));
Webhook subscriptions
Automation platforms switch triggers on and off through the API rather than asking the user to paste a URL (“REST hooks”): POST the platform’s own URL with the events it wants, keep the returned id, and DELETE it when the trigger is switched off. A subscription is an ordinary signed webhook — it shows in the panel, marked with the key that made it.
- A key sees and removes only the subscriptions it made; revoking the key removes them all.
- Addresses on hooks.zapier.com, make.com and pipedream.net are recognised and shown with the platform’s name; any other https address is a plain webhook.
- The signing secret is returned once, in the response to the POST; the owner can reveal it again in the panel.
curl -X POST "https://api.tapti.me/v1/company/webhooks" \
-H "Authorization: Bearer tt_live_…" -H "Content-Type: application/json" \
-d '{"url": "https://hooks.zapier.com/hooks/catch/123/abc/", "events": ["order.placed"]}'
Postbacks
A GET or POST to your tracker’s own URL with macros filled in, for platforms that speak query-string postbacks rather than JSON webhooks. For an order, {value} is the order’s value without the tip and {order_id} its id.
https://tracker.example/postback
?cid={click_id}&event={event}&status={status}
&payout={value}&cur={currency}
Available placeholders
{event}{event_id}{status}{booking_id}{booking_type}{order_id}{customer_id}{conversion_id}{value}{currency}{party_size}{branch_id}{timestamp}{utm_source}{utm_medium}{utm_campaign}{utm_term}{utm_content}{gclid}{fbclid}{msclkid}{ttclid}{click_id}
Errors
Every error is JSON: { "error": "<code>", "message": "<human-readable>" }.
| Status | error | Meaning |
|---|---|---|
| 401 | unauthorized |
The Authorization header is missing, or the key is invalid or revoked. |
| 403 | forbidden |
A read-only key called a write endpoint (a status change). |
| 404 | not_found |
No booking with that id belongs to the key's company. |
| 409 | conflict |
The status change would double-book the table or slot; nothing was changed. |
| 422 | invalid_status |
The status body value isn't one of the accepted values for that booking type. |
| 422 | invalid_type |
The event feed’s type names no event type or family. |
| 422 | invalid_url |
A subscription URL is not a public https address. |
| 429 | rate_limited |
More than 50 requests in a minute for this key. Slow down and retry. |
API access, webhooks and the automation and chat integrations are included with every module — no extra fee, no separate developer plan.
Start a free trial