For agents and developers

Developers

Everything an agent or an integrator needs to find events, quote prices, hand a buyer a checkout and — with consent — manage an organizer’s events. No key to request; the machine-readable documents are authoritative.

Start here

Everything an agent needs to find events, quote prices and hand a buyer a checkout is anonymous — there is no API key to request and no form to fill in. The machine-readable documents are authoritative; this page is for the person writing the client.

Quickstart

Three requests, no credentials: find an organizer, search their events, build the purchase link.

# 1. What is on this origin
curl -sS https://ticketing-staging.softjourn.if.ua/.well-known/api-catalog

# 2. Search one organizer's events (UCP catalog.search)
curl -sS -X POST https://ticketing-staging.softjourn.if.ua/demo-events/api/ucp/catalog/search \
  -H 'content-type: application/json' -H 'API-Version: 1' \
  -d '{"query":"jazz","pagination":{"limit":5}}'

# 3. Read the event page as markdown — it carries each tier's ticketTypePriceId
curl -sS -H 'Accept: text/markdown' https://ticketing-staging.softjourn.if.ua/demo-events/summer-jazz

# 4. Hand the buyer the link; the checkout opens with 2 of that tier selected
open "https://ticketing-staging.softjourn.if.ua/demo-events/summer-jazz?sel=<ticketTypePriceId>:2"

Sandbox

This deployment is its own sandbox: every read endpoint answers real, public data, a UCP checkout session (or an ACP one, once your agent is registered) holds real inventory for its short lifetime and releases it on cancel or expiry, and the purchase link reserves nothing until the buyer confirms. There is nothing to reset and nothing to be charged for while exploring — payment happens in the buyer's browser, or, on ACP, with a Stripe shared payment token; an organizer whose handler says environment: test takes only Stripe test-mode tokens, so no money moves.

Web Bot Auth (RFC 9421, Ed25519) is optional: sign requests with a key directory this deployment allowlists and the traffic is attributed to you. It grants no extra access, and an unsigned request is never refused for being unsigned.

ACP checkout — an agent buys and pays

The Agentic Commerce Protocol (2026-04-17) is served per organizer: the seller is the organizer, not this platform. Discovery is https://ticketing-staging.softjourn.if.ua/{organizer}/.well-known/acp.json; its api_base_url is https://ticketing-staging.softjourn.if.ua/{organizer}/api, under which the five checkout operations live. The root /.well-known/acp.json is the platform's own document and claims no service.

Item ids are the same ticketTypePriceIds UCP catalog.search returns as variant ids. Creating a session HOLDS the tickets (per-person cap enforced), so cancel what you will not buy. Send Authorization: Bearer <api key>, API-Version: 2026-04-17 and an Idempotency-Key on every POST; sessions are bound to your agent, so they survive a key rotation.

Credentials are ISSUED: we register your agent and hand over, once, an API key (acpk_…), a request-signing secret (acps_…) and a webhook secret (acpw_…). Ask us to be registered; an unknown, rotated-out or revoked key is a 401.

Signing (required unless we registered you without it): send Timestamp (RFC 3339, within ±300 s of our clock) and Signature = base64url( HMAC-SHA256( signing secret, Timestamp + "." + JCS(body) ) ), where JCS is RFC 8785 canonical JSON of the request body; a request with no body (GET, an empty cancel) signs Timestamp + ".". A presented signature is always verified, even where it is optional. It covers the timestamp and the body only — not the method or path (ACP's choice) — so within its 300 s a signed body is replay-bounded by the Idempotency-Key on POST, not by the signature. Refusals are 401 with code one of invalid_api_key, revoked, signature_required, invalid_timestamp, stale_timestamp, invalid_signature.

Order events: every order your session places is followed by order_create and then order_update (paid, cancelled, expired, refunded, rescheduled) POSTed to the webhook URL we registered for you — ACP's wire shape { "type", "data": <full Order> }, signed Merchant-Signature: t=<unix>,v1=<hex> = HMAC-SHA256( webhook secret, t + "." + raw body ). Reject a t more than 300 s old; deduplicate retries on Request-Id.

Paying: when the session offers the card_tokenized handler (dev.acp.tokenized.card, psp: stripe), mint a Stripe shared payment token scoped to its config.merchant_id and send it on complete as payment_data.instrument.credential = { "type": "spt", "token": "spt_…" }. The order is charged as a direct charge on the organizer's account and confirmed in the call. Tickets are emailed to the buyer; order.permalink_url shows the order, never the tickets.

  • completed + order — paid and confirmed.
  • ready_for_payment + a payment_declined message — declined or the token's limits were exceeded; the order and holds stay, complete again with a new token.
  • requires_escalation + continue_url — the card needs 3-D Secure, the organizer offers no handler, or you sent payment_data with no instrument: the buyer pays at continue_url in their browser. Accepting payment_data without an instrument is a deliberate deviation from the request schema, kept so the handoff always works.
# Signature for a body (node): canonical JSON (RFC 8785) of the exact body you send
#   ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
#   sig=$(node -e 'const c=require("crypto");const j=v=>v===null||typeof v!=="object"?JSON.stringify(v):Array.isArray(v)?"["+v.map(j)+"]":"{"+Object.keys(v).sort().map(k=>JSON.stringify(k)+":"+j(v[k]))+"}";process.stdout.write(c.createHmac("sha256",process.env.ACPS).update(process.argv[1]+"."+j(JSON.parse(process.argv[2]))).digest("base64url"))' "$ts" "$BODY")
curl -sS -X POST https://ticketing-staging.softjourn.if.ua/demo-events/api/checkout_sessions \
  -H "authorization: Bearer $ACPK" -H 'api-version: 2026-04-17' -H 'idempotency-key: 1' \
  -H "timestamp: $ts" -H "signature: $sig" -H 'content-type: application/json' \
  -d "$BODY"   # {"line_items":[{"id":"<ticketTypePriceId>","quantity":2}],"buyer":{...}}

curl -sS -X POST https://ticketing-staging.softjourn.if.ua/demo-events/api/checkout_sessions/<id>/complete \
  -H "authorization: Bearer $ACPK" -H 'api-version: 2026-04-17' -H 'idempotency-key: 2' \
  -H "timestamp: $ts2" -H "signature: $sig2" \
  -H 'content-type: application/json' \
  -d '{"payment_data":{"handler_id":"card_tokenized","instrument":{"type":"card","credential":{"type":"spt","token":"spt_…"}}}}'

Authentication and scopes

Reading needs no credentials. Managing an organizer's events does: register a client (RFC 7591, no secret — PKCE S256 protects the exchange), send the organizer through the consent screen, and exchange the code for a bearer token. The full walk-through, with the discovery documents, is at https://ticketing-staging.softjourn.if.ua/auth.md.

A token is pinned to the one organizer chosen at consent and narrowed to the scopes you asked for. Ask for the least you need:

  • organizer:read — Read the organizer’s events, venues, seating and readiness — nothing changes.
  • organizer:write — Create and change the organizer’s events, venues, maps, prices and schedules.

MCP connectors

Two Model Context Protocol servers over Streamable HTTP. The buyer connector at https://ticketing-staging.softjourn.if.ua/mcp needs no credentials and offers search, event detail and purchase-link tools. The organizer connector at https://ticketing-staging.softjourn.if.ua/mcp/organizer offers the management tools behind OAuth; a read-only grant sees no write tool in tools/list.

  • search_events — Search events across organizers who publish to AI assistants.
  • get_event — Read an event by organizer and event slug, with prices and availability.
  • list_organizer_events — List the public events of one organizer.
  • get_purchase_link — Return a pre-filled purchase URL; holds no inventory.

Errors

Every non-2xx answer outside the UCP envelope is an RFC 9457 application/problem+json document. Match on code, read detail for the specific occurrence, and follow hint — it names the request that will work. The UCP routes answer in their protocol's own envelope ({ ucp: { status: "error" }, code, content }) for the same reason: a protocol's shape beats a generic one where a protocol exists.

  • not_found — no such route or resource; hint points at the catalogue that lists what exists.
  • method_not_allowed — the route exists, the method does not.
  • bad_request — a header or field is missing or malformed.
  • unauthorized — no or invalid token; WWW-Authenticate names the RFC 9728 metadata.
  • forbidden — wrong realm, wrong account, or a scope the grant does not carry (insufficient_scope).
  • conflict — the resource moved on (a session that can no longer proceed).
  • rate_limited — wait Retry-After seconds.
  • internal — retry with the same Idempotency-Key where one applies.
{
  "type": "https://ticketing-staging.softjourn.if.ua/developers#error-not_found",
  "title": "Not Found",
  "status": 404,
  "detail": "There is no API route at /api/nope.",
  "instance": "/api/nope",
  "code": "not_found",
  "requestId": "trace:example",
  "hint": "The routes this origin serves are listed in https://ticketing-staging.softjourn.if.ua/openapi.json and https://ticketing-staging.softjourn.if.ua/.well-known/api-catalog."
}

Versioning and deprecation

This is API version 1. Send API-Version: 1 on every request; every response carries the version it served in the same header. The UCP paths are fixed by that protocol and versioned by the ucp.version field in their bodies; everything else is versioned by this header. Omitting it means the current version.

A breaking change ships as a new header value, and the previous value keeps working for at least 90 days. A deprecated operation or version says so on every response: the RFC 9745 Deprecation header (when it was deprecated), the RFC 8594 Sunset header (when it stops answering) and a Link; rel="deprecation" to its replacement. Nothing is deprecated today; the machine-readable statement of this policy is x-versioning in /openapi.json.

Limits

Anonymous reads are unmetered within reason. Verified agents (Web Bot Auth) have a per-key token bucket at the edge and receive 429 with Retry-After when it is exhausted. Anonymous holds are capped per call and per cart. A UCP checkout session holds inventory for a short window and then releases it; a purchase link holds nothing.

Terms of use

Read freely and cache briefly (each response states its Cache-Control). Do not present a hold as a purchase, do not quote a price other than the all-in one the API returns, and send the buyer to the continue_url or purchase link to pay — never collect payment details yourself. The site's robots.txt carries its Content-Signal preferences for training and retrieval.

This page is also served as markdown: request /developers with Accept: text/markdown.

Developers · Tickets