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_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXScopes: 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
| Method | Path | Scope |
|---|---|---|
| GET | /api/v1/bookings | bookings:read |
| POST | /api/v1/bookings | bookings:write |
| DELETE | /api/v1/bookings/{id} | bookings:write |
| GET | /api/v1/availability?date=YYYY-MM-DD | availability:read |
| GET | /api/v1/resources | resources:read |
| GET | /api/v1/clients | clients:read |
| POST | /api/v1/clients | clients:write |
| GET | /api/v1/wallet/transactions | wallet: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.