# Outgoing Partner API (v1.1)

OpenAPI spec: https://api.outgoing.world/partner/v1/openapi.json

REST API for partner integration with the Outgoing Local Experience Platform —
homescreen shelves, activity search, booking, and user provisioning.

## For AI agents

If you're an AI agent, prefer the machine-readable spec over this rendered page —
it parses more reliably than scraped HTML:

- **OpenAPI spec (JSON):** <https://api.outgoing.world/partner/v1/openapi.json> — the preferred format.
- **Docs index (llms.txt):** <https://api.outgoing.world/partner/v1/llms.txt> — Markdown index of endpoints and resources.
- **Full docs (llms-full.txt):** <https://api.outgoing.world/partner/v1/llms-full.txt> — every endpoint and schema as plain Markdown.

All three are public and require no authentication.

## Quick start

```bash
curl https://api.outgoing.world/partner/v1/homescreen \
  -H "Authorization: Bearer og_api_<your-key>" \
  -H "X-External-User-Id: <your-user-id>"
```

Every request needs two headers: a bearer API key (next section) and an
`X-External-User-Id` identifying *which* of your users the call is for.

## Authentication

API keys carry the `partner` scope and are issued per integration. Keys are
prefixed `og_api_` (production) or `og_api_staging_` (staging). Send them as
a bearer token:

```
Authorization: Bearer og_api_<secret>
```

### Per-request user assertion

A partner key doesn't bind to a single Outgoing user — your application
calls on behalf of many users, and you assert which one per request via:

```
X-External-User-Id: <your-stable-user-id>
```

The first time you reference a given `X-External-User-Id`, call
`POST /partner/v1/users` to provision the Outgoing user and bind the
external id to them. Subsequent calls with the same id resolve to the same
Outgoing user; by asserting that id you attest that you have verified the
end user's identity and have permission to act on their behalf.

## Rate limits

Limits are configured per API key (per-second, per-minute, per-hour). A
`429 Too Many Requests` response indicates you've exceeded them — back off
and retry after the current window resets. Contact us if your workload
needs a different ceiling.

## Errors

All error responses share one JSON shape:

```json
{ "error": "human-readable description" }
```

Status codes follow standard REST conventions — `401` for bad/missing
credentials, `404` for unknown resources, `400` for validation failures,
`429` for rate limits, `500` for our bugs.

## Changelog

### v1.1 — April 2026

**Breaking change:** ticket pricing is now session-based.

- The `types` array has been removed from `ticket_price`.
- `ticket_price.sessions[]` is always present. Each session has `date`
  (`YYYY-MM-DD` or `null`), `time` (`HH:MM` or `null`), and a `tickets`
  array with `name`, `price`, `fee`, `total`, and `available` fields.
- Single-date events have one session; multi-date events have one session
  per date/time.
- `min`, `max`, `currency`, and `label` on `ticket_price` are unchanged.

## Endpoints

### GET /partner/v1/activities/{activity_id}

Get Activity

Get activity details and record a SEEN interaction for the user.

**Parameters**

- `activity_id` (path, string, required)
- `Accept-Language` (header, string (nullable), optional) — BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names and descriptions. Defaults to `en`.
- `x-external-user-id` (header, string, required) — The partner's external user ID

**Responses**

- `200`: Successful Response → `PartnerActivityDetail`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### GET /partner/v1/activities/{activity_id}/prices

Get Activity Prices

Scrape the latest ticket prices for an activity.

Triggers an on-demand scrape of the activity's booking page and returns
the refreshed ticket pricing. Typically takes 5-15 seconds depending on
the ticketing platform.

**Parameters**

- `activity_id` (path, string, required)

**Responses**

- `200`: Successful Response → `PricesResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### POST /partner/v1/bookings

Create Booking

Submit a booking request for an activity.

Accepts booking details including the activity, ticket count,
pre-authorized amount, and a `webhook_url` where the result will be
delivered once the booking completes (or fails).

The synchronous response confirms acceptance. The final result is
delivered asynchronously as a **BookingWebhookPayload** POST to
your `webhook_url`. See the `BookingWebhookPayload` schema for the
full callback payload structure.

**Request body** (`application/json`): `CreateBookingRequest`

**Responses**

- `202`: Booking request accepted for asynchronous processing. Results will be delivered to the `webhook_url` as a `BookingWebhookPayload` (see Webhook Callback below). → `CreateBookingAcceptedResponse`
- `401`: Unauthorized — missing or invalid API key.
- `402`: Payment required — the `payment_token` is invalid, expired, or not registered. Verify the token with your payment provider and retry.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### GET /partner/v1/h3

Lat Lng To H3

Convert a latitude/longitude pair into an H3 cell index.

Convenience endpoint so partners don't need an H3 library client-side.
The default resolution matches what the ``/homescreen`` endpoint expects.

**Parameters**

- `lat` (query, number, required) — Latitude in decimal degrees.
- `lng` (query, number, required) — Longitude in decimal degrees.
- `resolution` (query, integer, optional) — H3 resolution (0–15). Defaults to 7 (neighborhood-sized, ~1.7 km edge).

**Responses**

- `200`: Successful Response → `H3CellResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### GET /partner/v1/homescreen

Get Homescreen

Return cacheable homescreen activities for a location and persona.

The partner sends an H3 resolution-7 (neighborhood-sized) cell
and an optional list of ``usual_plans`` that influence relevance ranking
via suitability boosts (e.g. kid-friendly, pet-friendly).
The cell is used only to determine the search centre; the actual
search radius uses density-based logic (ENTIRE_AREA base × max shelf multiplier).
The response includes ``Cache-Control`` headers indicating how long the caller should cache the response.

**Parameters**

- `h3_cell` (query, string, required) — 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.
- `limit` (query, integer (nullable), optional) — Maximum number of activities to return per shelf. Defaults to 250 (the server maximum) when omitted.
- `usual_plans` (query, array<string> (nullable), optional) — User's usual plans — controls relevance ranking via suitability boosts.  Valid values: - `fun-with-kids` - `pet-friendly` - `date-nights` - `meet-new-people` - `friends-hangout` - `staying-active` - `nature-time`
- `shelved` (query, boolean, optional) — 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.
- `booking_filter` (query, BookingFilter, optional) — Filter activities by booking availability.  - `all` (default): return all activities. - `bookable`: only activities with `is_bookable: true` (bookable via the POST /bookings endpoint). - `bookable_or_free`: activities that are bookable OR free (no ticket price / walk-in).
- `Accept-Language` (header, string (nullable), optional) — BCP-47 language tag (e.g. `en`, `es`, `fr`). Controls the language of activity display names. Defaults to `en`.

**Responses**

- `200`: Successful Response → `ShelvesResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### GET /partner/v1/orders

Get Orders

List bookings, deliverables, and emails for the authenticated partner user.

**Parameters**

- `Accept-Language` (header, string (nullable), optional)
- `x-external-user-id` (header, string, required) — The partner's external user ID

**Responses**

- `200`: Successful Response → `OrdersResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### GET /partner/v1/search

Search

Search for activities using natural language.

Powered by the same AI agent as the Outgoing app. Returns a ranked list of
matching activities plus a natural-language summary message.

**Parameters**

- `prompt` (query, string, required) — Natural language search query (e.g. 'jazz concerts this weekend', 'family-friendly brunch').
- `city` (query, string, optional) — City slug to search in. Supported: new_york, san_francisco, paris, tokyo, chicago, bengaluru, seville, marseille, ann_arbor, loire_atlantique, dutchess_county, basque_france.
- `lat` (query, number (nullable), optional) — Latitude to center the search (defaults to city centre).
- `lng` (query, number (nullable), optional) — Longitude to center the search (defaults to city centre).
- `x-external-user-id` (header, string, required) — The partner's external user ID

**Responses**

- `200`: Successful Response → `SearchResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### POST /partner/v1/slack/workspaces/bind

Bind Slack Workspace

Bind the Slack workspace referenced by ``bind_token`` to this API key.

**Request body** (`application/json`): `BindWorkspaceRequest`

**Responses**

- `200`: Successful Response → `BindWorkspaceResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

### POST /partner/v1/users

Provision User

Provision (or update) a partner user mapping.

Creates an Outgoing user with the provided personalization signals and maps
the partner's external user ID to it.  Idempotent: if the mapping already
exists the profile and onboarding info are updated.

If the email or phone matches an existing Outgoing user, the mapping is
created pointing to that user **without** modifying their profile or
onboarding preferences.  The response will have ``linked=True`` and
``updated=False`` to indicate this.

By calling this endpoint the partner **attests** they have verified the
user's identity and have permission to act on their behalf.

**Request body** (`application/json`): `ProvisionUserRequest`

**Responses**

- `201`: Successful Response → `ProvisionUserResponse`
- `401`: Unauthorized — missing or invalid API key.
- `422`: Validation Error → `HTTPValidationError`
- `429`: Rate limit exceeded. Back off and retry after the limit window resets.

## Schemas

### PartnerActivityDetail

- `activity_id` (string, required)
- `name` (string, required)
- `short_description` (string (nullable), optional)
- `picture_url` (string (nullable), optional)
- `location` (object (nullable), optional)
- `semantic_location` (string (nullable), optional)
- `highlights` (array<string>, optional)
- `display_label` (string, optional)
- `is_bookable` (boolean, optional) — Does not indicate whether the activity requires booking, it indicates that ticket purchase is available through the Create Booking endpoint of this API.
- `ticket_price` (PartnerTicketPrice (nullable), optional) — Ticket pricing with individual ticket types available for booking.
- `estimated_fulfillment` (string (nullable), optional) — 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.
- `next_datetime` (string (nullable), optional) — Next upcoming event date/time in ISO 8601 format, or null if not scheduled.
- `booking_domain` (string (nullable), optional) — (Debug only) Ticketing platform domain. Populated only when `debug=true` query param is set.
- `venue` (string (nullable), optional) — Venue name where the activity takes place.
- `tips` (array<string>, optional) — Curated tips about the activity.
- `upcoming_datetimes` (array<string>, optional) — Upcoming event dates/times in ISO 8601 format, sorted chronologically.
- `picture_urls` (array<string>, optional)
- `is_open_now` (boolean (nullable), optional)
- `search_category` (array<string>, optional)

### HTTPValidationError

- `detail` (array<ValidationError>, optional)

### PricesResponse

- `activity_id` (string, required)
- `ticket_price` (PartnerTicketPrice (nullable), optional) — Fresh ticket pricing after scrape, or null if no prices found.
- `scrape_status` (string, required) — Result of the scrape: 'success', 'no_prices', or 'error'.

### CreateBookingRequest

- `external_user_id` (string, required) — Your system's unique identifier for the user making this booking. Must have been provisioned via `POST /users` before calling this endpoint.
- `activity_id` (string, required) — UUID of the activity to book, as returned by the shelves or activity detail endpoints. Only activities with `is_bookable: true` can be booked.
- `tickets` (array<BookingTicketItem>, required) — List of ticket types and quantities to book. Each item specifies a ticket type name and how many to purchase.
- `authorization_amount_cents` (integer, required) — 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.
- `idempotency_key` (string, required) — 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 result without creating a new booking.
- `webhook_url` (string, required) — 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 DRYRUN tokens. The payload is a `BookingWebhookPayload` JSON object. Your server should respond with a 2xx status within 10 seconds.
- `payment_token` (string, required) — Payment authorization token from your payment system, representing a pre-authorized charge for the `authorization_amount_cents`. For testing, pass `DRYRUN_SUCCESS` or `DRYRUN_FAIL` to simulate booking outcomes without charging.

### CreateBookingAcceptedResponse

Synchronous response returned when a booking request is accepted for processing.

- `booking_id` (string, required) — Unique identifier for this booking, assigned by Outgoing.
- `idempotency_key` (string, required) — Echo of the idempotency key from the request.
- `status` (string, optional) — Always `accepted` — the booking is now being processed asynchronously.

### H3CellResponse

- `h3_cell` (string, required) — H3 cell index at the requested resolution.
- `resolution` (integer, required) — H3 resolution used.
- `lat` (number, required) — Input latitude.
- `lng` (number, required) — Input longitude.

### ShelvesResponse

- `shelves` (array<PartnerShelf>, required)
- `h3_cell` (string, required)
- `city` (string, required)

### OrdersResponse

- `bookings` (array<OrderBooking>, optional)
- `deliverables` (array<OrderDeliverable>, optional)
- `emails` (array<OrderEmail>, optional)

### SearchResponse

- `message` (string, required) — Natural-language summary of the search results.
- `activities` (array<PartnerActivity>, optional) — Activities matching the search query, ranked by relevance.

### BindWorkspaceRequest

- `bind_token` (string, required) — The token from the bot's welcome DM.

### BindWorkspaceResponse

- `ok` (boolean, required)
- `team_id` (string, required)
- `bound` (boolean, required) — True if this request wrote the FK (vs. already bound).

### ProvisionUserRequest

- `external_user_id` (string, required) — 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.
- `first_name` (string (nullable), optional) — User's first name. Used for ticket purchases that require separate name fields.
- `last_name` (string (nullable), optional) — User's last name. Used for ticket purchases that require separate name fields.
- `display_name` (string (nullable), optional) — A name used to identify this user, e.g. for display personalization, bookings, and shared plans.
- `email` (string (nullable), optional) — User's email address. Optional — used for account recovery and transactional notifications.
- `phone` (string (nullable), optional) — User's phone number in E.164 format.
- `usual_plans` (array<string>, optional) — 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.  Valid values: - `visit-the-city` - `fun-with-kids` - `friends-hangout` - `date-nights` - `work-meetups` - `meet-new-people` - `staying-active` - `pet-friendly` - `nature-time` - `accessible-wheelchair-outings`
- `dietary_preferences` (array<string>, optional) — Dietary restrictions or preferences used to filter and rank food-related activities. Invalid values return a 400 error.  Valid values: - `vegan` - `vegetarian` - `kosher` - `halal` - `gluten_free` - `pescatarian` - `alcohol_free`
- `city` (string (nullable), optional) — 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.
- `neighborhood_lat` (number (nullable), optional) — Latitude of the user's home neighborhood center. Pair with `neighborhood_lng` to define a default radius used for proximity ranking.
- `neighborhood_lng` (number (nullable), optional) — Longitude of the user's home neighborhood center. Pair with `neighborhood_lat` to define a default radius used for proximity ranking.

### ProvisionUserResponse

- `external_user_id` (string, required)
- `created` (boolean, required) — True if a brand-new Outgoing user was created for this request.
- `linked` (boolean, optional) — 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`).
- `updated` (boolean, optional) — 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.

### PartnerTicketPrice

Ticket pricing exposed to partners, with session-based ticket breakdown.

- `min` (number, required) — Minimum ticket price (including fees).
- `max` (number, required) — Maximum ticket price (including fees).
- `currency` (string, required) — ISO 4217 currency code (e.g. 'USD', 'EUR').
- `label` (string, required) — Human-readable price label (e.g. '$20-$50').
- `sessions` (array<PartnerSession>, optional) — Ticket availability grouped by session. Single-date events have one session.

### ValidationError

- `loc` (array<string | integer>, required)
- `msg` (string, required)
- `type` (string, required)

### BookingTicketItem

A single ticket type and quantity within a booking request.

- `ticket_type_name` (string, required) — Name of the ticket type to book, exactly as returned in `ticket_price.sessions[].tickets[].name` (e.g. 'General Admission', 'VIP').
- `num_tickets` (integer, optional) — Number of tickets to book for this ticket type.
- `session_date` (string (nullable), optional) — 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.
- `session_time` (string (nullable), optional) — Session start time in HH:MM 24h format matching `ticket_price.sessions[].time`. Paired with `session_date` to disambiguate multi-session events.

### PartnerShelf

- `slug` (string, required)
- `display_name` (string, required)
- `activities` (array<PartnerActivity>, required)

### OrderBooking

- `id` (string, required)
- `status` (string, required)
- `activity_name` (string (nullable), optional)
- `picture_url` (string (nullable), optional)
- `authorization_amount_cents` (integer (nullable), optional)
- `created_at` (string, required)

### OrderDeliverable

- `id` (string, required)
- `deliverable_type` (string, required)
- `filename` (string, required)
- `content_type` (string, required)
- `file_size_bytes` (integer (nullable), optional)
- `partner_booking_id` (string, required)

### OrderEmail

- `id` (string, required)
- `subject` (string (nullable), optional)
- `classification` (string (nullable), optional)
- `processing_status` (string, required)
- `partner_booking_id` (string, required)
- `received_at` (string, required)

### PartnerActivity

- `activity_id` (string, required)
- `name` (string, required)
- `short_description` (string (nullable), optional)
- `picture_url` (string (nullable), optional)
- `location` (object (nullable), optional)
- `semantic_location` (string (nullable), optional)
- `highlights` (array<string>, optional)
- `display_label` (string, optional)
- `is_bookable` (boolean, optional) — Does not indicate whether the activity requires booking, it indicates that ticket purchase is available through the Create Booking endpoint of this API.
- `ticket_price` (PartnerTicketPrice (nullable), optional) — Ticket pricing with individual ticket types available for booking.
- `estimated_fulfillment` (string (nullable), optional) — 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.
- `next_datetime` (string (nullable), optional) — Next upcoming event date/time in ISO 8601 format, or null if not scheduled.
- `booking_domain` (string (nullable), optional) — (Debug only) Ticketing platform domain. Populated only when `debug=true` query param is set.

### PartnerSession

A date/time session with its available tickets, exposed to partners.

- `date` (string (nullable), optional) — Session date in YYYY-MM-DD format, or null if not date-specific.
- `time` (string (nullable), optional) — Session start time in HH:MM (24h) format, or null.
- `tickets` (array<PartnerSessionTicket>, optional) — Tickets available for this session.

### PartnerSessionTicket

A ticket within a session, exposed to partners.

- `name` (string, required) — Ticket name (e.g. 'General Admission', 'VIP').
- `price` (number, required) — Ticket price in the activity's currency.
- `fee` (number, optional) — Additional fee on top of the base price.
- `total` (number, required) — Total the buyer pays (price + fee).
- `available` (boolean, optional) — Whether this ticket can currently be purchased.
- `description` (string (nullable), optional) — Additional description for this ticket.
- `on_sale_status` (string (nullable), optional) — Sale status (e.g. 'AVAILABLE', 'SOLD_OUT').
- `is_free` (boolean, optional) — True if this is a free/RSVP ticket.
- `min_quantity` (integer, optional) — Minimum number of tickets per order.
- `max_quantity` (integer (nullable), optional) — Maximum number of tickets per order, or null if unlimited.
- `category` (string (nullable), optional) — Ticket category (e.g. 'admission', 'add_on').
