# Sorted Documentation

Sorted's core product is a resilient, agent-ready event and registration platform built for high-concurrency signups. An organizer or agent can create an event, publish a public link or iframe embed, and let Sorted handle registration order, capacity, waitlists, dynamic pricing, and payment collection.

This page is intentionally plain Markdown so it is readable by humans and LLM agents.

## Product Hierarchy

The hosted event and registration platform is Sorted's primary product. It is the default when an organizer or agent can run the registration flow through Sorted.

Queue as a Service is a separate, secondary virtual waiting-room offering for merchants that need a quick way to absorb a traffic surge on an existing destination but cannot, or are not ready to, migrate their registration flow to Sorted. It should not be represented as the core Sorted product.

## What Sorted Does

Sorted hosts bookable events. Each event has a title, description, capacity, optional waitlist capacity, date/time, price, an optional location (venue name, address, and map coordinates), optional photos, and an organizer. The description supports **Markdown** (headings, bold, lists, and links; bare URLs auto-link). The first photo (`image_keys[0]`) is the primary image used for the event hero and the catalog card. Photos can be added by URL — Sorted downloads and stores the image for you (no object storage to manage).

An organizer can manage events through the admin UI. An external agent or system can manage events through the REST API.

When an event is created through `POST /api/events`, the response includes:

- `public_url`: a hosted public event page
- `embed_url`: an iframe-friendly hosted registration form
- `embed_code`: ready-to-paste iframe HTML
- `payment`: whether student payment is required and ready
- `agent`: machine-readable guidance for publishing the link or embed

## Who It Is For

Sorted is for organizers who expect bursts of registration traffic and want a reliable hosted flow.

It is also for LLM agents and automation systems that need to create a real event and return a usable registration link or embed without building their own booking infrastructure.

## Agent Workflow

1. Discover the service with `GET /.well-known/agent-card.json` or `GET /llms.txt`.
2. Read API details from `GET /api`.
3. Create an event with `POST /api/events`.
4. Return `public_url` or `embed_code` to the requester.
5. For paid events, verify `payment.status` is `required` before saying students can pay.

Agents do not pay Sorted to create or host events. For paid events, Sorted earns money from the configured platform fee on student payments.

## Human Workflow

1. An organizer creates or manages events in `/admin/events`.
2. The organizer shares the public event URL or embeds the iframe form on their own site.
3. Students submit registration through the hosted page or iframe.
4. Sorted confirms registration, waitlist, rejection, or pending payment state.
5. Paid events collect payment in the hosted flow when Stripe Connect is ready for the organizer.

## Registration Model

Sorted is designed for extreme concurrency. The hot path does not route every registration through a GenServer call.

Registration requests are written into ETS first. A per-event `EventWorker` drains ETS about every 50ms, resolves email-based users in bulk, validates entries, updates in-memory state, broadcasts results, and flushes registrations to the database asynchronously.

This means `202 Accepted` means "request received", not "seat confirmed". Confirmation arrives after validation.

Validation checks include:

- Duplicate registration
- Capacity and waitlist position
- Price lock
- Payment state
- Event expiry and registration windows

## Payments

Free events skip payment.

Paid events require the organizer to complete Stripe Connect setup. Sorted creates Stripe PaymentIntents using destination charges. Student money flows through Stripe, the organizer receives the destination transfer, and Sorted takes the configured platform fee with `application_fee_amount`.

The hosted page and iframe use Stripe Payment Element, not Checkout Session, so paid registration can happen inside embeds.

If organizer Stripe setup is incomplete, event creation can still succeed, but registration for that paid event is not payment-ready. API clients should check `payment.status`.

## Pricing Modes

- Free: `price = 0`, `min_price = 0`, `max_price = 0`
- Fixed: `price = min_price = max_price`
- Dynamic: `price` is the explicit initial price; `min_price` and `max_price` are bounds

Dynamic events do not automatically start at `min_price`.

Events can be priced in any Stripe-supported currency (134 codes). Amounts are
integers in the currency's Stripe minor units (USD `1050` = $10.50; zero-decimal
currencies like JPY use whole units, `5000` = ¥5000).

`currency` and `pricing_mode` are locked once the event is created — price locks
and payments are denominated in them. The price itself remains editable. If you
picked the wrong currency or pricing mode, cancel the event and create a new one
(the replacement gets a new public URL, so re-publish any shared links).

## Public Discovery

- `/llms.txt`: concise LLM-readable site map
- `/agents.md`: operational instructions for agents
- `/docs.md`: this human and agent overview
- `/api`: machine-readable API catalog
- `/.well-known/agent-card.json`: A2A Agent Card
- `/.well-known/agent.json`: legacy A2A Agent Card path

## Main API Endpoints

- `GET /api`: machine-readable API catalog (the authoritative, always-current list)
- `POST /api/organizers/register`: self-register as an organizer (no auth); returns an API key once
- `GET /api/events`: list events
- `POST /api/events`: create an event and get links/embed code
- `GET /api/events/:id`: get event status and live registration count
- `PATCH /api/events/:id`: update an event
- `DELETE /api/events/:id`: soft-cancel an event
- `GET /api/events/:id/price_history`: dynamic price history
- `POST /api/events/:id/photos`: add a photo by URL (Sorted fetches + stores it); `PUT /api/events/:id/photos/primary` and `DELETE /api/events/:id/photos` manage them
- `POST /api/uploads/presign`: presigned upload URL for raw image bytes
- `POST /api/registrations`: queue a registration request (accepts `user_id` or `email`)
- `GET /api/registrations`: list registrations
- `GET/POST /api/queues`, `GET/PATCH/DELETE /api/queues/:id`: queue events (virtual waiting rooms)
- `POST /api/queues/:id/join`, `GET /api/queues/:id/status/:visitor_id`: join / check position
- `GET /api/users`, `GET /api/organizers`: list users / organizers (admin)
- `GET /api/keys`, `POST /api/keys`, `DELETE /api/keys/:id`: manage API keys
- `POST /api/stripe/onboarding_link`: get the organizer's Stripe payout onboarding URL

For the complete, always-current endpoints with request/response shapes, use `GET /api`.

## Queues (Secondary Feature)

Sorted also includes queue events for virtual waiting rooms. This is a separate, secondary feature for merchants who need immediate surge protection without moving their existing registration flow onto Sorted. Queue workers use the same ETS-first pattern as event registration workers.

Important queue endpoints:

- `GET /api/queues`
- `POST /api/queues`
- `POST /api/queues/:id/join`
- `GET /api/queues/:id/status/:visitor_id`
- `POST /api/queues/verify_token`

## Operational Notes

Event listings are served from `EventCache` to avoid database queries on hot public paths.

Events auto-expire when their datetime passes. Workers shut down expired events on a periodic timer.

Sentry is used for error reporting. PostHog is configured for analytics.

## Current Standards Support

Sorted currently exposes A2A discovery, LLM-readable Markdown guidance, and a machine-readable JSON API catalog.

Recommended next standards work:

- Add a formal OpenAPI document at `/openapi.json`.
- Add a remote MCP server only if agent clients need tool-calling beyond REST.
- Track x402 and AP2 for future agent-side API payments, but do not use them for the current event-hosting model because agents do not pay Sorted today.
