Developers

Nola public API & webhooks

A REST API and outbound webhooks scoped to a single provider account — enough to connect n8n, Home Assistant, or your own backend without exposing anyone else's data. Manage keys and endpoints from Provider → API & webhooks.

Authentication

Bearer API keys

Every request is authenticated with an API key created in Provider → API & webhooks. Keys are shown once as plaintext at creation — store them like any other secret. Each key carries one or more scopes; a request fails with 403 forbidden if the key lacks the scope the endpoint requires.

Authorization header

Authorization: Bearer oh_live_XXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Scopes: bookings:read, bookings:write, availability:read, resources:read, clients:read, clients:write, wallet:read. Rate limit: 600 requests/minute per key (429 rate_limited above that).

Reference

Routes

MethodPathScope
GET/api/v1/bookingsbookings:read
POST/api/v1/bookingsbookings:write
DELETE/api/v1/bookings/{id}bookings:write
GET/api/v1/availability?date=YYYY-MM-DDavailability:read
GET/api/v1/resourcesresources:read
GET/api/v1/clientsclients:read
POST/api/v1/clientsclients:write
GET/api/v1/wallet/transactionswallet:read

Full request/response schemas are in the OpenAPI 3.1 spec. List endpoints return { items, nextCursor } — pass nextCursor back as cursor to page forward.

Examples

curl

List upcoming bookings

curl "https://<your-deployment>.convex.site/api/v1/bookings?from=1735689600000" \
  -H "Authorization: Bearer oh_live_XXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Create a booking

curl -X POST "https://<your-deployment>.convex.site/api/v1/bookings" \
  -H "Authorization: Bearer oh_live_XXXX_..." \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "svc_...",
    "startMs": 1735732800000,
    "client": { "name": "Jane Doe", "email": "jane@example.com" }
  }'

Webhooks

Verifying signatures (Node.js)

Each delivery is a POST with a JSON body { event, createdAt, data } and an X-Nola-Signature: t=<unix ts>,v1=<hmac hex> header, HMAC-SHA256 of `${t}.${rawBody}` using the endpoint's secret (shown once when you create the endpoint). Failed deliveries retry with exponential backoff for up to 8 attempts, then the endpoint is auto-disabled after 20 consecutive failures.

verify + Express handler

import { createHmac, timingSafeEqual } from "node:crypto";

// req.body must be the RAW request body string (not parsed JSON) —
// verify before you JSON.parse it.
function verifyNolaWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=")),
  );
  const timestamp = parts.t;
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const sigBuf = Buffer.from(parts.v1, "hex");
  const expBuf = Buffer.from(expected, "hex");
  if (sigBuf.length !== expBuf.length) return false;
  if (!timingSafeEqual(sigBuf, expBuf)) return false;

  // Reject events older than 5 minutes to prevent replay.
  const ageMs = Date.now() - Number(timestamp) * 1000;
  return ageMs < 5 * 60 * 1000;
}

// Express example:
app.post("/webhooks/nola", express.text({ type: "*/*" }), (req, res) => {
  const ok = verifyNolaWebhook(
    req.body,
    req.header("X-Nola-Signature"),
    process.env.NOLA_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(401).send("bad signature");

  const event = JSON.parse(req.body);
  console.log(event.event, event.data);
  res.sendStatus(200);
});

n8n

Wiring an n8n workflow

Steps

1. Add a "Webhook" node (Method: POST, Path: e.g. /nola) as the trigger.
2. In Nola → Provider → API & webhooks, add an endpoint with that
   webhook's Production URL and pick the events you want (e.g. booking.created).
3. In n8n, add a "Code" node right after the trigger to verify
   X-Nola-Signature using the Node snippet on this page (paste the
   endpoint's secret into an n8n credential, not into the workflow JSON).
4. Branch on {{$json.body.event}} (e.g. "booking.created",
   "booking.cancelled") with an "If" or "Switch" node.
5. Use the Nola REST nodes ("HTTP Request") with
   Authorization: Bearer <api key> to read back full booking/client details
   with the bookings:read / clients:read scopes, then continue your workflow
   (e.g. send a Slack message, update a spreadsheet, sync a calendar).

Home Assistant

Polling availability / booking from HA

For live occupancy and smart-lock codes tied to a specific resource, prefer the dedicated /access/occupancy and /access/codes/current endpoints (see Provider → Access & lights) — they use a lighter, resource-scoped key made for pollers. The public API below is for broader integrations (creating bookings, reading clients, etc.).

configuration.yaml

# configuration.yaml — poll today's availability for a resource
rest:
  - resource: "https://<your-deployment>.convex.site/api/v1/availability?date={{ now().strftime('%Y-%m-%d') }}&resourceId=<resourceId>"
    method: GET
    headers:
      Authorization: !secret nola_api_key
    scan_interval: 300
    sensor:
      - name: "Court 1 availability"
        value_template: >
          {{ value_json.slots | selectattr('available') | list | length }}

# Automations can POST a booking from Home Assistant, e.g. a physical
# button that books the next open slot — call the /api/v1/bookings
# endpoint from a "rest_command" with method: POST and your JSON body.