# Compare competitors

Read up to 10 iOS apps side by side: identity, rating and current monthly download and revenue estimates in one call.

## Contract status

Preview contract: these routes and tools are implemented in the shared contract, but the public host, customer API access and OAuth flow have not been deployment verified.

## Endpoint: GET /v1/competitors/compare

- Method: `GET`
- Path: `/v1/competitors/compare`
- Authentication: Bearer API credential or OAuth access token
- Required scope: `context:read`
- MCP tool: `search_apps`
- MCP action: `compare`

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `storeIds` | query | comma-separated numeric strings, 2–10 distinct | no | Apple App Store identifiers. Put the app in question first; the response keeps this order. |

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

Send every store ID in one storeIds value. Duplicates are removed before the 2–10 bound applies.

```
curl --get 'https://api.peekpanda.com/v1/competitors/compare' \
  --header "Authorization: Bearer $PEEKPANDA_TOKEN" \
  --header 'x-genviral-tenancy: personal' \
  --data-urlencode 'storeIds=0000000000,1145935738'
```

## MCP request

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

```
{
  "name": "search_apps",
  "arguments": {
    "request": {
      "action": "compare"
    }
  }
}

```

## Response

subjectStoreId is the first requested app, the one in question, even when PeekPanda does not hold it. data keeps the request order. Each estimate is the current worldwide monthly range (or below_floor under 5,000 a month) with the night it was scored, and previous is the estimate that held a month earlier. An estimate is null when no nightly score covers the app: unknown, not zero. Store IDs PeekPanda does not hold are listed in missingStoreIds.

```
{
  "subjectStoreId": "0000000000",
  "data": [
    {
      "app": {
        "storeId": "0000000000",
        "name": "Example Calorie Tracker",
        "iconUrl": null,
        "genre": "Health & Fitness",
        "category": "health_and_fitness",
        "developerName": "Example Labs",
        "rating": 4.7,
        "totalRatings": 12450
      },
      "monthlyInstalls": {
        "kind": "range",
        "scope": "worldwide",
        "range": {
          "low": 62000,
          "mid": 130000,
          "high": 270000
        },
        "observedAt": "2026-09-27T00:00:00.000Z",
        "previous": {
          "kind": "range",
          "scope": "worldwide",
          "range": {
            "low": 51000,
            "mid": 110000,
            "high": 220000
          },
          "observedAt": "2026-08-28T00:00:00.000Z"
        }
      },
      "monthlyRevenue": {
        "kind": "below_floor",
        "scope": "worldwide",
        "ceiling": 5000,
        "observedAt": "2026-09-27T00:00:00.000Z",
        "previous": null
      },
      "currency": "USD"
    }
  ],
  "missingStoreIds": [
    "1145935738"
  ]
}

```

## Source

PeekPanda estimates, refreshed nightly, beside dated public App Store listing observations. Figures are worldwide and monthly ranges, never store-reported numbers.

## Refresh

This read returns stored estimates and does not start collection.
