---
title: "Add keywords to a Niche's Master Keyword List"
method: POST
path: "/v1/niches/{nicheId}/keywords"
tags: ["v1"]
---

# Add keywords to a Niche's Master Keyword List

`POST /v1/niches/{nicheId}/keywords`

## Request

- Base URL: `https://api.datadive.tools`
- URL: `https://api.datadive.tools/v1/niches/{nicheId}/keywords`
- Auth: API key in header `x-api-key`

Adds your own keywords to a Niche's Master Keyword List (MKL), the same way the web app does. Every submitted keyword a phrase exclusion does not block is reported in exactly one of these `originBreakdown` groups:

- `alreadyInMkl` — already in the MKL; left untouched.
- `takenFromOutliers` / `takenFromResidue` — moved into the MKL from the Outliers or Residue list. Free.
- `addedManually` — new to the Niche. Ranked against the Niche's Competitors; relevancy can be 0.

Two groups report keywords that match one of the Niche's keyword exclusions:

- `takenFromExcluded` — matched an exact exclusion. The exclusion is removed, so the keyword shows again; it is also listed in the group above it landed in.
- `blockedByPhraseExclusion` — matched a phrase exclusion. The rule is left in place and the keyword is not added; it appears in no other group, and `matchedPhrase` names the rule to remove first.

**Cost.** While the Niche is fresh (dived within the last 24 hours, or a Dive is already running) the add is free and the keywords are readable from GET /v1/niches/{nicheId}/keywords at once. Once it is stale, adding any new keyword re-dives the Niche with its current Competitors and consumes one Dive token per Competitor, like a `same_competitors` re-dive. The response then carries `refresh.diveId`; poll GET /v1/niches/dives/{diveId} until `success`, after which the keywords are in the MKL.

Send `dryRun: true` first to see `tokensUsed` without saving or paying anything, then pass that figure as `maxTokensUsed` on the real call so it can never cost more than the dry run said. A dry run runs every check the real call does except the 422, which only the re-dive itself can discover. Undo an add with POST /v1/niches/{nicheId}/keywords/remove.

Rate limited to 2 requests per minute per Niche, dry runs included.

## Path parameters

- `nicheId` string, required

## Request body

- AddNicheKeywordsRequestDto
  - `keywords` string[], required: Keywords to add to the Niche's Master Keyword List, 100 at most and 255 characters each. They are lower-cased and trimmed and must be unique after that; blank entries are ignored.
  - `dryRun` boolean: When `true`, returns the same response a real call would — including `tokensUsed` — without saving anything or consuming Dive tokens. Counts towards the rate limit.
  - `maxTokensUsed` number: The most Dive tokens this call may consume. When adding the keywords would re-dive a stale Niche for more than this, the call fails with `error: "MAX_TOKENS_EXCEEDED"` and nothing is saved or charged. Pass the `tokensUsed` of a dry run to make sure the real call never costs more than you confirmed, or `0` to never pay for a re-dive. No limit when omitted.

## Response `200`

- object
  - `message` string
  - `data` object
    - `originBreakdown` NicheKeywordsOriginBreakdownDto, required
      - `alreadyInMkl` string[], required: Already in the Master Keyword List. Left untouched.
      - `takenFromOutliers` string[], required: Moved into the Master Keyword List from the Outliers list. Free.
      - `takenFromResidue` string[], required: Moved into the Master Keyword List from the Residue list. Free.
      - `addedManually` string[], required: New to the Niche, added by hand. Their relevancy is calculated against the Niche Competitors and can be 0.
      - `takenFromExcluded` string[], required: Matched one of the Niche's exact keyword exclusions. The exclusion was removed (not on a dry run), and the keyword is also listed in the group it landed in.
      - `blockedByPhraseExclusion` NicheKeywordPhraseBlockedDto[], required: Matched one of the Niche's phrase exclusions. Not added, and listed in no other group. The rule `matchedPhrase` was left in place; remove it with POST /v1/niches/{nicheId}/keyword-exclusions/remove first to add the keyword.
        - `keyword` string, required
        - `matchedPhrase` string, required: The phrase exclusion the keyword contains.
    - `tokensUsed` number, required: Dive tokens this call consumed, or would consume with `dryRun`. 0 while the Niche is fresh. For a stale Niche a dry run reports the number of the Niche's Competitors, the most the re-dive can cost; a real call only charges the Competitors that could still be found, so it can come in lower.
    - `refresh` NicheKeywordsRefreshDto
      - `diveId` string, required: Id of the re-dive. Poll `GET /v1/niches/dives/{diveId}` with it to track progress.
      - `estimatedCompletionDate` string, required: Estimated completion timestamp for the re-dive in ISO 8601 format

## Other responses

- `400`: Invalid nicheId or request body; or, for a stale Niche: it has fewer than 2 Competitors, its marketplace cannot be re-dived, the re-dive would cost more than `maxTokensUsed` (`error: "MAX_TOKENS_EXCEEDED"`), or there are not enough Dive tokens (`error: "QUOTA_EXCEEDED"`). Nothing is saved or charged.
- `401`: Api key is invalid
- `403`: The Niche belongs to another subscription, or, for a stale Niche, its number of Competitors exceeds your plan's limit
- `404`: Niche not found, or it has no completed research
- `422`: Stale Niche only: current product data could no longer be found for enough of the Niche's Competitors to re-dive. Nothing is saved and no Dive tokens are consumed. A dry run cannot predict this.
- `429`: Rate limit exceeded (2 requests/min per organization and Niche).
- `500`: Internal error

## Changes

- **2026-10-28** `810bf6128252`: 2 info
  - added the required property `allOf[subschema #2]/data/originBreakdown/blockedByPhraseExclusion` to the response with the `200` status
  - added the required property `allOf[subschema #2]/data/originBreakdown/takenFromExcluded` to the response with the `200` status
- **2026-10-13** `8ce2cc36c41a`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/datadive/apis/data-dive-developer-apis/changes/v1/niches/:nicheId/keywords/post.md)

---

[API](https://skmtc.dev/datadive/apis/data-dive-developer-apis.md) - [All operations](https://skmtc.dev/datadive/apis/data-dive-developer-apis/llms.txt) - [OpenAPI document](https://skmtc.dev/datadive/apis/data-dive-developer-apis/revisions/94c025cac171?raw)
