# Read a public starter preview

The longest-running live ads of a curated niche, and how long they have run, without an API key.

## Endpoint: GET /v1/public/starter-boards/{nicheKey}

- Method: `GET`
- Path: `/v1/public/starter-boards/{nicheKey}`
- Authentication: None required. Optional: a Bearer API credential or OAuth access token, which makes the call a metered credentialed call.
- Required scope: `context:read`
- MCP tool: `starter_boards`
- MCP action: `public_preview`

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `nicheKey` | path | string | yes | A curated niche key from GET /v1/public/starter-boards, such as calorie-tracking. App keys are refused. |

## Known errors

- 400 invalid_request for a malformed niche key or an app-<numericStoreId> key; seeded app previews need the authenticated GET /v1/starter-boards/{nicheKey}.
- 401 authentication_required for an invalid credential; a credential that fails is never treated as anonymous.
- 403 authorization_required for an MCP credential without context:read.
- 404 not_found for a niche key outside the curated starter boards.
- 429 rate_limited with window (minute or day) and retryAfterSeconds for an anonymous caller, or the credential’s per-minute read limit; honor Retry-After.
- 429 daily_call_limit; the plan's daily API and MCP calls are used until 00:00 UTC.
- 503 public_preview_unavailable while the public starter preview is switched off; retry later.
- 503 timeout when the stored read ran out of its time budget; retry after Retry-After.

## Request

No query or body, and no credential needed. Use a curated niche key.

```
curl --get 'https://api.peekpanda.com/v1/public/starter-boards/calorie-tracking'
```

## MCP request

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

```
{
  "name": "starter_boards",
  "arguments": {
    "request": {
      "action": "public_preview",
      "params": {
        "nicheKey": "<nicheKey>"
      }
    }
  }
}

```

## Response

For each app: longestRunningAds, its two longest-running live ads with a start date (earliest start first), each with platform, format, a short-lived thumbnail, its start, days live (whole UTC days from its start day to today, as board tiles count them) and when the ad library last saw it running (stillLiveCheckedAt). They are the longest-running ads, ordered by run length, not only ads past a minimum age. Then five top keywords; and how many live ads, keywords and organic posts the preview leaves out. liveAdsTotal is null when the app’s paid ads were never collected. Three organic posts are shown across the board, with organicPostsTotal. The preview returns never ad copy, advertiser names, video, links or ad identifiers. The example below is illustrative only, not a live observation.

```
{
  "key": "calorie-tracking",
  "label": "Calorie tracking",
  "apps": [
    {
      "storeId": "0000000000",
      "name": "Example Calories",
      "iconUrl": null,
      "developer": null,
      "category": null,
      "rating": null,
      "ratingCount": null,
      "listingObservedAt": null,
      "paidObservedAt": "2026-10-16T09:00:00.000Z",
      "longestRunningAds": [
        {
          "platform": "meta",
          "format": "video",
          "thumbnailUrl": "https://cdn.peekpanda.com/paid-ad-media/sha256/example?expires=1800000000&signature=example",
          "startedAt": "2026-05-01T08:00:00.000Z",
          "daysLive": 168,
          "stillLiveCheckedAt": "2026-10-16T08:00:00.000Z"
        }
      ],
      "keywords": [
        {
          "keyword": "calorie counter",
          "rank": 3,
          "popularity": 61,
          "observedAt": "2026-10-16T00:00:00.000Z"
        }
      ],
      "liveAdsTotal": 40,
      "liveAdsLocked": 39,
      "keywordsLocked": 12,
      "organicPostsLocked": 5
    }
  ],
  "organicPosts": [],
  "organicPostsTotal": 6
}

```

## Source

The curated PeekPanda starter-board niches and stored public competitor evidence: the app paid ad membership, the paid ad library’s last sighting of each ad, stored keyword rankings and the Viral Library. No private customer content is returned.

## Refresh

Reads stored observations only and never starts provider collection. A niche is read again at most every five minutes; thumbnail links are signed for each response and stay valid for 15 to 30 minutes.

## Access, limits and caching

Without an Authorization header the call is anonymous and free, counted in shared storage only (a 503 public_preview_unavailable answers when it cannot be counted): each client network (an IPv4 address or an IPv6 /64) may send 20 requests a minute and 200 a UTC day, and all anonymous callers together share 1,200 a minute. A 429 says which window was used up (`window`: `minute` or `day`) and when to retry (`retryAfterSeconds` and `Retry-After`); `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` describe the current window. With an Authorization header the call is credentialed: it needs context:read, counts toward the plan holder’s shared daily allowance (Peek 30, Panda 1,000, reset 00:00 UTC) and the per-minute read limit, and an invalid credential is refused rather than treated as anonymous. Responses are `Cache-Control: private, max-age=60`, vary by Authorization and are marked `X-Robots-Tag: noindex`.
