# Browse paid-ad shelves

Read paid ads as per-advertiser shelves, plus shelves for newly launched and long-running creatives.

## Endpoint: GET /v1/paid-ads/rails

- Method: `GET`
- Path: `/v1/paid-ads/rails`
- Authentication: Bearer API credential or OAuth access token
- Required scope: `context:read`
- MCP tool: `get_ads`
- MCP action: `rails`

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `category` | query | category | no | Limit shelves to one category. |
| `platform` | query | meta | tiktok | no | Advertising platform. |
| `search` | query | string, max 120 | no | Advertiser or creative text. Shelves are grouped from every match, so a search matching more than 30,000 ads answers 422 invalid_request; use a more specific term, or page the matches through Search paid ads. |
| `sort` | query | most_tested | longest_running | most_live | recently_tracked | no | Order of the advertiser shelves. Defaults to most_tested. |
| `railSize` | query | integer, 4-20 | no | Creatives on each shelf. Defaults to 10. |
| `advertiserLimit` | query | integer, 1-20 | no | Advertiser shelves to return. Defaults to 8. |

## Known errors

- 400 invalid_request for an invalid filter, identity or limit.
- 401 authentication_required for a missing or invalid credential.
- 402 subscription_required when access is not entitled.
- 403 authorization_required for a missing scope or unavailable preview access.
- 403 workspace_not_accessible when x-genviral-tenancy names a workspace the credential cannot reach.
- 404 not_found when the record is outside the accessible catalogue.
- 429 rate_limited; honor Retry-After when present.
- 429 daily_call_limit; the plan's daily API and MCP calls are used until 00:00 UTC.

## Request

Every filter is optional. This read serves stored ads and does not collect new ones.

```
curl --get 'https://api.peekpanda.com/v1/paid-ads/rails' \
  --header "Authorization: Bearer $PEEKPANDA_TOKEN" \
  --header 'x-genviral-tenancy: personal'
```

## MCP request

Call get_ads with action rails. Path fields go in params, filters in query, and a write body in body.

```
{
  "name": "get_ads",
  "arguments": {
    "request": {
      "action": "rails"
    }
  }
}

```

## Response

rails is empty when nothing is stored. totals uses the same counts as the ranking read.

```
{
  "generatedAt": "2026-09-24T09:00:00.000Z",
  "rails": [],
  "totals": {
    "ads": 1200,
    "liveAds": 360,
    "advertisers": 12,
    "liveAdvertisers": 9,
    "newAdsLast7Days": 48,
    "advertisersShippingLast7Days": 4,
    "stoppedLast7Days": 31,
    "halfLifeDays": 12,
    "stillRunningAt30Days": 0.3,
    "medianRunDays": 9,
    "longestRunDays": 188,
    "liveBeyond60Days": 40,
    "reachDisclosedAds": 0,
    "unlinkedAds": 25,
    "uncategorizedLiveAds": 20
  }
}

```

## Source

Stored paid-ad observations. Rankings count creatives and how long they stay live. There is no spend or impression field.

## Refresh

These reads do not collect new ads.
