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.
| Scope | Allows |
|---|---|
| clients:read, clients:write | List, read, create and update clients |
| appointments:read, appointments:write | List, read, book, move and cancel appointments |
| requests:read, requests:write | List, read and create requests and messages |
| invoices:read, invoices:write | List, read, create and send invoices |
| payments:write | Record 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
| Endpoint | What it does |
|---|---|
| GET /clients, POST /clients | List 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 /appointments | List 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}/send | Send an invoice by link, email or text message |
| POST /payments | Record 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.