# Rankora — RECON BUILD SPEC (from real "Adaptify SEO" corpus)

> 100%-accurate extraction. Real field names / enums / shapes only. Anything not in the
> recon is marked **NOT FOUND IN RECON**. Secrets replaced with «redacted».
> Primary sources: `/Users/bilalkarim/Documents/siki-etc/adaptify-local/` (endpoint-responses,
> deep-responses, `static/_next/static/chunks/*.js`), and the WP plugin at
> `/Users/bilalkarim/Downloads/adaptify-seo/plugin.php`.
>
> Note on Firestore: `fs_*.json` files wrap a Firestore REST response
> (`res.documents[].fields` in Firestore typed-value encoding). Values below are decoded.
> A "bot" document == a "site". Backend host is `https://api.adaptify.ai`; the WP plugin
> talks to a mirror host `https://api.ttsp4712.ai` (same `/open/*` routes).

---

## 1. Site / Bot settings object  (master shape)

Source: `endpoint-responses/fs_bot_doc_full.json` (single full SEO bot doc, decoded),
cross-checked with `endpoint-responses/fs_bots.json` + `deep-responses/fs_bots.json` (10 docs).

### Top-level keys, types, example values
Collection: `bots/{botId}` (botId e.g. `6Xxnfv8opt2WdrTweVoZ`).

| field | type | example |
|---|---|---|
| `companyName` | string | `"VSSR"` / `"Cow Boy Disco Hat Shop"` |
| `botName` | string | `"https://cowboydiscohatshop.com/"` (often the URL, can be `""`) |
| `sourceUrl` | string | `"https://www.vssrexotics.com"` |
| `sourceUrlCanonical` | string | `"https://straightline.golf/"` |
| `userId` | string | `"84f9LBFK0Tg5zs4WjSUZOIDjw2u2"` |
| `status` | string | `"no data"` (seen value) |
| `createdAt` | timestamp-string | `"2026-03-09T20:14:41.322Z"` |
| `analyzedAt` | timestamp-string | `"2026-03-09T20:14:47.651339Z"` |
| `logoUrl` | string(url) | `"https://cdn.prod.website-files.com/.../favicon.png"` |
| `fullLogoUrl` | string(url) | (full logo variant) |
| `brandColor` | string(hex) | `"#80478e"` |
| `chatBubbleColor` | string(hex) | `"#5251A6"` |
| **`websitePlatform`** | string enum | `"wordpress"` (see §3 for full enum) |
| `websitePlatformDetails` | string | `"not specified"` |
| **`autoPublishEvery`** | int | `2` (days between auto-publishes; drives freq label — §2) |
| `publishParams` | map | see §2 (WordPress creds + approval flags) |
| `planBuilder` | map | see §2 (contentPlan / backlinkPlan / activation) |
| `pluginStatus` | map | `{active, version:"1.5.9", wpVersion:"7.0", lastActivatedAt, lastEventAt}` |
| `wpConnectionSetup` | map | `{token:«redacted», createdBy, createdAt, expiresAt}` (§3 pairing) |
| `backlinkProfile` | map | legacy single profile (see §4) |
| `backlinkProfiles` | map<profileId,profile> | keyed by `profile_<ts>` (see §4) |
| `primaryBacklinkProfileId` | string | `"profile_1782504499291"` |
| `companyInfo` | string | long free-text company description |
| `companyService` | string | services text |
| `companyLocations` | string | `"USA\n6"` (locations text) |
| `companyReach` | string | reach text (national/local) |
| `companyCEO` | string | |
| `companyFounder` | string | |
| `companyContact` | string | |
| `companySeoGoals` | map | `{primaryGoal, secondaryGoal}` |
| `companySeoStrategy` | string | strategy paragraph |
| `companyBacklinkStrategy` | map | `{title, outlet, category}` e.g. category `"Fashion"` |
| `companyCompetitorsQuery` | string | `"exotic car slingshot rental Las Vegas unlimited miles"` |
| `competitorSites` | array<string(url)> | `["https://lvdroptop.com", ...]` (§7) |
| `competitorNoMention` | bool | `true` (do NOT mention competitors) (§7) |
| `companyAvatar` | string | primary ICP persona text |
| `companyAvatarName` | string | `"Vegas Adventure Renter"` |
| `companyAvatarTags` | array<string> | `["tourist","thrill-seeker","group-traveler"]` |
| `companySecondaryAvatar` / `...Name` / `...Tags` | string / string / array | secondary persona |
| `keywordsInitial` | array<map> | SEMrush seed keywords (see below) |
| `backlinkTopicsInitial` | array<string> | `["company building","customer experience",...]` |
| `siteDrInitial` | int | `3` (initial Domain Rating) |
| `clusterPlanning` | map | `{phase:"createInitialClusters", status:"done", startedAt, doneAt, source:"frontend", activeCount, activeTokens}` |
| `createInitialClusters` | bool | `false` (disable-auto-clusters flag, §5) |
| `geoCadence` | map | GEO refresh scheduler (§6) |
| `visibilityAnalysis` | map | AI-visibility run + prompts (§6) |
| `onboardingVisibilityResults` | map | per-prompt brand/competitor results (§6) |
| `enableOnboardingVisibility` | bool | `true` |
| `newOnboarding` | bool | `true` |
| `seoAudit` | map | PageSpeed + audit results (see below) |
| `localStrategy` | string | `"The Local SEO plan aims to enhance the nationwide visibility..."` (§8) |
| `googleAccessToken` / `googleRefreshToken` / `googleAuthCode` | string | «redacted» (GSC OAuth) |
| `bingAddSiteStatus` / `bingAddSiteCompletedAt` / `bingAddSiteRecoveredAt` / `bingAddSiteRecoverySiteUrl` / `bingAddSiteRecoverySource` | string | Bing Webmaster add-site state |
| `lastRescheduledAt` | timestamp-string | |
| `hasPreviousContent` / `previousContent` / `userHasGoal` / `goal` | string | onboarding answers (often `""`) |
| `photoSource` | bool | `true` |
| `nrSources` | int | `4` |
| `proposalViewCount` | int | `3` |
| `proposalViewsMap` | map | proposal analytics keyed by `<ts>_<rand>` w/ `{client, location, device...}` |

**Legacy chatbot fields also present on the doc** (Adaptify originally a chatbot product;
these persist but are unused by SEO UI): `model` (`"gpt-3.5-turbo"`), `temperature`
(`0.8`), `systemPrompt`, `instructionPrompt`, `answerPrompt`, `questionPrompt`,
`suggestionsPrompt`, `feedbackPrompt`, `customPrompt`, `initialMessage`,
`initialSuggestions`, `inputPlaceholder`, `supportEmailText`.

`keywordsInitial[]` element shape (SEMrush raw, capitalized keys):
```json
{"Keyword":"slingshot rent","Search Volume":18100,"Traffic":199900,
 "Position":20,"CPC":1.16,"Keyword Difficulty Index":18}
```

`seoAudit` map shape (trimmed):
```json
{"url":"https://cowboydiscohatshop.com/","domain_authority":2,
 "nr_customer_reviews":-1,"services_page":"-","companyCEO":"","companyFounder":"",
 "landing_services_pricing_pages":["https://cowboydiscohatshop.com/"],
 "finished":"2026-06-26T19:55:12.811888Z",
 "pagespeed_metrics":{"homepage":{...},"blog":[{...}],"article":[...]}}
```
`pagespeed_metrics.*` per-page keys (Lighthouse-derived, each has `_value` + `_score`):
`performance_score`, `first_contentful_paint_value/score`, `largest_contentful_paint_value/score`,
`speed_index_value/score`, `total_blocking_time_value/score`, `cumulative_layout_shift_value/score`,
`has_title`, `has_meta_description`, `valid_rel_canonical`, `valid_robots_txt`, `valid_hreflang`,
`links_are_crawlable`, `descriptive_link_text`, `page_not_blocked_from_indexing`,
`properly_sized_images` (array), `properly_sized_images_savings_bytes`, `uses_https`,
`successful_http_status_code`, `third_party_code_impact`, `url`.

---

## 2. One-Click Content / Publishing (content plan + frequency)

### `planBuilder` map (bot doc)
Source: `fs_bot_doc_full.json`.
```json
{"contentPlan":"automated","backlinkPlan":1,"annual":false,
 "humanEditorReview":false,"siteActivated":"2026-06-26T19:54:05.285Z",
 "activateSiteTriggerSeen":"...","activateSiteNoCustomer":"...","activateSiteNoCustomer(ts)"}
```

### `contentPlan` ENUM (a.k.a. publish frequency / content package)
Source: `static/_next/static/chunks/7117-*.js`, `5222-*.js`, `944-*.js`.
| value | UI label | articles/month |
|---|---|---|
| `"disabled"` | (no content) | 0 |
| `"automated"` | "Unlimited Content & Keyword research" — *"Publish weekly, daily, or up to 50 articles per month"* | up to 50 |
| `"manual"` | "Me or my team needs to manually approve before publishing" / "Manual Content Creation" | — |
| `"boost2x"` | "Up to 40 Automated articles per month" / "40+ Articles Content Boost" | 40 |
| `"boost3x"` | "Up to 60 Automated articles per month" / "60+ Articles Content Boost" | 60 |
| `"boost4x"` | "Up to 80 Automated articles per month" / "80+ Articles Content Boost" | 80 |

Also present at subscription level: `contentPlanBoost` (int, e.g. `0`) — the boost multiplier.

### `autoPublishEvery` → frequency label mapping
Source: JS `e=t>=5?1/3:t>1?2/3:1===t?1:4/3` where `t = autoPublishEvery`:
| autoPublishEvery (days) | label |
|---|---|
| `>= 5` | "a weekly article" |
| `2,3,4` (`>1`) | "10x articles per month" |
| `1` | "20x articles per month" |
| `<1` / falsy | "40+ articles per month" |

**Autopublish disabled** when `!autoPublishEvery` (0 / missing):
`isAutopublishDisabled = e => !(e?.autoPublishEvery) || …` (JS chunk `104-*.js`).
Related fields: `autoPublishHourUtc` (int), `autoPublishTopupDate`, `lastAutoPublish`,
`markForAutoPublish` / `markForAutoPublishTwice` (article-level flags).

### `publishParams` map (WordPress publishing config on the bot doc)
```json
{"platform":"wordpress","wordpressBlogUrl":"https://cowboydiscohatshop.com/",
 "username":"admin","credentialsStored":true,"hasCredentials":true,
 "pluginDownloaded":true,
 "approvalRequiredAgency":false,
 "approvalRequiredAgencyChangedAt":"2026-06-26T20:07:44.052Z",
 "approvalRequiredAgencyChangedBy":"bilalkarim20@gmail.com",
 "lastUpdated":"2026-06-26T20:07:02.872663Z"}
```

### Publish vs Draft / manual approval
- **Automatic publishing** vs **manual approval**: driven by `contentPlan` (`"manual"`) and
  `planBuilder.humanEditorReview` (bool). Settings section id `automated-publishing` /
  `customer-review-settings`.
- `draftMode` (bool, seen in JS state) — publish as draft.
- Article local publish uses WP status `article.status` defaulting to `'publish'` (plugin, §12).

### GEO in the content calendar
Toggle **"Include GEO in Content Calendar"** — settings section id `automated-geo-articles`
(JS `104-*.js`). Team-level opt-in flag: `geoContentBeta` (bool) + `geoContentBetaOptedAt`
(`fs_teams.json`).

### Settings page sections (data-course-section-id, from JS)
`ai-seo-strategy`, `existing-seo-data`, `select-keywords`, `automated-content-plan`,
`automated-content-planning`, `automated-seo-frequency`, `automated-publishing`,
`automated-settings`, `automated-geo-articles`, `customer-review-settings`,
`customer-article-review`, `article-statuses`.

---

## 3. Publishing platforms + WordPress one-click connection

### Supported platform ENUM
Source: JS select options (`static/_next/static/chunks/*.js`) + integration-form page files.
| value | label |
|---|---|
| `wordpress` | Wordpress |
| `webflow` | WebFlow |
| `shopify` | Shopify |
| `wix` | Wix |
| `squarespace` | SquareSpace |
| `ghost` | Ghost |
| `hubspot` | Hubspot CMS |
| `duda` | Duda |
| `lovable` | Lovable |
| `other` | Other Platform / Other |

Also has a **REST API** integration form (`page_seo-settings-integration-forms-rest-api-form.json`)
and a **Shopify OAuth** form (`...-shopify-form-oauth.json`). Per-platform integration form pages
exist for all of the above. Stored on bot as `websitePlatform`; publishing creds in `publishParams`.

### WordPress one-click connection / pairing
Two mechanisms in the recon:

**(a) Pairing token** — `wpConnectionSetup` on the bot doc:
```json
{"token":"«redacted»","createdBy":"<uid>",
 "createdAt":"2026-07-02T07:33:55.701Z",
 "expiresAt":"2026-07-16T07:33:55.701Z"}   // ~14-day expiry
```
Frontend endpoint: `GET https://api.adaptify.ai/open/wp-connect`; status `GET /open/wp-status`.

**(b) Application-Password auto-pairing (the real plugin flow, §12):** on plugin
activation the plugin creates a WP Application Password named `seo-connect-<YmdHis>` for the
admin user and POSTs `{user_name, application_pw, site_url}` to
`https://api.ttsp4712.ai/add-wp-pw`. Backend then stores `publishParams.username` +
credentials (`credentialsStored=true`) and publishes via WP REST using that password.

`pluginStatus` map tracks the plugin: `{active:true, version:"1.5.9", wpVersion:"7.0",
lastActivatedAt, lastEventAt}`.

Plugin download: `GET https://api.adaptify.ai/open/wp-plugin-download`; version check:
`GET /open/wp-plugin-version`. White-label plugin generation: `POST /open/generate-whitelabel-plugin`.

Webflow OAuth endpoint `GET https://api.adaptify.ai/webflow-auth` (captured response was a
captcha/403 — **OAuth token exchange details NOT FOUND IN RECON**). `GET /open/webflow-sites`
lists sites. `GET /open/shopify-auth` for Shopify OAuth.

---

## 4. Backlinks

### `backlinkProfile` (legacy single profile on bot doc)
Source: `fs_bot_doc_full.json`.
```json
{"backlinkPreference":"disabled",
 "authorName":"",
 "emailInbox":"contact@cowboydiscohatshop.co",
 "emailPassword":"«redacted»",
 "lookalikeDomain":"cowboydiscohatshop.co",
 "lookalikeDomainPurchaseDate":"2026-06-26T20:12:32.396089Z",
 "firstActivated":"2026-06-26T20:12:36.383733Z",
 "inboxySetup":"2026-06-26T20:12:35.124287Z",
 "topics":["event planning","marketing campaigns","marketing","personal branding", ...],
 "topicsAutoUpdated":"2026-06-26T20:12:36.319251Z",
 "topicsAIExpand":false}
```

### `backlinkProfiles` (map keyed by `profile_<ts>`) — the multi-profile author records
```json
{"profile_1782504499291":{
   "authorName":"", "bio":"", "linkedinUrl":"", "headshotUrl":"",
   "expertKeyQuestions":[],
   "backlinkPreference":"disabled",
   "topicsAIExpand":false,
   "createdAt":"2026-06-26T20:08:22.045Z",
   "lastUpdated":"2026-06-26T20:11:44.229Z"}}
```
`primaryBacklinkProfileId` points at the active one.

**`backlinkPreference` ENUM:** observed value `"disabled"` (only value in corpus). Other
values NOT FOUND IN RECON (likely enabled variants exist but not captured).

### Backlink plan / packages
- **`backlinkPlan`** (int) = number of backlinks/month. UI cards offer values
  **`0` (disabled), `1` (Recommended/Included), `2`, `3`, `5`, `10`, `20`**
  (JS `5222-*.js` `k("backlinkPlan", <n>, …)`). Stored on `planBuilder.backlinkPlan` and
  `siteSubscriptions.*.backlinkPlan`.
- `nr_additional_backlinks` (int) — add-on backlinks beyond plan.
- Team-level counters: `backlinksPackages`, `backlinksPackagesUsed`, `stripe.automated_backlinks` (bool).
- `companyBacklinkStrategy` = `{title, outlet, category}` (e.g. `outlet:"https://www.festforums.com/", category:"Fashion"`).

### Backlink queue / outreach
Endpoints: `GET /open/backlink-profile`, `GET /open/backlink-queue`, `GET /open/backlinks`.
The Firestore collection `backlinkProfiles` was 403 in capture (`fs_backlinkProfiles.json`),
so **per-queue-item status enums (pitch/outreach statuses) NOT FOUND IN RECON.**
Related product "LinkPricer" endpoints (buy links): `/open/linkpricer/offers`,
`/offers/search-domain`, `/offers/validate`, `/campaigns`, `/my-offers`, `/status`,
`/open/linkpricer/orders/pilot`.

---

## 5. Keyword Strategy

### `bot_keywords` collection — `bots/{botId}/keywords/{id}` doc shape
Source: `endpoint-responses/fs_bot_keywords.json` (decoded).
```json
{"keyword":"100 light up cowboy hats",
 "status":"discarded",
 "intent":"transactional",
 "type":"semrushKeyword",
 "source":"semrush",
 "seedSources":["semrush"],
 "search_source":"cache",
 "topic_cluster":"neon cowboy hat",
 "potential":46,
 "difficulty":0,
 "comp":0,
 "cpc_low":0, "cpc_high":0,
 "vol_avg":9, "vol_first":0, "vol_last":10,
 "vol_id_str":"0-10-10-10-10-10-10-10-10-10-10-10",
 "isSynonym":false,
 "note":"keyword rejected due to not relevant or branded or company comparison",
 "addedDone":"2026-06-26T19:51:31.086585Z"}
```
Enum values observed:
- `status`: `"discarded"` (only captured value; active/pending variants exist but NOT in capture)
- `intent`: `""`, `"transactional"`
- `type`: `"semrushKeyword"`
- `source`: `"semrush"`
- `search_source`: `"cache"`, `"google_keyword_planner"`

### Clusters
- `clusterPlanning` (bot doc): `{phase:"createInitialClusters", status:"done"|..., startedAt,
  doneAt, source:"frontend", activeCount:int, activeTokens:map}`.
- **`createInitialClusters`** (bool) — auto-cluster flag; **disable-auto-clusters** = set false.
- `topic_cluster` on each keyword ties it to a cluster.
- Endpoints: `GET/POST /open/keywords`, `GET /open/keyword-clusters`,
  `POST /open/keyword-research`, `GET /open/geo-clusters` / `POST /open/geo-clusters`.

### AI keyword instructions / CSV import
- Settings page section ids `select-keywords`, `ai-seo-strategy`. `companySeoStrategy` /
  `companySeoGoals` feed the AI keyword generation.
- **CSV import**: `GET https://api.adaptify.ai/crm-records/export.csv` exists (CRM export);
  a dedicated keyword-CSV-import endpoint / explicit "AI keyword instructions" free-text
  field name was **NOT FOUND IN RECON** (keywords page = `page_seo-settings-keywords.json`,
  data is server-rendered HTML only).

---

## 6. Brand mentions / GEO (AI visibility)

### AI platforms tracked (ENUM)
`chatgpt`, `claude`, `gemini`, `perplexity` (each appears as a per-prompt `platform` value;
`platformScores` object has exactly these 4 keys). Source: `o04_geo_perf_summary.json`,
`onboardingVisibilityResults`.

### `visibilityAnalysis` map (bot doc)
```json
{"status":"completed","numPrompts":10,
 "requestedAt":"...","startedAt":"...","lastRunDate":"...",
 "prompts":[{"text":"Can you return a long list of online shops...","keyword":"disco cowboy hat"}, ...]}
```
- **`numPrompts`** default = **10** (`F.visibilityAnalysis.numPrompts ?? 10`, JS). Other
  selectable option values (e.g. 25/50) **NOT FOUND IN RECON** — only `10` observed. Saved via
  update `{"numPrompts": t}`.

### `onboardingVisibilityResults.prompts[]` element
```json
{"keyword":"visual alignment aid golf",
 "chatgpt":{"brandMentioned":false,"sentiment":"notMentioned",
            "competitorsMentioned":["Eyeline Golf","FaceLine Golf"],"position":null},
 "claude":{...},"gemini":{...},"perplexity":{...}}
```
Per-platform result fields: `brandMentioned` (bool), `sentiment`
(observed `"notMentioned"`; other sentiment enum values NOT FOUND), `competitorsMentioned`
(array<string>), `position` (int|null). (JS also reads snake_case fallbacks
`brand_mentioned` / `competitors_mentioned`.)

### `geoCadence` map (GEO refresh scheduler, bot doc)
```json
{"bingAccountId":"primary","nextRefreshAt":"2026-07-11T14:08:35Z",
 "lastAttemptAt":"...","lastAttemptStatus":"success","lastAttemptSource":"daily_first_pull",
 "lastAttemptError":"","consecutiveFailureCount":0,
 "latestReportId":"tKGKtJn7Us3QDRHE5ZQq","latestReportUploadedAt":"...","firstReportUploadedAt":"..."}
```

### GEO performance report shape (`geoResults`)
Source: `endpoint-responses/o04_geo_perf_summary.json` (`GET /open/geo-performance-summary`).
```
res.botId
res.company.visibilityAnalysis.{overallScore, lastReportId, status,
   platformScores:{chatgpt,claude,gemini,perplexity}}   // floats 0.0–1.0
res.reportData.id / uploadedAt
res.reportData.period.{startDate, endDate, dayCount}
res.reportData.summary.{latestDayCitations, latestDayCitedPages,
   totalGroundingQueryCitations, threeMonthCitations, totalPageCitations,
   pagesCount, groundingQueriesCount,
   pagesByWindow.{7d,30d,3m}.{rowCount,totalCitations},
   groundingQueriesByWindow.{7d,30d,3m}.{rowCount,totalCitations}}
res.reportData.bingSearchPerformance.summary.{queryClicks, queryImpressions, queryRows,
   queryDetailItems, pageClicks, pageImpressions, pageDetailItems,
   rankClicks, rankImpressions, topQuery, topPage}
```
Firestore `geoResults` collection direct-read was 403 (`fs_geoResults.json`).
Endpoints: `GET/POST /open/geo-results`, `GET /open/geo-content`, `GET /open/geo-prompts`,
`GET /open/geo-cadence`, `GET /open/geo-autopilot-settings`, `POST /open/geo-performance-report`,
`GET /open/ai-visibility`, `GET /open/brand-mentions`, `GET /open/brand-visibility`.
Note: `/seo/brand-mentions` page 307-redirects to `/seo/geo-performance?tab=brand-mentions`.

---

## 7. Competitors

Fields on the bot doc:
- `competitorSites` : array<string(url)> — e.g. `["https://lvdroptop.com","https://vssr.vegas","https://adrenalinerushslingshot.com"]`.
- `competitorNoMention` : bool — `true` = instruct AI to **not** mention competitors.
- `companyCompetitorsQuery` : string — the competitor-research query used to find competitors.
- Per-prompt/per-article outputs carry `competitorsMentioned` (array) and `brandMentioned` (bool);
  aggregated as `platformMentionCount`.
Endpoints: `GET/POST /open/competitors`. (Explicit "research toggle" boolean field name
NOT FOUND IN RECON — competitor discovery is driven by `companyCompetitorsQuery`.)

---

## 8. Local SEO

- `localStrategy` : string — e.g. `"The Local SEO plan aims to enhance the nationwide
  visibility of the business"`.
- `companyLocations` : string — e.g. `"USA\n6"` (locations, possibly count).
- `companyReach` : string — reach (local/national).
- Maps feature: `GET https://api.adaptify.ai/open/maps` (page `page_seo-maps.json`); team
  flag `mapsBeta` (bool). No dedicated localSeo settings sub-object beyond `localStrategy` was
  found — **structured local-SEO settings flags NOT FOUND IN RECON.**

---

## 9. Reporting / White-label

### `reportParams` map (bot / team level)
Source: JS chunks + `fs_teams.json` (`reportingParams` map, often `{}` when unset).
Full field set observed:
`toEmail`, `testEmail`, `agencyNotificationEmail`, `customerName`, `clientName`,
`reportTitle`, `reportSubtitle`, `agencyName`,
`weeklyReport` (bool), `monthlyReport` (bool), `notifyBacklinks` (bool),
`notWhiteLabeled` (bool), `clientPortalEnabled` (bool), `allowCustomerEdits` (bool),
`simplifiedContentView` (bool),
`showBacklinks`, `showTechnical`, `showContentAnalysis`, `showPerformanceGraphs` (report section toggles),
`pricingPackages` (array of `{enabled,...}`), `sectionComments`, `menuParams`, `dataType`,
`lastUpdated`, `lastUpdatedField`,
proposal fields: `proposalBuilderRows`, `proposalBuilderSetupRows`, `proposalPublishedRows`,
`proposalPublishedSetupRows`, `proposalPublishedAt`, `proposalPublicSnapshotUpdatedAt`.

Notification toggles = `weeklyReport`, `monthlyReport`, `notifyBacklinks` ("backlinks placed").

### White-label plugin (team level)
Source: `fs_teams.json` — `whitelabelPlugin` map:
```json
{"name":"RankWriters SEO Plugin","author":"RankWriters",
 "configured":true,"lastUpdated":"2026-02-13T17:28:08.278Z"}   // or "configuredAt"
```
Other examples: `{"name":"AuraSearch SEO Plugin","author":"AuraSearch",...}`,
`{"name":"Sail-On Enterprises LLC","author":"Sail-On Enterprises LLC",...}`.
Related: `whitelabelEditLock` (map|null), `availableLabels` (array<string>),
`availableLabelsUpdated`.
Endpoints: `GET /open/whitelabel-settings`, `GET /open/whitelabel-branding`,
`GET /open/whitelabel-portal`, `POST /open/generate-whitelabel-plugin`,
`POST /open/generate-report`, `GET /open/get-reporting-params`.

### Team / billing shape (`teams/{teamId}`) — for multi-site + plans
Source: `deep-responses/fs_teams.json` (union). Key fields:
`owner`, `ownerEmail`, `uids` (array), `emails` (array), `idMap` (uid→email),
`customerName`, `first_name`/`last_name`, `email`, `billingEmails`, `billingAccess` (array),
`package` (e.g. `"content"`), `paid`/`paidUser` (e.g. `"starter"`),
`subscriptionStatus` (`"active"`), `highestSubscriptionStatus`, `isPro`, `churned`, `pause`,
`totalSites`, `nrSitesUsed`, `baseSites`, `additionalSites`, `siteSubscriptionEmptySlots`,
`autoPilots`/`autoPilotsUsed`, `backlinksPackages`/`backlinksPackagesUsed`,
`stripeId`, `stripe` (map), `stripeSubscriptions` (array), `pricingCohort`
(`"legacy_2025"`), `legacyPricingEnabled`, `selfServeEnabled`, `geoContentBeta`, `mapsBeta`.

`siteSubscriptions.{botId}` element:
```json
{"contentPlan":"automated","contentPlanBoost":0,"backlinkPlan":0,
 "nr_additional_backlinks":0,"annual":false,"seo_boost":false,
 "humanEditorReview":false,"status":"active","nextBillingDate":"2024-12-17T18:11:34Z",
 "subscription_ids":["sub_1ONJNiLoakldQGWULihsN6TV"],
 "metadata":{"site":"<botId>","subscription_breakdown":"starter"}}
```

---

## 10. Dashboard — "SEO Potential X/13"

Source: `static/_next/static/chunks/6280-*.js`, `1793-*.js`.
- The score is an integer `ed` (aka `p`) out of **13**; displayed as `ed + "/13"` and
  `Math.round(ed/13*100) + "%"`.
- **Color bands:** `< 6` → red (`text-red…`), `6–12` → orange
  (`text-orange-600 bg-orange-50`), `13` → green (`text-green-600 bg-green-50`).
- Two variants: **"Overall SEO Potential"** (SEO) and **"GEO Potential"** (when GEO view `F=true`).
- Tips panel: "SEO Potential Tips" / "AI Visibility Tips".
- **The exact 13-item checklist and how `ed` is summed is NOT cleanly recoverable from the
  minified JS** — it is computed client-side from company/audit/plugin/keyword/backlink state,
  not stored as a field. Treat as a 0–13 count of completed setup items with the bands above.

Dashboard/overview endpoints: `GET /open/seo-score`, `GET /open/performance`,
`GET /open/ai-performance-report`, `GET /open/visibility-report`, `GET /open/publishing-health`.

---

## 11. API endpoints (real, captured)

Base: `https://api.adaptify.ai`. Auth: most `/open/*` and root routes require headers
`user_id` + `api_key` (a 403 `{"detail":"Missing user_id or api_key in headers"}` is returned
without them — see `a06_publishing_setup.json`). Firestore reads go directly to
`firestore.googleapis.com`. Payload shapes only shown where captured (most POST bodies were
not captured because requests 403'd without creds).

### Content / publishing
| method | path | purpose |
|---|---|---|
| POST | `/open/one-click-publish` | one-click publish an article |
| GET | `/open/publishing-setup`, POST `/open/publishing-setup` | get/save publishing config (creds, platform, approval) |
| GET | `/open/publishing-status` | publish status |
| GET | `/open/publishing-health` | publishing health |
| GET | `/open/publishing-queue` | queued articles |
| GET | `/open/articles`, `/articles` | list articles |
| POST | `/open/generate-content` | generate an article |
| POST | `/open/setup-complete`, `/open/complete-onboarding` | finish onboarding |
| GET | `/open/setup-wizard`, `/open/onboarding-status`, `/open/onboarding-steps`, `/open/onboarding/v2`, `/open/onboarding/v3` | onboarding |

### Keywords / clusters
`GET/POST /open/keywords`, `GET /open/keyword-clusters`, `POST /open/keyword-research`.

### GEO / brand
`GET/POST /open/geo-results`, `GET /open/geo-content`, `GET/POST /open/geo-clusters`,
`GET/POST /open/geo-prompts`, `GET /open/geo-cadence`, `GET /open/geo-autopilot-settings`,
`GET /open/geo-performance-summary`, `POST /open/geo-performance-report`,
`GET /open/ai-visibility`, `GET /open/ai-performance-report`, `GET /open/brand-mentions`,
`GET /open/brand-visibility`, `GET /open/visibility-report`.

### Backlinks
`GET /open/backlink-profile`, `GET /open/backlink-queue`, `GET /open/backlinks`,
`GET /open/linkpricer/{offers,campaigns,my-offers,status}`,
`POST /open/linkpricer/offers/{search-domain,validate}`, `GET /open/linkpricer/orders/pilot`.

### Competitors / audit / performance
`GET/POST /open/competitors`, `GET/POST /open/seo-audit`, `GET /open/seo-score`,
`GET /open/performance`, `GET /open/maps`, `GET /open/pitch-mode`.

### WordPress / integrations
`GET /open/wp-connect`, `GET /open/wp-status`, `GET /open/wp-plugin-download`,
`GET /open/wp-plugin-version`, `POST /open/wp-plugin-event`,
`POST /open/wp-home-meta-tag`, `POST /open/wp-pending-articles`, `POST /open/wp-publish-callback`,
`POST /add-wp-pw`, `GET /open/integrations`, `GET /open/webflow-sites`, `GET /open/shopify-auth`,
`GET /webflow-auth`, `GET /open/gsc-connect`, `GET /open/gsc-sites`, `GET /open/gsc-data`.

### Reporting / white-label / proposals
`GET /open/get-reporting-params`, `POST /open/generate-report`, `POST /open/generate-proposal`,
`GET/POST /open/proposals`, `GET /open/whitelabel-{settings,branding,portal}`,
`POST /open/generate-whitelabel-plugin`.

### Billing / team / account
`GET /open/plans`, `GET /open/plan-details`, `GET /open/subscription-status`, `GET /open/billing`,
`POST /open/billing-portal`, `POST /open/create-checkout-session`, `GET /open/charges`,
`GET /open/user-profile`, `GET /open/user-bots`, `GET /open/team-members`,
`POST /open/invite-team-member`, `GET /open/referral`, `GET /open/referral-stats`.

### CRM / agency / admin (agency-side)
`POST /open/bots-list`, `GET/POST /open/crm-records`, `GET /open/crm-pipeline`,
`GET /open/crm-records/stages`, `GET /crm-records/export.csv`,
`GET/POST /open/crm-high-potential-feedback`, `POST /open/enrich-agency`,
`POST /open/email-inbox-setup`, `POST /open/outgoing-email-history/query`,
`POST /open/helpscout-create-manual-tech-ticket`, `GET /open/helpscout-tickets`,
`GET /open/approval-reminder/{list,pending,status}`, `POST /open/approval-reminder/{preview,send}`,
`GET /add-self-serve-user`+POST, `POST /open/admin/impersonate`, `GET/POST /impersonate`,
`GET /admin/{bots,users,dashboard,revenue,stats}`, `GET /sales-calls`,
`GET /sales-calls/gradeboard`, `GET /sales-metrics`.
PressYield (affiliate/partner): `POST /open/pressyield/{apply,calculate}`,
`GET /open/pressyield/{status,earnings}`.
API docs: `GET /docs`, `GET /openapi`.

---

## 12. WordPress plugin (`/Users/bilalkarim/Downloads/adaptify-seo/plugin.php`, v1.5.9)

Plugin header: **Name** `Adaptify SEO`, **Version** `1.5.9`, text domain `adaptify-seo`.
Backend host constant: `ADAPTIFY_UPDATE_URL = https://api.ttsp4712.ai/open/wp-plugin-version`.
Function/class prefix `as6270_` / `WPAPIYoast_init` / `Yoast_To_REST_API` (Yoast-compat).

### REST route it REGISTERS
- `GET /wp-json/adaptify/v1/info` — `permission_callback: __return_true` (public). Returns
  `{name, version, status:"active", wp_version, php_version, permalink_structure,
  is_multisite, home_url}`.
- It also injects Yoast-style SEO fields into the core WP REST API for `post`, `category`,
  `tag` (via `wp_api_encode_yoast*` `get_callback`s) so the cloud can read/write meta.

### Authentication (how the cloud connects)
Uses **WordPress Application Passwords** (no custom token):
1. On activation (`register_activation_hook` → `as6270_seo_plugin_activate`) it calls
   `create_application_password_for_user()`.
2. That creates `WP_Application_Passwords::create_new_application_password($user_id,
   ['name' => 'seo-connect-'.date('YmdHis')])`.
3. POSTs JSON `{user_name: <user_login>, application_pw: <new pw>, site_url: <site url>}` to
   `https://api.ttsp4712.ai/add-wp-pw`.
4. Cloud stores it and thereafter calls the WP REST API using HTTP Basic auth
   (username + application password) to publish/update posts.
5. Settings page ("Adaptify SEO", slug `one-click-seo-settings`) has buttons
   **Create New Application Password** / **Delete Application Password**
   (`delete_application_password_for_user()` deletes all app passwords for the user).

### Option keys stored
`seoplugin_enable_meta_sync` (`'1'`/`'0'`, default `'1'`),
`seo_plugin_verification_tags_html`, `seo_plugin_msvalidate_content`,
`seo_plugin_msvalidate_last_sync`.

### Publishing / lifecycle callbacks (plugin → cloud, host `api.ttsp4712.ai`)
| method | endpoint | body | response |
|---|---|---|---|
| POST | `/open/wp-plugin-event` | `{site_url, event, plugin_version, wp_version}` (`event` = `activated`/`deactivated`) | — |
| POST | `/open/wp-home-meta-tag` | `{site_url}` | `{bot_found, tags_html}` (or legacy `{content}` → builds `<meta name="msvalidate.01" …>`) |
| POST | `/open/wp-pending-articles` | `{site_url}` | `{articles:[…]}` (fallback publishing pull) |
| POST | `/open/wp-publish-callback` | `{site_url, article_id, keyword_id, post_id, post_url, status, error_message}` | — |
| GET | `/open/wp-plugin-version` (`ADAPTIFY_UPDATE_URL`) | (self-update check; sends `name`,`author` for white-label) | `{version, …}` |

### `articles[]` element consumed by `as6270_publish_article_locally()`
`wp_insert_post` mapping:
```
title            -> post_title       (required)
content          -> post_content     (wp_kses_post)
status           -> post_status      (default 'publish')
excerpt          -> post_excerpt     (optional)
category_id      -> post_category    (int, optional)
category_instruction -> resolved locally to a term when category_id absent
comment_status   -> post_status/comment_status (default 'closed')
article_id, keyword_id -> returned in publish callback
```
Meta sync gated by `seoplugin_enable_meta_sync`; home meta tag verification via
`as6270_sync_home_meta_tag()` (msvalidate / Bing verification), with a 20s transient lock
`seo_plugin_msvalidate_sync_lock`.

### White-label of the plugin
The self-update request sends `name` (`ADAPTIFY_PLUGIN_VERSION`-associated display name) and
`author` params (urlencoded) so the cloud can serve a white-labeled build — matches team
`whitelabelPlugin.{name, author}` (§9). Conflict guard: refuses to load if another copy /
`Yoast_To_REST_API` is already active.

Also present: `class-wpseo-frontend-to-rest-api.php` (Yoast frontend→REST bridge stub).

---

## Appendix — quick enum reference
- **websitePlatform**: wordpress | webflow | shopify | wix | squarespace | ghost | hubspot | duda | lovable | other  (+ rest-api form)
- **contentPlan**: disabled | automated(≤50/mo) | manual | boost2x(40) | boost3x(60) | boost4x(80)
- **autoPublishEvery** (days): ≥5→weekly · 2–4→10x/mo · 1→20x/mo · <1→40+/mo · 0/missing→autopublish disabled
- **backlinkPlan** (int, backlinks/mo): 0 | 1 | 2 | 3 | 5 | 10 | 20
- **backlinkPreference**: disabled (only captured)
- **GEO platforms**: chatgpt | claude | gemini | perplexity
- **numPrompts**: default 10 (other options NOT FOUND)
- **keyword.intent**: "" | transactional ; **keyword.status**: discarded (only captured) ; **type**: semrushKeyword ; **source/search_source**: semrush | cache | google_keyword_planner
- **SEO Potential**: 0–13 (bands: <6 red · 6–12 orange · 13 green)
