API

29 operations. Every one is also an MCP tool.

RESThttps://daybag.io/api/v1
MCPhttps://daybag.io/api/mcp
AuthAuthorization: Bearer daybag_live_…

Connect an agent

One line for Claude Code, one file for Cursor. Swap in a key from Dashboard → Developers.

claude code
claude mcp add --transport http daybag https://daybag.io/api/mcp --header "Authorization: Bearer daybag_live_…"
cursor · .cursor/mcp.json
{
  "mcpServers": {
    "daybag": {
      "type": "http",
      "url": "https://daybag.io/api/mcp",
      "headers": {
        "Authorization": "Bearer daybag_live_…"
      }
    }
  }
}

claude.ai and ChatGPT connect once OAuth sign-in lands.

Quickstart

Three calls: find an offering, pick a slot, book it.

  1. 1Find an offeringofferings_list
    curl
    curl "https://daybag.io/api/v1/offerings" \
      -H "Authorization: Bearer daybag_live_…"
    200response
    {
      "data": [
        {
          "id": "8f1c2e4a-0b7d-4c55-9a7e-3f2d1c0b9a88",
          "kind": "rental",
          "name": "Touring kayak",
          "duration_minutes": 1440,
          "capacity": 10,
          "max_per_booking": 4,
          "price_cents": 3500,
          "active": true
        }
      ]
    }
  2. 2Pick a slotavailability_get
    curl
    curl "https://daybag.io/api/v1/offerings/8f1c2e4a-0b7d-4c55-9a7e-3f2d1c0b9a88/availability?from=2026-12-05&to=2026-12-05" \
      -H "Authorization: Bearer daybag_live_…"
    200response
    {
      "timezone": "America/Denver",
      "data": [
        {
          "starts_at": "2026-12-05T15:00:00.000Z",
          "ends_at": "2026-12-06T15:00:00.000Z",
          "capacity": 10,
          "remaining": 8
        }
      ]
    }
  3. 3Book itbookings_create
    curl
    curl -X POST https://daybag.io/api/v1/bookings \
      -H "Authorization: Bearer daybag_live_…" \
      -H "Content-Type: application/json" \
      -d '{
        "offering_id": "8f1c2e4a-0b7d-4c55-9a7e-3f2d1c0b9a88",
        "starts_at": "2026-12-05T15:00:00Z",
        "quantity": 2,
        "customer": { "email": "pat@example.com", "name": "Pat" }
      }'
    201response
    {
      "id": "1e2d3c4b-5a69-4788-9a6b-5c4d3e2f1a0b",
      "reference": "0GWYRZ2V",
      "status": "confirmed",
      "starts_at": "2026-12-05T15:00:00+00:00",
      "ends_at": "2026-12-06T15:00:00+00:00",
      "quantity": 2,
      "total_cents": 7000,
      "offering": {
        "id": "8f1c2e4a-0b7d-4c55-9a7e-3f2d1c0b9a88",
        "name": "Touring kayak",
        "kind": "rental",
        "location": "North dock",
        "duration_minutes": 1440
      },
      "customer": {
        "id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
        "name": "Pat",
        "email": "pat@example.com",
        "phone": ""
      }
    }

Widget

One tag on any site. The booking flow renders in place, or behind a floating button.

html
<script src="https://daybag.io/widget.js" data-org="your-slug" async></script>

Options

data-orgYour booking page slug. Required.
data-offeringOpen straight to one offering.
data-modeinline (default) renders in place; button floats a pill that opens it.
data-labelThe button's text. Default "Book now".
data-targetCSS selector to render into, instead of next to the tag.

After a booking

window event · daybag:booked
window.addEventListener("daybag:booked", (event) => {
  const { org, reference } = event.detail; // e.g. "big-sky", "0GWYRZ2V"
});

Events and webhooks

Every change is an event. Poll for them, or have them pushed to your server, signed.

booking.heldA seat is held while the guest pays; it expires at expires_at
booking.confirmedA booking is confirmed, from any surface and any path
booking.cancelledA booking is cancelled
session.createdA class session is scheduled
session.cancelledA class session is cancelled, with its bookings
offering.createdAn offering is added
offering.updatedAn offering changes or is archived
offering.deletedAn offering that was never booked is deleted
offering.hours_updatedWeekly hours are replaced
customer.updatedA customer is added or edited
customer.deletedA customer's personal data is erased on request
org.updatedThe org's name, time zone or currency changes

A paid booking is held first (booking.held) and confirmed when the payment lands; a free one confirms at once. Every event carries a source: dashboard, api, mcp, widget or stripe.

Poll

curl · then after=<next_after>
curl "https://daybag.io/api/v1/events?after=0" \
  -H "Authorization: Bearer daybag_live_…"
200response
{
  "data": [
    {
      "id": 42,
      "type": "booking.confirmed",
      "data": {
        "id": "1e2d3c4b-5a69-4788-9a6b-5c4d3e2f1a0b",
        "reference": "0GWYRZ2V",
        "status": "confirmed"
      },
      "created_at": "2026-12-01T17:04:11+00:00"
    }
  ],
  "next_after": 42
}

Webhooks

Add an HTTPS endpoint with webhooks_create. Each event arrives as a JSON POST signed with the secret it returns once:

Daybag-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
  1. Keep the raw request body; verify before you parse it.
  2. Split the header at the commas: t is a unix time, v1 the signature.
  3. HMAC-SHA256 the string "<t>.<raw body>" with your webhook secret, hex encoded, and compare it to v1 in constant time.
  4. Reject a t more than five minutes away, so a captured request can't be replayed.
node
import { createHmac, timingSafeEqual } from "node:crypto";

// body: the raw request body · header: the Daybag-Signature header
function verify(secret, body, header) {
  const { t, v1 = "" } = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // missing, stale or replayed
  const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Reference

offerings7

GET
/offeringsList offerings (classes, appointments and rentals), including archived ones
offerings_list
POST
/offeringsCreate an offering. Only kind and name are required; everything else has defaults per kind. Appointments and rentals open daily 9:00–17:00 (org time) until you set hours; classes need sessions.
offerings_create
GET
/offerings/{id}Get one offering
offerings_get
PATCH
/offerings/{id}Update an offering. Set active: false to archive it.
offerings_update
DELETE
/offerings/{id}Delete an offering that has never been booked (otherwise archive it)
offerings_delete
GET
/offerings/{id}/hoursWeekly opening hours of an appointment or rental, in the org's local time
offerings_get_hours
PUT
/offerings/{id}/hoursReplace weekly opening hours, e.g. [{ weekday: 6, start_time: '08:00', end_time: '16:00' }]
offerings_set_hours

availability1

GET
/offerings/{id}/availabilityBookable slots with remaining capacity between two local dates (default: the next 14 days)
availability_get

sessions3

GET
/sessionsList class sessions with the number of seats booked
sessions_list
POST
/sessionsSchedule a class session; repeat_weeks repeats it weekly at the same local time
sessions_create
POST
/sessions/{id}/cancelCancel a class session and every booking in it
sessions_cancel

bookings5

GET
/bookingsList bookings filtered by time range, status, offering, customer or session (order=desc for newest first)
bookings_list
GET
/bookings/{id}Get one booking with its offering and customer
bookings_get
POST
/bookingsBook a slot from availability.get (classes need session_id; appointments and rentals need starts_at). Pass hold_minutes to hold it instead, and idempotency_key so retries never book twice.
bookings_create
POST
/bookings/{id}/cancelCancel a booking or a hold and free its capacity (safe to repeat)
bookings_cancel
POST
/bookings/{id}/confirmConfirm a held booking (safe to repeat). A hold past its expires_at confirms only if its place is still free.
bookings_confirm

customers5

GET
/customersList customers, newest first; q searches name, email and phone
customers_list
GET
/customers/{id}Get a customer with their bookings
customers_get
POST
/customersCreate a customer, or update the given fields when the email already exists
customers_upsert
PATCH
/customers/{id}Update a customer's name, phone, notes or metadata
customers_update
DELETE
/customers/{id}Erase a customer's personal data (name, email, phone, notes) on request; their bookings stay, anonymized
customers_delete

org3

GET
/orgYour organization: name, slug (booking page /book/{slug}), time zone and currency
org_get
PATCH
/orgRename the organization or change its time zone (IANA, e.g. America/Denver) or currency
org_update
GET
/org/exportExport all your data as JSON: org, offerings, hours, sessions, customers, bookings, webhooks and the last 1000 events
org_export

events1

GET
/eventsEverything that happened, oldest first. Poll with after=<next_after> to follow along.
events_list

webhooks3

GET
/webhooksList webhook endpoints and their last delivery status
webhooks_list
POST
/webhooksSend events to an HTTPS URL, signed with HMAC-SHA256. Returns the signing secret once.
webhooks_create
DELETE
/webhooks/{id}Stop sending events to a webhook endpoint
webhooks_delete

billing1

GET
/billingYour plan (free or pro) and this month's confirmed bookings against the free plan's soft cap of 25
billing_get