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

# SEO Playbook for Apps

> Turn app-store context into pages — alternative/vs seeds, complaint mining, page briefs and programmatic templates

The SEO playbook sits on top of an **SEO project** (`/dashboard/seo`) and uses what Appeeky already knows about your app — its store listing, its competitors and their reviews — to decide **which pages to build** and **what to put on them**.

It is four tools that feed each other:

| Step | Tool                       | Input                                  | Output                                                                                                  |
| ---- | -------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| 1    | **Alternative / vs seeds** | linked app + competitors               | `{competitor} alternative`, `{app} vs {competitor}`, `best {category} apps` … priced with search volume |
| 2    | **Complaint miner**        | 1–2★ reviews of your app + competitors | complaint themes, verbatim quotes, the Google queries a frustrated user would type                      |
| 3    | **Page briefs**            | one keyword + page type                | title, H1, meta, answer-first intro, outline, FAQ, competitors to include, screenshot brief             |
| 4    | **Programmatic templates** | `{category} app for {persona}`         | every expansion priced and sorted so you only build pages with demand                                   |

The fifth playbook piece — **AI Visibility → Sources cited → Fixes → re-measure** — lives under `/v1/ai-visibility` and is documented in [AI Visibility](/docs/ai-visibility-overview).

<Info>
  All endpoints require an SEO project and are scoped to the authenticated user. Reads and "track" writes cost **0 credits**. The routes that buy provider data or run an LLM (`seed/preview`, `complaints/mine`, `briefs` POST, `templates/expand`) cost **2 credits** each. See [Limits](#limits) below.
</Info>

***

## Where the app context comes from

Every tool resolves the project's app context the same way, in order of preference:

1. **My Apps** — when the project is linked to a My Apps entry (`userAppId`), the app name, description, category, icon and its **My Apps competitors** are used. Competitors with a store id are the ones the complaint miner can review-mine.
2. **Store lookup** — otherwise `seo_projects.app_id` is looked up on the App Store / Play Store.
3. **Project fields** — name and product description as a last resort.

Competitor **brand names** for seeds and templates additionally include the SEO project's own **Competitors tab** (`seo_competitors`): the label you gave, or a brand derived from the domain (`app.sensortower.com` → `sensortower`).

***

## Alternative / vs seeds

### Preview candidates

```
POST /v1/seo/keywords/seed/preview
```

Builds the candidate list and prices it with one metrics call. Nothing is tracked yet.

**Body:**

| Name          | Type      | Description                                |
| ------------- | --------- | ------------------------------------------ |
| `projectId`   | string    | SEO project id (required)                  |
| `competitors` | string\[] | Extra competitor names to include (max 20) |

**Patterns generated per competitor:** `{competitor} alternative`, `{competitor} alternatives`, `apps like {competitor}`, `{competitor} vs {app}`, `{app} vs {competitor}`, `{competitor} free alternative`. Per category: `best {category} apps`, `best {category} app`, `{category} app alternatives`, `free {category} apps`. Plus `{app} alternative`.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.appeeky.com/v1/seo/keywords/seed/preview" \
    -H "X-API-Key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "projectId": "prj_…", "competitors": ["Calm"] }'
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const res = await fetch("https://api.appeeky.com/v1/seo/keywords/seed/preview", {
    method: "POST",
    headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
    body: JSON.stringify({ projectId: "prj_…", competitors: ["Calm"] }),
  });
  const { data } = await res.json();
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "app": { "name": "Headspace", "category": "Health & Fitness", "competitors": ["Calm", "Balance"] },
    "candidates": [
      {
        "keyword": "calm alternative",
        "pattern": "{competitor} alternative",
        "searchVolume": 2400,
        "difficulty": 31,
        "cpc": 1.2,
        "searchIntent": "commercial",
        "alreadyTracked": false
      }
    ],
    "costMicros": 750
  }
}
```

Candidates are sorted by volume. `alreadyTracked` is true when the keyword is already active in the project.

### Track selected candidates

```
POST /v1/seo/keywords/seed/track
```

| Name        | Type      | Description                 |
| ----------- | --------- | --------------------------- |
| `projectId` | string    | required                    |
| `keywords`  | string\[] | keywords to track (max 500) |

Tracked keywords get `source: "template_seed"` and the tag `alt-vs`. Returns `{ keywords, added }` (201).

***

## Complaint miner

1–2★ reviews are people describing, in their own words, the problem they'd Google. The miner pulls them for a set of apps, clusters them with an LLM into themes, and returns the queries to rank for.

### Start a mine

```
POST /v1/seo/complaints/mine
```

| Name                 | Type    | Default | Description                                                                                 |
| -------------------- | ------- | ------- | ------------------------------------------------------------------------------------------- |
| `projectId`          | string  | —       | required                                                                                    |
| `apps`               | array   | `[]`    | Extra apps: `{ appId, platform?, name?, iconUrl? }` or plain id strings. Max 15.            |
| `includeCompetitors` | boolean | `true`  | Add the project's My Apps competitors                                                       |
| `includeOwnApp`      | boolean | `true`  | Add the project's own app — its 1–2★ reviews are the gaps you can own with a "problem" page |
| `pagesPerApp`        | number  | `4`     | Review pages fetched per app (1–8, \~50 reviews per page)                                   |

At most **15 apps** are mined per run (explicit apps first, then competitors, then own app). Names are resolved from the store up front so a queued mine already shows readable app names.

**Response (201 Created):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "mine": {
      "id": "cm_…",
      "projectId": "prj_…",
      "status": "queued",
      "apps": [
        { "appId": "1234567", "platform": "ios", "name": "Calm", "iconUrl": "…", "reviewsFetched": 0, "lowRatingCount": 0 }
      ],
      "reviewCount": 0,
      "clusters": [],
      "error": null,
      "costMicros": 0,
      "createdAt": "…",
      "finishedAt": null
    },
    "mode": "queued",
    "runId": "run_…"
  }
}
```

`mode` is `queued` when a background worker is configured (poll `GET /v1/seo/complaints/:id` until `status` is `done` or `failed`), `inline` otherwise (the response already contains the clusters).

Errors: `no_apps` (400) when nothing could be selected, `dispatch_failed` (502) when the job could not be queued.

### Read mines

```
GET /v1/seo/complaints?project=prj_…&limit=20
GET /v1/seo/complaints/:id
```

A finished mine contains up to **10 clusters**:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "theme": "No offline mode",
  "frequency": "high",
  "quotes": ["Can't play anything without wifi, useless on flights."],
  "searchQueries": ["meditation app that works offline", "offline sleep sounds app"],
  "suggestedPageType": "problem",
  "apps": ["Calm", "Balance"]
}
```

`suggestedPageType` is one of `problem`, `alternative`, `vs`, `roundup`, `use_case`.

### Track queries from a mine

```
POST /v1/seo/complaints/:id/track
```

| Name             | Type      | Description                                   |
| ---------------- | --------- | --------------------------------------------- |
| `queries`        | string\[] | Individual queries to track                   |
| `clusterIndexes` | number\[] | Track every query of these clusters (0-based) |

Pass at least one of the two. Keywords get `source: "complaint"` and the cluster theme as a tag. Metrics are fetched for the tracked queries in the same call.

***

## Page briefs

A brief is everything a writer (or an LLM) needs to produce one page, generated from the keyword's SERP context, your app description and the competitors it should mention honestly.

### Generate

```
POST /v1/seo/briefs
```

| Name              | Type   | Description                                                                |
| ----------------- | ------ | -------------------------------------------------------------------------- |
| `projectId`       | string | required                                                                   |
| `keyword`         | string | target keyword (required, ≤200 chars)                                      |
| `pageType`        | string | `problem` · `alternative` · `vs` · `roundup` · `use_case` (required)       |
| `sourceKind`      | string | `complaint` · `gap` · `keyword` · `template` · `manual` (default `manual`) |
| `sourceRef`       | string | free-form reference to where the idea came from                            |
| `complaintMineId` | string | pull quotes from this mine …                                               |
| `clusterIndex`    | number | … and this cluster                                                         |
| `notes`           | string | extra guidance for the writer (≤1,000 chars)                               |

**Response (201 Created)** — `brief.brief` has this shape:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "title": "Calm Alternative: 7 Meditation Apps That Work Offline (2026)",
  "h1": "Looking for a Calm alternative?",
  "metaDescription": "…",
  "targetKeyword": "calm alternative",
  "secondaryKeywords": ["apps like calm", "calm free alternative"],
  "answerFirst100Words": "…",
  "outline": [{ "h2": "Why people leave Calm", "bullets": ["…"] }],
  "competitorsToInclude": [{ "name": "Balance", "honestPositioning": "…" }],
  "ctaLine": "…",
  "screenshotBrief": "Show the offline library screen with downloads visible.",
  "faq": [{ "question": "…", "answer": "…" }]
}
```

### List, read, update, delete

```
GET    /v1/seo/briefs?project=prj_…&status=draft|published&limit=50
GET    /v1/seo/briefs/:id?format=markdown     → { brief, markdown }
PATCH  /v1/seo/briefs/:id                     { status?, publishedUrl? }
DELETE /v1/seo/briefs/:id
```

Marking a brief `published` with a `publishedUrl` is what lets the rank tracker attribute later movement to that page.

***

## Programmatic templates

### Presets

```
GET /v1/seo/templates/presets?project=prj_…
```

Returns the built-in variable presets (`personas`, `platforms`, `integrations`, `modifiers`, `goals`, `languages`), example templates, and `autoVariables` — `{app}`, `{category}` and `{competitor}` filled from the project's app context.

### Expand

```
POST /v1/seo/templates/expand
```

| Name        | Type      | Description                                                                     |
| ----------- | --------- | ------------------------------------------------------------------------------- |
| `projectId` | string    | required                                                                        |
| `template`  | string    | must contain at least one `{variable}` (≤200 chars)                             |
| `variables` | object    | `{ persona: ["students", "nurses"] }` — explicit values win (≤100 per variable) |
| `presets`   | string\[] | preset ids to use for variables you didn't pass (max 10)                        |
| `limit`     | number    | cap on expansions (1–500, default 500)                                          |

Resolution order per variable: explicit `variables` → chosen preset → any preset for that variable → auto variable. Missing values return `missing_variables` (400).

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "template": "{category} app for {persona}",
    "variables": { "category": ["meditation"], "persona": ["students", "nurses"] },
    "total": 2,
    "truncated": false,
    "rows": [
      { "keyword": "meditation app for students", "pattern": "{category} app for {persona}", "searchVolume": 320, "difficulty": 12, "cpc": null, "searchIntent": "commercial", "alreadyTracked": false, "zeroVolume": false }
    ],
    "costMicros": 750
  }
}
```

Rows are sorted by volume; `zeroVolume` rows are the pages you should **not** build.

### Track expansions

```
POST /v1/seo/templates/track
```

| Name        | Type      | Description                                               |
| ----------- | --------- | --------------------------------------------------------- |
| `projectId` | string    | required                                                  |
| `keywords`  | string\[] | expansions to track (max 500)                             |
| `template`  | string    | used to tag the keywords, e.g. `category-app-for-persona` |

***

## Limits

### Access

The whole SEO module requires a **Startup** or **Agentic Scale** web plan (or an explicit `seo` beta grant). Indie Dev and API-only plans get `403` on every `/v1/seo/*` route.

### Credits

| Route                                           | Credits |
| ----------------------------------------------- | ------: |
| `POST /seo/keywords/seed/preview`               |       2 |
| `POST /seo/complaints/mine`                     |       2 |
| `POST /seo/briefs`                              |       2 |
| `POST /seo/templates/expand`                    |       2 |
| every other `/seo/*` read, track, patch, delete |       0 |

Credits are deducted from the **general** bucket for both API-key and dashboard (web) calls.

### Provider spend

The real cost behind these calls is DataForSEO (keyword metrics) and the LLM (clustering, briefs). Every spend is recorded per project in the usage ledger and shows up as *Spent today* on the project's **Overview** tab. Typical costs:

| Operation                      | What is bought                                        | Approx. cost                                                |
| ------------------------------ | ----------------------------------------------------- | ----------------------------------------------------------- |
| Seed preview / template expand | one metrics batch per 700 keywords                    | \~\$0.00075 per batch; cached keywords are free for 30 days |
| Complaint mine                 | store review fetches (free) + one LLM clustering call | cents, scales with review count (≤600 reviews)              |
| Page brief                     | one LLM call                                          | cents                                                       |

<Warning>
  The project's `dailyProviderBudgetCents` (default **200** = \$2/day) pauses **scheduled rank scans** once exhausted. It does **not** currently block on-demand playbook calls — a seed preview, mine or brief still runs when the daily budget is spent. Credits are the only hard cap on these routes.
</Warning>

### Hard caps

| What                                       |             Cap |
| ------------------------------------------ | --------------: |
| Competitors used for seed patterns         |              12 |
| Seed candidates per preview                |             150 |
| Apps per complaint mine                    |              15 |
| Review pages per app                       | 1–8 (default 4) |
| Low-rating reviews kept per app / per mine |       120 / 600 |
| Clusters per mine                          |              10 |
| Template expansions per request            |             500 |
| Values per template variable               |             100 |
| Keywords tracked per `track` call          |             500 |
| Mines listed per request                   |              50 |
| Briefs listed per request                  |             200 |

There is **no per-plan cap** on the number of mines, briefs or tracked SEO keywords beyond the credit balance and the caps above.
