Connect Fonitix to your own software

A simple REST API and signed webhooks for a POS, tools like Zapier, Make and n8n, or your own code. Each key belongs to one business.

Authentication

The owner of a business creates a key in the dashboard under Integrations, API keys. The full key is shown once, looks like fxk_xxxxxxxxxx_... and is stored only as a hash. Send it with every request:

Authorization: Bearer YOUR_API_KEY
  • A key belongs to one business and can only see that business.
  • Each key has permissions (scopes). A request without the scope it needs answers 403 insufficient_scope.
  • Rotating a key makes a new one with the same permissions, and the old one keeps working for 24 hours. Revoking stops a key at once.
  • Each key can make 300 requests a minute (429 with Retry-After). Failed attempts are limited per address to 30 a minute.
ScopeAllows
clients:read, clients:writeList, read, create and update clients
appointments:read, appointments:writeList, read, book, move and cancel appointments
requests:read, requests:writeList, read and create requests and messages
invoices:read, invoices:writeList, read, create and send invoices
payments:writeRecord a payment you took outside Fonitix, such as on your own terminal

Requests and responses

The base address is https://fonitix.ai/api/v1. Bodies are JSON.

  • Success: { "data": ... }. Lists also return next_cursor, which is null on the last page.
  • Errors: { "error": { "code": "invalid_request", "message": "..." } } with the matching status: 400 invalid, 401 bad key, 403 scope, 404 not found, 409 conflict such as a double booking, 429 too many requests.
  • Money is always whole cents (total_cents). Times are ISO 8601.
  • Lists take limit (1 to 100, default 25) and cursor, newest first.
  • Unknown fields are refused with a 400, so a typo never silently does nothing. Prices and totals are never taken from the caller: the server works them out from the lines and your services.

Endpoints

EndpointWhat it does
GET /clients, POST /clientsList clients (filter by phone or email) and create one
GET /clients/{id}, PATCH /clients/{id}Read a client, and change name, email, notes, tags or archive
GET /appointments, POST /appointmentsList appointments by date, status or client, and book one
GET /appointments/{id}, PATCH /appointments/{id}Read, move, cancel or change the status of an appointment
GET /requests, POST /requests, GET /requests/{id}List and read requests (leads, messages) and create one
GET /invoices, POST /invoices, GET /invoices/{id}List and read invoices, and create a draft
POST /invoices/{id}/sendSend an invoice by link, email or text message
POST /paymentsRecord a payment against a sent invoice

Examples

Find a client by phone

curl https://fonitix.ai/api/v1/clients?phone=%2B15551230100 \
  -H "Authorization: Bearer YOUR_API_KEY"

Create a request

curl -X POST https://fonitix.ai/api/v1/requests \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "summary": "Leaking tap in the kitchen",
    "urgency": "urgent",
    "name": "Sam Rivera",
    "phone": "+15551230100"
  }'

Book an appointment

The time is in the business’s local time. If it is taken, the answer is 409.

curl -X POST https://fonitix.ai/api/v1/appointments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "SERVICE_ID",
    "start": "2026-11-03T10:00",
    "client": { "name": "Sam Rivera", "phone": "+15551230100" }
  }'

Webhooks

Add an address under Integrations, Webhooks (https only, public addresses only). The signing secret is shown once. Fonitix sends a POST with a JSON body and these headers: Fonitix-Event, Fonitix-Delivery and Fonitix-Signature.

Events: appointment.created, appointment.updated, appointment.cancelled, client.created, client.updated, request.created, invoice.created, invoice.sent, invoice.viewed, invoice.paid, invoice.partially_paid, invoice.void, invoice.refunded, quote.created, quote.sent, quote.accepted, quote.declined, payment.succeeded, payment.refunded, form.submitted, queue.joined. Subscribe to everything with *, a list, or a family such as invoice.*.

The signature is an HMAC-SHA256 of <t>.<raw body> with your secret. Check it against the raw body and refuse timestamps older than five minutes:

const crypto = require('crypto');

function verify(secret, header, rawBody) {
  const m = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const want = crypto.createHmac('sha256', secret).update(m[1] + '.' + rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}
  • Answer with any 2xx within 10 seconds. Otherwise Fonitix retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then marks the delivery failed. You can retry it from the dashboard.
  • Redirects are not followed. After 20 failures in a row the address is switched off.
  • The same event can arrive more than once: use the delivery id or the event id to ignore repeats.

Connecting your own POS or card terminal

  • Create a key with invoices:read and payments:write (add invoices:write if your POS creates the invoices).
  • When your POS takes a payment for an invoice, call POST /payments with the invoice id and your terminal’s transaction id as the reference. Sending the same reference again changes nothing.
  • Optionally add a webhook for invoice.paid so your system learns about payments made online.

Need something that is not here?

Tell us what you are connecting. Email info@fonitix.ai.