# Hector Keyword Harvesting

> Use this skill ANY TIME the user asks about keyword harvesting, finding keywords to add, identifying search terms not in exact match, or gap analysis — e.g. "harvest keywords", "what keywords should I add?", "which search terms should I be targeting in exact match?", "find terms with orders not in my exact targeting", "run a harvest for [brand/account]", "show me harvesting opportunities", "keyword gap analysis", or close paraphrases. Also fires when the user wants to add the harvested terms to existing or new campaigns (action phase). Does NOT fire for: "show me my top search terms" (performance report only), "which search terms are wasting spend" (negative keyword task), "what keywords should I pause/remove" (bid or status task), general search-term analytics with no intent to promote terms, placement analysis, or budget questions.

- Skill: `hectorai-live/hector-keyword-harvesting` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hectorai-live/hector-keyword-harvesting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hectorai-live/hector-keyword-harvesting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: hectorai-live (https://skillmd.com/u/hectorai-live)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hectorai-live/hector-keyword-harvesting

---


# Hector Keyword Harvesting (v2)

> ## ⚠️ CRITICAL OBLIGATIONS — READ BEFORE EVERY USE
>
> 1. **Resolve profile_id BEFORE any tool call.** Never guess or invent a profile ID. If not
>    in context, resolve via `find_profile_by_name` or `get_amazon_profiles` first. This is a
>    hard blocker — no tool call proceeds without a confirmed profile_id.
> 2. **Run date-resolution skill before setting any date.** Always run
>    `hector-mcp-date-resolution` before setting start_date / end_date. Never trust the
>    session clock.
> 3. **Pull the account snapshot FIRST to derive min_roas dynamically.** Use
>    `analytics_seller_vendor_master_overview` for the same date window as the harvest to get
>    average account ROAS. min_roas = account_average_ROAS × 0.60. Never hardcode a ROAS
>    threshold.
> 4. **Default date window is last_30_days.** If fewer than 5 harvest candidates are returned,
>    automatically re-run with last_60_days. Inform the user when this escalation happens.
>    Never ask the user for a date range unless they volunteer one.
> 5. **Always pull 250 rows (limit: 250).** Never use the default 5-row preview for harvest
>    calls. The full list must be shown before any action is taken.
> 6. **Always group results by product/category.** Claude reads the search terms semantically
>    and defines the categories — do not rely on any pre-built Hector table. Never present a
>    flat undifferentiated list.
> 7. **Hard threshold: min_orders = 2.** A search term with fewer than 2 orders never appears
>    in the harvest list, regardless of ROAS. No flagging, no grey zone — excluded entirely.
> 8. **NEVER add keywords without explicit two-phase preview → confirm flow.** Call
>    `bulk_add_keywords` with `preview_only: true` first. Show the table. Wait for explicit
>    user confirmation before executing.
> 9. **Never use CPC from analytics as a bid.** Always source bids from `get_suggested_bids`.
> 10. **Run the closing self-check before every response.**

---

## Why this skill exists

On Amazon Ads, **search terms** are the actual customer queries that triggered an ad.
**Targeting** is what you explicitly told Amazon to match against — an exact keyword, a phrase,
an auto campaign, etc. A search term can generate orders while being matched only through broad,
phrase, auto, or competitor campaigns — meaning it is never directly controlled or efficiently
bid on.

Harvesting is the process of identifying those high-performing search terms and promoting them
into **exact-match keywords**, giving the account direct, controllable, biddable coverage over
its best traffic. This is the single highest-ROI routine optimisation on Amazon Ads.

---

## When this skill activates

**Fires on:**
- "Harvest keywords / search terms for [brand/account/ASIN]"
- "What keywords should I add?"
- "Which search terms should I be targeting in exact match?"
- "Find me terms with orders that aren't in my exact targeting"
- "Run a harvest", "show me harvesting opportunities", "keyword gap analysis"
- "Add the harvested terms to [campaign / ad group]" (triggers action phase)
- Any request to identify and promote unconverted search terms into exact match

**Does NOT fire on:**
- "Show me my top search terms" — performance report, not a harvest (use
  `analytics_search_term_list` directly, no grouping or gap logic)
- "Which search terms are wasting spend?" — negative keyword task, not a harvest
- "What keywords should I pause / remove?" — bid or status task
- Pure search-term analytics with no intent to promote terms
- Placement, budget, or bid-only analysis

---

## Blocking Preconditions

> **You CANNOT call any Hector MCP analytics tool until ALL THREE of the following are true:**
>
> **Precondition A — Profile resolved.** profile_id must be confirmed. If missing, call
> `find_profile_by_name` or `get_amazon_profiles` and confirm with the user before proceeding.
>
> **Precondition B — Date resolved.** Run `hector-mcp-date-resolution` to get verified_today.
> Default harvest window: last_30_days. Auto-escalate to last_60_days if < 5 candidates found.
>
> **Precondition C — Account ROAS fetched.** Pull the account snapshot first (same date window)
> to derive min_roas = account_average_ROAS × 0.60. This must happen before the harvest call.
>
> These are preconditions, not optional setup. Skipping any one produces wrong results.

---

## Step-by-Step Execution

### Step 1 — Identify scope

Without asking the user (unless genuinely ambiguous):

- **Account-wide harvest?** → No ASIN filter. Proceed.
- **Product/ASIN-scoped harvest?** → Note the ASINs or category. Resolve to ASINs via
  `sku_lookup` if the user named a product without ASINs.
- **Category groupings:** Do not ask the user. Claude will define groupings semantically in
  Step 4 from the search term text and product names returned.

Default scope when not specified: **account-wide**.

### Step 2 — Resolve profile and date (BLOCKING)

2a. If profile_id is not in context: call `find_profile_by_name` with the brand/account name.
    Confirm the resolved profile_id in one line before proceeding.

2b. Run `hector-mcp-date-resolution` to get verified_today.
    Set: `start_date: last_30_days`, `end_date: yesterday`.

### Step 3 — Fetch account snapshot to derive min_roas (BLOCKING)

Call `analytics_seller_vendor_master_overview`:

```
profile_id: <resolved>
start_date: last_30_days
end_date: yesterday
```

From the response, extract **average account ROAS**.
Compute: `min_roas = account_average_ROAS × 0.60`

Example: account average ROAS = 8.5 → min_roas = 5.1

State this to the user in one line:
> "Account average ROAS (last 30 days): 8.5 → using min_roas of 5.1 (60% floor)"

### Step 4 — Pull harvest candidates

**Primary tool — `keyword_harvesting`** (use for all standard account-wide requests):

```
profile_id: <resolved>
start_date: last_30_days
end_date: yesterday
min_orders: 2
min_roas: <derived in Step 3>
metrics: [Orders, ROAS, Revenue, Spend]
limit: 250
```

**If fewer than 5 candidates returned → auto-escalate to last_60_days:**
Re-run the snapshot (Step 3) and the harvest call with `start_date: last_60_days`.
Inform the user:
> "Only [N] candidates found in 30 days — expanding to 60 days automatically."

**Alternate tool — `analytics_search_term_list` with harvesting filter:**
Use ONLY when:
- User scoped to a specific ASIN (`advertised_asin` param — not available in `keyword_harvesting`)
- User wants additional metric filters `keyword_harvesting` cannot express

```
profile_id: <resolved>
start_date: last_30_days        ← or last_60_days if escalated
end_date: yesterday
harvesting: { status: "gap", match_types: ["EXACT"] }
metrics: [Orders, ROAS, Revenue, Spend, Clicks]
metric_filters: [{ field: "Orders", operator: "greater_than", value: 2 }]
grouping_level: "ungrouped"     ← harvesting forces this; never override
advertised_asin: "<ASIN>"       ← only when ASIN-scoped
sort_column: Orders
sort_type: DESC
limit: 250
```

**Threshold rule (hard):** Any search term with fewer than 2 orders is excluded entirely.
No flagging. No partial display. Not shown.

### Step 5 — Group results by product/category (MANDATORY)

Claude reads the search terms semantically and defines categories. Do not ask the user.
Do not rely on any Hector categorization table.

**How to group:**
- Read the `SearchTerm` text and `ProductName` / ASIN columns from the results
- Identify product signals in the term (e.g. "men's sunscreen spf50" → Sunscreen › Men's)
- Group like products together (e.g. sunscreen for men + sunscreen for women = Sunscreen)
- Terms that don't fit a clear product category → group under "General / Mixed"

**Present results in this format for each group:**

```
## 📦 [Category Name] — [N] harvest candidates

| Search Term              | Orders | ROAS | Revenue  | Spend   |
|--------------------------|--------|------|----------|---------|
| men's sunscreen spf 50   |  42    | 6.2  | ₹18,400  | ₹2,960  |
| sunscreen for men        |  31    | 5.8  | ₹14,260  | ₹2,459  |
...
```

**After all groups, show a summary table:**

```
| Category         | Harvest Candidates | Already in Exact | Action          |
|------------------|--------------------|------------------|-----------------|
| Sunscreen        | 18                 | 4                | Add 18 to exact |
| Hair Oil         | 12                 | 9                | Add 12 to exact |
| Face Wash        | 7                  | 11               | Add 7 to exact  |
| General / Mixed  | 4                  | 2                | Review manually |
```

### Step 6 — Prompt for action

Immediately after showing the grouped results and summary, ask:

> **"Which of these keywords do you want to add to a campaign?"**

Wait for the user's response. They may:
- Select specific terms or entire category groups
- Say "all of them"
- Say "skip for now"

### Step 7 — Existing or new campaign?

Once the user has identified which keywords to add, ask:

> **"Do you want to add these to existing campaigns or create new campaigns?"**

**If existing campaigns:**
- The user will map category groups to campaigns (e.g. "Sunscreen keywords → SP-Sunscreen-Exact")
- Resolve campaign_id via `get_campaign_id` or `search_records`
- Fetch suggested bids via `get_suggested_bids` for each term
- Call `bulk_add_keywords` with `preview_only: true` → show preview table
- Wait for explicit confirmation ("confirm", "yes proceed", "looks good")
- Call `bulk_add_keywords` with `preview_only: false`
- Report outcome. Note: "New keywords appear in analytics within 24–48 hours."

**If new campaigns:**
- Ask: "Which ASINs should be advertised in this campaign, and what is the daily budget?"
- There may be multiple ASINs/ad groups — collect the full list before proceeding
- Use `sp_campaign_preview` to show the proposed campaign structure
- Wait for explicit confirmation
- Use `sp_campaign_execute` to launch
- Report outcome

---

## What NOT to Do

- **Never call any tool before profile_id, date, and account ROAS are all resolved.** All three
  are blocking preconditions.
- **Never hardcode min_roas.** It must always be derived as account_average_ROAS × 0.60 from
  the snapshot in the same date window.
- **Never show fewer than 200 rows when candidates exist.** limit must be set to 250.
- **Never present results as a flat unsorted list.** Always group by Claude-defined
  product/category first.
- **Never show a search term with fewer than 2 orders.** Hard exclusion — no exceptions.
- **Never use CPC from analytics as a bid.** Use `get_suggested_bids` only.
- **Never execute keyword adds without preview → explicit confirm flow.**
- **Never hand-build a `master_search_filters` KeywordHarvesting block.** Use the flat
  `harvesting` param in `analytics_search_term_list`.
- **Never set `grouping_level: "grouped"` when using the harvesting filter.** The endpoint
  forces ungrouped — do not override.
- **Never ask the user for a date range** unless they volunteer one. Default to last_30_days
  and auto-escalate if needed.
- **Never confuse "top search terms" with harvesting.** Showing performance data is not
  harvesting. The skill does not fire on performance-only requests.

---

## Edge Cases

| Situation | Behaviour |
|---|---|
| Fewer than 5 harvest candidates on last_30_days | Auto re-run snapshot + harvest with last_60_days. Inform user. Do not ask. |
| Still fewer than 5 candidates on last_60_days | Report the count, suggest the user review min_orders (currently 2) or check if account has enough volume. Do not auto-escalate further. |
| Account snapshot returns no ROAS (new account / no spend) | Flag: "Cannot compute dynamic ROAS floor — account has no ROAS data in this window." Ask user to provide a manual min_roas before proceeding. |
| User scopes to a specific ASIN | Switch to `analytics_search_term_list` with `advertised_asin` + harvesting filter. `keyword_harvesting` is account-wide only. |
| All candidates are already in exact match | Report as a positive finding: "Exact-match coverage is strong for this window." Show already_targeted count as validation. |
| User says "add all" to existing campaigns | Group by category, confirm one campaign per category group with the user before calling `bulk_add_keywords`. Never map all terms to one campaign blindly. |
| User says "new campaign" but gives only one ASIN | Ask for the full list of ASINs to advertise and the daily budget before calling `sp_campaign_preview`. |
| Search terms don't clearly map to any product | Group under "General / Mixed" and flag for manual review. Do not force an incorrect category. |
| `get_suggested_bids` returns zero or very high bid | Flag the anomaly to the user. Do not silently cap or floor it. Let the user decide the bid. |
| User volunteers a date range (e.g. "last 90 days") | Use their range. Skip the 30-day default and the auto-escalation logic. Still run snapshot in the same window to derive min_roas. |

---

## Worked Examples

### Example 1 — Standard account-wide harvest, existing campaigns

**User:** "Run a keyword harvest for <brand> India."

1. profile_id not in context → call `find_profile_by_name("<brand> India")` → confirmed.
2. Run date-resolution skill → verified_today confirmed.
3. Call `analytics_seller_vendor_master_overview` (last_30_days) → account average ROAS = 9.0
   → min_roas = 5.4. State to user.
4. Call `keyword_harvesting` (last_30_days, min_orders: 2, min_roas: 5.4, limit: 250).
5. 47 candidates returned (> 5 threshold — no escalation needed).
6. Group semantically: Onion Hair Oil (18), Face Wash (12), Sunscreen (10), Body Lotion (7).
7. Present grouped tables + summary table.
8. Ask: "Which of these keywords do you want to add to a campaign?"
9. User: "All sunscreen ones."
10. Ask: "Existing or new campaign?"
11. User: "Existing — SP-Sunscreen-Exact."
12. Resolve campaign_id. Fetch suggested bids. Call `bulk_add_keywords` (preview_only: true).
13. Show preview table. Wait for confirm.
14. User: "Confirm." → Call `bulk_add_keywords` (preview_only: false). Report outcome.

---

### Example 2 — Fewer than 5 candidates, auto-escalation

**User:** "Harvest keywords for our new moisturiser brand."

1. profile_id resolved. Date resolved.
2. Snapshot (last_30_days) → account average ROAS = 6.0 → min_roas = 3.6.
3. `keyword_harvesting` (last_30_days) → 3 candidates returned.
4. Auto-escalate: re-run snapshot (last_60_days) → ROAS = 6.2 → min_roas = 3.72.
5. Re-run `keyword_harvesting` (last_60_days) → 14 candidates returned.
6. Inform user: "Only 3 candidates found in 30 days — expanded to 60 days, found 14."
7. Group: Moisturiser (11), General / Mixed (3). Present tables.
8. Ask: "Which of these keywords do you want to add to a campaign?"

---

### Example 3 — New campaign path

**User (after seeing harvest):** "Add the hair oil keywords to a new campaign."

1. Ask: "Which ASINs should be advertised in this campaign, and what is the daily budget?
   List all ASINs if there are multiple ad groups."
2. User provides ASINs + budget.
3. Call `sp_campaign_preview` → show proposed campaign structure.
4. Wait for user confirmation.
5. Call `sp_campaign_execute`. Report outcome.

---

### Example 4 — ASIN-scoped harvest

**User:** "Harvest for our men's sunscreen ASIN B0ABC12345 only."

1. profile_id and date resolved. Snapshot → min_roas derived.
2. Use `analytics_search_term_list` (ASIN filter not in `keyword_harvesting`):
   ```
   advertised_asin: "B0ABC12345"
   harvesting: { status: "gap", match_types: ["EXACT"] }
   metric_filters: [{ field: "Orders", operator: "greater_than", value: 2 }]
   limit: 250
   sort_column: Orders, sort_type: DESC
   ```
3. All results under one ASIN → single group "Sunscreen › Men's."
4. Present table and proceed to Step 6.

---

## Closing Self-Check (RUN BEFORE EVERY RESPONSE)

- [ ] **Profile resolved?** Is profile_id confirmed before any MCP tool was called?
- [ ] **Date resolved?** Was `hector-mcp-date-resolution` run and the window set correctly?
- [ ] **Snapshot pulled?** Was `analytics_seller_vendor_master_overview` called first to derive
      min_roas = account_average_ROAS × 0.60?
- [ ] **min_orders = 2 enforced?** No term with fewer than 2 orders appears anywhere in output.
- [ ] **min_roas is dynamic, not hardcoded?** It was derived from the account snapshot.
- [ ] **limit set to 250?** Not using the default 5-row preview for the harvest call.
- [ ] **Auto-escalation logic applied?** If < 5 candidates on 30 days, did I re-run on 60 days?
- [ ] **Results grouped by Claude-defined product/category?** No flat list presented.
- [ ] **Summary table shown** after all category groups?
- [ ] **Action prompt given immediately** after results: "Which of these keywords do you want
      to add to a campaign?"
- [ ] **Existing vs new campaign path followed correctly?** Existing → group mapping confirmed.
      New → ASINs + budget collected before `sp_campaign_preview`.
- [ ] **Bids from `get_suggested_bids`?** Not using CPC from analytics.
- [ ] **Preview shown before execute?** `preview_only: true` called and confirmed before
      `preview_only: false`.

If any check fails, fix it before responding.

---

## Versioning

**v1** — July 2026. Initial draft (discarded — thresholds invented without user input).

**v2** — July 2026. Full rebuild after 8-question elicitation. Key changes from v1:
min_orders fixed to 2 (not invented); min_roas made dynamic (account_average_ROAS × 0.60,
derived from snapshot); default date window set to last_30_days with auto-escalation to
last_60_days if < 5 candidates; limit fixed to 250 rows; category grouping defined by Claude
semantically (no pre-built table); post-harvest action flow fully specified (existing vs new
campaign paths); trigger boundaries tightened (top search terms and wasting spend explicitly
excluded).

