Vai al contenuto

Usiamo cookie tecnici per far funzionare il sito. Con il tuo consenso useremmo anche cookie per ricordare le tue preferenze, misurare le visite e mostrarti pubblicità più adatta. Puoi cambiare idea quando vuoi dal fondo della pagina. Leggi l’informativa sui cookie

API e webhook per sviluppatori

Collega il tuo sito, il tuo gestionale o le tue app al tuo account Yuppi. Chiavi API e webhook sono compresi nei piani Pro e Agency Suite, senza costi in più.

Contratto in fase di implementazione

Questa è la versione 1 dell’interfaccia, ancora in costruzione: indirizzi e campi possono cambiare prima dell’apertura. Ogni modifica sarà segnata in questa pagina.

Autenticazione

Crea una chiave dal Gestionale, in “Calendario, API e webhook”. Dai a ogni chiave un nome e solo i permessi che servono.

La chiave completa si vede una volta sola, quando la crei: la salviamo in modo che nemmeno noi possiamo rileggerla. Se la perdi, revocala e creane un’altra.

Mandala in ogni richiesta nell’intestazione Authorization. Usala solo dal tuo server: mai nel codice di una pagina web o di un’app che altri possono scaricare.

Intestazione
Authorization: Bearer yk_live_...
Permessi
PermessoCosa permette
bookings:readLeggere le prenotazioni
bookings:writeConfermare, rifiutare e aggiornare le prenotazioni
calendar:readLeggere l’agenda, compresi gli impegni importati dal tuo calendario
clients:readLeggere i clienti
quotes:readLeggere i preventivi
services:readLeggere i servizi

Indirizzo e formati

Indirizzo di base
https://yuppi.now/v1
  • Richieste e risposte sono in JSON (UTF-8), con nomi dei campi in camelCase.
  • I giorni sono nel formato “YYYY-MM-DD”; gli istanti in ISO 8601 con fuso, per esempio “2026-10-10T14:00:00+02:00”.
  • Gli importi sono in centesimi di euro, IVA inclusa: 28000 vuol dire 280,00 €.
  • Le liste sono a pagine: ?page=1 e la risposta ha items, page, pageSize e total.
  • Le scritture (POST) portano un’intestazione Idempotency-Key con un valore unico: se ripeti la stessa richiesta, non viene eseguita due volte.
Esempio di richiesta
curl "https://yuppi.now/v1/provider/bookings?status=pending&page=1" \
  -H "Authorization: Bearer yk_live_..." \
  -H "Accept: application/json"

Errori

Gli errori seguono RFC 9457 (application/problem+json): lo stato HTTP, un code stabile da usare nel tuo codice e, per i dati non validi, errors con un messaggio per campo.

401
chiave mancante, sbagliata o revocata
403
insufficient_scope: la chiave non ha il permesso; addon_required: il piano non comprende gli strumenti
404
non trovato, o non è del tuo account
409
lo stato non lo permette, per esempio una prenotazione già confermata
422
dati non validi
429
troppe richieste: aspetta i secondi indicati in Retry-After
application/problem+json
{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "detail": "La chiave non ha il permesso bookings:read.",
  "code": "insufficient_scope",
  "requestId": "req_7f3a9c2e"
}

Endpoint principali

Leggono e scrivono solo i dati del tuo account. Gli esempi mostrano il formato delle risposte, con dati di prova.

  • GET /provider/bookings?status=&page=

    Prenotazioni, dalla più recente; status: pending, confirmed, declined, cancelled, completed

    Permesso: bookings:read

    Esempio di risposta
    {
      "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}

    Una prenotazione, con storico e caparra

    Permesso: bookings:read

  • POST /provider/bookings/{id}/confirm

    Conferma una prenotazione in attesa

    Permesso: bookings:write

    Confermare una prenotazione: depositPercent (10-50) chiede una caparra alla famiglia; le agenzie possono indicare assigneeIds, chi fa la festa.

    Esempio di richiesta
    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}/decline

    Rifiuta una prenotazione in attesa, con motivo e messaggio

    Permesso: bookings:write

  • GET /provider/calendar?month=YYYY-MM

    L’agenda di un mese: feste, eventi aggiunti a mano, impegni importati (source: external) e giorni bloccati

    Permesso: calendar:read

    Esempio di risposta
    {
      "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=

    Clienti, con numero di feste e spesa totale

    Permesso: clients:read

    Esempio di risposta
    {
      "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=

    Preventivi; status: draft, sent, accepted, declined, expired

    Permesso: quotes:read

    Esempio di risposta
    {
      "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/services

    I tuoi servizi, con stato e statistiche degli ultimi 30 giorni

    Permesso: services:read

    Esempio di risposta
    [
      {
        "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"
        }
      }
    ]

Webhook

Aggiungi un webhook dal Gestionale: scegli l’indirizzo https che riceve le chiamate e gli eventi che ti interessano. Al massimo 5 webhook per account.

Per ogni evento Yuppi manda una richiesta POST con un corpo JSON: id dell’evento, tipo, istante e i dati in data. Con “Prova” ricevi un evento ping.

Webhook
EventoQuando arriva
booking.createdUna famiglia manda una richiesta di prenotazione
booking.confirmedConfermi una prenotazione
booking.cancelledUna prenotazione viene annullata, da te o dalla famiglia
quote.acceptedIl cliente accetta un preventivo
quote.declinedIl cliente rifiuta un preventivo
deposit.paidUna caparra viene pagata
review.publishedViene pubblicata una recensione su un tuo servizio
pingPremi “Prova” nel Gestionale
Esempio: booking.confirmed
{
  "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
    }
  }
}
Esempio: review.published
{
  "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"
  }
}

Controllare la firma

Ogni chiamata ha l’intestazione Yuppi-Signature: l’HMAC-SHA256 del corpo della richiesta, così come arriva, calcolato con il segreto del webhook e scritto in esadecimale minuscolo.

Il segreto si vede per intero una volta sola, quando crei il webhook. Calcola la firma sul corpo grezzo, prima di leggere il JSON, e confrontala in tempo costante. Se non corrisponde, rispondi 401 e ignora la chiamata.

Intestazione
Yuppi-Signature: 3f9a0c6d1e...b27e
Node.js
import { 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
<?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);

Risposte e nuovi tentativi

Rispondi con un codice 2xx entro 10 secondi: fai il lavoro lungo dopo aver risposto.

Se il tuo indirizzo non risponde o risponde con un errore, riproviamo con attese sempre più lunghe per 24 ore. Nel Gestionale vedi le ultime 30 consegne, con stato e durata.

Lo stesso evento può arrivare più di una volta o in ordine diverso: usa l’id dell’evento per non elaborarlo due volte e l’istante createdAt per l’ordine.

Limiti

  • 60 richieste al minuto per chiave. Oltre, rispondiamo 429 con Retry-After.
  • Al massimo 10 chiavi API e 5 webhook per account.
  • Le chiavi e i webhook funzionano finché il piano Pro o Agency Suite è attivo.