G uid ly

Guidly API

Version 1 · Last updated: August 13, 2026

The Guidly API lets you read your bookings, clients and payments, and receive real-time webhooks when things happen. It is what the Guidly integration for Zapier is built on, and it is equally usable from Make, n8n, or your own code.

API access requires a Guidly Pro plan. Every endpoint is scoped to the provider who owns the API key — there is no way to reach another provider's data.

On this page

Authentication

All requests are authenticated with an API key. Create one in the Guidly dashboard under Integrations → API & Zapier.

Guidly stores only a SHA-256 hash of your key, so it is displayed once, at creation, and cannot be retrieved afterwards. If you lose it, revoke it and create another. Revocation takes effect immediately — every request checks it.

Send the key on every request, in either header:

Authorization: Bearer gdly_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: gdly_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The base URL for all endpoints is:

https://api.guidly.ca/api/v1

To check a key works, call GET /me:

curl https://api.guidly.ca/api/v1/me \
  -H "Authorization: Bearer $GUIDLY_API_KEY"
{
  "id": "d957fd57-8574-40a5-a513-795ec33456e5",
  "email": "you@example.com",
  "first_name": "Wayne",
  "last_name": "Shimoon",
  "full_name": "Wayne Shimoon",
  "plan": "pro",
  "profile_url": "https://guidly.ca/wayne-shimoon",
  "connection_label": "Wayne Shimoon"
}

Errors

Errors return a JSON body with an error field describing what went wrong.

StatusMeaning
400The request was malformed — a missing required field, an unknown event name, or a non-HTTPS webhook URL.
401No API key was sent, or the key is invalid or revoked.
403The key is valid but the account is not on a Pro plan.
404The resource does not exist, or belongs to another provider.
429Rate limited. See Rate limits.
{ "error": "API access requires a Guidly Pro plan.", "upgrade_url": "https://guidly.ca" }

Endpoints

Account

GET /me

Returns the provider the API key belongs to. Useful as a connection test.

GET /events

Returns the list of event names you can subscribe to.

{ "events": ["booking.created", "booking.confirmed", "booking.cancelled",
             "booking.rescheduled", "payment.received", "client.created"] }

Bookings

GET /bookings

Your bookings, most recent session date first.

ParameterDescription
limit1–100. Defaults to 25.
statusFilter by booking status, e.g. pending, confirmed, cancelled.
payment_statusFilter by payment status: none, unpaid, paid, failed.
curl "https://api.guidly.ca/api/v1/bookings?status=confirmed&limit=5" \
  -H "Authorization: Bearer $GUIDLY_API_KEY"
{ "bookings": [ { /* booking object */ } ] }
GET /bookings/:id

A single booking. Returns 404 if it belongs to another provider.

Payments

GET /payments

Bookings that have been paid for. Same object shape as /bookings, filtered to payment_status: "paid". Accepts limit.

{ "payments": [ { /* booking object */ } ] }

Clients

GET /clients

Your clients — both people you added directly and anyone who has booked you. Accepts limit.

{ "clients": [ { /* client object */ } ] }
POST /clients

Add a client. Either email or phone is required, along with first_name.

This endpoint is match-or-create: if someone already exists with the same email (or, when no email is given, the same phone), that client is returned instead of a duplicate being made. The created field tells you which happened, so a job that re-runs over the same source data will not produce duplicates.

curl -X POST https://api.guidly.ca/api/v1/clients \
  -H "Authorization: Bearer $GUIDLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"Dana","last_name":"Whitfield","email":"dana@example.com"}'
{ "created": true, "client": { /* client object */ } }

Clients created through this endpoint deliberately do not fire the client.created webhook. That prevents an automation which creates clients and an automation which listens for them from triggering each other in a loop.

Webhooks

Rather than polling, subscribe a URL and Guidly will POST to it when something happens. Delivery is typically within a second.

POST /webhooks

Subscribe. Requires event and target_url. The URL must be absolute and https://.

curl -X POST https://api.guidly.ca/api/v1/webhooks \
  -H "Authorization: Bearer $GUIDLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"booking.confirmed","target_url":"https://example.com/hooks/guidly"}'
{
  "id": "7b88b22c-035b-41e9-93f8-ae5ae65ce097",
  "event": "booking.confirmed",
  "target_url": "https://example.com/hooks/guidly",
  "secret": "0c35f88254c4b9b9a47e352f263573ae..."
}

Keep the secret — it is what you verify signatures with. Subscribing the same event and URL twice is idempotent: you get the original subscription back rather than a duplicate.

DELETE /webhooks/:id

Unsubscribe. Returns 204. Deleting a subscription that no longer exists is also a success.

GET /webhooks

List your subscriptions, including their health.

{
  "webhooks": [
    {
      "id": "7b88b22c-035b-41e9-93f8-ae5ae65ce097",
      "event": "booking.confirmed",
      "target_url": "https://example.com/hooks/guidly",
      "source": "api",
      "created_at": "2026-08-13T14:52:10.442Z",
      "last_delivery_at": "2026-08-13T14:55:26.751Z",
      "failure_count": 0,
      "disabled_at": null
    }
  ]
}

Delivery, retries and failures

Deliveries are not strictly ordered, and a retry can arrive after a later event. Use the X-Guidly-Delivery header to make your handler idempotent if a duplicate would cause a problem.

Events and payloads

EventFires when
booking.createdA client books a session. Fires when the booking is made — for paid or approval-required sessions, this is before it is confirmed.
booking.confirmedA session is confirmed, either approved by the provider or paid for by the client. The meeting link is populated by this point.
booking.cancelledA session is cancelled by either party.
booking.rescheduledA session moves to a new time. starts_at carries the new time.
payment.receivedA session is paid for, by card or recorded as paid by the provider. Delivered alongside booking.confirmed when payment is what confirmed it.
client.createdA provider adds a client in Guidly. Not fired for clients created via POST /clients.

Every webhook body has the same envelope. data holds a booking object for the booking and payment events, and a client object for client.created.

{
  "event": "booking.confirmed",
  "created_at": "2026-08-13T14:55:26.751Z",
  "data": { /* booking or client object */ }
}

Headers

HeaderValue
X-Guidly-EventThe event name, e.g. booking.confirmed.
X-Guidly-DeliveryA unique id for this delivery attempt. Use it to deduplicate.
X-Guidly-Signaturesha256=<hex digest> — see below.
User-AgentGuidly-Webhooks/1.0

Verifying signatures

Every delivery is signed with the subscription's secret using HMAC-SHA256 over the raw request body. Verify it before trusting a payload — your webhook URL is reachable by anyone who learns it.

Compute the digest over the raw bytes you received, before any JSON parsing. Re-serialising the parsed object produces different bytes and the signature will not match.

// Node.js / Express
const crypto = require('crypto');

app.post('/hooks/guidly',
  express.raw({ type: 'application/json' }),   // raw body, not express.json()
  (req, res) => {
    const expected = 'sha256=' + crypto
      .createHmac('sha256', process.env.GUIDLY_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    const received = req.get('x-guidly-signature') || '';

    // Constant-time compare, so a mismatch cannot be found by timing.
    const ok = expected.length === received.length &&
      crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));

    if (!ok) return res.status(401).end();

    const { event, data } = JSON.parse(req.body);
    res.status(200).end();          // acknowledge first
    handle(event, data);            // then do the work
  });
# Python / Flask
import hmac, hashlib
from flask import request

@app.post("/hooks/guidly")
def guidly_hook():
    expected = "sha256=" + hmac.new(
        SECRET.encode(), request.get_data(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, request.headers.get("X-Guidly-Signature", "")):
        return "", 401

    payload = request.get_json()
    return "", 200

Object reference

Booking

FieldTypeNotes
idstringUUID. Stable primary key — use it to deduplicate.
statusstringpending, confirmed, or cancelled.
payment_statusstringnone (free session), unpaid, paid, or failed.
starts_atstringISO 8601, UTC.
timezonestringIANA zone the client booked in, e.g. America/Toronto. Use it to render a local time.
duration_hoursnumberMay be fractional, e.g. 0.5.
duration_minutesintegerThe same value in minutes, for convenience.
topicstringSession title.
meeting_modestringzoom, teams, or in_person.
meeting_linkstringJoin URL. Empty until confirmed, and always empty for in-person sessions.
pricenumberTotal for the session, not an hourly rate. 0 for a free session.
currencystringISO code, e.g. CAD. Do not assume — providers can bill in more than one.
paid_viastringstripe, offline, or empty.
clientobjectNested client object.
providerobjectThe provider: id, name, email, specialty, timezone, profile URL.
client_namestringFlat duplicate of client.full_name.
client_emailstringFlat duplicate of client.email.
provider_namestringFlat duplicate of provider.full_name.
dashboard_urlstringDeep link to the booking in the Guidly dashboard.
{
  "id": "9f1c2b7e-4a3d-4e51-8b6f-2c9d7a1e5b30",
  "status": "confirmed",
  "payment_status": "paid",
  "starts_at": "2026-09-04T18:00:00.000Z",
  "timezone": "America/Toronto",
  "duration_hours": 1,
  "duration_minutes": 60,
  "topic": "Session with Wayne Shimoon",
  "meeting_mode": "zoom",
  "meeting_link": "https://us02web.zoom.us/j/85512349876",
  "price": 120,
  "currency": "CAD",
  "paid_via": "stripe",
  "client": {
    "id": "3b7d1f92-6c48-4a20-9e15-7d4c8a2f6b11",
    "first_name": "Dana",
    "last_name": "Whitfield",
    "full_name": "Dana Whitfield",
    "email": "dana.whitfield@example.com",
    "phone": "+16135550142",
    "timezone": "America/Toronto",
    "created_at": "2026-06-12T14:22:05.000Z"
  },
  "provider": {
    "id": "7b47321f-f007-4506-8ffe-62bad8eb264e",
    "first_name": "Wayne",
    "last_name": "Shimoon",
    "full_name": "Wayne Shimoon",
    "email": "wayne@example.com",
    "specialty": "Business coaching",
    "timezone": "America/Toronto",
    "profile_url": "https://guidly.ca/wayne-shimoon"
  },
  "client_name": "Dana Whitfield",
  "client_email": "dana.whitfield@example.com",
  "provider_name": "Wayne Shimoon",
  "dashboard_url": "https://guidly.ca/?booking=9f1c2b7e-4a3d-4e51-8b6f-2c9d7a1e5b30"
}

Client

FieldTypeNotes
idstringUUID. May be null on a booking whose client record has since been removed.
first_namestring
last_namestringMay be empty.
full_namestring
emailstringMay be empty — a client can be created with a phone number only.
phonestringMay be empty.
timezonestringIANA zone, may be empty.
created_atstringISO 8601.

Rate limits and versioning

Requests are limited to 200 per 15 minutes per IP address across the Guidly API. Exceeding it returns 429. Webhooks are the intended way to react to events — polling /bookings on a short interval will hit the limit and lag behind real-time delivery anyway.

The version is in the path. Fields will be added to responses over time, so parse defensively and ignore what you do not recognise. Existing fields will not be renamed or removed within v1; a breaking change would ship as /api/v2 with v1 continuing to work.

Using Zapier instead

If you would rather not write code, the Guidly integration for Zapier exposes all six events as triggers and POST /clients as an action, letting you connect Guidly to QuickBooks, Google Sheets, Mailchimp and thousands of other tools without a server of your own. Create an API key under Integrations → API & Zapier and paste it when Zapier asks you to connect your account.

Questions

Guidly Booking Inc.

Ottawa, Ontario, Canada

API support: support@guidly.ca