Developers
The Avena Flow Partner Booking API lets marketplaces, calendars, and integration platforms read a tenant’s services + availability and create bookings on their behalf. Two-way sync via signed webhooks keeps your system aligned with the tenant’s live calendar.
Every request must include a Bearer token. Tenants generate keys self-serve from Settings → Integrations in their Avena Flow dashboard. Keys are scoped to one tenant and can be revoked instantly.
Authorization: Bearer afp_live_8k3...Key format: afp_live_ prefix + 32 random bytes (URL-safe base64). Total length ≈ 52 chars.
/api/v1/public/servicesReturns active, bookable services for the authenticated tenant.
curl https://avenaflow.com/api/v1/public/services \
-H "Authorization: Bearer afp_live_..."Response
{
"data": [
{
"id": "a7b1...",
"name": "Microblading Touch-Up",
"description": "Color refresh, 1 year follow-up.",
"category": "PMU",
"duration_minutes": 90,
"cleanup_minutes": 15,
"price_cents": 25000,
"currency": "USD",
"image_url": "https://...",
"service_type": "treatment"
}
],
"meta": { "tenant_id": "...", "partner_label": "ClassPass", "count": 12 }
}/api/v1/public/availabilityReturns open slots over a date range. Honors the tenant’s working hours, blocks, and lead-time rules.
| Param | Type | Notes |
|---|---|---|
| service_id | string | required — UUID from /services |
| from | YYYY-MM-DD | optional — defaults to today |
| to | YYYY-MM-DD | optional — defaults to from + 14d (max 60) |
| staff_id | string | optional — filter to one staff member |
curl "https://avenaflow.com/api/v1/public/availability?service_id=a7b1&from=2026-06-10&to=2026-06-17" \
-H "Authorization: Bearer afp_live_..."Response
{
"data": {
"service": { "id": "a7b1", "name": "Microblading Touch-Up", "duration_minutes": 90, "price_cents": 25000 },
"from": "2026-06-10",
"to": "2026-06-17",
"timezone": "America/New_York",
"days": [
{
"date": "2026-06-10",
"day_of_week": "Wednesday",
"slots": [
{ "start": "2026-06-10T10:00:00.000Z", "end": "2026-06-10T11:30:00.000Z" },
{ "start": "2026-06-10T14:00:00.000Z", "end": "2026-06-10T15:30:00.000Z" }
]
}
]
}
}/api/v1/public/bookingsCreates a confirmed appointment in the tenant’s calendar. Auto-creates the contact if their email/phone doesn’t match an existing client. Returns 409 if the slot was taken between your availability check and this call.
Idempotency: include partner_reference_id. Re-POSTing the same value returns the original booking instead of creating a duplicate.
curl -X POST https://avenaflow.com/api/v1/public/bookings \
-H "Authorization: Bearer afp_live_..." \
-H "Content-Type: application/json" \
-d '{
"service_id": "a7b1...",
"start_time": "2026-06-10T14:00:00Z",
"customer": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+14015551234"
},
"partner_reference_id": "classpass_res_42",
"notes": "First-time client, allergic to lidocaine"
}'Response (201)
{
"data": {
"id": "ap_9f2...",
"status": "confirmed",
"service_id": "a7b1...",
"contact_id": "co_4d1...",
"start": "2026-06-10T14:00:00",
"end": "2026-06-10T15:30:00",
"price_cents": 25000,
"currency": "USD",
"partner_source": "classpass",
"partner_reference_id": "classpass_res_42"
}
}/api/v1/public/bookings/{id}Soft-cancels a booking — status flips to cancelled and the slot reopens. You can only cancel bookings created by the same partner key.
curl -X DELETE "https://avenaflow.com/api/v1/public/bookings/ap_9f2?reason=customer_request" \
-H "Authorization: Bearer afp_live_..."When the tenant cancels, reschedules, or otherwise changes a booking inside Avena Flow, we POST a signed event to every active webhook endpoint they’ve registered with you.
Event types
booking.createdbooking.cancelledbooking.rescheduledservice.updatedavailability.changedRequest headers
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-Avena-Signature: t=1717084800,v1=4f7b2...
X-Avena-Event: booking.cancelled
User-Agent: AvenaFlow-Webhooks/1.0Verify the signature
Concatenate {timestamp}.{raw_body} and HMAC-SHA256 it with your signing secret. Constant-time compare against the v1 value. Reject any timestamp older than 5 minutes to prevent replay.
import crypto from 'crypto'
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const ts = Number(parts.t)
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
}Retry policy
Non-2xx responses or timeouts (10s) are retried at 1m, 5m, 30m, 2h. After 5 failed attempts we mark the delivery failed. After 3 consecutive failures across deliveries we auto-disable the endpoint until the tenant re-enables it.
/api/v1/public/calendar/{slug}.icsRead-only iCal feed scoped by the tenant’s booking slug. No auth needed — the URL itself is the credential. Useful for partners that only need read access (Google Reserve discovery, Yelp Book, calendar mirrors).
All errors return a JSON body with an error key and (when helpful) a message.
| Status | Error | Meaning |
|---|---|---|
| 400 | missing_required_fields | Body missing service_id, start_time, or customer.first_name |
| 401 | missing_bearer_token | No Authorization header |
| 401 | invalid_api_key | Key not found or hash mismatch |
| 401 | api_key_revoked | Key was revoked by the tenant |
| 403 | insufficient_scope | Key lacks the required scope |
| 404 | service_not_found | Service doesn’t exist or isn’t active |
| 409 | slot_unavailable | Slot was taken between your availability check and the booking |