> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gomry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Product Feed

> The whole public catalog as an ACP-conformant product feed

# Get Product Feed

Returns the entire public catalog as a [ACP Product Feed](https://developers.openai.com/commerce/specs/feed) in JSONL — one JSON object per line, no wrapping array.

<Note>
  Requires `catalog: read`. This is the format agent platforms ingest to index your events. It is a **whole-catalog snapshot**, not a paginated endpoint.
</Note>

## Query Parameters

<ParamField query="format" type="string">
  Set to `json` to get a wrapped JSON object with build counters instead of JSONL. Useful for inspecting a feed before shipping it; JSONL is what an uploader consumes.
</ParamField>

## Response

`200` with `Content-Type: application/x-ndjson; charset=utf-8`. Each line is one product row:

<ResponseField name="item_id" type="string">Stable across refreshes — an agent's dedupe key. The event id.</ResponseField>
<ResponseField name="title" type="string">Event name, truncated to 150 characters.</ResponseField>
<ResponseField name="description" type="string">Truncated to 5,000 characters. Falls back to a factual line derived from the event when the organizer left it blank.</ResponseField>
<ResponseField name="url" type="string">The canonical, buyable event page.</ResponseField>
<ResponseField name="brand" type="string">The organizer's name. A real name, never a placeholder.</ResponseField>
<ResponseField name="seller_name" type="string">Also the organizer. Gomry is the marketplace, not the seller.</ResponseField>
<ResponseField name="image_url" type="string">Cover image. Required — see skipped rows below.</ResponseField>
<ResponseField name="availability" type="string">`in_stock`, `out_of_stock`, `pre_order`, `backorder`, or `unknown`.</ResponseField>
<ResponseField name="price" type="string">`"79.99 USD"` — major units, one space, uppercase ISO 4217.</ResponseField>
<ResponseField name="is_eligible_search" type="boolean">Always `true`.</ResponseField>

<ResponseField name="is_eligible_checkout" type="boolean">
  Currently `false` on every row. See below.
</ResponseField>

<ResponseField name="is_digital" type="boolean">
  Always `true`. A ticket is delivered, not shipped — this keeps rows out of shipping-cost and delivery-estimate surfaces.
</ResponseField>

<ResponseField name="product_category" type="string">`Events > Music`, `Events > Virtual`, or `Events`.</ResponseField>
<ResponseField name="target_countries" type="array">Uppercase ISO 3166-1 alpha-2. Where the ticket can actually be bought.</ResponseField>
<ResponseField name="accepts_returns" type="boolean">Always `false`.</ResponseField>

### Length caps

Truncation prefers a word boundary, but only when one falls in the last 15% of the limit — otherwise the text is cut at the limit exactly, so a long unbroken string does not lose a large tail to a distant space. An event is always truncated, never dropped, for being too long.

## Availability mapping

The feed's vocabulary is narrower than the catalog's:

| Catalog        | Feed           |
| -------------- | -------------- |
| `on_sale`      | `in_stock`     |
| `sold_out`     | `out_of_stock` |
| `not_yet_open` | `out_of_stock` |
| `closed`       | `out_of_stock` |
| `unknown`      | `unknown`      |

<Warning>
  `not_yet_open` maps to `out_of_stock`, **not** `pre_order`. Pre-order means "buy now, delivered later"; a sales window that has not opened cannot be bought at all, and saying otherwise sends a buyer to a checkout that refuses them.
</Warning>

## About `is_eligible_checkout`

Every row currently reports `false`.

This flag is a claim about the **merchant's checkout integration**, not about one event's sales window. The spec is explicit that it does not itself complete checkout onboarding and requires a separately enabled integration. Gomry's ACP checkout endpoints are live and conformant, but no agent platform currently consumes them for public purchases — so asserting `true` would claim an integration that is not in place.

It flips in the same change that turns a real integration on. Never per-event.

## Why the feed has no date or venue

It has no field for either. The ACP feed format was written for retail, and there is no date, venue, or location field anywhere in the spec.

`product_category` is therefore the only place the nature of the item survives, which is why it is always populated. Date, venue, and coordinates are available on the [catalog endpoints](/api-reference/catalog/get-catalog-event).

## Rows that are skipped

An event is omitted rather than published with invented data. A row is skipped when it has no quotable price, no organizer name, or **no cover image**.

<Note>
  A missing cover image is the most common reason a live event does not appear in the feed. It will be listable on `/catalog/events` and absent here, with no error. If you expect an event in the feed and it is missing, check its cover image first.
</Note>

## Errors

<ResponseField name="503 feed_not_ready">
  No feed has been built yet. **Retry shortly — do not treat this as an empty catalog.** Sent with `Retry-After`.
</ResponseField>

<ResponseField name="503 feed_stale">
  A feed exists but is too old to stand behind, which means recent rebuilds have been failing. We withhold it rather than serve prices nobody has checked since. Sent with `Retry-After`.
</ResponseField>

<ResponseField name="500 internal_error">
  The stored feed could not be read.
</ResponseField>

<Warning>
  Anything we cannot vouch for returns `503` rather than a short feed. A `200` carrying fewer rows than the catalog holds would be read as "these are all your events", and the platform would de-index everything missing. Treat a non-2xx as "keep yesterday's feed", never as "the catalog shrank".
</Warning>

<Note>
  One case is **not** covered by that guarantee: if an individual event's detail lookup fails, the feed falls back to the list row for that event, which may then be skipped for want of a price. The response is still a `200` and the event is counted in `skipped` rather than `count`.

  So a `200` is not proof the feed is complete. Compare `count` against the previous run with `?format=json`, and treat a sudden drop as a signal to investigate rather than as a catalog that shrank.
</Note>

## Freshness and caching

The feed is **built on a schedule and served from the prepared copy**, so the request returns quickly however large the catalog is — it is not rebuilt while you wait. `Last-Modified` tells you when the copy you received was built, and `?format=json` carries the same timestamp as `meta.built_at`, so you can tell whether anything moved since your last pull.

Responses are `Cache-Control: public, s-maxage=900, stale-while-revalidate=3600`. The feed is identical for every caller, so one cached copy serves all of them. The spec's own refresh cadence is daily; fetching more often than the rebuild interval returns the same copy.

<RequestExample>
  ```bash cURL theme={null}
  curl -H "X-API-KEY: your_api_key" \
    "https://www.gomry.com/api/v1/catalog/feed" \
    -o gomry-feed.jsonl
  ```

  ```bash Inspect with counters theme={null}
  curl -H "X-API-KEY: your_api_key" \
    "https://www.gomry.com/api/v1/catalog/feed?format=json" | jq '.meta'
  ```

  ```python Python theme={null}
  import requests, json

  res = requests.get(
      "https://www.gomry.com/api/v1/catalog/feed",
      headers={"X-API-KEY": "your_api_key"},
  )
  res.raise_for_status()  # never treat a failure as an empty catalog
  rows = [json.loads(line) for line in res.text.splitlines() if line]
  ```
</RequestExample>

<ResponseExample>
  ```json JSONL (one line shown) theme={null}
  {
    "item_id": "AbCdEfGhIjKlMnOpQrSt",
    "title": "Summer Tech Conference 2026",
    "description": "A two-day conference covering the latest in tech.",
    "url": "https://www.gomry.com/event/summer-tech-conference-2026-AbCdEfGhIjKlMnOpQrSt",
    "brand": "Tech Events Co",
    "seller_name": "Tech Events Co",
    "image_url": "https://storage.googleapis.com/...",
    "availability": "in_stock",
    "price": "79.00 USD",
    "is_eligible_search": true,
    "is_eligible_checkout": false,
    "is_digital": true,
    "product_category": "Events > Technology",
    "target_countries": ["US"],
    "accepts_returns": false
  }
  ```

  ```json format=json theme={null}
  {
    "data": [ "…rows…" ],
    "meta": { "count": 11, "scanned": 15, "skipped": 4 }
  }
  ```
</ResponseExample>
