# Creavi Booking API v1

This API lets integrations approved by a site owner read services, find available appointments and create/retrieve bookings. It runs inside the WordPress plugin. No hosted Creavi account, MCP installation or Abilities API is required.

## Enable and connect

Use plugin version **1.6.0 or later** for the managed connection screen. Version 1.6.0 creates a restricted technical account and WordPress Application Password for each connection, without requiring the administrator to create another user or log out.

1. Open **Creavi Booking → Booking API** and enable approved integrations.
2. Under **Add API connection**, enter a friendly name, select only the permissions the tool needs, and choose an expiration period.
3. Select **Create connection**. Creavi enables the Booking API and displays the generated technical username and Application Password once.
4. Copy the connection details into the tool's secret storage, or download the ready-to-import Postman collection.
5. Send HTTP Basic authentication with the generated username and Application Password over HTTPS.

The settings address is `/wp-admin/edit.php?post_type=creavibc_service&page=creavibc-api`, relative to your own WordPress installation. `/wp-admin/creavibc-api` is not a valid settings address. After an FTP update, confirm the installed version in **Plugins** and upload every file in the release, including `includes/api-settings.php`.

The connection name is only a friendly label. The generated username is required because WordPress Basic authentication uses it to locate the technical account and its permissions; nobody needs to sign in or out of WordPress with that account. The technical account has no general WordPress read capability, and its authenticated REST requests are restricted to `/creavi-booking/v1/`. The normal login password is random and is never shown. The Application Password is shown once and stored only as WordPress's one-way hash.

Use one connection per tool: booking ownership and request limits are per account. The connection table shows status, expiry and WordPress's last-used information. **Rotate password** creates a replacement, immediately removes the previous credential, and starts the selected expiration period again. **Revoke** removes the credential and its API permissions while retaining the technical account so booking ownership remains auditable. Expired credentials are denied and revoked automatically; their chosen permissions remain available if an administrator rotates the connection.

Manual WordPress user and Application Password setup remains available under **Advanced** for existing or custom integrations. Application Passwords inherit the account's permissions, so do not use administrator credentials for an external integration. The standard **Creavi Booking Integration** role receives the original service, availability, create, and owned-booking permissions. Bulk access to every booking must be granted explicitly. Custom roles can receive only the operations they need.

The API is disabled by default. Disabling it, removing the account's granular API capabilities, or revoking its Application Password removes access. Existing website booking forms continue to work when the API is disabled.

## Endpoints

Base URL: `https://your-site.example/wp-json/creavi-booking/v1`

| Method | Path | Description |
| --- | --- | --- |
| GET | `/connection` | Verify authentication and return effective API permissions, limits and retry-key retention. Managed connections also return their friendly name and credential expiry. |
| GET | `/services?page=1&per_page=20` | Published, non-password-protected services; maximum 50 per page. Pagination headers: `X-WP-Total`, `X-WP-TotalPages`. |
| GET | `/services/{id}` | Name, duration, service timezone, required customer fields, custom fields and consent policy. |
| GET | `/services/{id}/availability?date=2026-10-01` | Available appointments for one date in the service's timezone. |
| POST | `/bookings` | Create a booking using JSON and an `Idempotency-Key` header. |
| GET | `/bookings` | List today's bookings. Optional `from`, `to`, `service_id`, `page`, `per_page`, and `order` filters are described below. |
| GET | `/bookings/{id}` | Retrieve full details for a booking visible to this connection. |

All endpoints require authentication, at least the operation-specific capability and HTTPS. Service responses are a deliberate whitelist: raw post metadata, calendar credentials and host-only video links are not returned. There is no public customer directory or unauthenticated booking-list endpoint.

| Operation | WordPress capability |
| --- | --- |
| Test the connection | Any Booking API capability |
| List or read services | `creavibc_api_read_services` |
| Read availability | `creavibc_api_read_availability` |
| Create a booking | `creavibc_api_create_booking` |
| List/retrieve bookings created by this connection | `creavibc_api_read_booking` |
| List/retrieve every site's booking | `creavibc_api_read_all_bookings` |

The **Read all bookings** permission exposes customer data from the website and other integrations. It is disabled by default for new managed connections and is never added by the legacy-capability fallback. Administrators receive it. Existing integration roles retain their four original operations without silently receiving bulk customer-data access.

During upgrade, roles that had the original `creavibc_use_booking_api` capability retain the four original granular capabilities. A legacy custom account that has only the old capability temporarily retains those original v1 operations; assigning any granular capability switches that account to the granular model.

## Listing bookings

The booking-list endpoint is included in plugin version 1.6.0 and later.

`GET /bookings` defaults to the current date. These examples require the **Read created bookings** permission; add **Read all bookings** to include bookings created through the website and other connections.

```text
GET /bookings
GET /bookings?from=2026-09-15&to=2026-09-21
GET /bookings?from=2026-09-15&to=2026-09-21&service_id=2050&page=1&per_page=50&order=asc
```

`from` and `to` are inclusive `YYYY-MM-DD` dates. If `to` is omitted, it equals `from`; if both are omitted, both default to today in the WordPress site timezone. The filter uses each booking's service-local appointment date. A range can contain at most 93 inclusive days. `per_page` defaults to 50 and cannot exceed 100. Results are ordered by appointment date, time, and booking ID.

The response contains `scope` (`created_by_connection` or `all`), compact `bookings`, and a `pagination` object with `page`, `per_page`, `total`, `total_pages`, and `next_page`. Response headers also include `X-WP-Total` and `X-WP-TotalPages`. Each compact booking includes its service, UTC and service-local start/end times, duration, customer name, source, and delivery status. Use `GET /bookings/{id}` only when the assistant needs full customer contact details, comment, custom answers, location, meeting, or delivery results. This keeps routine MCP context smaller and reduces unnecessary customer-data exposure.

## Booking flow

1. Read service details to learn which fields and consent are required.
2. Fetch availability for a date. Display the returned appointment time, duration and timezone to the customer.
3. Ask for missing required answers and present the service's consent text/link. An integration must obtain consent from the customer rather than infer it.
4. Send a returned `starts_at`, customer details and custom-field answers to `/bookings`.
5. Save the returned booking ID. After a timeout, repeat the **same body and same Idempotency-Key**; do not generate another key.

Example body (replace service ID, custom-field ID, time and policy version with values returned by your site):

```json
{
  "service_id": 123,
  "starts_at": "2026-10-01T10:00:00+02:00",
  "customer": {
    "name": "Alex Smith",
    "email": "alex@example.com",
    "phone": "+33123456789",
    "timezone": "Europe/Paris"
  },
  "custom_fields": {
    "field_0123456789abcdef0123": "5"
  },
  "consent": {
    "accepted": true,
    "policy_version": "COPY_THE_64_CHARACTER_POLICY_VERSION_FROM_SERVICE_DETAILS"
  },
  "comment": "First visit"
}
```

Use a new random UUID as the `Idempotency-Key` for each distinct booking intent. Keys must contain 16–128 letters, digits, dots, colons, underscores or hyphens. Send `Content-Type: application/json`; the maximum request body is 64 KiB.

The API guarantees retry-key replay for **7 days**. Retry records store only a request hash, booking ID and creation time; they never store customer details or credentials. An hourly bounded cleanup removes expired records and timestamps records created by older plugin versions. After the retention period, integrations must treat the booking ID as the source of truth and must not reuse an old key.

Times must include seconds `00` and an explicit offset or `Z`. The availability date is always in the service's timezone. `customer.timezone` controls customer time display; it never changes the instant in `starts_at`. Booking responses report `starts_at` in UTC and retain both service and customer timezones. Spring DST gaps are not offered. The existing schedule supports one occurrence of repeated wall-clock times at autumn DST transitions; use the exact instant returned by availability.

Custom-field values are strings, including number and date answers. Dates use `YYYY-MM-DD`. Required fields, numeric values, and real calendar dates are checked again when booking. Use the permanent field IDs from service details, not translated labels. The editor preserves those IDs through label changes and reordering. Refresh service details after configuration changes. Unknown fields are rejected; a changed consent policy requires the customer's renewed acceptance.

## Responses and retries

A new booking returns **201** and a `Location` header. A replay returns **200**, the same booking ID and `replayed: true`. Reusing a key with different JSON data returns **409**. JSON object property order does not matter; other body changes do.

Successful responses include:

- `id`, `service_id`, `status: confirmed`, `starts_at`, `duration_minutes`, `timezone`, `customer_timezone`.
- Customer-facing `location` and `meeting` details.
- `delivery_status`: `processing`, `complete`, `needs_attention` or `unknown`.
- `delivery`: results for `email_admin`, `email_customer`, `google`, `outlook` and `meeting`.

Email `accepted` means WordPress's mail transport accepted the message, not proof of inbox delivery. Calendar results are `success`, `failed` or `not_requested`; an active delivery may say `processing`. Meeting results can be `ready`, `failed` or `not_requested`.

**A confirmed booking remains confirmed if email, video or calendar delivery fails.** Display the booking result and flag `needs_attention` for the business. Fetching or retrying a booking does not resend mail, recreate meetings or repeat calendar insertions. There is no automatic delivery retry queue in v1: after an interrupted/uncertain remote call, automatic retry could duplicate a calendar event. An interrupted process may leave `delivery_status: processing`; the business should review it if it persists.

Request markers contain a payload hash and booking ID, not customer details, raw keys or credentials. They remain as small, non-autoloaded WordPress options after retention deletes a booking, so an old retry cannot recreate it. Plan for this additional storage when assessing very large installations. A removed booking returns **410** on replay. A request interrupted before the booking was fully saved returns **409 `creavibc_booking_incomplete`** once no save is active. An administrator must inspect the draft/request marker; clients must not bypass it with a fresh key.

| Status | Meaning |
| --- | --- |
| 400 | Missing/invalid input, required field or consent. |
| 401 | Missing authentication. |
| 403 | Disabled API, missing permission, expired/revoked managed connection, or HTTPS required. |
| 404 | Service/booking is absent or inaccessible to this account. |
| 409 | Slot unavailable, changed consent, conflicting key or interrupted operation requiring review. Inspect `code`. |
| 410 | Original booking was removed. |
| 413 / 415 | Body too large / JSON content type required. |
| 429 | Account request limit reached. |
| 503 | Availability could not be verified, database lock unavailable or temporary save failure. Retry with the same key. |

Errors use WordPress's `{ "code", "message", "data": { "status" } }` format. HTTP responses use `Cache-Control: private, no-store`; 429/503 include `Retry-After`. Limits are 120 API requests per minute per integration account, including at most 20 booking attempts. These limits complement the hosting provider's traffic protection.

## Architecture and extension points

- `includes/booking-service.php`: shared validation, booking command, request replay and delivery orchestration. Inputs are unslashed PHP values; authentication identity and source come from the adapter, not request JSON.
- `includes/booking-availability.php`: shared available-time queries and verified external calendar intervals.
- `includes/rest-api.php`: versioned HTTP interface, data schemas and authorization.
- `includes/api-connections.php`: managed technical accounts, granular permissions, expiry, rotation and revocation.
- `includes/api-settings.php`: site-owner opt-in and setup guidance.
- Existing AJAX endpoints keep their response shape and call the shared booking command. Emails use the stored customer timezone rather than a global browser request.

Bookings continue to use the plugin's existing WordPress post/meta storage. A short, connection-owned MySQL/MariaDB named lock protects the local check-and-save operation across API and website requests; remote calls run outside that lock. The database must support `GET_LOCK`, `IS_USED_LOCK` and `RELEASE_LOCK`. Unsupported/proxied database setups fail safely with a temporary error rather than silently allow races. Test those hosting arrangements before deploying.

Connected calendars configured to block busy times must be successfully checked before saving; cached display availability is bypassed on creation. An unavailable or malformed calendar response is not interpreted as an empty calendar. Availability is not a temporary reservation. WordPress and external calendars do not share a transaction, so an unrelated calendar edit made just after the check remains a possible conflict.

Each service retains independent availability. Shared staff/resources, cancellation, rescheduling, payments, webhooks and customer identity are separate future additions. No new staff or payment model is implied by v1. An MCP adapter inside WordPress can call the shared functions; a future hosted MCP service can call this REST API using an approved account. Keep customer permissions separate from that site's integration credentials.

## Verification

`tests/api-integration.php` runs actual WordPress REST dispatch, Application Password checks, website booking commands and concurrent PHP processes against a disposable MySQL database. All external HTTP and mail delivery must be intercepted by the test installation. Never run this suite against a live site.

Use `CREAVI_TEST_WP_PATH=/path/to/test-wordpress php tests/api-integration.php`. The directory must contain `.creavi-api-test`, use `WP_ENVIRONMENT_TYPE=local`, have a database name beginning `creavi_api_test`, have the plugin active and include an administrator named `api_test_admin`. The suite creates disposable users, services and bookings. The machine-readable contract is in `openapi.json` beside this guide.

### Verification of version 1.6.0

The current 131-check integration suite and 11-check admin regression suite passed on WordPress 6.7.2 / PHP 8.4.11 using an isolated MySQL 5.7 database. Coverage includes the actual AJAX nonce/response flow, frontend and API availability, booking creation, Application Password authentication and revocation, managed-connection permissions and expiry, simultaneous retries, buffer conflicts, required Date fields, rolling day/month windows, failed storage, calendar response validation, notification failures, API meeting locations, and simulated secure-video token and invite responses. The admin checks cover menu routing, settings rendering, granular permissions, managed connection actions, and Postman downloads. Plugin PHP syntax was also checked with PHP 8.4.11. Real Google, Outlook, video, and email delivery were not exercised; verify those connections on staging before replacing a live installation.
