# Get competitor media

Read stored organic matches and paid-ad observations for one listing.

## Endpoint: GET /v1/competitors/{platform}/{storeId}/media

- Method: `GET`
- Path: `/v1/competitors/{platform}/{storeId}/media`
- Authentication: Bearer API credential or OAuth access token
- Required scope: `context:read`
- MCP tool: `get_app`
- MCP action: `media`

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `platform` | path | ios | yes | The preview supports iOS only. |
| `storeId` | path | numeric string | yes | Apple App Store identifier. |

## 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

Identify the listing. This GET does not invent paid-ad rows when none are stored.

```
curl --get 'https://api.peekpanda.com/v1/competitors/ios/0000000000/media' \
  --header "Authorization: Bearer $PEEKPANDA_TOKEN" \
  --header 'x-genviral-tenancy: personal'
```

## MCP request

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

```
{
  "name": "get_app",
  "arguments": {
    "request": {
      "action": "media",
      "params": {
        "platform": "<platform>",
        "storeId": "<storeId>"
      }
    }
  }
}

```

## Response

organic uses the organic-match page. paidAds stays empty when no ads are stored, and paidAdDestinations reports where the stored ads sent their clicks. paidAds returns a current cursor page with items, nextCursor and hasMore, without collection totals: the listing's live ads, most recently discovered first. Continue it with GET /v1/paid-ads?storeIds={storeId}&status=active&sort=recently_discovered&cursor={nextCursor}; a cursor only continues the query and sort that produced it. Both read the app's stored paid projection, about a minute behind new ads; landing pages not built yet read as an empty list.

```
{
  "organic": {
    "data": [
      {
        "sourceId": "organic-post-example",
        "productName": "Example Calorie Tracker",
        "caption": "A stored organic clip",
        "link": "https://www.tiktok.com/@example/video/1",
        "match": {
          "status": "suggested",
          "evidence": "product_name",
          "score": 0.91
        }
      }
    ],
    "candidateLimitReached": false
  },
  "paidAds": {
    "items": [],
    "nextCursor": null,
    "hasMore": false
  },
  "paidAdDestinations": {
    "appStore": 0,
    "playStore": 0,
    "web": 0,
    "unknown": 0,
    "landingPages": []
  }
}

```

## Source

The public organic corpus, tied to apps by App Store link, website or name; suggestions, not verified ownership.

## Refresh

This read returns stored observations and does not start collection.
