CitationBenchTalk to Sales
API referenceResearch

Research · Content Gap API — Find Missing Topics, Keyword Gaps & Weaker Coverage vs. Competitors

Compare your content library against competitor coverage to surface missing topics, keyword gaps, and weaker pages — ranked by lift with suggested briefs ready to queue into bulk content production.

Given your content library + a competitor URL list, find the topics they cover that you don't, the keywords they rank for where you don't, and the topics where their content is materially better.

The output is a ranked gap report with suggested content briefs — straightforward to feed into produce.blog_post.bulk.

Conceptual overview

A content-gap analysis compares:

  • Your content — every published BlogPost + LandingPage in the workspace
  • Competitor coverage — pages on the competitor domains (crawled and embedded)
  • Keyword surfaces — where each side ranks

It produces three gap types:

Gap typeMeaning
MISSING_TOPICThey have a piece on this topic; you have nothing
KEYWORD_GAPThey rank for these keywords; you have no content targeting them
WEAKER_COVERAGEYou have something but theirs ranks better (depth, freshness, links)

Each gap comes with a recommended angle and an estimated lift.

Endpoints

MethodPathPurpose
POST/v1/research/content-gapRun a gap analysis
GET/v1/research/content-gapList recent reports
GET/v1/research/content-gap/{id}Get one report
POST/v1/research/content-gap/{id}/queue-contentAuto-create blog posts / landing pages for the report's gaps

POST /v1/research/content-gap

Request

POST /v1/research/content-gap HTTP/1.1
Authorization: Bearer sk_live_***
X-Workspace-Id: ws_acme

{
  "competitorIds": ["cmp_monday", "cmp_asana", "cmp_linear"],
  "options": {
    "includeWeakerCoverage": true,
    "maxGapsPerType":        25,
    "minCompetitorRank":     20,
    "minKeywordVolume":      100,
    "considerOwnedSubdomains":true
  },
  "tags": ["q2-2026"]
}
FieldTypeRequiredDefaultNotes
competitorIdsstring[]yes (one of)Competitor IDs (see Research · competitor)
competitorDomainsstring[]yes (one of)Or raw domains (auto-creates competitor records)
options.includeWeakerCoveragebooleannotrue
options.maxGapsPerTypenumberno25
options.minCompetitorRanknumberno20Skip very deep results
options.minKeywordVolumenumberno100Skip low-volume
options.pillarFilterstring[]noLimit gaps to specific pillars

Response (final)

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "research.content_gap",
  "status": "SUCCEEDED",
  "creditsUsed": 28,
  "result": {
    "reportId": "gap_***",
    "competitorsAnalyzed": ["cmp_monday", "cmp_asana", "cmp_linear"],
    "summary": {
      "missingTopics": 17,
      "keywordGaps": 82,
      "weakerCoverage": 11,
      "totalRecommendations": 110
    },
    "gaps": [
      {
        "id": "gap_item_***",
        "type": "MISSING_TOPIC",
        "topic": "Capacity forecasting for engineering teams",
        "competitorsCovering": [
          {
            "competitorId": "cmp_monday",
            "url": "https://monday.com/blog/capacity-forecasting"
          }
        ],
        "relatedKeywords": [
          "capacity forecasting",
          "engineering capacity planning"
        ],
        "estimatedTraffic": 1240,
        "recommendedAngle": "Tie capacity forecasting to sprint planning specifically — the existing content treats them separately.",
        "estimatedLiftMonths": 4,
        "priority": "HIGH"
      },
      {
        "id": "gap_item_***",
        "type": "KEYWORD_GAP",
        "keyword": "monday.com vs linear",
        "competitorsRanking": [
          { "competitorId": "cmp_monday", "position": 1 },
          { "competitorId": "cmp_linear", "position": 3 }
        ],
        "estimatedTraffic": 480,
        "recommendedAngle": "Write a 3-way comparison adding our take.",
        "priority": "MEDIUM"
      },
      {
        "id": "gap_item_***",
        "type": "WEAKER_COVERAGE",
        "ourContentId": "bp_***",
        "ourRank": 14,
        "competitorRank": 2,
        "competitorUrl": "https://monday.com/blog/best-pm-tools",
        "weaknessReason": "Ours: 1,800 words, 2 references. Theirs: 3,200 words, 14 references.",
        "recommendedAction": "Refresh with more depth + add fresh case study",
        "priority": "HIGH"
      }
    ]
  },
  "raw": "Compared your content library against 3 competitors. Found 17 missing topics, 82 keyword gaps, 11 weaker-coverage cases. The biggest opportunities are around capacity forecasting and Linear comparisons ...",
  "files": ["agent-workspace/coverage-matrix.csv", "agent-output/gap-report.md"]
}

POST /v1/research/content-gap/{id}/queue-content

Turn the gaps into actual draft work in one call.

curl -X POST .../v1/research/content-gap/gap_***/queue-content -d '{
  "selectGaps": [
    { "type": "MISSING_TOPIC",    "priority": ["HIGH"] },
    { "type": "WEAKER_COVERAGE",  "priority": ["HIGH"] }
  ],
  "blogPostDefaults": {
    "pillarSlug": "performance",
    "mode":       "with-research",
    "refinerIds": ["rfn_brand-voice"]
  },
  "landingPageDefaults": {
    "pillarSlug": "pricing"
  }
}'

The system spawns one produce.blog_post.create or produce.landing_page.create invocation per matched gap. Returns the batch handle.

{
  "batchId": "batch_***",
  "queued": {
    "blogPosts": 17,
    "landingPages": 4
  },
  "totalEstimatedCost": { "credits": 587 }
}

GET /v1/research/content-gap

curl -G .../v1/research/content-gap \
  --data-urlencode "tag=q2-2026" \
  --data-urlencode "limit=20"

GET /v1/research/content-gap/{id}

Returns the full report.


MCP

> Find content gaps between us and monday.com, asana.com, linear.app.

Claude calls research.content_gap.find.

> Queue blog posts for the high-priority missing-topic gaps from that report.

Claude calls research.content_gap.queue_content.


Errors

StatusCodeCause
400validation_errorNeed competitorIds or competitorDomains
422no_gaps_foundCoverage is comparable; nothing significant
503external_unavailableAhrefs / DataForSEO down during analysis

Cost

ActionCredits
POST /v1/research/content-gap per competitor~10
queue-content per content item spawnedper the underlying produce.* skill

Use cases (string things together)

A. Quarterly gap audit

# Quarterly cron
curl -X POST .../v1/research/content-gap -d '{
  "competitorIds": ["cmp_monday", "cmp_asana", "cmp_linear"],
  "tags":          ["q2-2026", "quarterly-audit"]
}'

B. Auto-content from gaps

GAP=$(curl -sf -X POST .../v1/research/content-gap -d '{...}' | jq -r '.invocationId')
# (wait)
curl -X POST .../v1/research/content-gap/$GAP/queue-content -d '{...}'

C. Cross-portfolio agency view

curl -X POST .../v1/workspaces/bulk-action -d '{
  "action":     "research.content_gap.find",
  "workspaces": "all",
  "config":     { "competitorIds": "{{ workspace.competitors.top_3 }}" }
}'

One call → one gap report per client workspace.

On this page