Developer documentation

Build on Orkestr

Read or write offerings, bookings, coupons, and availability over a Bearer-authenticated REST API. Subscribe to HMAC-signed webhooks for state-change events. Drop a one-line booking widget into any site.

1. Get an API key

Provider portal → API keys. The full token is shown once; copy it somewhere safe.

2. Make your first call

curl /v1/api/me with the Bearer header to verify auth, then list /v1/api/offerings.

3. Subscribe to webhooks

Provider portal → Webhooks. Pick events, save the signing secret, verify HMAC.

4. Embed a booking widget

One-line script; renders a Book-now button that opens an iframe modal.

Quick start

The base URL is https://api.orkestr.in/v1. Every request carries Authorization: Bearer ork_<prefix>.<secret>.

curl https://api.orkestr.in/v1/api/me \
  -H "Authorization: Bearer ork_a1b2c3d4.deadbeefdeadbeef…"
{
  "tenant": {
    "id": "tnt_…",
    "name": "Atlas Studios",
    "subdomain": "atlas",
    "displayCurrency": "USD"
  },
  "scopes": ["read", "write"],
  "keyId": "key_…"
}

Scopes

Three scopes today — keys default to read + write. Resources are tenant-scoped server-side; you can't see or mutate another tenant's data regardless of scope.

Endpoint reference (v1)

MethodPathScopeDescription
GET/v1/api/mereadIdentity check (use as a ping)
GET/v1/api/offeringsreadList published offerings
GET/v1/api/offerings/:idOrSlugreadGet one offering
POST/v1/api/offeringswriteCreate an offering
PATCH/v1/api/offerings/:idwriteUpdate an offering
DELETE/v1/api/offerings/:idwriteSoft-delete an offering
GET/v1/api/bookingsreadList bookings (?status, ?limit)
GET/v1/api/bookings/:idreadGet one booking
POST/v1/api/bookings/:id/approvewriteApprove a PENDING booking → fires booking.confirmed
POST/v1/api/bookings/:id/cancelwriteCancel a booking → fires booking.cancelled
PATCH/v1/api/bookings/:idwriteReschedule item + edit notes → fires booking.rescheduled
GET/v1/api/couponsreadList coupons
POST/v1/api/couponswriteCreate a coupon
PATCH/v1/api/coupons/:idwriteUpdate a coupon
DELETE/v1/api/coupons/:idwriteDelete a coupon (soft)
GET/v1/api/availabilityreadAvailability rows for an offering

Provider lifecycle

The end-to-end story of running a tenant on Orkestr — from first sign-in to running a mature operation. Each step links to the feature reference and the relevant API surface.

1

Sign up & pick a subdomain

A new provider creates an account and is assigned a subdomain (e.g. acme.orkestr.in). The subdomain becomes their storefront URL, their portal URL, and the host their API requests resolve under.

  • /provider — provider portal home
2

Configure brand, currency, and (optionally) custom domain

Set logo + brand colour, pick a storefront template, choose displayCurrency, write announcement banners. Map a custom domain via CNAME — DNS verification runs every 10 min and flips the status to VERIFIED automatically.

  • /provider/branding
  • /provider/domain
3

Submit KYB

Upload business docs + attestations (insurance, deposit policy, terms). Platform admin reviews and approves; tenant earns the 'verified' badge that appears on storefronts. Webhook fires when KYB completes so your marketing site can light up the badge automatically.

  • /provider/kyb
  • Webhook: tenant.kyb_verified
4

Build your catalogue

Create offerings — manually in the portal, via CSV bulk import, or via POST /v1/api/offerings looped over your existing PMS / ERP. Set typed attributes, base price, tax-bps, cancellation policy. Save as DRAFT until ready, then PUBLISH.

  • /provider/offerings
  • POST /v1/api/offerings
  • PATCH /v1/api/offerings/:id
5

Define availability

For each offering, declare a recurring weekly schedule — day-of-week, start/end time, slot minutes, capacity. Add exceptions for blackout days and special hours without touching the base schedule.

  • /provider/availability
  • GET /v1/api/availability
6

Run promotions

Issue PERCENT or FIXED coupons with validity windows, redemption caps, and minimum-subtotal thresholds. Soft-deletion preserves audit trails when a campaign ends.

  • /provider/coupons
  • POST /v1/api/coupons
7

Take bookings

Customers book via the storefront or your embedded widget. Pending bookings land in /provider/bookings. Approve to confirm (or auto-approve via API for VIP customers). Cancel runs the offering's policy and computes refundPercent. Reschedule blocks the new slot and frees the old one.

  • /provider/bookings
  • POST /v1/api/bookings/:id/approve
  • POST /v1/api/bookings/:id/cancel
  • PATCH /v1/api/bookings/:id
8

Wire integrations

Issue an API key (read+write by default), subscribe webhooks for booking.* / dispute.* / review.* events, drop the embed widget on your existing marketing site. Most integrations take an afternoon.

  • /provider/api-keys
  • /provider/webhooks
  • /provider/embed
9

Handle exceptions: disputes, reviews, refunds

When a customer opens a dispute, it lands in /provider/disputes as a threaded conversation. Reply, upload evidence URLs, escalate to platform admin if needed. Reviews land automatically after a COMPLETED booking; reply once per review.

  • /provider/disputes
  • /provider/reviews
  • Webhook: dispute.opened
  • Webhook: review.created
10

Watch the metrics

The performance score (0–1, recomputed nightly: 0.5 × rating/5 + 0.3 × responseRate + 0.2 × confirmationRate) drives marketplace ranking. /provider/insights shows the breakdown so you know which lever to pull.

  • /provider/insights
11

Grow the team

Invite teammates as OWNER / ADMIN / STAFF / CLIENT. Each role gets a different slice of the portal — STAFF can confirm bookings but not change pricing; CLIENT sees only the customer-facing surface.

  • /provider/members

Customer journey

What the booker sees end-to-end. The storefront UI is rendered by Orkestr but appears under the tenant's subdomain (or custom domain) and is themed with their brand — most customers never know Orkestr is underneath.

1

Land on the storefront

Customer arrives at acme.orkestr.in or book.acme.com. Sees the tenant's branding, announcement banner, featured offerings, and the verified-business badge if KYB completed. Storefront content renders in the customer's locale via Accept-Language.

2

Discover an offering

Browse the catalogue, filter by category / attributes / price, or use natural-language search ('quiet space for 4 people downtown next Tuesday morning'). Each card shows photos, aggregate rating, and a Book button.

  • Storefront list view (public, no auth)
3

Open the offering page

Full description (auto-translated), photo gallery, amenities, cancellation policy, recent reviews, and a live availability calendar. Slots overlapping confirmed bookings are auto-hidden — the calendar always reflects current state.

  • Storefront detail view
4

Pick a slot

Customer chooses a date + time slot. The slot is held briefly while they fill out the form; if abandoned, it returns to the pool.

5

Apply a coupon (optional)

Enters a promo code. The platform validates code, validity window, max-redemptions, and minimum-subtotal server-side, then computes the discounted total before payment.

6

Check out

Customer enters contact + payment details. The booking lands in PENDING (or CONFIRMED if the offering is auto-approve + payment lands). A risk score (0–100) is computed at create time; high-risk bookings get held for review instead of confirming straight through.

7

Get confirmation

On status flip to CONFIRMED, the storefront shows a receipt. Provider receives the booking.confirmed webhook. Customer can return to /bookings on the storefront to see their list of upcoming bookings any time.

  • Webhook: booking.confirmed
8

Reschedule or cancel

From the customer's bookings page (if the offering's policy allows), reschedule into another slot or cancel. The cancellation policy decides refundPercent automatically — no negotiation, no provider round-trip.

  • Webhook: booking.rescheduled
  • Webhook: booking.cancelled
9

Show up & experience the service

The booking moves to COMPLETED automatically after the slot ends (unless cancelled). At this point the customer becomes a verified buyer — eligible to leave a review.

10

Leave a review

1–5 stars + optional body. Auto-translated into every enabled locale. The aggregate rating updates the offering's surface and feeds into the tenant's performance score.

  • Webhook: review.created
11

Open a dispute (if needed)

If something went wrong, customer opens a dispute on the booking with a reason and evidence URLs. Threaded conversation between customer + provider, with platform admin available to resolve refund-vs-keep-payment outcomes.

  • Webhook: dispute.opened

Platform features

A tour of every product surface — what it does, who uses it, what's available over the API or as a webhook, and a worked example for each.

Offerings catalog

Provider POV

The set of things you sell — rooms, rides, classes, equipment. Each offering has a title, slug, description, status (DRAFT / PUBLISHED / ARCHIVED), product type (e.g. space, taxi, service), categories, and typed attributes.

Customer POV

What shows up on the storefront — searchable, filterable, with photos, reviews, and a live availability calendar.

API surface

  • GET /v1/api/offerings
  • GET /v1/api/offerings/:idOrSlug
  • POST /v1/api/offerings (write)
  • PATCH /v1/api/offerings/:id (write)
  • DELETE /v1/api/offerings/:id (write)

Webhook events

Use case

Sync inventory from your existing system

A property-management system pushes new rooms into Orkestr nightly. The CMS calls POST /offerings for new listings and PATCH /offerings/:id when prices or descriptions change.

# create a new offering
curl https://api.orkestr.in/v1/api/offerings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Soho Loft",
    "slug": "soho-loft",
    "productTypeKey": "space",
    "basePriceCents": 12000,
    "currency": "USD",
    "attributes": { "sqft": 720, "amenities": ["wifi","kitchen"] },
    "status": "PUBLISHED"
  }'

Pricing

Provider POV

Pick a model — HOURLY / DAILY / ONE_TIME / SUBSCRIPTION. Set a base price, a tax-rate-bps, and (in the provider portal) optional dynamic-pricing rules that bump prices for peak periods or discount slow ones.

Customer POV

Sees the final, fully-resolved price during checkout, including discounts, applied coupons, and tax.

API surface

  • Pricing fields are part of an offering — set on POST/PATCH /v1/api/offerings.
  • Dynamic pricing rules are not exposed in v1 — configure in the provider portal.

Webhook events

Use case

Bump weekend prices by 20%

Manage one-off price changes via PATCH; for recurring weekday/weekend differentials use the dynamic-pricing UI.

curl -X PATCH https://api.orkestr.in/v1/api/offerings/$OFFERING_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "basePriceCents": 14400 }'

Availability

Provider POV

Define a recurring weekly schedule per offering (day-of-week, start/end time, slot minutes, capacity, validity window). Add exceptions (BLOCK holidays, OPEN special hours) without touching the base schedule.

Customer POV

Sees a calendar with bookable slots. Slots that overlap confirmed bookings are auto-hidden.

API surface

  • GET /v1/api/availability?offeringId=…&from=…&to=…

Webhook events

Use case

Pull the next 7 days of slots into your scheduling app

Read the recurring rules + apply your own exception logic (booked / blocked) client-side.

curl "https://api.orkestr.in/v1/api/availability?offeringId=$ID&from=2026-05-01&to=2026-05-08" \
  -H "Authorization: Bearer $TOKEN"

Bookings

Provider POV

Receive booking requests, approve / decline pending ones, reschedule, cancel. The state machine — PENDING → CONFIRMED → COMPLETED | CANCELLED | REFUNDED — is enforced server-side. Cancelling runs the offering's cancellation policy and computes the refund% automatically.

Customer POV

Pick an offering, choose a slot, optionally apply a coupon, check out. Track booking status, reschedule (if the offering allows), or cancel from their account.

API surface

  • GET /v1/api/bookings — list (?status, ?limit)
  • GET /v1/api/bookings/:id — full detail with items + payments
  • POST /v1/api/bookings/:id/approve (write)
  • POST /v1/api/bookings/:id/cancel (write)
  • PATCH /v1/api/bookings/:id (write) — reschedule or edit notes

Webhook events

  • booking.confirmed — fires on approve() and on Stripe payment success
  • booking.cancelled — fires on cancel(), with refundPercent in payload
  • booking.rescheduled — fires when an item's start/end changes

Use case

Approve high-trust bookings automatically from your CRM

Subscribe to booking.confirmed and your CRM bumps customer LTV. Subscribe to a future booking.created event to auto-approve customers in your VIP segment via POST /approve.

# approve a pending booking
curl -X POST https://api.orkestr.in/v1/api/bookings/$BOOKING_ID/approve \
  -H "Authorization: Bearer $TOKEN"

# reschedule a booking item
curl -X PATCH https://api.orkestr.in/v1/api/bookings/$BOOKING_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "itemId": "bki_…", "startsAt": "2026-05-12T15:00:00Z", "endsAt": "2026-05-12T17:00:00Z" }'

Coupons & promotions

Provider POV

Create promo codes with a percent or flat-amount discount, validity window, max-redemptions cap, and a minimum-subtotal threshold. Soft-delete (trash + restore) keeps audit trails clean.

Customer POV

Enters the code at checkout. The platform validates it server-side and computes the discount before payment.

API surface

  • GET / POST / PATCH / DELETE /v1/api/coupons

Webhook events

Use case

Issue a one-off campaign code that expires in 30 days

A marketing tool issues SUMMER25 → loops over coupons two weeks before validUntil to send reminder emails.

curl https://api.orkestr.in/v1/api/coupons \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "SUMMER25",
    "kind": "PERCENT",
    "amount": 25,
    "validUntil": "2026-06-30T23:59:59Z",
    "maxRedemptions": 500,
    "minSubtotalCents": 5000
  }'

Reviews & ratings

Provider POV

Receive verified-buyer reviews (only customers with a CONFIRMED or COMPLETED booking on the offering can post). Reply once per review. Storefront shows the badge + aggregate rating.

Customer POV

After a booking completes, leave a 1–5 star rating + optional body. Auto-translated to other locales for cross-language storefronts.

API surface

  • No public REST endpoints in v1 — moderation lives in the provider portal.
  • The aggregate rating + review count appear inside offering responses.

Webhook events

  • review.created — { reviewId, offeringId, rating, authorId }

Use case

Auto-publish 5-star reviews to your homepage

A webhook handler filters review.created where rating === 5 and POSTs to your CMS as featured content.

// Express handler — verify signature first (see Webhooks section)
app.post('/orkestr/webhook', (req, res) => {
  if (!verify(req.rawBody, req.header('X-Orkestr-Signature'), SECRET))
    return res.sendStatus(400);
  if (req.body.event === 'review.created' && req.body.data.rating === 5) {
    cms.publishFeaturedReview(req.body.data);
  }
  res.sendStatus(200);
});

Disputes

Provider POV

Handle customer complaints with a threaded conversation per dispute. Upload evidence URLs. Platform admin resolves with refund or keep-payment, with a written note that the customer sees.

Customer POV

Open a dispute on a booking with a reason and optional evidence URLs. Reply in-thread.

API surface

  • No public REST endpoints in v1 — manage in the dispute centre.

Webhook events

  • dispute.opened — { disputeId, bookingId, openedById, reason }

Use case

Forward dispute notifications to a Zendesk queue

A webhook handler creates a Zendesk ticket the moment a dispute opens — no engineer-in-the-middle delay.

if (req.body.event === 'dispute.opened') {
  await zendesk.tickets.create({
    subject: `Dispute on booking ${req.body.data.bookingId.slice(0,8)}`,
    body: req.body.data.reason,
    requesterId: req.body.data.openedById,
  });
}

Trust & safety: KYB + risk scoring

Provider POV

Submit business documents + attestations (insurance, terms, deposit policy) for a verified-business badge. Platform admin reviews; status goes PENDING → VERIFIED | REJECTED. Bookings get a risk score (0–100) at create time; flagged ones land in a review queue.

Customer POV

Sees the verified badge on storefronts that have it. Bookings are blocked or held for review when they trip the risk score.

API surface

  • Risk fields appear in GET /v1/api/bookings/:id (riskScore, riskReviewStatus).
  • KYB submission is provider-portal-only.

Webhook events

  • tenant.kyb_verified — { tenantId, verifiedAt }

Use case

Light up a 'verified' badge on your homepage when KYB completes

The webhook handler flips a feature flag in your CMS the moment KYB is approved.

if (req.body.event === 'tenant.kyb_verified') {
  await cms.setFlag('verified_badge', true);
}

Branding & custom domains

Provider POV

Pick a storefront template, set a brand colour + logo, write announcements + sale badges. Connect a custom domain (e.g. book.acme.com) via CNAME — DNS verification runs every 10 min.

Customer POV

Sees the tenant's brand and domain — they may not realise Orkestr is underneath.

API surface

  • Read-only via GET /v1/api/me (returns subdomain, displayCurrency, etc.). Branding lives in the provider portal.

Webhook events

Use case

Pull current branding into a marketing site

A landing-page builder fetches /me to grab the logo + brand colour and inject them server-side.

curl https://api.orkestr.in/v1/api/me \
  -H "Authorization: Bearer $TOKEN"

Team & access control

Provider POV

Invite teammates as OWNER / ADMIN / STAFF / CLIENT. Each role gets a different slice of the provider portal. Each member also gets a personal calendar token for iCal subscriptions.

Customer POV

N/A — provider-only.

API surface

  • No public REST endpoints in v1 — invitations + role changes live in the provider portal.

Webhook events

Use case

Provision team members from your IdP

Today: invite teammates from /provider/members — set role + send invite link. On the roadmap: SCIM-style member endpoints so Okta / Google Workspace can push joiners + leavers automatically. Today, write a tiny shim: when your IdP fires an off-boarding event, your shim opens the portal via headless session and revokes; the personal calendar token revokes alongside the member.

// Pseudo: react to your IdP's off-boarding webhook.
// While SCIM is on the roadmap, you can scope an API key to 'admin'
// and call provider endpoints from your own automation.
onIdpOffboard(async (user) => {
  await orkestr.members.deactivate(user.email);
  // Their personal calendar token is invalidated as part of deactivation.
});

Notifications

Provider POV

In-app notification bell shows BOOKING_CONFIRMED, BOOKING_CANCELLED, BOOKING_RESCHEDULED, DISPUTE_OPENED, DISPUTE_MESSAGE, REVIEW_LEFT, COUPON_REDEEMED, TENANT_APPROVED, GENERIC. Click-through deep-links to the related entity.

Customer POV

Same in-app bell for client users (booking updates, dispute replies).

API surface

  • No public REST endpoints in v1 — listen via webhooks for the same events instead.

Webhook events

  • See booking.* / dispute.* / review.created / tenant.kyb_verified.

Use case

Mirror the bell to a Slack channel

Subscribe to the relevant webhook events; post a Slack message keyed off X-Orkestr-Delivery-Id for idempotency. Now your front-of-house staff sees new bookings in #ops without logging into the portal.

// Express handler — verify HMAC first (omitted), then route to Slack.
const seen = new Set<string>(); // swap for Redis in prod

app.post('/orkestr/webhook', async (req, res) => {
  const id = req.header('X-Orkestr-Delivery-Id')!;
  if (seen.has(id)) return res.sendStatus(200); // idempotent retry
  seen.add(id);

  const { event, data } = req.body;
  if (event === 'booking.confirmed') {
    await slack.chat.postMessage({
      channel: '#ops',
      text: `✅ ${data.offeringTitle} — $${(data.totalCents/100).toFixed(2)}`,
    });
  }
  res.sendStatus(200);
});

Multi-language content

Provider POV

Write offering titles + descriptions, coupons, reviews, tenant about pages once. The translation worker fans them out into every enabled locale (BCP-47 codes). Manually override any string when the auto-translation isn't right.

Customer POV

Storefront automatically renders in their locale; falls back to source language if a translation is missing.

API surface

  • API responses surface the source-language fields. Per-locale content is injected server-side based on the request locale on the storefront.

Webhook events

Use case

Detect locale from your own UI and pass it through

Set the request's Accept-Language header on storefront proxies; the marketing site picks the right translation when injecting content. New offerings get fanned out to every enabled locale within ~30s by the translation worker — no manual step.

# The storefront resolves the right locale from Accept-Language.
# Pass a target locale to your reverse-proxy and Orkestr renders accordingly.
curl https://acme.orkestr.in/o/soho-loft \
  -H "Accept-Language: es-ES,es;q=0.9"

Embed widget

Provider POV

Generate a one-line <script> tag in the provider portal. Paste it on any external site to show a Book-now button that opens an iframe modal handling the full booking flow.

Customer POV

Click the embedded button on the tenant's existing website; complete booking + payment without leaving the page.

API surface

  • Generated by /provider/embed in the portal — uses public listing routes underneath.

Webhook events

Use case

Drop a booking button on a Squarespace landing page

Provider copies the snippet from /provider/embed into a Code Block; book-now button renders immediately.

<script
  src="https://orkestr.in/embed/v1/widget.js"
  data-tenant="acme"
  data-offering="soho-loft"
  data-label="Book now"
  data-color="#0F172A"
  async
></script>

Calendar feed

Provider POV

Subscribe a personal iCal URL in Google Calendar / Outlook / Apple Calendar. Bookings show up as events with the booker's name + offering. The token is opaque per-user; resetting it invalidates all subscriptions.

Customer POV

Optional iCal URL for client users to track their own upcoming bookings in their personal calendar.

API surface

  • iCal endpoint is public-but-opaque (token-gated). Generated in /provider/calendar.

Webhook events

Use case

Add bookings to a shared team calendar

Provider grabs the iCal URL from /provider/calendar, adds it to a shared Google Calendar; whole team sees the booking pipeline. Resetting the token in the portal invalidates every existing subscription — useful when an employee leaves.

# Provider grabs this URL from /provider/calendar and pastes it
# into Google Calendar → Other calendars → From URL.
https://acme.orkestr.in/calendar/<opaque-token>.ics

Bulk import

Provider POV

Upload a CSV of offerings to bring an existing catalogue across in one shot. The importer creates offerings + availability rows in a single transaction; rolls back the whole batch if any row fails.

Customer POV

N/A — provider-only.

API surface

  • Provider-portal upload only in v1. For programmatic onboarding, loop POST /v1/api/offerings.

Webhook events

Use case

Onboard a new tenant with 200 rooms in one go

The provider downloads the CSV template from /provider/import, fills it from their PMS export, uploads it; offerings appear in DRAFT status with availability rows attached, ready to review and publish. For programmatic onboarding (no spreadsheet), loop POST /v1/api/offerings instead.

// Programmatic alternative: loop your existing inventory.
for (const room of pms.listRooms()) {
  await fetch('https://api.orkestr.in/v1/api/offerings', {
    method: 'POST',
    headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      title: room.name,
      slug: slugify(room.name),
      productTypeKey: 'space',
      basePriceCents: room.nightlyRateCents,
      currency: 'USD',
      attributes: { sqft: room.sqft, amenities: room.amenities },
      status: 'DRAFT',
    }),
  });
}

AI co-pilot

Provider POV

A booking-side LLM that summarises long descriptions, suggests prices, and answers questions about your own catalogue. Conversational search lets customers type natural language ('a quiet space for 4 people downtown next Tuesday morning') and resolves it to filtered listings.

Customer POV

Search on the storefront accepts natural-language queries; chat assistant available on listing pages.

API surface

  • No public REST endpoints in v1 — the assistant runs against your own catalogue inside the storefront.

Webhook events

Use case

Use natural-language search on your storefront

The storefront's search bar already accepts queries like 'a quiet space for 4 people downtown next Tuesday morning' and resolves them to filtered listings. Providers don't have to wire anything — it's on by default once an offering is PUBLISHED.

Performance score

Provider POV

A composite 0-1 score recomputed daily: 0.5 × avgRating/5 + 0.3 × responseRate + 0.2 × confirmationRate. Surfaced on storefronts as a trust signal and used for marketplace ranking.

Customer POV

Indirectly — drives the order in which providers appear on the marketplace.

API surface

  • Currently surfaced in the provider portal only. Marketplace responses include it implicitly via ranking.

Webhook events

Use case

Watch your sub-scores to know what to fix

The provider portal /provider/insights page breaks the score into three inputs: ratings, response rate (how fast you confirm or decline), and confirmation rate (PENDING → CONFIRMED). If your score dips, the breakdown points at which lever to move. Improving response rate to 100% is usually the fastest win.

Webhooks

Provider POV

Subscribe to platform events at /provider/webhooks. Each delivery is HMAC-SHA256-signed with a per-subscription secret. Up to 5 retry attempts with exponential backoff; terminal failures persist on the delivery row for debugging.

Customer POV

N/A — provider-side integration plumbing.

API surface

  • CRUD via /provider/webhooks (provider-authed); not exposed under /v1/api in v1.

Webhook events

  • See the full event catalogue below.

Use case

See the dedicated Webhooks section below for signature verification + the full event catalogue.

API keys

Provider POV

Issue scoped Bearer tokens at /provider/api-keys. Each key is bcrypt-hashed at rest; the full token is shown once at creation. Revoke is instant — every subsequent request rejected at the guard layer before any handler runs.

Customer POV

N/A — provider-side plumbing.

API surface

  • Self-service via /provider/api-keys.

Webhook events

Use case

Rotate keys for a deploy without downtime

Issue a second key, deploy your service with both old and new tokens accepted, watch /provider/api-keys → Last used to confirm traffic moved, then revoke the old one. Bcrypt-hashed at rest, prefix-indexed for O(1) lookup, scope-checked on every request.

# Use the new key on every request
curl https://api.orkestr.in/v1/api/me \
  -H "Authorization: Bearer ork_NEWKEY.deadbeef…"

# Once Last-used on the OLD key is stale, revoke it from
# /provider/api-keys → Revoke. Effect is immediate.

Common integration patterns

Cross-cutting workflows that stitch multiple endpoints + events together. Each one is a real integration shape we've seen — copy and adapt.

A) Mirror new bookings into a CRM

  1. Subscribe to booking.confirmed in /provider/webhooks.
  2. On each delivery, verify the X-Orkestr-Signature header.
  3. Create or upsert a contact in your CRM keyed by the customer id.
  4. Attach the booking as an activity with totalCents + offeringTitle.
  5. Use X-Orkestr-Delivery-Id as the idempotency key on the CRM side.

B) Auto-approve VIP bookings

  1. Subscribe to a future booking.created webhook (today: poll GET /v1/api/bookings?status=PENDING).
  2. Match the customer id against your VIP segment.
  3. POST /v1/api/bookings/:id/approve to auto-confirm — fires booking.confirmed → notifications fan out.

C) Drive a coupon campaign from your email tool

  1. POST /v1/api/coupons with a unique code per cohort.
  2. Pass the code into your email merge fields.
  3. Track redemptions by polling GET /v1/api/coupons/:id (returns redeemedCount).
  4. PATCH /v1/api/coupons/:id { active: false } when the campaign ends.

D) Forward disputes to a support tool

  1. Subscribe to dispute.opened.
  2. Create a ticket in Zendesk / Linear / Intercom with the dispute reason.
  3. Use the same X-Orkestr-Delivery-Id as the support tool's external id so you can dedup retries.

E) Push catalogue from a PMS / ERP nightly

  1. Run a nightly cron on your side.
  2. List your existing inventory in your PMS.
  3. Diff against GET /v1/api/offerings — POST new ones, PATCH changed ones, DELETE retired ones.
  4. Re-run the diff with a smaller window (last hour) for near-real-time sync.

F) Verified-badge gating on your marketing site

  1. Subscribe to tenant.kyb_verified.
  2. When it fires, flip a feature flag in your CMS.
  3. Render the verified badge or unlock premium copy server-side from there.

Webhooks (full reference)

We POST a JSON body to your URL. The HMAC-SHA256 signature of the raw body lives in X-Orkestr-Signature. Re-stringifying the body before hashing breaks verification — read the raw bytes.

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

function verify(body: string, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret).update(body).digest('hex');
  return (
    expected.length === signature.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  );
}

Event catalogue

EventWhen
booking.confirmedA booking moves to CONFIRMED — provider /approve, or Stripe payment success.
booking.cancelledA booking moves to CANCELLED. Payload includes resolved refundPercent.
booking.rescheduledAn item's start/end is updated by admin / staff / public API.
dispute.openedA client or staff member opens a dispute on a booking.
review.createdA verified buyer leaves a review on an offering.
tenant.kyb_verifiedPlatform admin approves the tenant's KYB submission.
webhook.testProvider clicks Send test ping in the UI.

Security

Need the long form?

The full reference doc — every endpoint, parameter, response shape — ships at API.md in the repo root and is also published at github.com/orkestr/orkestr.