Справочник
Формуляри, контакт и срещи
Публични адреси, които пишат във входящата ви поща и в календара. Не изискват 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…"
}'const enquiry = await citron.submitContact(
{ name: 'Ivan Petrov', email: 'ivan@example.com', subject: 'Quote', message: 'A quote for BIO 6, please.' },
{ turnstileToken, idempotencyKey, orgSlug: 'your-org' }, // idempotencyKey is generated when omitted
);
console.log(enquiry.id, enquiry.status);{
"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// When the form is SHOWN: fields to render, the render token, the Turnstile site key
const form = await citron.forms.get('quote');
// When it is sent
const result = await citron.forms.submit(
'quote',
{ name: 'Ivan Petrov', email: 'ivan@example.com', product: 'BIO 6' },
{ renderToken: form.renderToken, turnstileToken, utm: { utm_source: 'newsletter' } },
);
console.log(result.id, result.status, result.warnings);{
"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 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 към адреса за пренасочване на формуляра или към собствената си страница с благодарност, а грешките показва като кратка страница.
<!-- 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"
}'const types = await citron.booking.types({ orgSlug: 'your-org' });
const slots = await citron.booking.slots(
{ typeId: types[0].id, mode: 'online', from: new Date(), to: new Date(Date.now() + 14 * 86_400_000) },
{ orgSlug: 'your-org' },
);
const appointment = await citron.booking.book(
{
contact: { name: 'Ivan Petrov', email: 'ivan@example.com', phone: '+359888000000' },
appointmentTypeId: types[0].id,
staffUserId: slots[0].staffUserId,
mode: 'online',
startsAt: slots[0].startsAt,
},
{ idempotencyKey, orgSlug: 'your-org' },
);
console.log(appointment.id, appointment.status);{
"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" }
}| Поле в тялото | |
|---|---|
contact | name (от 2 до 120 знака, задължително), email, phone (от 6 до 40 знака). |
appointmentTypeId | Задължително. |
mode | online или in_office. Задължително. |
startsAt | ISO 8601, startsAt на свободен час. Задължително. |
staffUserId, locationId | От свободния час. |
note | До 1000 знака. |
- Тялото се проверява стриктно: непознато поле връща
400. Няма поле за captcha; адресът е защитен от ограничението по IP. - Сървърът проверява часа отново. Ако междувременно някой друг го е заел, отговорът е
400; вземете свободните часове наново. - Новите срещи започват като
requested, докато екипът ви не ги потвърди в конзолата. - Повторна заявка със същия
Idempotency-Keyвръща срещата, записана първия път.
Справочникът описва само публичното API и следва договора му за съвместимост: публичните отговори само получават нови полета. Описанието на тези адреси във формат OpenAPI е на /developers/openapi.json.