Referință

Formulare, contact și rezervări

Endpoint-uri publice care scriu în căsuța de mesaje primite și în calendarul dumneavoastră. Nu necesită cheie API, deoarece sunt concepute pentru a fi apelate de vizitatori: captcha, idempotența și limitele de cereri fac parte din contract.

Înainte de a începe#

  • Fără cheie API. Contactul și rezervările sunt adresate prin slug-ul spațiului de lucru: /v1/public/{orgSlug}/…. Formularele sunt adresate prin cheia proprie a formularului: /v1/forms/{key}/….
  • Fiecare scriere acceptă un antet Idempotency-Key, orice șir unic de până la 200 de caractere. Refolosiți-l când reîncercați aceeași trimitere, iar API-ul va returna primul rezultat în loc să salveze unul nou. SDK-ul îl generează pentru dumneavoastră.
  • Fiecare scriere este limitată per IP al vizitatorului (implicit 10 pe minut și 60 pe oră). Dacă trimiteți de pe propriul server, transmiteți mai departe x-forwarded-for și user-agent ale vizitatorului; altfel, toți vizitatorii împart aceeași limită.
  • Un formular răspunde cu 404 FORM_UNAVAILABLE dacă nu este activ sau dacă modulul Formulare nu este activat pentru spațiul de lucru.

Aceleași rute există și fără /public/{orgSlug} (/v1/contacts, /v1/appointments, …). Acestea deservesc un singur site, anterior spațiilor de lucru; folosiți întotdeauna varianta cu slug.

Formular de contact#

POST/v1/public/{orgSlug}/contacts

Salvează o solicitare în mesajele primite și notifică echipa.

CâmpTip
nameșirObligatoriu.
emailșirOpțional; dacă este trimis, trebuie să fie o adresă validă.
phone, subject, messageșirOpțional.
turnstileTokenșirToken-ul Cloudflare Turnstile, atunci când spațiul de lucru îl verifică.

Orice alt câmp este respins cu 400. Răspunsul este 201, cu solicitarea salvată.

curl https://api.console.citronos.com/v1/public/your-org/contacts \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b0d7c1e-8f3a-4c62-9d1b-2e7f6a4c9b10" \
  -d '{
    "name": "Ivan Petrov",
    "email": "ivan@example.com",
    "phone": "+359888000000",
    "subject": "Quote",
    "message": "A quote for BIO 6, please.",
    "turnstileToken": "0.AbC…"
  }'
Răspuns (prescurtat)
{
  "data": {
    "id": "cmf3m1c2d0010s601k4j3h2g1",
    "organizationId": "cmf2x0r1k0000s6014h3j5k7l",
    "name": "Ivan Petrov",
    "email": "ivan@example.com",
    "phone": "+359888000000",
    "subject": "Quote",
    "message": "A quote for BIO 6, please.",
    "status": "new",
    "source": "contact_form",
    "consentAt": "2026-09-24T08:15:02.114Z",
    "meta": { "idempotencyKey": "5b0d7c1e-8f3a-4c62-9d1b-2e7f6a4c9b10" },
    "createdAt": "2026-09-24T08:15:02.120Z",
    "updatedAt": "2026-09-24T08:15:02.120Z"
  },
  "meta": { "requestId": "0f6c1d2e-3b4a-4c5d-8e9f-a0b1c2d3e4f5" }
}

Turnstile. Fiecare spațiu de lucru verifică token-ul într-unul din trei moduri: dezactivat, monitorizare (verificat și înregistrat, dar niciodată respins) sau obligatoriu (un token lipsă sau invalid primește 400). Dacă Cloudflare nu poate fi contactat, solicitarea este acceptată, astfel încât o întrerupere nu vă face niciodată să pierdeți un lead.

Reîncercări. O reîncercare cu același Idempotency-Key returnează solicitarea salvată prima dată și primește răspuns înaintea verificării captcha, deoarece token-urile Turnstile sunt de unică folosință.

Formulare#

Formularele create în modulul Formulare al consolei au propria cheie publică. Preluați definiția când formularul este afișat, randați câmpurile acestuia și trimiteți valorile înapoi împreună cu token-ul de randare.

GET/v1/forms/{key}

Câmpurile formularului, mesajele, setările anti-spam și un token de randare semnat.

POST/v1/forms/{key}/submissions

Trimite un formular, ca JSON (201) sau ca simplu formular HTML (303).

# No API key: the form key is public
curl https://api.console.citronos.com/v1/forms/quote
GET /v1/forms/quote
{
  "data": {
    "key": "quote",
    "name": "Request a quote",
    "fields": [
      { "key": "name", "type": "text", "label": "Name", "required": true },
      { "key": "email", "type": "email", "label": "Email", "required": true },
      { "key": "product", "type": "select", "label": "Product", "required": false, "options": ["BIO 6", "BIO 10"] },
      { "key": "message", "type": "longText", "label": "Message", "required": false }
    ],
    "successMessage": "Thank you, we will be in touch.",
    "redirectUrl": null,
    "honeypotField": "_hp",
    "renderTokenField": "_rt",
    "renderToken": "Y21mM24wYS4xNzU4…",
    "turnstileSiteKey": "0x4AAAAAAA…"
  }
}
curl
curl https://api.console.citronos.com/v1/forms/quote/submissions \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9a4e2f10-6b7c-4d8e-a1f2-3c4d5e6f7a8b" \
  -d '{
    "data": { "name": "Ivan Petrov", "email": "ivan@example.com", "product": "BIO 6" },
    "renderToken": "Y21mM24wYS4xNzU4…",
    "turnstileToken": "0.AbC…",
    "utm": { "utm_source": "newsletter" }
  }'
Răspuns
{
  "data": { "id": "cmf3n4q5r0020s601t6y7u8i9", "status": "accepted" },
  "warnings": [{ "code": "UNKNOWN_FIELDS_DROPPED", "keys": ["phone2"] }]
}
Câmp din corpul cererii
dataCheia câmpului → valoare, conform definiției. Cheile necunoscute sunt eliminate și raportate în warnings ca UNKNOWN_FIELDS_DROPPED.
renderTokenDin definiția preluată la afișarea formularului.
turnstileTokenObligatoriu când formularul are Turnstile activat (turnstileSiteKey este setat).
referrer, utmAtribuire opțională; utm păstrează doar cheile utm_*.

Protecție anti-spam#

  • O trimitere cu câmpul-capcană completat (_hp), cu token de randare lipsă sau invalid ori trimisă mai repede decât timpul minim de completare al formularului (implicit 3 secunde) este păstrată ca spam. Primește totuși același răspuns accepted, astfel încât boții nu află nimic.
  • Cu Turnstile activat, un token lipsă sau invalid primește 400.
  • Pe lângă limitele per IP se aplică și limite per formular: implicit 30 de trimiteri per IP pe oră și 500 per formular pe oră, ajustabile pentru fiecare formular. Peste acestea: 429 RATE_LIMITED.
  • Un formular poate enumera site-urile care au voie să îl trimită; orice alt Origin primește 403 ORIGIN_NOT_ALLOWED.

Fără JavaScript#

Funcționează și un simplu <form method="post">. API-ul răspunde cu o redirecționare 303 către URL-ul de redirecționare al formularului sau către propria pagină de mulțumire și afișează erorile într-o pagină scurtă.

HTML
<!-- No JavaScript needed: the API answers 303 to your redirect URL or its thank-you page -->
<form method="post" action="https://api.console.citronos.com/v1/forms/quote/submissions">
  <input name="name" required>
  <input name="email" type="email" required>
  <!-- honeypot: keep it empty and hidden from people -->
  <input name="_hp" tabindex="-1" autocomplete="off" hidden>
  <!-- when the form has Turnstile on, the widget adds cf-turnstile-response -->
  <div class="cf-turnstile" data-sitekey="0x4AAAAAAA…"></div>
  <button>Send</button>
</form>

Rezervări#

GET/v1/public/{orgSlug}/appointment-types

Tipurile de programări pe care le pot rezerva vizitatorii.

GET/v1/public/{orgSlug}/appointments/slots

Intervalele libere pentru un tip de programare: typeId (obligatoriu), mode, from, to, staffId, locationId.

POST/v1/public/{orgSlug}/appointments

Rezervă un interval.

# 1. What can be booked
curl https://api.console.citronos.com/v1/public/your-org/appointment-types

# 2. Free slots for one type (from/to default to now and 30 days ahead)
curl "https://api.console.citronos.com/v1/public/your-org/appointments/slots?typeId=cmf3p1…&mode=online&from=2026-09-25T00:00:00.000Z&to=2026-10-09T00:00:00.000Z"

# 3. Book one of them
curl https://api.console.citronos.com/v1/public/your-org/appointments \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2c9f8e7d-6b5a-4d3c-9b2a-1f0e9d8c7b6a" \
  -d '{
    "contact": { "name": "Ivan Petrov", "email": "ivan@example.com", "phone": "+359888000000" },
    "appointmentTypeId": "cmf3p1…",
    "staffUserId": "cmf2y7…",
    "mode": "online",
    "startsAt": "2026-09-29T07:30:00.000Z",
    "note": "About BIO 6"
  }'
Intervale libere
{
  "data": [
    {
      "startsAt": "2026-09-29T07:30:00.000Z",
      "endsAt": "2026-09-29T08:00:00.000Z",
      "staffUserId": "cmf2y7…",
      "staffName": "Maria Georgieva",
      "mode": "online",
      "label": "пн 29 септ. · 10:30"
    }
  ],
  "meta": { "requestId": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a" }
}
Rezervare (prescurtat)
{
  "data": {
    "id": "cmf3q2w3e0030s601r4t5y6u7",
    "organizationId": "cmf2x0r1k0000s6014h3j5k7l",
    "appointmentTypeId": "cmf3p1…",
    "staffUserId": "cmf2y7…",
    "contactId": "cmf3q2w3e0029s601a1s2d3f4",
    "mode": "online",
    "status": "requested",
    "startsAt": "2026-09-29T07:30:00.000Z",
    "endsAt": "2026-09-29T08:00:00.000Z",
    "timezone": "Europe/Sofia",
    "idempotencyKey": "2c9f8e7d-6b5a-4d3c-9b2a-1f0e9d8c7b6a",
    "appointmentType": { "id": "cmf3p1…", "slug": "consultation", "nameBg": "Консултация", "nameEn": "Consultation", "durationMinutes": 30, "mode": "both", "active": true },
    "contact": { "id": "cmf3q2w3e0029s601a1s2d3f4", "name": "Ivan Petrov", "email": "ivan@example.com", "phone": "+359888000000" },
    "createdAt": "2026-09-24T08:20:11.504Z",
    "updatedAt": "2026-09-24T08:20:11.504Z"
  },
  "meta": { "requestId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
}
Câmp din corpul cererii
contactname (între 2 și 120 de caractere, obligatoriu), email, phone (între 6 și 40 de caractere).
appointmentTypeIdObligatoriu.
modeonline sau in_office. Obligatoriu.
startsAtISO 8601, valoarea startsAt a unui interval. Obligatoriu.
staffUserId, locationIdDin interval.
notePână la 1000 de caractere.
  • Corpul cererii este validat strict: un câmp necunoscut primește 400. Nu există câmp captcha; acest endpoint este protejat de limita per IP.
  • Serverul verifică din nou intervalul. Dacă între timp l-a ocupat altcineva, răspunsul este 400; preluați din nou intervalele.
  • Rezervările noi pornesc cu starea requested până când echipa dumneavoastră le confirmă în consolă.
  • O reîncercare cu același Idempotency-Key returnează programarea rezervată prima dată.

Această referință acoperă doar API-ul public și respectă contractul de compatibilitate al API-ului: răspunsurile publice pot doar să primească câmpuri noi. Descrierea OpenAPI a acestor rute se află la /developers/openapi.json.