Покажете свободните си часове
Вградете календар в сайта си, без да разкривате данни за клиенти или тайни API ключове.
Book365 API · v1
Свържете сайта си с календарите и резервациите на Book365 чрез фирмени API ключове.
Вградете календар в сайта си, без да разкривате данни за клиенти или тайни API ключове.
Създавайте резервации от своя сайт с услуги с определена продължителност и защита от дублиране.
Четете промените и отмените на резервации чрез API.
Book365 stores all companies, calendars, reservations, API credentials and delivery history in its own database. It runs independently. Each key is restricted to one business. Revocation, expiry, loss of owner membership or disabling the business stops API access, embeds and pending webhooks.
Base URL: https://book365.cloud/api/v1. Use HTTPS and Authorization: Bearer YOUR_SECRET_KEY. API keys belong on your backend, never in URLs or browser JavaScript. API requests do not use login cookies. Limits: 60 requests/minute/key and 120/minute/IP. HTTP 429 includes Retry-After.
curl 'https://book365.cloud/api/v1/calendars' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Accept: application/json'
Success contains data. Errors contain message; validation errors can also contain errors. IDs are integers, except business_id (UUID) and event/cursor IDs (decimal strings). Use only the fields listed below. All reservation timestamps are UTC, formatted YYYY-MM-DDTHH:mm:ssZ. Availability dates and labels use the selected timezone, defaulting to the business timezone, including daylight saving changes.
| Method & path | Input | Result |
|---|---|---|
GET /calendars | Optional page 1–10000; 50/page. | data: [{id, name}]; meta: {page, has_more}. |
GET /calendars/{id}/services | A calendar in this business. | data: [{id, name, duration_minutes, price, currency, configuration}]. Active services only; price is the base amount in EUR. See configuration below. |
GET /calendars/{id}/availability | Required service_id, date (YYYY-MM-DD; today through one year ahead in the selected timezone). Optional timezone: valid IANA identifier; defaults to the business timezone and ignores browser cookies. | data: {date, timezone, business_timezone, calendar_id, service_id, duration_minutes, slots: [{starts_at, ends_at, label, available}]}. |
GET /calendars/{calendar}/services/{service}/quote | passengers and addons selection; see transport and pricing below. Read-only keys allowed. | data: pricing breakdown; does not reserve a calendar. |
GET /reservations | Optional page 1–10000, limit 1–100 (default 50), updated_since (UTC timestamp). | data: reservation array; meta: {page, has_more}. Includes cancelled reservations. |
GET /reservations/{id} | Reservation in this business. | data: reservation. |
GET /reservation-changes | Optional after (nonnegative event cursor; default 0), limit 1–100 (default 50). | data: event array; meta: {next_cursor, has_more}. Stable, increasing cursor; includes every committed booking transition. |
POST /reservations | JSON below and required Idempotency-Key. | 201 on creation; 200 on replay. data: reservation. Requires read/write access. |
PATCH /reservations/{id} | Exactly {"status":"cancelled"}. | 200 with data: reservation. Requires read/write access. Completed bookings cannot be cancelled. |
Required: calendar_id, service_id, starts_at (UTC timestamp with zero seconds), customer.name (1–120 characters), customer.email (valid email, max 255). Optional: customer.phone (max 40), external_reference (max 120), notes (max 2000), passengers, addons and expected_total (see below). Customer accepts only name/email/phone. Top-level fields not documented here are rejected.
curl 'https://book365.cloud/api/v1/reservations' \
-X POST -H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7d61cd5d-f097-4c68-bfad-0a6b8b832977' \
-d '{
"calendar_id": 1,
"service_id": 1,
"starts_at": "2027-01-20T08:00:00Z",
"customer": {"name": "Alex Example", "email": "[email protected]"},
"external_reference": "my-site-1001"
}'
Use a starts_at value returned by availability. New bookings must start in the future, within one year, fit entirely inside working hours and start on a 15-minute grid measured from opening. Service duration can be 1–1440 minutes. Prices are nonnegative EUR amounts, configured by the business; request input cannot override them. Bookings start pending and immediately occupy their time. Staff can confirm, complete or cancel them in My businesses. Adjacent bookings are allowed; overlapping active bookings are rejected under a database transaction. Cancellation releases the time. Changing schedules never silently changes existing reservations.
POST /reservations accepts optional service_location (object or null), with only address (nullable string, max 500 characters), latitude (nullable number, -90 to 90) and longitude (nullable number, -180 to 180). Coordinates must be supplied together, even when an address is present. If configuration.requires_location is true, a nonblank address or both coordinates is required. Zero coordinates are valid. Invalid or missing required location returns 422. Changing location on an idempotent retry returns 409.
"service_location": {"address":"12 Example Street, Sofia","latitude":42.6977,"longitude":23.3219}Reservation responses, polling events and signed webhook snapshots include service_location or null. The location and event payload are encrypted at rest. Public availability never reveals customer locations. API creation requires customer name and email; staff can book with only a name, so response customer.email can be null. Phone remains optional.
Admin → Integrations → Google Maps configures the browser key for Book365’s address/map controls. The company’s shareable /book/{business_uuid} page lets signed-in clients reserve active services and enter any required location, with typed address, map selection or device location requested on click. Keys remain on your backend for your own website’s API integration. The Google Maps browser key is a separate, referrer-restricted credential.
{
"data": {
"id": 123, "business_id": "11111111-1111-4111-8111-111111111111",
"calendar_id": 1, "service_id": 1, "service_name": "Haircut",
"duration_minutes": 60, "price": "30.00", "currency": "EUR",
"pricing": {"base_price": "30.00", "addons_total": "0.00", "total": "30.00",
"currency": "EUR", "passengers": null, "transport": null, "addons": []},
"starts_at": "2027-01-20T08:00:00Z", "ends_at": "2027-01-20T09:00:00Z",
"status": "pending",
"customer": {"name": "Alex Example", "email": "[email protected]"},
"service_location": null,
"external_reference": "my-site-1001", "notes": null,
"updated_at": "2026-09-15T10:00:00Z"
}
}
Status: pending, confirmed, completed or cancelled. Allowed staff transitions: pending → confirmed/cancelled; confirmed → completed/cancelled. Completed and cancelled are terminal. Repeating the current status is a successful no-op. The API permits cancellation only. Bookings are retained for synchronization, with no hard-delete endpoint. The API does not collect payments; a service price does not enable Stripe checkout or subscription charging.
Company owners/managers create or edit company profiles under My businesses (name, email, phone, address, country, registration number, website, description). Create a business, then a calendar for each vehicle or worker. Under that calendar, Add service / Edit service opens the service editor. All services can have optional extras. Transport services additionally require an operating country, pickup location, destination, included passenger count and maximum passenger count. Country codes use the bundled 249-country ISO list. Routes are descriptive fixed routes; there is no distance calculation, dispatch or shared-seat inventory. Each reservation exclusively occupies its calendar for the full configured duration (1–1440 minutes); include driving and turnaround time in that duration.
The company sets every price, unit size and quantity limit. The service price
is the base EUR amount. configuration has:
requires_location: boolean, defaults to false. When true, booking requires a client address or coordinate pair.type: appointment or transport (legacy services default to appointment).description: nullable Markdown text, up to 2000 characters including formatting. Strip raw HTML, unsafe links and images before rendering.country_code: ISO alpha-2, origin and destination: up to 200 characters;
these are required for transport and null for appointments.included_passengers, max_passengers: integers 1–100 for transport, with
maximum >= included; null for appointments.addons: up to 20 definitions, including inactive definitions. Each has UUID
id, name (1–120 characters), kind (optional or passengers), price
(EUR decimal string, 0–999999.99), unit_size (1–100), max_quantity (1–100),
and boolean active. IDs belong to their service and are retained when edited.An optional add-on charges quantity × price; unit_size describes how many items
that unit covers. For example, one unit can represent two bikes. At most one
passenger rule is allowed per transport service: its automatically calculated
quantity is ceil(max(0, passengers - included_passengers) / unit_size).
The company can choose one person per unit, two people, or another group size.
Each started group is charged in full. Capacity above the included count requires
an active passenger rule with enough units to cover it; zero-priced units allow
free extra passengers. Quantities never increase the service's declared capacity.
GET /api/v1/calendars/{calendar}/services/{service}/quote accepts the same
passengers and addons selection fields as booking creation, as query parameters.
Both read-only and read/write API keys can quote within their business. Unknown
query fields are rejected. Example:
GET /api/v1/calendars/1/services/2/quote?passengers=5&addons[0][id]=11111111-1111-4111-8111-111111111111&addons[0][quantity]=2
Authorization: Bearer YOUR_SECRET_KEY
Accept: application/json
For POST /reservations, add these top-level fields when needed:
passengers: required for transport, integer 1–100 not exceeding capacity;
omit or null for appointments.addons: optional array of at most 20 {id, quantity} objects. IDs must be
distinct active optional add-ons of the selected service. quantity is an
integer 0–100 within the configured maximum; zero means not selected. Omitted
extras cost nothing. Passenger-rule IDs must never be submitted here: the server
applies the rule automatically. Extra nested fields such as price are rejected.expected_total: optional EUR amount 0–999999.99 with at most two decimals.
If it differs from the server-calculated total, booking returns 409 with no
reservation created. Get a fresh quote and show the new amount before submitting
revised booking details. A retry of a previously accepted identical payload still
returns the original reservation even after prices change.Quotes return data: {base_price, addons_total, total, currency, passengers,
transport, addons}. Amounts are two-decimal EUR strings, calculated in integer
cents; the total is limited to EUR 999,999.99. transport is null for appointments,
otherwise {country_code, origin, destination, included_passengers, max_passengers}.
Each selected add-on line has {id, name, kind, quantity, unit_size, unit_price,
total}. Zero-quantity and inactive add-ons are omitted. A quote does not hold a
vehicle or freeze the price. Always handle overlap and price conflicts on booking.
Example company configuration: base EUR 100 includes two passengers; extra groups of two cost EUR 30, one bike costs EUR 10.50, and one ski-equipment unit costs EUR 5.25. Five passengers, two bikes and one ski unit total EUR 186.25: 100 + 2×30 + 2×10.50 + 5.25. These are illustrative prices, never seeded plans.
New reservation responses expose the complete quote as pricing, and reservation
price is the total, including extras. Both staff bookings and API bookings use
this calculation. Stored snapshots retain route, passenger count and add-on names,
units and prices after service edits/removal. Polling and signed webhooks carry the
same immutable event-time breakdown. Existing reservations without a breakdown
return pricing: null; older stored events can omit pricing entirely.
Pricing failures return 422; missing/out-of-scope resources return 404; changed
expected totals return 409. No new payment collection or tax calculation is added.
The calendar iframe still displays free/busy slots, without a price-selection form.
External sites use the API for options, quotes and booking submission.
Idempotency-Key must contain 16–128 letters, digits, dots, underscores, colons or hyphens. Use a UUID for each intended booking. After a timeout or 503, retry the same payload and key. The same business and key return the existing reservation, even after service settings change or the booking time passes. A different payload with that key returns 409. The Idempotency-Replayed response header is true/false. Keys and request hashes are retained with booking history. Never change the key just because a request timed out.
Start with GET /reservation-changes?after=0. Apply each returned event in id order, save next_cursor only after processing the page, and continue while has_more is true. Use your saved cursor on the next poll. Event IDs may have gaps. Reprocessing a page is safe when you deduplicate by event id. Unlike timestamp filtering, this cursor captures multiple changes in the same second. The reservation list is useful for a full reconciliation; updated_since is inclusive.
Enter your public HTTPS endpoint when generating the API key. Only port 443 is supported; credentials/fragments, private/reserved IPs and redirects are rejected. Book365 sends a POST for reservation.created, reservation.updated and reservation.cancelled. Each notification contains:
{
"id": "456",
"type": "reservation.created",
"occurred_at": "2026-09-15T10:00:00Z",
"data": { "id": 123, "status": "pending", "...": "full reservation as above" }
}The Book365-Event-Id header matches id. Book365-Signature contains t=UNIX_SECONDS,v1=HEX_SIGNATURE. Compute HMAC-SHA256 using the signing secret over timestamp + "." + exact_raw_request_body. Compare signatures in constant time, reject timestamps outside a five-minute clock window, and only then decode/apply the event. Example verification in PHP:
$header = $_SERVER['HTTP_BOOK365_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if (!preg_match('/^t=([0-9]+),v1=([a-f0-9]{64})$/D', $header, $m)
|| abs(time() - (int) $m[1]) > 300
|| !hash_equals(hash_hmac('sha256', $m[1].'.'.$body, $signingSecret), $m[2])) {
http_response_code(401);
exit;
}
$event = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
// Durably enqueue the verified event, deduplicating by $event['id'].
http_response_code(204);
Acknowledge with any 2xx status within 10 seconds after durably accepting the event. Delivery is at least once: duplicates and out-of-order arrivals are possible. Deduplicate by event id and avoid replacing newer reservation state with an older event. Use the polling cursor to recover missed events. API and manual staff changes use the same transactional event log.
The scheduler runs every minute and processes a bounded batch; delivery normally begins on the next run, and busy queues may take longer. Non-2xx responses/timeouts retry after 1, 5, 15, 60, 180, 720 and 1440 minutes (8 attempts total). Delivery status and the latest 30 delivery records are visible under API & website calendar. Owners can password-confirm a retry of failed deliveries. Revoked/expired keys or removed access cancel pending deliveries. New keys receive future events only; polling can retrieve older history.
Set the exact HTTPS origin of the company website, for example https://www.example.com, without a path or trailing slash. Copy the generated iframe from your account. It contains a public identifier, never an API secret. It shows calendar/service/date selection and free/busy slots without customer names or booking details. Booking submission stays on your server through the API. The embed uses no third-party cookies. A same-origin script detects the device timezone; without JavaScript it defaults to the business timezone. Optional tz selects an IANA timezone; tz_auto=1 restores device detection. CSP frame-ancestors limits where the page can be embedded; the availability page itself is public. Revoke its key to disable it.
200/201 success; 401 invalid/expired/revoked key; 403 insufficient permission; 404 missing or out-of-scope resource; 409 occupied time, changed expected total, idempotency mismatch or invalid status transition; 422 invalid fields, closed hours or off-grid time; 429 rate limit; 503 temporary unavailability. Availability is a snapshot, so always handle a booking conflict. Scheduling and reservation storage are local; no external service connection is required.