Αναφορά
Φόρμες, επικοινωνία και κρατήσεις
Δημόσια endpoint που γράφουν στα εισερχόμενα και στο ημερολόγιό σας. Δεν απαιτούν κλειδί API, επειδή είναι σχεδιασμένα να καλούνται από επισκέπτες: το captcha, η idempotency και τα όρια αιτημάτων αποτελούν μέρος της σύμβασης.
Πριν ξεκινήσετε#
- Χωρίς κλειδί 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 | συμβολοσειρά | Το token του 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. Κάθε χώρος εργασίας ελέγχει το token σε μία από τρεις λειτουργίες: απενεργοποιημένη, παρακολούθηση (ελέγχεται και καταγράφεται, χωρίς ποτέ απόρριψη) ή επιβολή (ένα token που λείπει ή είναι άκυρο λαμβάνει 400). Αν το Cloudflare δεν είναι προσβάσιμο, το αίτημα γίνεται δεκτό, ώστε μια διακοπή να μη σας κοστίσει ποτέ έναν δυνητικό πελάτη.
Επαναλήψεις. Μια επανάληψη με το ίδιο Idempotency-Key επιστρέφει το αίτημα που αποθηκεύτηκε την πρώτη φορά και απαντάται πριν από τον έλεγχο captcha, επειδή τα token του Turnstile είναι μίας χρήσης.
Φόρμες#
Οι φόρμες που δημιουργούνται στην ενότητα Φόρμες της κονσόλας έχουν δικό τους δημόσιο κλειδί. Ανακτήστε τον ορισμό όταν εμφανίζεται η φόρμα, αποδώστε τα πεδία της και στείλτε πίσω τις τιμές μαζί με το token απόδοσης.
GET/v1/forms/{key}
Τα πεδία, τα μηνύματα και οι ρυθμίσεις spam της φόρμας, καθώς και ένα υπογεγραμμένο token απόδοσης.
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_*. |
Προστασία από spam#
- Μια υποβολή με συμπληρωμένο πεδίο-παγίδα (
_hp), με token απόδοσης που λείπει ή είναι άκυρο, ή που στάλθηκε γρηγορότερα από τον ελάχιστο χρόνο συμπλήρωσης της φόρμας (3 δευτερόλεπτα από προεπιλογή) κρατείται ως spam. Λαμβάνει ωστόσο την ίδια απάντησηaccepted, ώστε τα bot να μη μαθαίνουν τίποτα. - Με ενεργό Turnstile, ένα token που λείπει ή είναι άκυρο λαμβάνει
400. - Πέρα από τα όρια ανά IP ισχύουν και όρια ανά φόρμα: από προεπιλογή 30 υποβολές ανά IP την ώρα και 500 ανά φόρμα την ώρα, με δυνατότητα ρύθμισης για κάθε φόρμα. Πάνω από αυτά:
429 RATE_LIMITED. - Μια φόρμα μπορεί να ορίσει τους ιστοτόπους από τους οποίους επιτρέπεται η υποβολή της· κάθε άλλο
Originλαμβάνει403 ORIGIN_NOT_ALLOWED.
Χωρίς JavaScript#
Λειτουργεί και ένα απλό <form method="post">. Το API απαντά με ανακατεύθυνση 303 προς το URL ανακατεύθυνσης της φόρμας ή προς τη δική του σελίδα ευχαριστιών, και εμφανίζει τα σφάλματα σε μια μικρή σελίδα.
<!-- 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· αυτό το endpoint προστατεύεται από το όριο ανά IP. - Ο διακομιστής ελέγχει ξανά τη χρονοθυρίδα. Αν στο μεταξύ την έχει δεσμεύσει κάποιος άλλος, η απάντηση είναι
400· ανακτήστε ξανά τις χρονοθυρίδες. - Οι νέες κρατήσεις ξεκινούν ως
requestedμέχρι να τις επιβεβαιώσει η ομάδα σας στην κονσόλα. - Μια επανάληψη με το ίδιο
Idempotency-Keyεπιστρέφει το ραντεβού που κλείστηκε την πρώτη φορά.
Αυτή η αναφορά καλύπτει μόνο το δημόσιο API και ακολουθεί τη σύμβαση συμβατότητας του API: οι δημόσιες αποκρίσεις μόνο αποκτούν νέα πεδία. Η περιγραφή OpenAPI αυτών των διαδρομών βρίσκεται στο /developers/openapi.json.