API reference

Events, orders and webhooks, for organisers and venues.

The Venued API is a small, deliberately narrow REST surface over data you already own on Venued. Every key belongs to one principal — an organiser account or a venue — and every response is filtered to it. GET /v1/me tells you which one a key is.

What this API deliberately cannot do

It cannot publish an event. POST and PATCH accept visibility of draft or unlisted only; anything else is refused with 403 forbidden. Publishing is a decision with a public consequence and a phone-verification gate behind it, so it stays in the dashboard where the person making it can see what they are about to make public.

⚠️ Note also that draftunlisted is one-way. An event that has been made unlisted cannot be returned to draft through this API or any other.

It cannot touch money. Orders are read-only. There is no refund, no payout and no ticket issuance endpoint.

It cannot write server-owned fields. Anything not on the writable list (see EventPatch) is silently dropped rather than rejected — including ticketing, organizerId, priority and the embedding fields.

Getting a key

API keys are created from Studio (Business account → API), not through this API. A key is shown once, at creation, and only its SHA-256 hash is stored — Venued cannot recover or re-display it. Creating and revoking keys requires the owner or org_manager role on the organiser account.

A key looks like vnd_live_ followed by 48 hex characters.

Who can hold a key

A key belongs to a principal — an organiser, a venue, or (not yet) a professional — and GET /v1/me tells you which. Authorisation is scope ∧ principal type, not scope alone, and the second half is not decoration:

  • events:read means my events to an organiser and the events at my address to a venue. Same scope string, different set.

  • A venue key cannot be minted with events:write or orders:read at all. A venue does not own the events booked into it, and the buyer names and emails on an order are the organiser's business, not the building's.

  • A venue can subscribe to webhooks, for event.published only — it fires when an event at the venue's address becomes public, alongside the organiser's own delivery of the same event.

  • What a venue sees at its own address:

    • every public event;
    • an unlisted event only when no real account chose to unlist it — that is, when it belongs to an unclaimed placeholder business or to no organiser at all. Most unlisted events are catalogue rows collected from public sources that nobody has hidden from anyone; an unlisted event owned by an active organiser is that organiser's deliberate choice, and stays hidden;
    • never a draft, whoever owns it. A draft is an organiser's private working copy.

    Paging stays exact across that filter: has_more is observed rather than inferred, so a hidden row is never mistaken for the end of the list.

  • Professional keys are not available yet — there is nothing for one to read until /v1/profile and /v1/offerings exist. Minting one is refused rather than silently issuing a key that can reach nothing.

Conventions

  • Success bodies are {"data": …} — all of them.
  • List bodies add has_more and next_cursor beside data, on every list endpoint, including ones that cannot currently have a second page. One shape means one mapping in whatever tool you are wiring this into, rather than one per endpoint.
  • Error bodies are {"error": "<code>", "message": "<sentence>", "request_id": "<id>"}. message is for a human and may be reworded without notice; branch on error. request_id is on every response as the X-Request-Id header too — quote it when reporting a problem. Send your own and it is echoed rather than replaced, provided it is a plausible token of at most 64 characters.
  • Paging is by cursor. Pass ?limit= (max 100) and follow next_cursor into ?cursor=. A cursor is opaque and belongs to the query that produced it: it is refused with invalid_cursor if the ordering changed under it, or if the row it anchors on was deleted.
  • ?updated_after= is the poller's filter, on every list endpoint. ISO 8601 or epoch milliseconds, and it reorders the result by updatedAt ascending rather than the endpoint's default order — so changes arrive in the order they happened and the last updatedAt you saw is the only state you need to keep.
  • An unknown query parameter is a 400 unsupported_parameter, never silently ignored. A filter that looks applied but is not is how a poller quietly ships every row it meant to exclude.
  • Money is in minor units, as an integer, beside a non-null uppercase ISO-4217 currency. totalAmountMinor: 1999 is €19.99.
  • Concurrency: GET /v1/events/{eventId} returns an ETag. Send it back as If-Match on the PATCH and an edit that would overwrite somebody else's change fails with 412 precondition_failed instead. Omitting If-Match still succeeds.
  • HEAD is served wherever GET is: same status, same headers, no body. A 405 always carries an Allow header naming what the path does accept.
  • Timestamps are ISO 8601 strings in responses. Requests accept either ISO 8601 or epoch milliseconds.
  • title and description on an event are localized maps, not strings: {"EN": "Sunrise Run"}. Locale keys are normalised to UPPERCASE. The title in a response is already flattened to a single string.

Changes

The reference above describes the API as it is. What changed, and when, is kept here rather than threaded through it.

2026-09-21 — all breaking, all made while production held no API keys or webhook subscriptions, so nobody had anything to migrate:

  • Venue keys. A key belongs to a principal — an organiser or a venue — rather than always an organiser. See Who can hold a key.
  • Paging. Every list is ordered and pageable by cursor, with has_more and next_cursor. GET /v1/events was previously unordered, so an organiser with more than 100 events had no way to reach the rest.
  • Money. Order.totalAmount, a float in major units, became totalAmountMinor, an integer. The float is not kept alongside it.
  • DELETE /v1/webhooks/{webhookId} returns the {"data": …} envelope (it returned {"ok": true}, the only endpoint with its own shape) and is metered as a write (it was metered as a read).
  • Rate limits belong to the account, not the key. Minting a second key no longer doubles the budget.
  • Webhook envelopes name their principal instead of organizerId. A venue receiving organizerId: "<its venue id>" would have been wrong.

Version 1.0.0https://europe-west3-venued.cloudfunctions.net/publicApiDownload the OpenAPI document

Discovery

What this key can do.

GET/v1any valid key

What this key can reach

Returns the scopes on the calling key, the endpoint paths, the available webhook event types, the paging and filtering conventions and the current rate limits. Useful as a connectivity and permission check — it requires a valid key but no particular scope.

The endpoints map is derived from the route table the server dispatches from, not maintained beside it, so it cannot describe a surface the server does not serve.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1 \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Discovery document

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

GET/v1/meany valid key

Who this key is

The principal this key acts as, and the scopes it was minted with.

Why this is separate from GET /v1

GET /v1 answers what can I reach — the endpoint map, the paging conventions, the rate limits — and is the same for everybody. This answers who am I, which is per key and is the thing an integration branches on: a no-code step that must decide whether to offer event actions or venue actions needs the principal, not the endpoint table.

Requires a valid key but no particular scope. A key that cannot tell you what it is cannot be debugged by the person holding it, and "which account is this key for" is the first question anybody asks when an integration has written to the wrong place.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/me \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    The calling principal

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

Events

GET/v1/eventsevents:read

List events

Newest first, by startTime descending, and pageable with cursor.

Which events depends on the key: an organiser key lists the organiser's own events; a venue key lists the events at its address (see What a venue sees above).

⚠️ An event with no stored startTime is not returned. Nothing reachable is hidden by that — an event without a start is not on the site, in the app or in search either, and this API will not create one.

With updated_after the order changes to updatedAt ascending; see the conventions above.

Parameters

NameInTypeDescription
limitqueryinteger, 1–100, default 50

Clamped to 100. A missing, non-numeric, zero or negative value becomes the default of 50 rather than an error.

cursorquerystring

The next_cursor from the previous page. Opaque — do not construct, parse or store one as though it were an offset.

It is anchored to a specific row of a specific ordering, and it is rejected with 400 invalid_cursor rather than quietly obeyed when that no longer holds: if you add or remove updated_after (which changes the sort), or if the row it points at has since been deleted. Restart the pagination without a cursor in either case.

updated_afterquerystring

Only rows whose updatedAt is at or after this instant. ISO 8601 or epoch milliseconds.

⚠️ This changes the ordering to updatedAt ascending, on every endpoint that accepts it. That is what a poller wants — changes in the order they happened, so the last updatedAt processed is the whole of the state it has to keep — and it is also what lets the query be served by an index.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/events \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Events owned by this organiser, newest first

  • 400

    unsupported_parameter — a query parameter this endpoint does not accept. invalid_cursor — a malformed cursor, one belonging to a different ordering, or one anchored to a row that no longer exists. invalid_argumentupdated_after was not ISO 8601 or epoch ms.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

POST/v1/eventsevents:write

Create a draft or unlisted event

Creates an event owned by the calling organiser.

Idempotency-Key is required. A retried request carrying a key that already succeeded replays the stored response verbatim rather than creating a second event. A key whose first attempt FAILED is treated as fresh and re-run — only successful results are recorded — so a failed create is safe to retry with the same key. Keys are scoped per organiser and retained 7 days.

Parameters

NameInTypeDescription
Idempotency-Keyrequiredheaderstring

Any opaque string you generate per logical operation — a UUID is the usual choice. Scoped to your organiser account and retained 7 days.

Request

curl
curl -X POST https://europe-west3-venued.cloudfunctions.net/publicApi/v1/events \
  -H "Authorization: Bearer vnd_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "title": {
    "EN": "Sunrise Run & Rave"
  },
  "startTime": "2026-11-21T18:00:00Z"
}'

Responses

  • 201

    Created

  • 400

    idempotency_key_required — no Idempotency-Key header. invalid_argumentstartTime missing or unparseable, or endTime unparseable.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope, or forbidden when visibility is anything other than draft or unlisted.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

GET/v1/events/{eventId}events:read

Fetch one event

⚠️ An event belonging to a different organiser returns 404 not_found, not 403. That is deliberate: a 403 would confirm the id exists.

Parameters

NameInTypeDescription
eventIdrequiredpathstring

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/events/{eventId} \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    The event

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 404

    not_found. Also returned for a resource that exists but belongs to a different organiser — see the note on GET /v1/events/{eventId}.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

PATCH/v1/events/{eventId}events:write

Update an event

Merges the writable fields of the body into the event. Fields outside the writable list are dropped silently, not rejected — so a body of nothing but server-owned keys is a 400 invalid_argument ("No writable fields in the request body"), which is usually the sign of a typo'd field name.

No Idempotency-Key is required: a PATCH of the same body is naturally idempotent.

If-Match makes the edit conditional. Send the ETag you got from GET /v1/events/{eventId} and the write applies only if nothing has changed since; otherwise it is refused with 412 precondition_failed and nothing is written. Without the header the write applies unconditionally, exactly as before — this is opt-in protection against two editors overwriting each other, not a new requirement.

Parameters

NameInTypeDescription
eventIdrequiredpathstring
If-Matchheaderstring

An ETag previously returned for this resource. The write applies only if the resource still matches; otherwise 412 precondition_failed and nothing is written. * means "it must exist". Omit it and the write is unconditional.

Request

curl
curl -X PATCH https://europe-west3-venued.cloudfunctions.net/publicApi/v1/events/{eventId} \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    The updated event

  • 400

    invalid_argument — no writable fields in the body, or startTime / endTime unparseable.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope, or forbidden when visibility is anything other than draft or unlisted.

  • 404

    not_found. Also returned for a resource that exists but belongs to a different organiser — see the note on GET /v1/events/{eventId}.

  • 412

    precondition_failed — the If-Match you sent does not match the resource's current ETag, so somebody else changed it after you read it. Nothing was written. Re-read the resource, reapply your change to the new version, and retry.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

Orders

GET/v1/events/{eventId}/ordersorders:read

Confirmed orders for one event

Only orders with status: "confirmed" are returned — pending, failed and abandoned checkouts are not visible through this API.

Newest first, by createdAt descending, and pageable with cursor.

Parameters

NameInTypeDescription
eventIdrequiredpathstring
limitqueryinteger, 1–100, default 50

Clamped to 100. A missing, non-numeric, zero or negative value becomes the default of 50 rather than an error.

cursorquerystring

The next_cursor from the previous page. Opaque — do not construct, parse or store one as though it were an offset.

It is anchored to a specific row of a specific ordering, and it is rejected with 400 invalid_cursor rather than quietly obeyed when that no longer holds: if you add or remove updated_after (which changes the sort), or if the row it points at has since been deleted. Restart the pagination without a cursor in either case.

updated_afterquerystring

Only rows whose updatedAt is at or after this instant. ISO 8601 or epoch milliseconds.

⚠️ This changes the ordering to updatedAt ascending, on every endpoint that accepts it. That is what a poller wants — changes in the order they happened, so the last updatedAt processed is the whole of the state it has to keep — and it is also what lets the query be served by an index.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/events/{eventId}/orders \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Confirmed orders for this event, newest first

  • 400

    unsupported_parameter — a query parameter this endpoint does not accept. invalid_cursor — a malformed cursor, one belonging to a different ordering, or one anchored to a row that no longer exists. invalid_argumentupdated_after was not ISO 8601 or epoch ms.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 404

    not_found. Also returned for a resource that exists but belongs to a different organiser — see the note on GET /v1/events/{eventId}.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

GET/v1/ordersorders:read

Confirmed orders across all of this organiser's events

Newest first (createdAt descending). Confirmed orders only — pending, failed and abandoned checkouts are never visible here.

This is the poll source a "new order" trigger in Zapier or Make needs; the order.confirmed webhook is the push counterpart. A robust integration usually wants both: the webhook for latency, and ?updated_after= to backfill whatever the receiver was down for.

Parameters

NameInTypeDescription
limitqueryinteger, 1–100, default 50

Clamped to 100. A missing, non-numeric, zero or negative value becomes the default of 50 rather than an error.

cursorquerystring

The next_cursor from the previous page. Opaque — do not construct, parse or store one as though it were an offset.

It is anchored to a specific row of a specific ordering, and it is rejected with 400 invalid_cursor rather than quietly obeyed when that no longer holds: if you add or remove updated_after (which changes the sort), or if the row it points at has since been deleted. Restart the pagination without a cursor in either case.

updated_afterquerystring

Only rows whose updatedAt is at or after this instant. ISO 8601 or epoch milliseconds.

⚠️ This changes the ordering to updatedAt ascending, on every endpoint that accepts it. That is what a poller wants — changes in the order they happened, so the last updatedAt processed is the whole of the state it has to keep — and it is also what lets the query be served by an index.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/orders \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Confirmed orders, newest first

  • 400

    unsupported_parameter — a query parameter this endpoint does not accept. invalid_cursor — a malformed cursor, one belonging to a different ordering, or one anchored to a row that no longer exists. invalid_argumentupdated_after was not ISO 8601 or epoch ms.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

Venues

For a venue key — the place, and what happens at it.

GET/v1/venuesevents:read

The venue this key is, and the places it is

A venue key's own record, with its locations resolved.

⚠️ Always exactly one entry today. A venue key's principal IS a venue, so "list" is one row. It carries the page envelope anyway, for the same reason every other list does: one mapping in a no-code tool beats one per endpoint.

url is built from the venue id, not its slug. Venue slug uniqueness is not enforced — the Studio editor writes the field directly — so a slug URL can resolve to a different venue. The slug is published as a field for display; it is not the link.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/venues \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    The venue, with its locations

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 404

    not_found. Also returned for a resource that exists but belongs to a different organiser — see the note on GET /v1/events/{eventId}.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

Webhooks

GET/v1/webhookswebhooks:read

List webhook subscriptions

⚠️ The secret is returned here in plaintext. Treat this response as a credential.

Request

curl
curl https://europe-west3-venued.cloudfunctions.net/publicApi/v1/webhooks \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Subscriptions

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

POST/v1/webhookswebhooks:write

Subscribe to events

Creates a subscription and returns its signing secret. Store the secret now — it is also readable from GET /v1/webhooks, but it is what verifies every delivery and it should be treated as a credential from the moment it exists.

A maximum of 10 subscriptions per account is enforced.

A venue key may subscribe to event.published only — an event at its address became public. order.confirmed is refused with 403 forbidden rather than silently dropped: it carries a buyer's name and email, which is the organiser's record and not the building's, and a subscription quietly narrower than the one you asked for would only announce itself by never firing.

Request

curl
curl -X POST https://europe-west3-venued.cloudfunctions.net/publicApi/v1/webhooks \
  -H "Authorization: Bearer vnd_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "url": "https://example.com/hooks/venued",
  "events": [
    "order.confirmed"
  ],
  "description": "Push confirmed orders into the CRM"
}'

Responses

  • 201

    Created. Carries the signing secret.

  • 400

    invalid_argumenturl missing, unparseable, not https, or not a public host; or events contains no known type.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope, or forbidden when a venue key asks for an event type a venue may not receive (anything but event.published).

  • 409

    limit_reached — already 10 subscriptions.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

DELETE/v1/webhooks/{webhookId}webhooks:write

Delete a webhook subscription

Removes the subscription and returns {"data": {"id": "…", "deleted": true}}. Deliveries already in flight are not recalled.

Metered as a write (120/min), like every other request that changes something.

Parameters

NameInTypeDescription
webhookIdrequiredpathstring

Request

curl
curl -X DELETE https://europe-west3-venued.cloudfunctions.net/publicApi/v1/webhooks/{webhookId} \
  -H "Authorization: Bearer vnd_live_your_key_here"

Responses

  • 200

    Deleted

  • 400

    invalid_argument — no webhookId in the path.

  • 401

    unauthorized — missing, malformed, unknown or revoked key.

  • 403

    insufficient_scope — the key is valid but lacks the scope.

  • 404

    not_found. Also returned for a resource that exists but belongs to a different organiser — see the note on GET /v1/events/{eventId}.

  • 429

    rate_limited. 600 reads/min and 120 writes/min in a fixed 60-second window. POST, PATCH and DELETE are writes; GET and HEAD are reads.

    ⚠️ The budget belongs to the organiser account, not to the key. It was metered per key, which meant minting a second key doubled the allowance — so there was no ceiling, only a formality. Two changes, both breaking, both made while the API has no keys in existence: this one, and DELETE moving from the read budget to the write budget.

    ⚠️ The limit is a soft ceiling under burst. The counter is a single Firestore document per key per window, and contention means a hard burst can push somewhat past the nominal figure before 429s begin. It also fails open: if the counter cannot be read, the request is allowed rather than refused.

Webhook events

These are sent to you: Venued POSTs the envelope below to every endpoint subscribed to that event type.

EVENTorder.confirmed

A ticket order or RSVP was confirmed

Sent once, on the pending → confirmed transition of an order. It does not re-fire if the order is written again while already confirmed.

EVENTevent.published

An event became public

Sent once, the first time an event transitions to public. Making an already-public event public again does not re-fire it.