The Pixelvile API.
Read and change a business’s bookings, customers, invoices and enquiries from your own software, and hear about each one as it happens. For businesses on Pixelvile with the Integrations add-on.
Getting started
The owner makes a key in the dashboard under Settings › Integrations › API keys. A key belongs to one business and is shown once. It can do only what was ticked when it was made: read or change bookings, customers, invoices and enquiries.
A key never does more than the person who made it can do in the dashboard today. If their role changes, the key changes with it. If they leave the team, it stops working.
Send the key with every request, over HTTPS:
curl https://pixelvile.com/api/v1/me \
-H "Authorization: Bearer $PIXELVILE_API_KEY"
Money, times and phone numbers
- Amounts are whole paise in Indian rupees (₹1,180.00 is
118000). GST rates are basis points (18% is1800). - Booking times are the business’s local time in Kolkata, written
2026-11-09T10:30. Dates areYYYY-MM-DD. - Phone numbers come back with the country code, like
+919830012345. You can send 10-digit Indian numbers.
Errors and limits
Errors come back as JSON with the HTTP status and a message you can show to a person:
{ "error": { "status": 403, "message": "This key doesn’t have the “customers:read” scope." } }
401: the key is missing, wrong or revoked.403: the add-on is switched off, or the key’s scopes or its maker’s role don’t allow it.400,404,409: a field is wrong, the record isn’t there, or it clashes with something (a time that’s already booked).429: too many requests. Each key may make 120 a minute. Every answer carriesX-RateLimit-LimitandX-RateLimit-Remaining, and a 429 carriesRetry-Afterin seconds.
Endpoints
This is the full list. Each endpoint goes through the same checks as the dashboard: the business’s add-ons, the person’s role and the same validation. A double booking is refused here too.
Who am I
GET /api/v1/mereturns the business, the key’s name and scopes, and the rate limit. Works with any key.
Bookings
GET /api/v1/bookings?from=2026-11-09&to=2026-11-15lists bookings in a range of up to two months (by default, the next 7 days). Addstaff_idfor one calendar. Scopebookings:read.GET /api/v1/bookings/:idreturns one booking. Scopebookings:read.POST /api/v1/bookingsadds a booking. Sendservice_id,date,time,customer_nameandphone. You can also sendstaff_id,notesandsource:phone,walk_inorstaff(the default). Scopebookings:write.PATCH /api/v1/bookings/:idchanges thestatus(confirmed,done,no_showorcancelled) or thenotes. Scopebookings:write.
curl -X POST https://pixelvile.com/api/v1/bookings \
-H "Authorization: Bearer $PIXELVILE_KEY" \
-H "Content-Type: application/json" \
-d '{"service_id": 12, "date": "2026-11-09", "time": "10:30", "customer_name": "Ananya Sen", "phone": "9830012345"}'
{ "booking": { "id": 481, "status": "confirmed", "start_at": "2026-11-09T10:30", "duration_min": 30,
"service_id": 12, "service": "Check-up", "staff_id": null, "staff_name": null, "source": "staff",
"customer": { "id": 77, "name": "Ananya Sen", "phone": "+919830012345" }, "notes": null } }
Customers
GET /api/v1/customers?q=ananya&page=1lists customers, most recent visit first.qsearches names, emails and phone numbers. Scopecustomers:read.GET /api/v1/customers/:idreturns one customer and their bookings. Scopecustomers:read.PATCH /api/v1/customers/:idchanges thename,emailornotes. Scopecustomers:write.
Invoices
GET /api/v1/invoices?status=sent&page=1lists invoices.statusisdraft,sent(unpaid),paidorvoid. AddmonthasYYYY-MM, orcustomer_idfor one customer’s. Scopeinvoices:read.GET /api/v1/invoices/:idreturns one invoice with its lines and its public link. Scopeinvoices:read.POST /api/v1/invoicesmakes a draft for the owner to check and send from the dashboard. Sendcustomer_id, orcustomerwith anameandphone. Then senditems, each with adescription,qtyandunit_pricein paise, and if needed atax_rate,discountandhsn. Scopeinvoices:write.
curl -X POST https://pixelvile.com/api/v1/invoices \
-H "Authorization: Bearer $PIXELVILE_KEY" -H "Content-Type: application/json" \
-d '{"customer_id": 77, "items": [{"description": "Scaling", "qty": 1, "unit_price": 90000, "tax_rate": 1800}]}'
Enquiries
GET /api/v1/enquiries?status=newlists the latest 200 enquiries.statusisnew,repliedorclosed. Scopeenquiries:read.PATCH /api/v1/enquiries/:idsets thestatus. Scopeenquiries:write.
A key with a :write scope can also read. Nothing can be deleted through the API.
Webhooks
The owner adds an https address under Settings › Integrations › Webhooks and picks the events. As each one happens, we send a POST with a JSON body:
booking.created: a booking is made, online or in the dashboard.booking.updated: a booking is moved, marked done or no-show, or given to someone else, or its notes change.booking.cancelled: a booking is cancelled, by the business or by the customer.enquiry.created: an enquiry comes in from the website form.invoice.issued: an invoice gets its number.invoice.paid: an invoice is paid in full.customer.created: a booking or an online order brings in a new customer.order.created: an online order is placed.
POST /your-endpoint
Content-Type: application/json
Pixelvile-Event: booking.created
Pixelvile-Signature: t=1762682400,v1=5f1c…e9
{ "id": "evt_Qm3…", "type": "booking.created", "created": 1762682400, "business": "smile-craft",
"data": { "id": 481, "status": "confirmed", "start_at": "2026-11-09T10:30", "service": "Check-up",
"staff_id": null, "customer": { "id": 77, "first_name": "Ananya" } } }
Events carry ids and first names only. They never include phone numbers or messages. If your key allows it, use the API to read more.
Checking the signature
Each webhook has its own signing secret (whsec_…), shown once when the webhook is made. The Pixelvile-Signature header has two parts:
t=: the time, in Unix seconds.v1=: the hex HMAC-SHA256, made with the secret, of the time, a full stop and the raw body.
Compare the signature in constant time, and refuse anything older than five minutes:
import crypto from 'node:crypto';
function fromPixelvile(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
const want = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && v1?.length === want.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(want));
}
Answers, retries and switching off
- Answer with any
2xxwithin 10 seconds. We don’t follow redirects. - If you answer anything else, we try again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. That’s seven tries over about a day. Use the event
idto ignore a repeat. - After 10 failed tries in a row, we switch the webhook off and email the owner. The dashboard has a delivery log with each answer code, and a Send test button.
- Addresses must use https on the standard port and be on the public internet. We refuse private, local and IP addresses.
Calendars and accounting
The same add-on gives every team member a private calendar link, an .ics subscription for Google, Apple or Outlook that shows only the bookings they can see. Owners also get Tally Prime and Zoho Books exports under Invoices › Accounting. Neither needs a key.
Questions
Email developers@pixelvile.com. If you’ve found a security problem, see our security page.