creno.
Documentation

Get booking in under an hour.

Two paths, one backend. Take our widget, or build your own UI on the same API. Pick whichever matches your stack.

On this page

1. Get your API key

Every account gets a publishable key (pk_live_...) from your dashboard. It's safe to put in client-side code. The real security boundary is the list of origins you allow it to be called from, which you also set in the dashboard. Register every domain you'll embed on (including localhost while you're testing).

Use our widget

We render the booking UI, you just paste it in.

React, Next.js, Astro

If your site is built with a React-based framework, use the component directly.

View on npm ↗
npm install @creno/react
import { BookingWidget } from "@creno/react";
import "@creno/react/style.css";

export default function BookingPage() {
  return (
    <BookingWidget
      apiUrl="https://api.crenoapp.com"
      apiKey="pk_live_..."
    />
  );
}

Props

Prop Type Description
apiUrl * string Your Créno API base URL.
apiKey * string Your publishable key (safe to expose in browser JS).
resourceId string Target a specific calendar. Omit to use your default.
labels Partial<Labels> Override any UI string. French and English ship built-in; use this for any other language.
formatLocale string Date/time formatting locale, e.g. "fr-CA". Defaults to the browser locale.
onBooked (booking) => void Called after a booking is successfully created.

Vue

Vue 3 wrapper around the same component. Auto-loads the embed script, no <script> tag to add yourself.

View on npm ↗
npm install @creno/vue
<script setup>
import { BookingWidget } from "@creno/vue";
</script>

<template>
  <BookingWidget
    api-url="https://api.crenoapp.com"
    api-key="pk_live_..."
    @booked="(booking) => console.log(booking.id)"
  />
</template>

Props

Prop Description
apiUrl * Your Créno API base URL.
apiKey * Your publishable key.
resourceId Target a specific calendar.
labels Override any UI string.
@booked Emitted with the created booking.

Any other website

Plain HTML, Shopify, Webflow, Squarespace, or anywhere else you can paste a script tag. No build step, no framework required.

<script src="https://cdn.crenoapp.com/embed.js" defer></script>

<creno-widget
  api-url="https://api.crenoapp.com"
  api-key="pk_live_..."
></creno-widget>

Renders inside a Shadow DOM, so it can never clash with your site's own CSS in either direction: your styles won't leak in, and the widget's styles won't leak out.

Attributes

Attribute Description
api-url * Your Créno API base URL.
api-key * Your publishable key.
resource-id Target a specific calendar.
locale e.g. "fr-CA".

Theming

Every color, radius, and font is a CSS custom property. Override them from your own stylesheet, with no build config and no component props needed, and it works identically for both the React component and the script embed.

creno-widget, .my-booking-page {
  --pbw-color-accent: #b8452f;
  --pbw-color-accent-hover: #963a27;
  --pbw-radius-lg: 8px;
}
Variable Default Controls
--pbw-color-accent #2563eb Primary brand color: buttons, selected date, links
--pbw-color-accent-hover #1d4ed8 Hover state for the primary button
--pbw-color-accent-soft #eff6ff Light tint used for hover backgrounds
--pbw-color-surface #ffffff Card background
--pbw-color-border #e5e7eb Default border color
--pbw-color-text #111827 Primary text color
--pbw-radius-lg 16px Card corner radius
--pbw-font system font stack Font family used throughout

Build your own UI

You do the rendering. We stay the backend and the infrastructure.

Node.js

TypeScript client, zero runtime dependencies (Node 18+'s native fetch). A distinct error class per error type, same taxonomy as the Python kit.

View on npm ↗
npm install @creno/node
import { CrenoClient, CrenoConflictError } from "@creno/node";

const client = new CrenoClient("pk_live_...");

const availability = await client.getAvailability({ from: "2026-08-01", to: "2026-08-07" });

try {
  const booking = await client.createBooking({
    startAt: availability.slots[0].startAt,
    customerName: "Jane Doe",
    customerEmail: "jane@example.com",
  });
} catch (err) {
  if (err instanceof CrenoConflictError) {
    // that slot was just taken, re-check availability and retry
  }
}

Python

A typed client for any Python backend: normal methods, plus a distinct exception per error type (slot taken, plan limit, too many requests...) instead of parsing status codes by hand.

View on PyPI ↗
pip install creno
from creno import CrenoClient

client = CrenoClient(api_key="pk_live_...")

availability = client.get_availability(from_="2026-08-01", to="2026-08-07")
booking = client.create_booking(
    start_at=availability.slots[0].start_at,
    customer_name="Jane Doe",
    customer_email="jane@example.com",
)

API reference

Both the widget and the script embed call this same REST API underneath. If you're building your own UI, or integrating from a backend (Python, Ruby, PHP, Go, whatever), you can call it directly, no client library required, just an HTTP client.

Authentication

Every request needs your publishable key in the X-API-Key header. That key identifies your account; the real protection is the origin allowlist you configure in the dashboard, which applies to requests coming from a browser (CORS). Server-side calls (Python, etc.) never go through a browser, so origin checking doesn't apply to them, still keep your key server-side and out of source control.

X-API-Key: pk_live_...

Base URL

https://api.crenoapp.com

Endpoints

GET /v1/public/service-types

Lists the active service types for your calendar. If it returns 0 or 1 results, your integration can skip this step entirely and omit serviceTypeId everywhere else.

Params Description
resourceId Optional. Omit for your default calendar.
[
  { "id": "...", "resourceId": "...", "name": "Haircut", "active": true, "sortOrder": 0 }
]
GET /v1/public/availability

Returns actually-open slots for a date range, after applying recurring hours, exceptions, and already-taken bookings.

Params Description
from* Start date, YYYY-MM-DD.
to* End date, YYYY-MM-DD.
resourceId Optional. Omit for your default calendar.
serviceTypeId Optional, if you use a service-type menu.
{
  "resourceId": "...",
  "timezone": "America/Toronto",
  "slots": [
    { "startAt": "2026-08-03T13:00:00.000Z", "endAt": "2026-08-03T13:30:00.000Z" }
  ]
}
POST /v1/public/bookings

Creates a booking. Re-validated server-side at write time (never trusts an earlier availability call alone), and protected against double-booking at the database level.

{
  "startAt": "2026-08-03T13:00:00.000Z",
  "customerName": "Jane Doe",
  "customerEmail": "jane@example.com",
  "customerPhone": "+1 514 555 0100",
  "notes": "First visit",
  "resourceId": null,
  "serviceTypeId": null,
  "lang": "en"
}

startAt, customerName, and customerEmail are required, everything else is optional. The response (201) is the created booking.

Rate limits

Endpoint Rate limits
POST /v1/public/bookings 5 / minute
GET /v1/public/service-types, /v1/public/availability 60 / minute

Error responses

Every error returns a JSON body with an error field. The ones that matter for integration:

Code Description
401 Missing or invalid X-API-Key header.
403 Origin not allowed for this account (see the origin allowlist in your dashboard).
404 No calendar configured for this account, or resource not found.
409 That slot was just taken by someone else, re-check availability and retry.
402 The account's plan hit a limit (bookings, calendars, or websites); the body includes limitType and plan.
429 Too many requests; the limit is enforced per API key, not per IP.

Any other language

No official kit for Ruby, Go, PHP, etc., the REST API itself is enough, with any HTTP client:

curl "https://api.crenoapp.com/v1/public/availability?from=2026-08-01&to=2026-08-07" \
  -H "X-API-Key: pk_live_..."

curl -X POST "https://api.crenoapp.com/v1/public/bookings" \
  -H "X-API-Key: pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"startAt":"2026-08-03T13:00:00.000Z","customerName":"Jane Doe","customerEmail":"jane@example.com"}'

Under the hood

  • Real calendar month view, not a flat list: pick a date, then a time.
  • Recurring schedules: weekly, bi-weekly, or "3rd Tuesday of the month" patterns.
  • Double-booking is prevented at the database level, tested under real concurrent load.
  • Optional service menus. A picker only appears when there's actually something to pick.