# Sorted Agent Instructions

Sorted's primary product lets an agent create an event, get back a public link or iframe embed, and let Sorted run resilient registration, waitlists, and student payments at high concurrency.

## Product Hierarchy

Use the event and registration APIs as the default Sorted workflow. They are the core product and include the hosted registration page, iframe embed, capacity, waitlists, pricing, and payment flow.

Queue as a Service is a separate, secondary virtual waiting-room feature. Use it only when a merchant needs immediate surge protection around an existing destination and cannot, or is not ready to, migrate their registration flow to Sorted. Do not present queues as the main Sorted product or substitute them for event registration when the hosted flow is viable.

## Discovery

- A2A Agent Card: `GET /.well-known/agent-card.json`
- Legacy A2A Agent Card: `GET /.well-known/agent.json`
- Machine-readable API docs: `GET /api`
- Human overview: `GET /docs.md`
- LLM site map: `GET /llms.txt`

## Authentication

Use the `X-API-Key` header for API calls under `/api`.

Organizer-scoped keys are tied to one organizer and do not need `organizer_id` when creating events. Admin keys are unrestricted and must provide `organizer_id` when creating organizer-owned resources.

## Getting Started From Scratch

If you have no account yet, call `POST /api/organizers/register` (no auth) with `email`, `name`, and a URL-safe `slug`. The response includes a one-time API key — use it as `X-API-Key` for all further calls. Registration (`POST /api/registrations`) also accepts an `email` instead of `user_id` to onboard brand-new students.

## Create And Publish A Event

1. Call `POST /api/events` with an `event` object containing `title`, `body`, `capacity`, `datetime`, `price`, and optional pricing, recurrence, or location fields (`location_name`, `address`, `latitude`, `longitude`). `body` supports **Markdown** (headings, bold, lists, `[links](https://…)`; bare URLs auto-link). The optional `image_keys` array sets the event photos — the first key is the primary image (event hero + catalog card) — but you normally don't set it by hand; use the photo endpoints below to add images and Sorted fills `image_keys` for you.
2. Read the response `data.public_url`, `data.embed_url`, and `data.embed_code`.
3. Give the human or downstream system either the public URL or the iframe HTML.
4. Check `data.agent.hosted_registration_ready` before claiming the link can accept students.
5. Check `data.payment.status` before publishing paid events:
   - `not_required`: free event; registration can proceed without payment.
   - `required`: paid event; organizer Stripe setup is ready and students can pay in the hosted flow.
   - `unavailable`: paid event; organizer Stripe setup is incomplete and students cannot register until setup is done.

## Event Photos

You do not need to pre-upload anything or handle object storage — Sorted fetches and stores images for you.

- **Add a photo by URL (easiest):** `POST /api/events/:event_id/photos` with `{ "url": "https://…/photo.jpg" }` (MCP: `add_event_photo`). Sorted downloads the image (publicly reachable http/https, max 5 MB, jpeg/png/webp/gif), stores it, and appends it to the event. The first photo is the primary/cover image.
- **Upload raw bytes you already hold:** `POST /api/uploads/presign` with `{ "filename", "content_type", "content_length" }` (MCP: `create_photo_upload_url`) returns a presigned PUT URL; PUT the bytes there, then attach the returned `key` via `PATCH /api/events/:event_id` `image_keys`.
- **Set the cover / remove:** `PUT /api/events/:event_id/photos/primary` `{ "key" }` (MCP: `set_primary_event_photo`) reorders to make a photo primary; `DELETE /api/events/:event_id/photos?key=…` (MCP: `remove_event_photo`) removes one.

Photo changes are rate-limited per key and never interrupt an event's live registration.

## Payment Rules

Agents do not pay Sorted to create or host events.

The event response also says this explicitly as `data.agent.agent_payment_required: false` and `data.payment.agent_payment_required: false`.

For paid events, student payment happens inside the Sorted-hosted registration flow using Stripe Payment Element. Sorted uses Stripe Connect destination charges and takes the configured platform fee through `application_fee_amount`.

Do not ask the agent user for card details for Sorted API access. If payment setup is unavailable, tell the organizer to complete Stripe Connect onboarding.

To enable payouts for paid events, call `POST /api/stripe/onboarding_link` (or MCP `get_payout_onboarding_link`) and hand the returned `onboarding_url` to the human account organizer. Stripe's KYC (identity + bank) can only be completed by a human in a browser — agents cannot complete it.

## Registration Semantics

Registration intake is asynchronous. `POST /api/registrations` returns `202 Accepted`, which means the request entered the queue. It does not mean the student is confirmed.

Final statuses are delivered by Sorted after the worker validates duplicate registration, capacity, waitlist, price lock, and payment state.

The hosted embed flow supports email-based registration for visitors without an existing account. Sorted resolves or creates users after the hot-path request is accepted.

## Event Pricing

- `pricing_mode: "free"` means `price`, `min_price`, and `max_price` are all `0`.
- `pricing_mode: "fixed"` means `price`, `min_price`, and `max_price` are the same paid amount.
- `pricing_mode: "dynamic"` means `price` is the explicit initial price. `min_price` and `max_price` are bounds, not defaults.

## Currency

- `currency` accepts any Stripe-supported presentment currency (134 ISO 4217 codes, e.g. `USD`, `EUR`, `JPY`, `INR`, `BRL`). Defaults to `USD`.
- All price fields are integers in the currency's **Stripe minor units**: USD `1050` = $10.50; zero-decimal currencies (JPY, KRW, VND, …) use whole units, so JPY `5000` = ¥5000. ISK and UGX only accept whole units (amounts divisible by 100).
- **`currency` and `pricing_mode` are locked after creation.** Registrations, checkout price locks, and payments are denominated in them, so changing them mid-flight would silently change what students are charged. `price` itself stays editable — in-progress checkouts are protected by price locks.
- **To change the currency or pricing mode, cancel the event (`DELETE /api/events/:id`) and create a new one.** This costs one extra call; note the new event gets a new `public_url`/`embed_code`, so re-publish those if you already shared them.

## Safety

- Do not expose API keys in public pages or embeds.
- Do not invent payment readiness. Use `data.payment.status`.
- Do not treat `202 Accepted` as a registration confirmation.
- Use the returned URLs (e.g. `public_url`, `embed_url`) instead of constructing organizer URLs by hand.
