DiveConnect

DiveConnect API

Connect your booking website, CRM or accounting to DiveConnect with a REST API and signed webhooks. Included with Fleet.

Who can use the API

The API is included with Fleet. Only the shop owner or an admin can create, rotate or revoke keys, in Settings → API & webhooks.

Authentication

Send your key in the Authorization header of every request. A key belongs to one shop and only ever sees that shop's data. Keys are shown once: keep them on your server or in a secret manager, never in a browser or mobile app. A revoked key stops working immediately.

curl https://www.diveconnect.ai/api/v1/bookings?limit=25 \
  -H "Authorization: Bearer dck_live_xxxxxxxx_..."

Scopes

Give each key only the scopes it needs. A request outside the key's scopes gets 403 insufficient_scope.

read:bookings
List and read bookings. Customer email and phone are included only when the key also has Read customers.
write:bookings
Create and cancel bookings.
read:trips
Read trips, departures and availability.
read:customers
Read customers, including email, phone and marketing consent.
webhooks
Add and remove webhook endpoints. Adding one also needs Read bookings and Read customers, because events carry the booking and the customer's contact details.

Rate limits

Each key has its own limit: 60 requests per minute by default, up to 100. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with a Retry-After header.

Pagination

Lists return up to limit items (default 25, max 100) with has_more and next_cursor. Pass next_cursor as cursor to get the next page.

{
  "object": "list",
  "data": [ { "id": "…", "object": "booking", "status": "confirmed", … } ],
  "has_more": true,
  "next_cursor": "WyJ0c191dWlkIiwi…"
}

Errors

Errors use HTTP status codes and a JSON body with a stable code: 401 missing or invalid key, 402 the shop's plan doesn't include the API or the subscription isn't active, 403 missing scope, 404 not found in this shop, 409 conflict, 429 rate limited.

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have the scope this request needs.",
    "required_scope": "write:bookings"
  }
}

Endpoints

Base URL: https://www.diveconnect.ai/api/v1. The full reference, with every field, is in the OpenAPI spec.

GET/bookingsread:bookings
POST/bookingswrite:bookings
GET/bookings/{id}read:bookings
POST/bookings/{id}/cancelwrite:bookings
GET/tripsread:trips
GET/trips/{id}read:trips
GET/departuresread:trips
GET/departures/{id}read:trips
GET/availabilityread:trips
GET/customersread:customers
GET/customers/{id}read:customers
GET/webhook_endpointswebhooks
POST/webhook_endpointswebhooks + read:bookings + read:customers
DELETE/webhook_endpoints/{id}webhooks

Creating bookings

A booking created through the API works like one entered in the dashboard: DiveConnect computes the price from the trip or departure, checks the seats left, and creates the booking as pending (or confirmed) without taking a payment. Send an Idempotency-Key header so a retried request never creates a second booking.

curl -X POST https://www.diveconnect.ai/api/v1/bookings \
  -H "Authorization: Bearer dck_live_xxxxxxxx_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "departure_id": "6f1c…",
    "participants": 2,
    "customer": { "name": "Ana Reyes", "email": "ana@example.com" }
  }'

Webhooks

Add an endpoint (HTTPS, port 443) in the dashboard or through the API and choose its events. We POST a JSON event whenever a booking is created, cancelled or completed, wherever it happened: the dashboard, the app, your booking site, the marketplace or the API.

{
  "id": "0b9e…",
  "object": "event",
  "type": "booking.created",
  "api_version": "v1",
  "created": 1760400000,
  "data": { "object": { "id": "…", "object": "booking", "status": "pending", … } }
}

Verifying signatures

Every request has a DiveConnect-Signature header in the form t=TIMESTAMP,v1=SIGNATURE. Compute HMAC-SHA256 of TIMESTAMP.RAW_BODY with the endpoint's signing secret, compare it in constant time, and reject timestamps more than 5 minutes old.

import crypto from 'node:crypto'

// rawBody: the request body exactly as received (a string, before JSON.parse)
export function verifyDiveConnect(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest()
  const given = Buffer.from(parts.v1 || '', 'hex')
  return given.length === expected.length && crypto.timingSafeEqual(given, expected)
}

Retries

Answer with any 2xx status within 5 seconds. Otherwise we retry with exponential backoff (about 30 seconds, doubling up to 6 hours), for up to 10 attempts. An event can arrive more than once or out of order, so deduplicate on the event id. An endpoint that keeps failing is paused; resume it in the dashboard.