API and webhooks for developers
Connect your website, your management software or your apps to your Yuppi account. API keys and webhooks are included in the Pro and Agency Suite plans, at no extra cost.
Contract being implemented
This is version 1 of the interface, still being built: addresses and fields may change before the launch. Every change will be noted on this page.
Authentication
Create a key in the dashboard, under “Calendar, API and webhooks”. Give each key a name and only the permissions it needs.
The full key is shown only once, when you create it: we store it so that not even we can read it again. If you lose it, revoke it and create another one.
Send it with every request in the Authorization header. Use it only from your server: never in the code of a web page or an app others can download.
Authorization: Bearer yk_live_...| Permission | What it allows |
|---|---|
| bookings:read | Read the bookings |
| bookings:write | Confirm, decline and update the bookings |
| calendar:read | Read the calendar, including the busy times imported from your calendar |
| clients:read | Read the clients |
| quotes:read | Read the quotes |
| services:read | Read the services |
Address and formats
https://yuppi.now/v1- Requests and responses are JSON (UTF-8), with camelCase field names.
- Days use the “YYYY-MM-DD” format; instants are ISO 8601 with offset, for example “2026-10-10T14:00:00+02:00”.
- Amounts are euro cents, VAT included: 28000 means €280.00.
- Lists come in pages: ?page=1, and the response has items, page, pageSize and total.
- Writes (POST) carry an Idempotency-Key header with a unique value: if you repeat the same request, it is not run twice.
curl "https://yuppi.now/v1/provider/bookings?status=pending&page=1" \
-H "Authorization: Bearer yk_live_..." \
-H "Accept: application/json"Errors
Errors follow RFC 9457 (application/problem+json): the HTTP status, a stable code to use in your code and, for invalid data, errors with one message per field.
- 401
- key missing, wrong or revoked
- 403
- insufficient_scope: the key lacks the permission; addon_required: the plan does not include the tools
- 404
- not found, or not in your account
- 409
- the state does not allow it, for example a booking already confirmed
- 422
- invalid data
- 429
- too many requests: wait the seconds given in Retry-After
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"detail": "La chiave non ha il permesso bookings:read.",
"code": "insufficient_scope",
"requestId": "req_7f3a9c2e"
}Main endpoints
They read and write only the data of your account. The examples show the format of the responses, with sample data.
GET
/provider/bookings?status=&page=Bookings, newest first; status: pending, confirmed, declined, cancelled, completed
Permission:
bookings:readExample response { "items": [ { "id": "bkg_m3x8q2", "code": "YU-7KQ2XH", "status": "confirmed", "service": { "id": "svc_4r9t1c", "title": "Festa di compleanno con il mago" }, "package": { "name": "Festa completa", "durationMinutes": 120, "maxChildren": 25 }, "date": "2026-10-17", "startTime": "16:00", "address": "Via Toledo 120", "town": "Napoli", "children": 18, "ageBand": "4-7", "notes": "Festa a tema pirati, torta alle 17:30", "customer": { "id": "cus_8h2k4d", "name": "Giulia Esposito", "email": "giulia@example.com", "phone": "+39 333 000 0000" }, "totalCents": 28000, "createdAt": "2026-09-20T10:12:00+02:00", "respondBy": "2026-09-21T10:12:00+02:00", "declineReason": null, "timeline": [ { "at": "2026-09-20T10:12:00+02:00", "type": "created", "by": "customer", "note": null }, { "at": "2026-09-20T11:03:00+02:00", "type": "confirmed", "by": "provider", "note": null } ], "assignees": [ { "id": "stf_2p7w9z", "name": "Marco Russo" } ], "entertainersNeeded": 1, "deposit": { "amountCents": 8400, "status": "paid", "paidAt": "2026-09-20T18:40:00+02:00" }, "balance": { "amountCents": 19600, "status": "due", "paidAt": null }, "dispute": null, "noShow": null, "canMarkNoShow": false, "canDispute": false } ], "page": 1, "pageSize": 20, "total": 1 }GET
/provider/bookings/{id}One booking, with history and deposit
Permission:
bookings:readPOST
/provider/bookings/{id}/confirmConfirms a pending booking
Permission:
bookings:writeConfirming a booking: depositPercent (10-50) asks the family for a deposit; agencies can pass assigneeIds, who runs the party.
Example request curl -X POST "https://yuppi.now/v1/provider/bookings/bkg_m3x8q2/confirm" \ -H "Authorization: Bearer yk_live_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 4f1c2a9e-8d3b-4f6a-9c1e-2b7d5a0e3f18" \ -d '{ "depositPercent": 30 }'POST
/provider/bookings/{id}/declineDeclines a pending booking, with a reason and a message
Permission:
bookings:writeGET
/provider/calendar?month=YYYY-MMThe calendar of a month: parties, events added by hand, imported busy times (source: external) and blocked days
Permission:
calendar:readExample response { "month": "2026-10", "entries": [ { "id": "bkg_m3x8q2", "source": "booking", "bookingId": "bkg_m3x8q2", "code": "YU-7KQ2XH", "status": "confirmed", "date": "2026-10-17", "startTime": "16:00", "durationMinutes": 120, "customerName": "Giulia Esposito", "serviceTitle": "Festa di compleanno con il mago", "town": "Napoli", "assignees": [ { "id": "stf_2p7w9z", "name": "Marco Russo" } ], "entertainersNeeded": 1, "venueId": null }, { "id": "ext_9d1f6b", "source": "external", "bookingId": null, "code": null, "status": "confirmed", "date": "2026-10-20", "startTime": "10:00", "durationMinutes": 90, "customerName": "", "serviceTitle": "Impegno privato", "town": "", "assignees": [], "entertainersNeeded": 0, "venueId": null } ], "blocks": [ { "date": "2026-10-25", "note": "Ferie", "managedBy": "provider" } ], "calendarMode": "self_managed" }GET
/provider/clients?q=&page=Clients, with number of parties and total spent
Permission:
clients:readExample response { "items": [ { "id": "cus_8h2k4d", "source": "site", "name": "Giulia Esposito", "email": "giulia@example.com", "phone": "+39 333 000 0000", "town": "Napoli", "bookings": 2, "lastPartyDate": "2026-10-17", "totalCents": 52000 } ], "page": 1, "pageSize": 20, "total": 1 }GET
/provider/quotes?status=&page=Quotes; status: draft, sent, accepted, declined, expired
Permission:
quotes:readExample response { "items": [ { "id": "quo_5b8n3v", "number": "P-2026-014", "status": "sent", "client": { "id": "cli_1s6j0e", "name": "Anna De Luca", "email": "anna@example.com", "phone": "+39 347 000 0000" }, "title": "Comunione di Sofia: animazione e truccabimbi", "eventDate": "2026-11-08", "totalCents": 45000, "validUntil": "2026-10-08", "sentAt": "2026-09-24T09:30:00+02:00", "outcome": null } ], "page": 1, "pageSize": 20, "total": 1 }GET
/provider/servicesYour services, with status and statistics of the last 30 days
Permission:
services:readExample response [ { "id": "svc_4r9t1c", "slug": "festa-di-compleanno-con-il-mago", "title": "Festa di compleanno con il mago", "category": "feste-bambini", "status": "published", "fromPriceCents": 18000, "stats": { "views30d": 412, "requests30d": 9 }, "sponsorship": null, "pendingChanges": false, "photo": { "id": "pho_3k7m2x", "url": "https://yuppi.now/media/pho_3k7m2x.webp", "alt": "Il mago con i bambini" } } ]
Webhooks
Add a webhook in the dashboard: choose the https address that receives the calls and the events you care about. At most 5 webhooks per account.
For each event Yuppi sends a POST request with a JSON body: event id, type, instant and the details in data. With “Test” you receive a ping event.
| Event | When it arrives | Content of data |
|---|---|---|
| booking.created | A family sends a booking request | { "booking": ProviderBooking } |
| booking.confirmed | You confirm a booking | { "booking": ProviderBooking } |
| booking.cancelled | A booking is cancelled, by you or by the family | { "booking": ProviderBooking } |
| quote.accepted | The client accepts a quote | { "quote": QuoteSummary } |
| quote.declined | The client declines a quote | { "quote": QuoteSummary } |
| deposit.paid | A deposit is paid | { "booking": ProviderBooking } | { "quote": QuoteSummary } |
| review.published | A review of one of your services is published | { "review": Review, "serviceId": string } |
| ping | You press “Test” in the dashboard | {} |
{
"id": "evt_2w9c4r",
"type": "booking.confirmed",
"createdAt": "2026-09-20T11:03:02+02:00",
"data": {
"booking": {
"id": "bkg_m3x8q2",
"code": "YU-7KQ2XH",
"status": "confirmed",
"service": {
"id": "svc_4r9t1c",
"title": "Festa di compleanno con il mago"
},
"package": {
"name": "Festa completa",
"durationMinutes": 120,
"maxChildren": 25
},
"date": "2026-10-17",
"startTime": "16:00",
"address": "Via Toledo 120",
"town": "Napoli",
"children": 18,
"ageBand": "4-7",
"notes": "Festa a tema pirati, torta alle 17:30",
"customer": {
"id": "cus_8h2k4d",
"name": "Giulia Esposito",
"email": "giulia@example.com",
"phone": "+39 333 000 0000"
},
"totalCents": 28000,
"createdAt": "2026-09-20T10:12:00+02:00",
"respondBy": "2026-09-21T10:12:00+02:00",
"declineReason": null,
"timeline": [
{
"at": "2026-09-20T10:12:00+02:00",
"type": "created",
"by": "customer",
"note": null
},
{
"at": "2026-09-20T11:03:00+02:00",
"type": "confirmed",
"by": "provider",
"note": null
}
],
"assignees": [
{
"id": "stf_2p7w9z",
"name": "Marco Russo"
}
],
"entertainersNeeded": 1,
"deposit": {
"amountCents": 8400,
"status": "paid",
"paidAt": "2026-09-20T18:40:00+02:00"
},
"balance": {
"amountCents": 19600,
"status": "due",
"paidAt": null
},
"dispute": null,
"noShow": null,
"canMarkNoShow": false,
"canDispute": false
}
}
}{
"id": "evt_8m1q5z",
"type": "review.published",
"createdAt": "2026-10-19T09:00:00+02:00",
"data": {
"review": {
"id": "rev_6t2y8u",
"author": "Giulia E.",
"occasion": "Compleanno",
"partyDate": "2026-10-17",
"rating": 5,
"title": "Festa perfetta",
"body": "Bambini entusiasti dall’inizio alla fine.",
"reply": null
},
"serviceId": "svc_4r9t1c"
}
}Checking the signature
Every call carries the Yuppi-Signature header: the HMAC-SHA256 of the request body, exactly as it arrives, computed with the webhook secret and written in lowercase hexadecimal.
The secret is shown in full only once, when you create the webhook. Compute the signature on the raw body, before reading the JSON, and compare it in constant time. If it does not match, answer 401 and ignore the call.
Yuppi-Signature: 3f9a0c6d1e...b27eimport { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the body exactly as received (Buffer), before any JSON parsing
export function isFromYuppi(rawBody, signature, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(String(signature ?? ''));
return a.length === b.length && timingSafeEqual(a, b);
}<?php
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, getenv('YUPPI_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_YUPPI_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
http_response_code(200);Answers and retries
Answer with a 2xx code within 10 seconds: do the long work after answering.
If your address does not answer or answers with an error, we try again with longer and longer waits for 24 hours. In the dashboard you see the last 30 deliveries, with status and duration.
The same event can arrive more than once or out of order: use the event id to avoid handling it twice and the createdAt instant for the order.
Limits
- 60 requests per minute per key. Beyond that, we answer 429 with Retry-After.
- At most 10 API keys and 5 webhooks per account.
- Keys and webhooks work while the Pro or Agency Suite plan is active.