Справочник

Формуляри, контакт и срещи

Публични адреси, които пишат във входящата ви поща и в календара. Не изискват API ключ, защото ги викат посетителите; captcha, идемпотентност и ограничения на заявките са част от договора.

Преди да започнете#

  • Без API ключ. Контактът и срещите се адресират със slug на работното ви пространство: /v1/public/{orgSlug}/…. Формулярите се адресират със собствения си ключ: /v1/forms/{key}/….
  • Всяко записване приема заглавка Idempotency-Key, произволен уникален низ до 200 знака. Използвайте същия, когато повтаряте едно и също изпращане, и API ще върне първия резултат, вместо да запише втори. SDK го генерира вместо вас.
  • Всяко записване е ограничено по IP адреса на посетителя (по подразбиране 10 в минута и 60 в час). Ако изпращате от своя сървър, препращайте x-forwarded-for и user-agent на посетителя, иначе всички посетители делят един лимит.
  • Формулярът отговаря с 404 FORM_UNAVAILABLE, освен ако е активен и модулът „Формуляри“ е включен за работното пространство.

Същите адреси съществуват и без /public/{orgSlug} (/v1/contacts, /v1/appointments, …). Те обслужват един сайт отпреди работните пространства; винаги ползвайте формата със slug.

Форма за контакт#

POST/v1/public/{orgSlug}/contacts

Записва запитване във входящите и уведомява екипа.

ПолеТип
nameнизЗадължително.
emailнизПо желание; ако е подадено, трябва да е валиден адрес.
phone, subject, messageнизПо желание.
turnstileTokenнизТокенът от Cloudflare Turnstile, когато работното пространство го проверява.

Всяко друго поле се отказва с 400. Отговорът е 201 със записаното запитване.

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…"
  }'
Отговор (съкратен)
{
  "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. Всяко работно пространство проверява токена в един от три режима: изключено, наблюдение (проверява се и се записва, но никога не се отказва) или задължително (липсващ или невалиден токен връща 400). Ако Cloudflare е недостъпен, запитването се приема, така че прекъсване никога не губи клиент.

Повторни опити. Повторна заявка със същия Idempotency-Key връща запитването, записано първия път, и получава отговор още преди проверката на captcha, защото токените от Turnstile са еднократни.

Формуляри#

Формулярите, създадени в модула „Формуляри“ на конзолата, имат собствен публичен ключ. Вземете дефиницията, когато формулярът се показва, изобразете полетата му и изпратете стойностите заедно с токена за изобразяване.

GET/v1/forms/{key}

Полетата, съобщенията и настройките против спам на формуляра, плюс подписан токен за изобразяване.

POST/v1/forms/{key}/submissions

Изпраща формуляра като JSON (201) или като обикновен 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" }
  }'
Отговор
{
  "data": { "id": "cmf3n4q5r0020s601t6y7u8i9", "status": "accepted" },
  "warnings": [{ "code": "UNKNOWN_FIELDS_DROPPED", "keys": ["phone2"] }]
}
Поле в тялото
dataКлюч на поле → стойност, според дефиницията. Непознатите ключове се изпускат и се съобщават в warnings като UNKNOWN_FIELDS_DROPPED.
renderTokenОт дефиницията, взета при показването на формуляра.
turnstileTokenЗадължително, когато формулярът е с включен Turnstile (turnstileSiteKey е зададен).
referrer, utmИзточник по желание; utm запазва само ключовете utm_*.

Защита от спам#

  • Изпращане с попълнено поле-капан (_hp), с липсващ или невалиден токен за изобразяване или изпратено по-бързо от минималното време за попълване (3 секунди по подразбиране) се пази като спам. То пак получава същия отговор accepted, така че ботовете не научават нищо.
  • При включен Turnstile липсващ или невалиден токен връща 400.
  • Освен ограниченията по IP важат и тези на формуляра: по подразбиране 30 изпращания на IP адрес в час и 500 на формуляр в час, настройват се за всеки формуляр. Над тях: 429 RATE_LIMITED.
  • Формулярът може да изброи сайтовете, от които се приема; всеки друг Origin получава 403 ORIGIN_NOT_ALLOWED.

Без JavaScript#

Работи и обикновен <form method="post">. API отговаря с пренасочване 303 към адреса за пренасочване на формуляра или към собствената си страница с благодарност, а грешките показва като кратка страница.

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>

Записване на срещи#

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

Видовете срещи, за които посетителите могат да се запишат.

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

Свободните часове за един вид среща: typeId (задължително), mode, from, to, staffId, locationId.

POST/v1/public/{orgSlug}/appointments

Записва час.

# 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"
  }'
Свободни часове
{
  "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" }
}
Записана среща (съкратено)
{
  "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" }
}
Поле в тялото
contactname (от 2 до 120 знака, задължително), email, phone (от 6 до 40 знака).
appointmentTypeIdЗадължително.
modeonline или in_office. Задължително.
startsAtISO 8601, startsAt на свободен час. Задължително.
staffUserId, locationIdОт свободния час.
noteДо 1000 знака.
  • Тялото се проверява стриктно: непознато поле връща 400. Няма поле за captcha; адресът е защитен от ограничението по IP.
  • Сървърът проверява часа отново. Ако междувременно някой друг го е заел, отговорът е 400; вземете свободните часове наново.
  • Новите срещи започват като requested, докато екипът ви не ги потвърди в конзолата.
  • Повторна заявка със същия Idempotency-Key връща срещата, записана първия път.

Справочникът описва само публичното API и следва договора му за съвместимост: публичните отговори само получават нови полета. Описанието на тези адреси във формат OpenAPI е на /developers/openapi.json.