{"openapi":"3.1.0","info":{"title":"Outgoing API","version":"1.1","description":"REST API for partner integration with the Outgoing Local Experience Platform —\nhomescreen shelves, activity search, booking, and user provisioning.\n\n## For AI agents\n\nIf you're an AI agent, prefer the machine-readable spec over this rendered page —\nit parses more reliably than scraped HTML:\n\n- **OpenAPI spec (JSON):** <https://api.outgoing.world/partner/v1/openapi.json> — the preferred format.\n- **Docs index (llms.txt):** <https://api.outgoing.world/partner/v1/llms.txt> — Markdown index of endpoints and resources.\n- **Full docs (llms-full.txt):** <https://api.outgoing.world/partner/v1/llms-full.txt> — every endpoint and schema as plain Markdown.\n\nAll three are public and require no authentication.\n\n## Quick start\n\n```bash\ncurl https://api.outgoing.world/partner/v1/homescreen \\\n  -H \"Authorization: Bearer og_api_<your-key>\" \\\n  -H \"X-External-User-Id: <your-user-id>\"\n```\n\nEvery request needs a bearer API key (next section). Multi-user **negotiated**\nkeys also send an `X-External-User-Id` identifying *which* of your users the call\nis for; single-user keys (self-serve) and AAuth agents omit it — they already\nidentify one user (see \"Identifying the user\" below).\n\n## Authentication\n\nAPI keys carry the `partner` scope and are issued per integration. Keys are\nprefixed `og_api_` (production) or `og_api_staging_` (staging). Send them as\na bearer token:\n\n```\nAuthorization: Bearer og_api_<secret>\n```\n\nVerify a key any time with `GET /partner/v1/auth` — it returns `200 {\"ok\": true}`\nfor a valid key (or `401` with a `code` otherwise) and does **not** count against\nyour daily quota.\n\n### Identifying the user\n\nHow the acting Outgoing user is determined depends on your credential:\n\n- **Negotiated key** (multi-user): you assert which user per request via\n  `X-External-User-Id: <your-stable-user-id>`. The header is **required**.\n  The first time you reference a given id, call `POST /partner/v1/users` to\n  provision the Outgoing user and bind the external id to them; subsequent\n  calls with the same id resolve to the same user. By asserting that id you\n  attest that you have verified the end user's identity and have permission\n  to act on their behalf. `POST /partner/v1/users` and `POST /partner/v1/bookings`\n  are available only to negotiated keys.\n- **Self-serve key**: bound to the single Outgoing user who issued it. Do\n  **not** send `X-External-User-Id` — it identifies one user already, and a\n  stray header is rejected with `400`.\n- **AAuth agent**: acts as the agent's own mapped Outgoing user. Do **not**\n  send `X-External-User-Id` (same `400`).\n\n### Agent auth (AAuth)\n\nAs an alternative to a bearer key, AI agents may authenticate with **AAuth**\n([aauth.dev](https://www.aauth.dev)) — cryptographic identity via HTTP Message\nSignatures (RFC 9421: the `Signature-Input`, `Signature`, and `Signature-Key`\nheaders), with proof-of-possession on every request. A request with **no**\ncredential receives `401 Unauthorized` with a `WWW-Authenticate` header pointing\nat our AAuth Resource Metadata (RFC 9728) plus an\n`AAuth-Requirement: requirement=agent-token` header telling agents to send a\nsigned request with their agent token.\n\nA new agent gets a small free trial; once exhausted it receives `202` with an\n`AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header: send\nyour user to `{url}?code={code}` in a browser (and optionally display `code`\nout of band so they can confirm the page matches). The `Location` header is\nyour poll URL — poll it (`GET /aauth/interaction?state=…`) until it returns\n`200` once the user finishes the upgrade, then retry the original request.\nAgents are subject to a daily call quota (`429` once exceeded).\n\n## Rate limits\n\nLimits are configured per API key (per-second, per-minute, per-hour). A\n`429 Too Many Requests` response indicates you've exceeded them — back off\nand retry after the current window resets. Contact us if your workload\nneeds a different ceiling.\n\n## Errors\n\nAll error responses share one JSON shape:\n\n```json\n{ \"error\": \"human-readable description\" }\n```\n\nAuthentication failures (`401`) also include a machine-readable `code`:\n`key_malformed` (the value isn't a valid `og_api_` token — check for stray\nwhitespace or truncation) or `key_invalid` (the key isn't accepted — unknown,\ndisabled, or lacking the `partner` scope).\n\nStatus codes follow standard REST conventions — `401` for invalid credentials,\n`404` for unknown resources, `400` for validation failures, `429` for rate\nlimits or daily-quota exhaustion, `500` for our bugs. Note: a request with **no**\ncredential returns `401` with a `WWW-Authenticate` discovery challenge, while an\nAAuth agent that exhausts its free trial returns `202` with an upgrade URL — see\nAgent auth (AAuth) above.\n\n## Changelog\n\n### v1.2 — June 2026\n\n- `X-External-User-Id` is now **rejected with `400`** when sent by a self-serve\n  key or an AAuth agent — those credentials already identify a single user.\n  Negotiated keys are unchanged (the header remains required). See\n  \"Identifying the user\".\n- `POST /partner/v1/users` and `POST /partner/v1/bookings` are restricted to\n  negotiated keys.\n\n### v1.1 — April 2026\n\n**Breaking change:** ticket pricing is now session-based.\n\n- The `types` array has been removed from `ticket_price`.\n- `ticket_price.sessions[]` is always present. Each session has `date`\n  (`YYYY-MM-DD` or `null`), `time` (`HH:MM` or `null`), and a `tickets`\n  array with `name`, `price`, `fee`, `total`, and `available` fields.\n- Single-date events have one session; multi-date events have one session\n  per date/time.\n- `min`, `max`, `currency`, and `label` on `ticket_price` are unchanged.\n"},"paths":{"/partner/v1/auth":{"get":{"tags":["partner"],"summary":"Verify API key","description":"Verify the supplied API key (or AAuth credential) is valid.\n\nReturns ``{\"ok\": true}`` when accepted. Invalid credentials are rejected by\nthe middleware with ``401`` + a coarse ``code`` before this runs. Exempt from\nthe daily quota (free to poll lightly); still subject to per-key window rate\nlimits, so it can't be hammered.","operationId":"checkAuth","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Checkauth"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/users":{"post":{"tags":["partner"],"summary":"Provision User","description":"Provision (or update) a partner user mapping.\n\nCreates an Outgoing user with the provided personalization signals and maps\nthe partner's external user ID to it.  Idempotent: if the mapping already\nexists the profile and onboarding info are updated.\n\nIf the email or phone matches an existing Outgoing user, the mapping is\ncreated pointing to that user **without** modifying their profile or\nonboarding preferences.  The response will have ``linked=True`` and\n``updated=False`` to indicate this.\n\nBy calling this endpoint the partner **attests** they have verified the\nuser's identity and have permission to act on their behalf.","operationId":"provisionUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionUserRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionUserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/homescreen":{"get":{"tags":["partner"],"summary":"Get Homescreen","description":"Return cacheable homescreen activities for a location and persona.\n\nThe partner sends an H3 resolution-7 (neighborhood-sized) cell\nand an optional list of ``usual_plans`` that influence relevance ranking\nvia suitability boosts (e.g. kid-friendly, pet-friendly).\nThe cell is used only to determine the search centre; the actual\nsearch radius uses density-based logic (ENTIRE_AREA base × max shelf multiplier).\nThe response includes ``Cache-Control`` headers indicating how long the caller should cache the response.","operationId":"getHomescreen","parameters":[{"name":"h3_cell","in":"query","required":true,"schema":{"type":"string","description":"H3 cell index at resolution 7 (~1.7 km edge). See [H3 Docs](https://h3geo.org/docs/api/indexing#latlngtocell) for how to compute this from a lat/lng. E.g. `872a100deffffff` for Brooklyn, NY or `872830828ffffff` for San Francisco, CA.","examples":["872a100deffffff","872830828ffffff"],"title":"H3 Cell"},"description":"H3 cell index at resolution 7 (~1.7 km edge). See [H3 Docs](https://h3geo.org/docs/api/indexing#latlngtocell) for how to compute this from a lat/lng. E.g. `872a100deffffff` for Brooklyn, NY or `872830828ffffff` for San Francisco, CA."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":250,"minimum":1},{"type":"null"}],"description":"Maximum number of activities to return per shelf. Defaults to 250 (the server maximum) when omitted.","title":"Limit"},"description":"Maximum number of activities to return per shelf. Defaults to 250 (the server maximum) when omitted."},{"name":"usual_plans","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"User's usual plans — controls relevance ranking via suitability boosts.\n\nValid values:\n- `fun-with-kids`\n- `pet-friendly`\n- `date-nights`\n- `meet-new-people`\n- `friends-hangout`\n- `staying-active`\n- `nature-time`","examples":[["date-nights","fun-with-kids"]],"title":"Usual Plans"},"description":"User's usual plans — controls relevance ranking via suitability boosts.\n\nValid values:\n- `fun-with-kids`\n- `pet-friendly`\n- `date-nights`\n- `meet-new-people`\n- `friends-hangout`\n- `staying-active`\n- `nature-time`"},{"name":"shelved","in":"query","required":false,"schema":{"type":"boolean","description":"When true, activities are grouped into thematic shelves (e.g. indoor, outdoor, date night, kids) based on weather, persona, and time conditions. When false (default), all activities are returned in a single flat shelf.","default":false,"title":"Shelved"},"description":"When true, activities are grouped into thematic shelves (e.g. indoor, outdoor, date night, kids) based on weather, persona, and time conditions. When false (default), all activities are returned in a single flat shelf."},{"name":"booking_filter","in":"query","required":false,"schema":{"$ref":"#/components/schemas/BookingFilter","description":"Filter activities by booking availability.\n\n- `all` (default): return all activities.\n- `bookable`: only activities with `is_bookable: true` (bookable via the POST /bookings endpoint).\n- `bookable_or_free`: activities that are bookable OR free (no ticket price / walk-in).","default":"all"},"description":"Filter activities by booking availability.\n\n- `all` (default): return all activities.\n- `bookable`: only activities with `is_bookable: true` (bookable via the POST /bookings endpoint).\n- `bookable_or_free`: activities that are bookable OR free (no ticket price / walk-in)."},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names. Defaults to `en`.","title":"Accept-Language"},"description":"BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names. Defaults to `en`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShelvesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/h3":{"get":{"tags":["partner"],"summary":"Lat Lng To H3","description":"Convert a latitude/longitude pair into an H3 cell index.\n\nConvenience endpoint so partners don't need an H3 library client-side.\nThe default resolution matches what the ``/homescreen`` endpoint expects.","operationId":"getH3Cell","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"Latitude in decimal degrees.","title":"Lat"},"description":"Latitude in decimal degrees."},{"name":"lng","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"Longitude in decimal degrees.","title":"Lng"},"description":"Longitude in decimal degrees."},{"name":"resolution","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":0,"description":"H3 resolution (0–15). Defaults to 7 (neighborhood-sized, ~1.7 km edge).","default":7,"title":"Resolution"},"description":"H3 resolution (0–15). Defaults to 7 (neighborhood-sized, ~1.7 km edge)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/H3CellResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/search":{"get":{"tags":["partner"],"summary":"Search","description":"Search for activities and places using natural language.\n\nPowered by the same agentic search engine as the Outgoing app: it interprets\nthe query, searches the live web, and enriches each pick. Returns ranked\nresults with the agent's rationale (``why``); call\n``GET /activities/{activity_id}`` for full booking detail.\n\n**Personalization** uses the resolved Outgoing user's biography, resolved by\npriority: the key/AAuth principal's bound user → the ``X-External-User-Id``\nmapping → anonymous. **Location** is a free-text hint; omitting it searches\nglobally (no default city).\n\nSearches typically take 30 seconds to 2 minutes, so use a client timeout of at\nleast 120 seconds; a shorter timeout aborts the request even though the server\nmay still return a valid result, and the attempt still counts against your daily\nquota.","operationId":"searchActivities","parameters":[{"name":"prompt","in":"query","required":true,"schema":{"type":"string","description":"Natural-language search query (e.g. 'jazz concerts this weekend in Brooklyn', 'family-friendly brunch near the Mission').","title":"Prompt"},"description":"Natural-language search query (e.g. 'jazz concerts this weekend in Brooklyn', 'family-friendly brunch near the Mission')."},{"name":"location","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional free-text location hint (e.g. 'Paris', 'Brooklyn', 'near the Eiffel Tower'). Omit to search globally — there is no default city, so an unscoped query is a global search rather than a fallback to a specific city.","title":"Location"},"description":"Optional free-text location hint (e.g. 'Paris', 'Brooklyn', 'near the Eiffel Tower'). Omit to search globally — there is no default city, so an unscoped query is a global search rather than a fallback to a specific city."},{"name":"x-external-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The partner's external user ID, used to personalize search for negotiated multi-user keys. Optional; ignored for self-serve keys and AAuth agents.","title":"X-External-User-Id"},"description":"The partner's external user ID, used to personalize search for negotiated multi-user keys. Optional; ignored for self-serve keys and AAuth agents."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/activities/{activity_id}":{"get":{"tags":["partner"],"summary":"Get Activity","description":"Get activity details and record a SEEN interaction for the user.","operationId":"getActivity","parameters":[{"name":"activity_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Activity Id"}},{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names and descriptions. Defaults to `en`.","title":"Accept-Language"},"description":"BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names and descriptions. Defaults to `en`."},{"name":"x-external-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The partner's external user ID. Required for negotiated multi-user keys; omit for self-serve keys and AAuth agents (sending it returns 400).","title":"X-External-User-Id"},"description":"The partner's external user ID. Required for negotiated multi-user keys; omit for self-serve keys and AAuth agents (sending it returns 400)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerActivityDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/activities/{activity_id}/prices":{"get":{"tags":["partner"],"summary":"Get Activity Prices","description":"Scrape the latest ticket prices for an activity.\n\nTriggers an on-demand scrape of the activity's booking page and returns\nthe refreshed ticket pricing. Typically takes 5-15 seconds depending on\nthe ticketing platform.","operationId":"getActivityPrices","parameters":[{"name":"activity_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Activity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/orders":{"get":{"tags":["partner"],"summary":"Get Orders","description":"List bookings, deliverables, and emails for the authenticated partner user.","operationId":"listOrders","parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-external-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The partner's external user ID. Required for negotiated multi-user keys; omit for self-serve keys and AAuth agents (sending it returns 400).","title":"X-External-User-Id"},"description":"The partner's external user ID. Required for negotiated multi-user keys; omit for self-serve keys and AAuth agents (sending it returns 400)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrdersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/bookings":{"post":{"tags":["partner"],"summary":"Create Booking","description":"Submit a booking request for an activity.\n\nAccepts booking details including the activity, ticket count,\npre-authorized amount, and a `webhook_url` where the result will be\ndelivered once the booking completes (or fails).\n\nThe synchronous response confirms acceptance. The final result is\ndelivered asynchronously as a **BookingWebhookPayload** POST to\nyour `webhook_url`. See the `BookingWebhookPayload` schema for the\nfull callback payload structure.","operationId":"createBooking","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingRequest"}}},"required":true},"responses":{"202":{"description":"Booking request accepted for asynchronous processing. Results will be delivered to the `webhook_url` as a `BookingWebhookPayload` (see Webhook Callback below).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingAcceptedResponse"}}}},"402":{"description":"Payment required — a `live_purchase` was requested but this API key is not authorized to charge the stored payment card. Contact us to enable stored-card bookings for your key, or use a `dry_run_*` mode to test.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object","example":{"detail":"This API key is not authorized to charge the stored payment card."}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}},"callbacks":{"_booking_webhook_callback":{"{$callback_url}":{"post":{"summary":"Booking result webhook","description":"POST delivered to the `webhook_url` you provided in the booking request once the booking resolves. Your server should respond with a 2xx status.","operationId":"_booking_webhook_callback__callback_url__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingWebhookPayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}}}}},"/partner/v1/slack/workspaces/bind":{"post":{"tags":["partner"],"summary":"Bind Slack Workspace","description":"Bind the Slack workspace referenced by ``bind_token`` to this API key.","operationId":"bindSlackWorkspace","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindWorkspaceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindWorkspaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}},"/partner/v1/aauth/interaction":{"get":{"tags":["partner"],"summary":"Poll Interaction","description":"Agent polls here until the user completes the upgrade.\n\nReturns 200 once the principal is upgraded, 202 (still pending) otherwise.","operationId":"pollAAuthInteraction","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"202":{"description":"Accepted, but interaction required. Returned to an AAuth agent that has used up its free trial (or has no bound user yet). The response carries an `AAuth-Requirement: requirement=interaction; url=\"…\"; code=\"…\"` header — send the user to `{url}?code={code}` in a browser (and optionally display `code` out of band so they can confirm the page matches) — plus a `Location` header with the poll URL (`GET /aauth/interaction?state=…`); poll until it returns 200, then retry the original request. (Bearer-key callers never see this.)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Free trial exhausted. Direct the user to the upgrade URL to continue."}}}}},"401":{"description":"Unauthorized — invalid API key, or no credential at all. Invalid-key responses carry a machine-readable `code`: `key_malformed` (not a valid `og_api_` token — check for stray whitespace/truncation) or `key_invalid` (not accepted — unknown, disabled, or missing the `partner` scope). A credential-less request instead carries a `WWW-Authenticate` discovery challenge (RFC 9728) plus an `AAuth-Requirement: requirement=agent-token` header so AAuth agents know to send a signed request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"example":{"error":"Invalid API key.","code":"key_invalid"}}}}},"429":{"description":"Rate limit exceeded. Back off and retry after the limit window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"example":{"error":"Rate limit exceeded (60/min). Try again shortly."}}}}}}}}},"components":{"schemas":{"BindWorkspaceRequest":{"properties":{"bind_token":{"type":"string","title":"Bind Token","description":"The token from the bot's welcome DM."}},"type":"object","required":["bind_token"],"title":"BindWorkspaceRequest"},"BindWorkspaceResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"team_id":{"type":"string","title":"Team Id"},"bound":{"type":"boolean","title":"Bound","description":"True if this request wrote the FK (vs. already bound)."}},"type":"object","required":["ok","team_id","bound"],"title":"BindWorkspaceResponse"},"BookingFilter":{"type":"string","enum":["all","bookable","bookable_or_free"],"title":"BookingFilter","description":"Controls which activities are returned based on booking availability."},"BookingStatus":{"type":"string","enum":["confirmed","failed","cancelled","pending","user_input_required","deliverable_ready"],"title":"BookingStatus","description":"Status of a completed booking attempt, delivered via the webhook callback."},"BookingTicketItem":{"properties":{"ticket_type_name":{"type":"string","title":"Ticket Type Name","description":"Name of the ticket type to book, exactly as returned in `ticket_price.sessions[].tickets[].name` (e.g. 'General Admission', 'VIP').","examples":["General Admission"]},"num_tickets":{"type":"integer","minimum":1.0,"title":"Num Tickets","description":"Number of tickets to book for this ticket type.","default":1,"examples":[2]},"session_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Date","description":"Session date in YYYY-MM-DD format matching `ticket_price.sessions[].date`. Required for multi-session events: the (session_date, session_time) pair must exactly match one session (null matches null). For single-session events, may be omitted — the only session will be used.","examples":["2026-04-25"]},"session_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Time","description":"Session start time in HH:MM 24h format matching `ticket_price.sessions[].time`. Paired with `session_date` to disambiguate multi-session events.","examples":["19:00"]}},"type":"object","required":["ticket_type_name"],"title":"BookingTicketItem","description":"A single ticket type and quantity within a booking request."},"BookingWebhookPayload":{"properties":{"booking_id":{"type":"string","title":"Booking Id","description":"Unique identifier for this booking, assigned by Outgoing.","examples":["c1b7a5db-d2b4-4fb6-921f-4c96ee8fe674"]},"idempotency_key":{"type":"string","title":"Idempotency Key","description":"The idempotency key from the original booking request.","examples":["01929f3b-7c2a-7000-8d3f-1b2c3d4e5f60"]},"status":{"$ref":"#/components/schemas/BookingStatus","description":"Outcome of the booking attempt.","examples":["confirmed"]},"activity_id":{"type":"string","title":"Activity Id","description":"UUID of the booked activity.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"external_user_id":{"type":"string","title":"External User Id","description":"The partner's external user ID from the original request.","examples":["user_12345"]},"tickets":{"items":{"$ref":"#/components/schemas/BookingTicketItem"},"type":"array","title":"Tickets","description":"Ticket types and quantities from the original request."},"authorization_amount_cents":{"type":"integer","title":"Authorization Amount Cents","description":"The authorized amount from the original request (cents, USD)."},"purchase_amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Purchase Amount Cents","description":"Actual ticket purchase cost charged by the venue (cents, USD). Present when status is confirmed.","examples":[3862]},"booking_fee_cents":{"type":"integer","title":"Booking Fee Cents","description":"Outgoing booking fee (cents, USD).","examples":[399]},"total_charged_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Charged Cents","description":"Total amount charged (purchase_amount_cents + booking_fee_cents). Present when status is confirmed.","examples":[4261]},"resolved_at":{"type":"string","format":"date-time","title":"Resolved At","description":"ISO-8601 timestamp when the booking was resolved.","examples":["2026-03-30T14:22:00Z"]},"ticket_details":{"anyOf":[{"$ref":"#/components/schemas/TicketDetails"},{"type":"null"}],"description":"Ticket/pass details — present when `status` is `confirmed`. May include download URLs, confirmation codes, and venue notes."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Human-readable error description when `status` is `failed`.","examples":["Payment declined by processor"]}},"type":"object","required":["booking_id","idempotency_key","status","activity_id","external_user_id","tickets","authorization_amount_cents","booking_fee_cents","resolved_at"],"title":"BookingWebhookPayload","description":"Payload delivered via POST to the `webhook_url` when the booking resolves.\n\nThis model documents the webhook callback shape — it is not returned\nin the synchronous 202 response."},"CreateBookingAcceptedResponse":{"properties":{"booking_id":{"type":"string","title":"Booking Id","description":"Unique identifier for this booking, assigned by Outgoing.","examples":["c1b7a5db-d2b4-4fb6-921f-4c96ee8fe674"]},"idempotency_key":{"type":"string","title":"Idempotency Key","description":"Echo of the idempotency key from the request.","examples":["01929f3b-7c2a-7000-8d3f-1b2c3d4e5f60"]},"status":{"type":"string","title":"Status","description":"For a new booking, `accepted` — it is now being processed asynchronously. For a replayed `idempotency_key`, the existing booking's current status instead (e.g. `pending`, `processing`, `failed`, `confirmed`), so a replay never masks a failed booking as `accepted`. A booking that has already `failed` cannot be retried under the same key — use a new `idempotency_key`.","default":"accepted"}},"type":"object","required":["booking_id","idempotency_key"],"title":"CreateBookingAcceptedResponse","description":"Synchronous response returned when a booking request is accepted for processing."},"CreateBookingRequest":{"properties":{"external_user_id":{"type":"string","title":"External User Id","description":"Your system's unique identifier for the user making this booking. Must have been provisioned via `POST /users` before calling this endpoint.","examples":["user_12345"]},"activity_id":{"type":"string","format":"uuid","title":"Activity Id","description":"UUID of the activity to book, as returned by the shelves or activity detail endpoints. Only activities with `is_bookable: true` can be booked.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tickets":{"items":{"$ref":"#/components/schemas/BookingTicketItem"},"type":"array","minItems":1,"title":"Tickets","description":"List of ticket types and quantities to book. Each item specifies a ticket type name and how many to purchase.","examples":[[{"num_tickets":2,"ticket_type_name":"General Admission"}]]},"authorization_amount_cents":{"type":"integer","minimum":0.0,"title":"Authorization Amount Cents","description":"Total pre-authorized amount in cents (USD) for all tickets combined — not per ticket. For example, 2 tickets at $15.00 each should be `3000`. This amount is captured once the booking is confirmed.","examples":[3000]},"idempotency_key":{"type":"string","title":"Idempotency Key","description":"Client-generated unique key to prevent duplicate bookings. Use a UUID or similarly unique string per booking attempt. Replaying the same key returns the original booking (with its current status) without creating a new one. To retry a booking that **failed**, generate a NEW idempotency_key — replaying the original key returns the failed booking, not a fresh attempt.","examples":["01929f3b-7c2a-7000-8d3f-1b2c3d4e5f60"]},"webhook_url":{"type":"string","pattern":"^https?://","title":"Webhook Url","description":"URL we will POST to once the booking completes (or fails). Must be an HTTPS endpoint in production. HTTP is allowed for local testing with dry-run modes. The payload is a `BookingWebhookPayload` JSON object. Your server should respond with a 2xx status within 10 seconds.","examples":["https://partner.example.com/webhooks/outgoing-booking"]},"payment_mode":{"$ref":"#/components/schemas/PaymentMode","description":"How to fulfill this booking. Required — one of: `live_purchase` (charge the shared payment card and book for real; your API key must be authorized for stored-card bookings or the request is rejected with 402), `dry_run_success` (simulate a successful booking, no charge), or `dry_run_failure` (simulate a declined/failed booking, no charge). The dry-run modes are for integration testing.","examples":["dry_run_success","live_purchase"]}},"type":"object","required":["external_user_id","activity_id","tickets","authorization_amount_cents","idempotency_key","webhook_url","payment_mode"],"title":"CreateBookingRequest"},"H3CellResponse":{"properties":{"h3_cell":{"type":"string","title":"H3 Cell","description":"H3 cell index at the requested resolution."},"resolution":{"type":"integer","title":"Resolution","description":"H3 resolution used."},"lat":{"type":"number","title":"Lat","description":"Input latitude."},"lng":{"type":"number","title":"Lng","description":"Input longitude."}},"type":"object","required":["h3_cell","resolution","lat","lng"],"title":"H3CellResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"OrderBooking":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"activity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Activity Name"},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url"},"authorization_amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Authorization Amount Cents"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","status","created_at"],"title":"OrderBooking"},"OrderDeliverable":{"properties":{"id":{"type":"string","title":"Id"},"deliverable_type":{"type":"string","title":"Deliverable Type"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes"},"partner_booking_id":{"type":"string","title":"Partner Booking Id"}},"type":"object","required":["id","deliverable_type","filename","content_type","partner_booking_id"],"title":"OrderDeliverable"},"OrderEmail":{"properties":{"id":{"type":"string","title":"Id"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"processing_status":{"type":"string","title":"Processing Status"},"partner_booking_id":{"type":"string","title":"Partner Booking Id"},"received_at":{"type":"string","title":"Received At"}},"type":"object","required":["id","processing_status","partner_booking_id","received_at"],"title":"OrderEmail"},"OrdersResponse":{"properties":{"bookings":{"items":{"$ref":"#/components/schemas/OrderBooking"},"type":"array","title":"Bookings"},"deliverables":{"items":{"$ref":"#/components/schemas/OrderDeliverable"},"type":"array","title":"Deliverables"},"emails":{"items":{"$ref":"#/components/schemas/OrderEmail"},"type":"array","title":"Emails"}},"type":"object","title":"OrdersResponse"},"PartnerActivity":{"properties":{"activity_id":{"type":"string","title":"Activity Id"},"name":{"type":"string","title":"Name"},"short_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Description"},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url"},"location":{"anyOf":[{"properties":{"lat":{"type":"number"},"lng":{"type":"number"},"plus_code":{"anyOf":[{"type":"string"},{"type":"null"}]}},"type":"object","required":["lat","lng"],"title":"Location","description":"A latitude/longitude pair, with an optional Plus Code.\n\n    Plus Codes are like street addresses for places that don't have one\n    (https://maps.google.com/pluscodes/).\n\n    `frozen=True` makes instances hashable and immutable. msgspec auto-generates\n    `__hash__` and `__eq__` from the field values."},{"type":"null"}],"title":"Location"},"semantic_location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Semantic Location"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights"},"display_label":{"type":"string","title":"Display Label","default":""},"is_bookable":{"type":"boolean","title":"Is Bookable","description":"Does not indicate whether the activity requires booking, it indicates that ticket purchase is available through the Create Booking endpoint of this API.","default":false},"ticket_price":{"anyOf":[{"$ref":"#/components/schemas/PartnerTicketPrice"},{"type":"null"}],"description":"Ticket pricing with individual ticket types available for booking."},"estimated_fulfillment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Fulfillment","description":"Expected fulfillment speed when booking. 'instant' — tickets are typically delivered within minutes. 'delayed' — fulfillment may take up to a few hours. Null if the activity is not bookable.","examples":["instant","delayed"]},"next_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Datetime","description":"Next upcoming event date/time in ISO 8601 format, or null if not scheduled."},"booking_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Booking Domain","description":"(Debug only) Ticketing platform domain. Populated only when `debug=true` query param is set."}},"type":"object","required":["activity_id","name"],"title":"PartnerActivity"},"PartnerActivityDetail":{"properties":{"activity_id":{"type":"string","title":"Activity Id"},"name":{"type":"string","title":"Name"},"short_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Description"},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url"},"location":{"anyOf":[{"properties":{"lat":{"type":"number"},"lng":{"type":"number"},"plus_code":{"anyOf":[{"type":"string"},{"type":"null"}]}},"type":"object","required":["lat","lng"],"title":"Location","description":"A latitude/longitude pair, with an optional Plus Code.\n\n    Plus Codes are like street addresses for places that don't have one\n    (https://maps.google.com/pluscodes/).\n\n    `frozen=True` makes instances hashable and immutable. msgspec auto-generates\n    `__hash__` and `__eq__` from the field values."},{"type":"null"}],"title":"Location"},"semantic_location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Semantic Location"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights"},"display_label":{"type":"string","title":"Display Label","default":""},"is_bookable":{"type":"boolean","title":"Is Bookable","description":"Does not indicate whether the activity requires booking, it indicates that ticket purchase is available through the Create Booking endpoint of this API.","default":false},"ticket_price":{"anyOf":[{"$ref":"#/components/schemas/PartnerTicketPrice"},{"type":"null"}],"description":"Ticket pricing with individual ticket types available for booking."},"estimated_fulfillment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Fulfillment","description":"Expected fulfillment speed when booking. 'instant' — tickets are typically delivered within minutes. 'delayed' — fulfillment may take up to a few hours. Null if the activity is not bookable.","examples":["instant","delayed"]},"next_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Datetime","description":"Next upcoming event date/time in ISO 8601 format, or null if not scheduled."},"booking_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Booking Domain","description":"(Debug only) Ticketing platform domain. Populated only when `debug=true` query param is set."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue","description":"Venue name where the activity takes place."},"tips":{"items":{"type":"string"},"type":"array","title":"Tips","description":"Curated tips about the activity."},"upcoming_datetimes":{"items":{"type":"string"},"type":"array","title":"Upcoming Datetimes","description":"Upcoming event dates/times in ISO 8601 format, sorted chronologically."},"picture_urls":{"items":{"type":"string"},"type":"array","title":"Picture Urls"},"is_open_now":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Open Now"},"search_category":{"items":{"type":"string"},"type":"array","title":"Search Category"}},"type":"object","required":["activity_id","name"],"title":"PartnerActivityDetail"},"PartnerSearchResult":{"properties":{"activity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Activity Id","description":"Outgoing activity id. Pass to GET /activities/{activity_id} for full booking detail. Null if the pick could not be persisted."},"name":{"type":"string","title":"Name","description":"Activity or place name."},"why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why","description":"Why this result matches the query — the agent's rationale."},"short_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Description","description":"One-line description."},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights","description":"Short highlight tags."},"venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Venue","description":"Venue name where the activity takes place."},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating","description":"Aggregate venue rating (e.g. Google), if known."},"next_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Datetime","description":"Soonest upcoming start time (ISO 8601), if the result is dated."},"entity_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type","description":"'event' or 'place'."},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url","description":"Thumbnail image URL, if available."},"location":{"anyOf":[{"properties":{"lat":{"type":"number"},"lng":{"type":"number"},"plus_code":{"anyOf":[{"type":"string"},{"type":"null"}]}},"type":"object","required":["lat","lng"],"title":"Location","description":"A latitude/longitude pair, with an optional Plus Code.\n\n    Plus Codes are like street addresses for places that don't have one\n    (https://maps.google.com/pluscodes/).\n\n    `frozen=True` makes instances hashable and immutable. msgspec auto-generates\n    `__hash__` and `__eq__` from the field values."},{"type":"null"}],"title":"Location","description":"Geographic location of the activity."}},"type":"object","required":["name"],"title":"PartnerSearchResult","description":"A single agentic-search result.\n\nDiscovery-shaped (not the transactional ``PartnerActivity``): it carries the\nagent's rationale (``why``) and editorial fields, plus ``activity_id`` —\nfetch full booking detail (price, tickets, sessions) via\n``GET /activities/{activity_id}``."},"PartnerSession":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Session date in YYYY-MM-DD format, or null if not date-specific."},"time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time","description":"Session start time in HH:MM (24h) format, or null."},"tickets":{"items":{"$ref":"#/components/schemas/PartnerSessionTicket"},"type":"array","title":"Tickets","description":"Tickets available for this session."}},"type":"object","title":"PartnerSession","description":"A date/time session with its available tickets, exposed to partners."},"PartnerSessionTicket":{"properties":{"name":{"type":"string","title":"Name","description":"Ticket name (e.g. 'General Admission', 'VIP')."},"price":{"type":"number","title":"Price","description":"Ticket price in the activity's currency."},"fee":{"type":"number","title":"Fee","description":"Additional fee on top of the base price.","default":0.0},"total":{"type":"number","title":"Total","description":"Total the buyer pays (price + fee)."},"available":{"type":"boolean","title":"Available","description":"Whether this ticket can currently be purchased.","default":true},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Additional description for this ticket."},"on_sale_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"On Sale Status","description":"Sale status (e.g. 'AVAILABLE', 'SOLD_OUT')."},"is_free":{"type":"boolean","title":"Is Free","description":"True if this is a free/RSVP ticket.","default":false},"min_quantity":{"type":"integer","title":"Min Quantity","description":"Minimum number of tickets per order.","default":1},"max_quantity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Quantity","description":"Maximum number of tickets per order, or null if unlimited."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Ticket category (e.g. 'admission', 'add_on')."}},"type":"object","required":["name","price","total"],"title":"PartnerSessionTicket","description":"A ticket within a session, exposed to partners."},"PartnerShelf":{"properties":{"slug":{"type":"string","title":"Slug"},"display_name":{"type":"string","title":"Display Name"},"activities":{"items":{"$ref":"#/components/schemas/PartnerActivity"},"type":"array","title":"Activities"}},"type":"object","required":["slug","display_name","activities"],"title":"PartnerShelf"},"PartnerTicketPrice":{"properties":{"min":{"type":"number","title":"Min","description":"Minimum ticket price (including fees)."},"max":{"type":"number","title":"Max","description":"Maximum ticket price (including fees)."},"currency":{"type":"string","title":"Currency","description":"ISO 4217 currency code (e.g. 'USD', 'EUR')."},"label":{"type":"string","title":"Label","description":"Human-readable price label (e.g. '$20-$50')."},"sessions":{"items":{"$ref":"#/components/schemas/PartnerSession"},"type":"array","title":"Sessions","description":"Ticket availability grouped by session. Single-date events have one session."}},"type":"object","required":["min","max","currency","label"],"title":"PartnerTicketPrice","description":"Ticket pricing exposed to partners, with session-based ticket breakdown."},"PaymentMode":{"type":"string","enum":["live_purchase","dry_run_success","dry_run_failure"],"title":"PaymentMode","description":"How a booking is fulfilled.\n\n``live_purchase`` charges the shared agentic-checkout card and books for real\n(the key must be authorized via ``can_use_stored_card``). The ``dry_run_*``\nmodes simulate the respective outcome without any charge, for integration\ntesting."},"PricesResponse":{"properties":{"activity_id":{"type":"string","title":"Activity Id"},"ticket_price":{"anyOf":[{"$ref":"#/components/schemas/PartnerTicketPrice"},{"type":"null"}],"description":"Fresh ticket pricing after scrape, or null if no prices found."},"scrape_status":{"type":"string","title":"Scrape Status","description":"Result of the scrape: 'success', 'no_prices', or 'error'."}},"type":"object","required":["activity_id","scrape_status"],"title":"PricesResponse"},"ProvisionUserRequest":{"properties":{"external_user_id":{"type":"string","title":"External User Id","description":"Your system's unique identifier for this user. Used to reference the user in all subsequent API calls. Must be stable — changing this value creates a new user mapping.","examples":["user_12345","cust_abc123"]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"User's first name. Used for ticket purchases that require separate name fields.","examples":["Alice"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"User's last name. Used for ticket purchases that require separate name fields.","examples":["Smith"]},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"A name used to identify this user, e.g. for display personalization, bookings, and shared plans.","examples":["Alice Smith"]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"User's email address. Optional — used for account recovery and transactional notifications.","examples":["alice@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"User's phone number in E.164 format.","examples":["+14155552671"]},"usual_plans":{"items":{"type":"string"},"type":"array","title":"Usual Plans","description":"Personalization signals describing what this user typically enjoys. Controls which persona shelves are shown and how activities are ranked. Invalid values return a 400 error.\n\nValid values:\n- `visit-the-city`\n- `fun-with-kids`\n- `friends-hangout`\n- `date-nights`\n- `work-meetups`\n- `meet-new-people`\n- `staying-active`\n- `pet-friendly`\n- `nature-time`\n- `accessible-wheelchair-outings`","examples":[["date-nights","staying-active"]]},"dietary_preferences":{"items":{"type":"string"},"type":"array","title":"Dietary Preferences","description":"Dietary restrictions or preferences used to filter and rank food-related activities. Invalid values return a 400 error.\n\nValid values:\n- `vegan`\n- `vegetarian`\n- `kosher`\n- `halal`\n- `gluten_free`\n- `pescatarian`\n- `alcohol_free`","examples":[["vegan","gluten_free"]]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"User's home city slug (e.g. `new-york`, `san-francisco`). Used as a default macro location context when no explicit location is provided in a query.","examples":["new-york"]},"neighborhood_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Neighborhood Lat","description":"Latitude of the user's home neighborhood center. Pair with `neighborhood_lng` to define a default radius used for proximity ranking.","examples":[40.7128]},"neighborhood_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Neighborhood Lng","description":"Longitude of the user's home neighborhood center. Pair with `neighborhood_lat` to define a default radius used for proximity ranking.","examples":[-74.006]}},"type":"object","required":["external_user_id"],"title":"ProvisionUserRequest"},"ProvisionUserResponse":{"properties":{"external_user_id":{"type":"string","title":"External User Id"},"created":{"type":"boolean","title":"Created","description":"True if a brand-new Outgoing user was created for this request."},"linked":{"type":"boolean","title":"Linked","description":"True if the partner mapping was linked to a pre-existing Outgoing user matched by canonicalized email or phone. When linked, the existing user's profile and onboarding preferences are preserved (see `updated`).","default":false},"updated":{"type":"boolean","title":"Updated","description":"True if onboarding info (usual_plans, dietary_preferences, neighborhood) was written to the user. False when linking to an existing user — their profile is preserved.","default":true}},"type":"object","required":["external_user_id","created"],"title":"ProvisionUserResponse"},"SearchResponse":{"properties":{"message":{"type":"string","title":"Message","description":"Natural-language summary of the search results."},"results":{"items":{"$ref":"#/components/schemas/PartnerSearchResult"},"type":"array","title":"Results","description":"Search results, ranked by relevance. Each carries an activity_id — fetch full booking detail via GET /activities/{activity_id}."}},"type":"object","required":["message"],"title":"SearchResponse"},"ShelvesResponse":{"properties":{"shelves":{"items":{"$ref":"#/components/schemas/PartnerShelf"},"type":"array","title":"Shelves"},"h3_cell":{"type":"string","title":"H3 Cell"},"city":{"type":"string","title":"City"}},"type":"object","required":["shelves","h3_cell","city"],"title":"ShelvesResponse"},"TicketDetails":{"properties":{"download_urls":{"items":{"type":"string"},"type":"array","title":"Download Urls","description":"URLs to download individual ticket PDFs or passes, if available.","examples":[["https://tickets.outgoing.world/dl/abc123.pdf"]]},"confirmation_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Code","description":"Human-readable confirmation code for the booking.","examples":["OG-2024-ABC123"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional information from the venue (e.g. check-in instructions, age restrictions)."}},"type":"object","title":"TicketDetails","description":"Details about the booked tickets, returned once the booking is confirmed."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Outgoing API key"}}},"tags":[{"name":"partner","description":"Partner integration endpoints (Lyft, etc.)"},{"name":"external-identities","description":"Link external accounts (Google Calendar, etc.)"},{"name":"ticket-deliverables","description":"Ticket and booking deliverables"},{"name":"slack","description":"Slack integration webhooks"}],"servers":[{"url":"https://api.outgoing.world"}],"security":[{"BearerAuth":[]}]}