TableFox Developer API

REST endpoints for partner integrations — POS systems, chat widgets, AI phone agents, anything that needs to read availability or write bookings on behalf of a restaurant.

Download OpenAPI 3.0 spec Request an API key

Overview

Base URL: https://tablefox.co.uk
Format: JSON in, JSON out. UTF-8.
Versioning: /api/v1/* — semver, breaking changes only on a major bump.
Errors: standard { "error": "...", "errors": ["...", "..."] } shape with the right HTTP status (400/401/403/404/409/500).

Each restaurant has its own API key, issued by TableFox. To integrate on behalf of a restaurant, ask them to generate a key from their TableFox dashboard, or email hello@tablefox.co.uk. Master keys (one key, many restaurants) are available for accredited partners — get in touch and we'll provision one.

Quick start

Check availability for a party of 4 on 5 April 2026:

# cURL
curl 'https://tablefox.co.uk/api/v1/availability?date=2026-04-05&covers=4' \
  -H 'X-API-Key: your-key-here'

Create a booking:

# cURL
curl 'https://tablefox.co.uk/api/v1/bookings' \
  -X POST \
  -H 'X-API-Key: your-key-here' \
  -H 'Content-Type: application/json' \
  -d '{"date":"2026-04-05","time":"19:00","covers":4,"first_name":"Alex","phone":"+447450000000"}'

Once you've got that working, download the OpenAPI spec and import it into Postman, Insomnia or your client generator of choice for the rest.

Authentication & endpoint reference

Three ways to send your key — pick whichever your HTTP client prefers. Click any endpoint below to see request fields, responses and error codes.

Heads-up — self-service endpoints.
Three endpoints (GET /bookings/lookup, POST /bookings/modify, POST /bookings/cancel) are tagged self-service in the sidebar — they're the customer-facing variants used by chat widgets and AI phone agents. They require the booking's phone number to match the request and respect the restaurant's edit/cancel cutoff window. If you're building an integration on behalf of the restaurant (POS, EPOS, CRM, etc.) you'll usually want the equivalent untagged endpoints instead: POST /bookings/:id/modify and POST /bookings/:id/status with CANCELLED.

All integration endpoints require authentication via one of:

X-API-Key headerX-API-Key: your-key-here
Query parameter?api_key=your-key-here
Bearer tokenAuthorization: Bearer your-key-here

Per-restaurant key: Identifies the restaurant automatically. No restaurant_id needed.

Master key: Requires restaurant_id parameter to identify which restaurant.

Check available time slots for a date and party size.

Query Parameters
ParamTypeRequiredDescription
datestringYesDate in YYYY-MM-DD format
coversintegerYesNumber of guests. Capped at the restaurant's maximum online party size setting (same limit as the booking widget); above it the endpoint returns 400 with a max_covers field.
servicestringNoService code filter (e.g. LUNCH, DINNER)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "restaurant": "Viva Brazil", "date": "2026-04-05", "covers": 4, "slots": [{ "time": "12:00", "service": "LUNCH", "available": true }, ...], "count": 15 }

List a restaurant's call-transfer destinations for the AI phone agent's "Call Transfer" action. Returns a bare JSON array; the agent matches the caller's request against each destination's trigger. Each row carries an available flag computed from its hours (weekly schedule + date overrides, evaluated in Europe/London). Open destinations include a phone_number; closed destinations have phone_number: null plus opens_label / opens_iso so the agent can tell the caller when to try again (and still offer a callback). An empty array means no transfers are configured at all.

Query Parameters
ParamTypeRequiredDescription
restaurant_idintegerMaster key onlyRestaurant ID
Response
[
  { "phone_number": "+441234567890", "label": "Takeaway", "trigger": "caller wants a takeaway or collection order", "priority": 1, "available": true },
  { "phone_number": null, "label": "Events", "trigger": "large group or private event enquiry", "priority": 2, "available": false, "opens_label": "tomorrow at 10:00am", "opens_iso": "2026-07-17T10:00:00+01:00" }
]

Resolve a dialed AI phone number to its restaurant. Used by the AI phone agent at call setup: the dialed number comes in, the restaurant's identity and prompt FAQs come back. Numbers are assigned per restaurant by the platform team. Master key auth only — this is the reverse lookup (no restaurant_id is known yet), so per-restaurant keys are rejected.

Query Parameters
ParamTypeRequiredDescription
numberstringYesThe dialed number in E.164 format, e.g. +447700900000
Response
{ "restaurant_id": 12, "name": "The Fox & Hound", "faqs": "Q: Do you have parking? A: ...", "voice": "",
  "hours": "Monday: Lunch 12:00 to 15:00, Dinner 17:00 to 22:00\nTuesday: Closed\n..." }

Returns 404 when no restaurant has that number assigned, 400 for a malformed number. voice is the restaurant's TTS voice id and tts_provider its speech-engine preference (both empty = agent defaults). spoken_name is an optional phonetic spelling used only when the agent says the restaurant name aloud (fixes mispronounced names like "Knutsford" → "Nutsford"). hours is a plain-text opening-hours summary — the weekly service schedule plus any date exceptions in the next 14 days — so the agent can answer hours questions from real data (empty when no schedule is configured).

Returning-caller lookup used by the AI phone agent at call setup: does this caller-ID number have an email address on file from a previous booking at this restaurant? Only a masked address is ever returned — the agent offers "the email ending …" instead of making the caller spell one out, and POST /api/v1/bookings with email_on_file: true resolves the real address server-side, so the full email never crosses the voice channel.

Query Parameters
ParamTypeRequiredDescription
restaurant_idintegerMaster key onlyRestaurant ID
phonestringYesCaller-ID number in E.164 format, e.g. +447700900000
Response
{ "known": true, "first_name": "Chris", "email_masked": "c***@apexia.uk" }

Returns { "known": false } when the number has no previous booking with an email at this restaurant, 400 for a malformed number. The lookup uses the most recent matching booking.

Per-call usage and cost record, posted by the in-house voice agent when a call ends. Feeds the AI 30d usage column above and monthly per-restaurant cost reporting. Costs are estimates computed by the voice agent from its configured rates until reconciled against provider bills.

Body
{ "restaurant_id": 12, "call_sid": "CA...", "caller_phone": "+447...", "started_at": "2026-07-20T17:31:02Z",
  "duration_seconds": 142, "turns": 9, "input_tokens": 1200, "output_tokens": 950,
  "cache_read_tokens": 18400, "cache_write_tokens": 5200,
  "llm_cost": 0.031, "telephony_cost": 0.142, "currency": "GBP", "outcome": "booking_confirmed",
  "transcript": "[0:02] Agent: Hello, thank you for calling...\n[0:07] Caller: ..." }

transcript is optional: timestamped plain-text conversation lines, viewable per call in the platform admin. tts_provider and voice_id optionally record which speech engine and voice spoke on the call.

Response
{ "success": true }

Create a new booking. Sends confirmation email/SMS if auto-accepted. Sends payment link if restaurant requires deposit/card hold. When the restaurant has new-booking email alerts switched on (Settings → Notifications, off by default), a staff alert email is also sent for customer-driven sources (ONLINE, AI_PHONE) — never for INTERNAL, WALKIN or PHONE.

Once the booking is created, this endpoint always returns success: true. If a confirmation email/SMS or payment link fails afterwards, the booking still exists and is still returned — the failure is recorded in the API log as [POST-PROCESSING FAILED] rather than reported as an error. Do not retry on a non-response: retrying a booking that succeeded creates a duplicate.

Request Body (JSON)
FieldTypeRequiredDescription
datestringYesYYYY-MM-DD
timestringYesHH:MM (24-hour)
coversintegerYesNumber of guests. Capped at the restaurant's maximum online party size setting (same limit as the booking widget); above it the endpoint returns 400 with a max_covers field.
first_namestringYesCustomer first name
last_namestringNoCustomer last name
phonestringConditionalPhone or email required
emailstringConditionalRequired if restaurant has payment mode enabled
email_on_filebooleanNoReturning-caller shortcut used by the AI phone agent: when true (and email is not sent), the email from this phone number's most recent booking at the restaurant is reused server-side. Ignored when email is provided or nothing is on file.
notesstringNoSpecial requests
tagsstring/arrayNoComma-separated or array: BIRTHDAY, ANNIVERSARY, ALLERGY, HIGH_CHAIR, VIP
marketing_opt_in_emailbooleanNoSet true ONLY when the customer explicitly consented to email marketing. Stored on the customer record and synced into marketing contacts.
marketing_opt_in_smsbooleanNoSet true ONLY when the customer explicitly consented to SMS marketing.
sourcestringNoONLINE, INTERNAL, PHONE, WALKIN, AI_PHONE. Default: PHONE
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "booking": { "booking_id": 123, "booking_reference": "RB-20260405-A1B2C3", "status": "CONFIRMED", "date": "2026-04-05", "time": "19:00", "covers": 4, "customer_name": "John Smith" }, "payment": null, "message": "Booking confirmed successfully" }

Payment response (when deposit/hold required):

{ "success": true, "booking": { ... "status": "PENDING_PAYMENT" }, "payment": { "required": true, "mode": "DEPOSIT", "amount_formatted": "£20.00", "message": "A deposit of £20.00 is required. A payment link has been sent." } }

List bookings for a date with optional filters. Cancelled bookings are excluded unless you ask for them via status=CANCELLED.

Query Parameters
ParamTypeRequiredDescription
datestringNoYYYY-MM-DD (defaults to today)
statusstringNoFilter: PENDING, CONFIRMED, SEATED, COMPLETED, NO_SHOW, CANCELLED
servicestringNoService code (LUNCH, DINNER, …)
qstringNoSearch name / phone / email / reference (min 2 chars)
search_all_datesbooleanNoSet 1 with q to search across all dates
pageintegerNoPage number (default 1)
per_pageintegerNo1–50 (default 30)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "date": "2026-04-05", "bookings": [{ "booking_id": 123, "reference": "RB-...", "name": "John Smith", "phone": "+44...", "covers": 4, "time": "19:00", "status": "CONFIRMED", "service": "DINNER", "tables": ["T5"], "notes": "Window seat", "extras_count": 0, ... }], "pagination": { "page": 1, "per_page": 30, "total": 17, "total_pages": 1 } }

Full detail for a single booking — covers, tables, tags, voucher redemptions, extras, customer stats (VIP / blocked / total visits) and timestamps.

Path Parameters
ParamTypeRequiredDescription
idintegerYesBooking ID
restaurant_idintegerMaster key onlyRestaurant ID (query param)
Response (abridged)
{ "success": true, "booking": { "booking_id": 123, "reference": "RB-...", "status": "CONFIRMED", "date": "2026-04-05", "time": "19:00", "covers": 4, "customer": { "customer_id": 42, "name": "John Smith", "phone": "+44...", "email": "...", "is_vip": false, "total_bookings": 5, "total_no_shows": 0 }, "tables": [{ "table_id": 7, "name": "T5", "zone": "Window" }], "tags": ["BIRTHDAY"], "notes": "...", "internal_notes": "...", "vouchers": [...], "extras": [...] } }

Look up a customer's upcoming bookings by phone number. Only returns bookings matching the phone — no data leakage.

Query Parameters
ParamTypeRequiredDescription
phonestringYesCustomer phone number
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "bookings": [{ "booking_id": 123, "booking_reference": "RB-...", "date": "2026-04-05", "time": "19:00", "covers": 4, "status": "CONFIRMED", "name": "John Smith", "editable": true, "editable_reason": null, "cancellable": true, "cancellable_reason": null }], "count": 1, "editing_allowed": true, "editing_cutoff_hours": 24 }

editable requires both customer editing to be enabled AND the booking to be outside the cutoff window. cancellable only requires the booking to be outside the cutoff window. The same editing_cutoff_hours applies to both.

Modify an existing booking. Requires phone match for security. Respects restaurant edit settings and cutoff.

Request Body (JSON)
FieldTypeRequiredDescription
booking_idintegerYesBooking ID from lookup
phonestringYesMust match booking's customer phone
new_datestringNoNew date YYYY-MM-DD (omit to keep same)
new_timestringNoNew time HH:MM (omit to keep same)
new_coversintegerNoNew party size (omit to keep same). Capped at the restaurant's maximum online party size setting; above it the endpoint returns 400 with a max_covers field.
new_emailstringNoCorrected email address. If the booking is awaiting payment, a fresh payment link is automatically emailed to the new address (response gains a payment.resent object).
notesstringNoUpdated special requests (omit to keep same)
tagsstring/arrayNoUpdated tags: BIRTHDAY, ANNIVERSARY, ALLERGY, HIGH_CHAIR (omit to keep same)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "message": "Booking updated successfully", "booking": { "booking_id": 123, "booking_reference": "RB-...", "date": "2026-04-06", "time": "20:00", "covers": 4, "status": "CONFIRMED" } }
Error Responses
  • 403 — Editing not allowed or within cutoff period
  • 404 — Booking not found or phone doesn't match
  • 400 — Time conflict with another booking

Change a booking's date, time, covers, notes, tags, or assigned tables. Acts on behalf of the restaurant — no phone match, no cutoff window. Use this from POS, EPOS, CRM or any other restaurant-side integration. (The self-service /api/v1/bookings/modify is the customer-facing equivalent with phone-match + cutoff.)

Request Body (JSON)
FieldTypeRequiredDescription
datestringNoNew YYYY-MM-DD
timestringNoNew HH:MM
coversintegerNoNew party size
notesstringNoReplace customer-facing notes
internal_notesstringNoReplace staff-only notes
tagsarrayNoReplace tag list
table_idsarrayNoReplace table assignment (use GET /api/v1/tables/available for options)
restaurant_idintegerMaster key onlyRestaurant ID
Error Responses
  • 404 — Booking not found
  • 409OVERLAP_WARNING when the new time/tables conflict with another booking

Cancel an existing booking. Requires phone match for security. Respects the restaurant's customer edit/cancel cutoff (see editing_cutoff_hours from /bookings/lookup) — cancellations inside that window are rejected with 403.

Request Body (JSON)
FieldTypeRequiredDescription
booking_idintegerYesBooking ID from lookup
phonestringYesMust match booking's customer phone
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "message": "Booking cancelled successfully", "booking_reference": "RB-..." }
Error Codes
  • 403 — Within the cutoff window; customer must speak to the restaurant directly
  • 404 — Booking not found or phone mismatch

Change a booking's status — e.g. SEATED when the host seats the guest, COMPLETED when the bill is closed, NO_SHOW after a grace window.

Request Body (JSON)
FieldTypeRequiredDescription
statusstringYesOne of: PENDING, CONFIRMED, SEATED, COMPLETED, NO_SHOW, CANCELLED
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "booking": { "booking_id": 123, "reference": "RB-...", "status": "SEATED" }, "message": "Status updated to SEATED" }

Same as POST /api/v1/vouchers/redeem, but the redemption is automatically linked to the booking at :id — convenient when the POS already knows which check the voucher applies to.

Request Body (JSON)
FieldTypeRequiredDescription
codestringYesVoucher code
amount_penceintegerYesAmount to redeem, in pence
notestringNoFree-text note
restaurant_idintegerMaster key onlyRestaurant ID

Search customers by name, email or phone. Useful when a POS wants to surface "is this a returning guest?" before opening a tab.

Query Parameters
ParamTypeRequiredDescription
qstringYesSearch term (min 2 chars). Matches name / email / phone.
pageintegerNoPage number (default 1)
per_pageintegerNo1–50 (default 20)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "customers": [{ "customer_id": 42, "name": "John Smith", "email": "...", "phone": "+44...", "total_bookings": 5, "total_no_shows": 0, "last_visit": "2026-03-12", "is_vip": false, "blocked": false }], "pagination": { ... } }

Full customer record plus recent booking history (last 50 bookings most-recent first). Use this to show "this is their Nth visit" pop-ups in the POS.

Path Parameters
ParamTypeRequiredDescription
idintegerYesCustomer ID
restaurant_idintegerMaster key onlyRestaurant ID (query param)
Response (abridged)
{ "success": true, "customer": { "customer_id": 42, "first_name": "John", "last_name": "Smith", "email": "...", "phone": "+44...", "total_bookings": 5, "total_no_shows": 0, "is_vip": false, "blocked": false }, "bookings": [{ "booking_id": 123, "reference": "RB-...", "date": "2026-03-12", "time": "19:00", "covers": 4, "status": "COMPLETED" }] }

Returns every active table with an is_occupied flag for the requested time window and an is_suggested flag for the best-fit suggestion. Used to power POS floor plans.

Query Parameters
ParamTypeRequiredDescription
coversintegerNoParty size (default 2)
datestringNoYYYY-MM-DD (default today)
timestringNoHH:MM (default now)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "tables": [{ "table_id": 7, "name": "T5", "zone": "Window", "min_covers": 2, "max_covers": 4, "is_occupied": false, "is_suggested": true, "fits_party": true }], "suggested_ids": [7] }

Look up a voucher by exact code. Returns value, remaining balance, expiry, and a convenience can_redeem flag (active & not expired & positive balance). Always returns 200 with found: false when the code doesn't exist — no 404 — so POS can show a friendly "voucher not found" message.

Query Parameters
ParamTypeRequiredDescription
codestringYesVoucher code (case-insensitive)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "found": true, "voucher": { "voucher_id": 88, "code": "TF-ABC123", "status": "ACTIVE", "value_pence": 10000, "balance_pence": 6000, "expires_at": "2027-04-05 23:59:59", "is_expired": false, "recipient_name": "Alex", "product_name": "Gift £100", "can_redeem": true } }

Redeem (partially or fully) against a voucher's remaining balance. Standalone — pass booking_reference if you want the redemption linked to a specific booking.

Request Body (JSON)
FieldTypeRequiredDescription
codestringYesVoucher code
amount_penceintegerYesAmount to redeem, in pence. Can't exceed remaining balance.
booking_referencestringNoBooking to link the redemption to (e.g. RB-20260405-A1B2C3)
notestringNoFree-text note attached to the redemption
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "redemption_id": 555, "new_balance_pence": 3000, "fully_redeemed": false, "voucher": { "voucher_id": 88, "code": "TF-ABC123", "balance_pence": 3000, "status": "ACTIVE" }, "message": "Redeemed £30.00" }

Get restaurant info and services for a given date.

Query Parameters
ParamTypeRequiredDescription
datestringNoDate in YYYY-MM-DD (defaults to today)
restaurant_idintegerMaster key onlyRestaurant ID
Response
{ "success": true, "restaurant": { "name": "Viva Brazil", "phone": "0330...", "email": "...", "max_online_covers": 8 }, "date": "2026-04-05", "services": [{ "name": "Lunch", "code": "LUNCH", "start_time": "12:00:00", "end_time": "16:00:00" }, ...] }

Submit a callback request (typically from an AI phone agent). The request lands in the restaurant's in-app inbox (phone icon in the top nav) and fires a push notification to staff. An email is also sent to the restaurant address when the restaurant has turned on Email me too in Notification settings (off by default for new restaurants).

Body Parameters
ParamTypeRequiredDescription
phonestringRecommendedCaller's phone number
first_namestringNoCaller's first name
last_namestringNoCaller's last name
reasonstringNoShort reason from the AI (e.g. "wants to book", "menu enquiry")
notesstringNoFree-form notes / transcript from the caller
restaurant_idintegerMaster key onlyRestaurant ID

At least one of phone, first_name, or notes is required.

Response
{ "success": true, "message": "Callback request received", "callback_request_id": 42 }

Tools & clients

Point any of these at /developers/openapi.json to get an interactive sandbox or generated client SDK:

Support

Questions, missing fields, or want to integrate something we haven't published yet? Email hello@tablefox.co.uk — we actively support POS, EPOS, ticketing and CRM integrations.