API reference · Developer guide

Entity coverage and missing topics

Understand what counts as missing, distinguish it from wording already present, and turn competitor evidence into a useful review queue.

Missing is a report observation, not an editing instruction

The natural-language entity list combines a concept, its importance, and a coverage state. The term matrix adds occurrence counts for the target and comparison pages. Read these signals together before recommending an edit.

coverage_statusMeaningApplication behavior
goodThe analysis recognized the entity on the page.Preserve useful coverage; assess depth separately.
present_not_entityThe wording is present, but it was not recognized as an entity in this analysis.Do not label it a missing term.
missingThe entity analysis did not find coverage on the analyzed page.Check the term count, source passage, and relevance before suggesting a change.

In the saved example, testing is missing and has zero target occurrences. Integration is present_not_entity and occurs three times. A rule such as coverage_status !== "good" would incorrectly queue integration as missing.

Inspect these examples in the report explorer →

Join coverage to the comparison evidence

FieldHow to use it
entity_coverage.natural_language_entities[]Use entity as the label, importance as a relative ordering signal, and coverage_status as the recognition state.
competitor_term_coverage.terms[]Match keyword to the entity label. your_url_count describes the target; competitor_counts describes the comparison pages.
competitor_term_coverage.domains[]Counts align by index with this array. Count nonzero entries to calculate prevalence, using the actual array length as the denominator.
your_url_related_entity_density_scoreCompare with competitor_related_entity_density_score when both are available. These are report scores, not percentages or keyword-frequency targets.

The example's density scores are 396.9 for the target and 539.2 for the comparison cohort. That is a reason to investigate useful coverage and depth, not a requirement to reach a particular number by repeating terms.

Normalize labels consistently when joining the lists. Treat malformed count arrays or unsupported statuses as data to inspect; do not silently turn them into a clean audit. A missing report field is different from a valid empty array.

Which gaps deserve attention?

  1. Qualify the evidence. Require missing, a target count of zero, and a meaningful presence among comparison pages.
  2. Check the reader's task. Does the concept help this page deliver on its purpose? Inspect the actual paragraph before recommending new content.
  3. Order the candidates. This example sorts by competitor prevalence, then importance. These are review priorities, not estimates of ranking impact.
  4. Choose a useful change. Add an explanation, example, method, or answer where it fits. Dismiss generic or irrelevant terms.

Testing appears on seven of ten compared pages and fits an API comparison: readers could use a repeatable testing method. Company appears on six, but the term alone does not identify a useful omission. Both pass the numerical rule; only the editorial review determines the action.

The code below uses 50% prevalence as a starting rule. Tune it to your application and retain an ignored-term list so dismissed suggestions do not keep returning.

Build the review queue in JavaScript or Python

Pass the JSON body from the result endpoint to this helper. It returns metadata, qualified gaps, and optional information-gain fields. The complete content optimizer clients handle submission, polling, and saved-response testing.

Entity coverage parser
function buildOptimizer(report, { minCoverage = 0.5, ignoredTerms = [] } = {}) {
  if (minCoverage < 0 || minCoverage > 1 || !Number.isFinite(minCoverage)) {
    throw new Error("minCoverage must be between 0 and 1.");
  }
  const entities = report.entity_coverage?.natural_language_entities;
  const matrix = report.competitor_term_coverage;
  if (!Array.isArray(entities) || !Array.isArray(matrix?.domains) || !Array.isArray(matrix?.terms)) {
    throw new Error("Expected entity_coverage and competitor_term_coverage arrays.");
  }
  const key = (text) => String(text).trim().toLowerCase();
  const statuses = new Map(entities.map((row) => [key(row.entity), row.coverage_status]));
  const ignored = new Set(ignoredTerms.map(key));
  const seen = new Set();
  const gaps = [];
  for (const row of matrix.terms) {
    const counts = row.competitor_counts;
    if (!Array.isArray(counts) || counts.length !== matrix.domains.length ||
        counts.some((n) => typeof n !== "number" || !Number.isFinite(n) || n < 0)) {
      throw new Error("Competitor counts must align with domains and contain numbers.");
    }
    const term = key(row.keyword);
    if (!term || seen.has(term)) continue;
    seen.add(term);
    // Require both signals; present_not_entity is already present in the page.
    if (statuses.get(term) !== "missing" || row.your_url_count !== 0 || ignored.has(term)) continue;
    const competitorPages = counts.filter((n) => n > 0).length;
    const comparedPages = counts.length;
    if (!comparedPages || !competitorPages || competitorPages / comparedPages < minCoverage) continue;
    gaps.push({
      id: "coverage:" + term, term: row.keyword,
      importance: Number.isFinite(row.importance) ? row.importance : 0,
      competitorPages, comparedPages,
      prevalence: competitorPages / comparedPages,
      status: "needs_review",
    });
  }
  gaps.sort((a, b) => b.prevalence - a.prevalence || b.importance - a.importance ||
    (a.term < b.term ? -1 : a.term > b.term ? 1 : 0));
  const gain = report.originality?.information_gain;
  const ideas = gain?.potential_uncovered_topics_for_information_gain;
  return {
    jobId: report.jobId,
    targetUrl: report.meta?.url ?? null,
    keyword: report.meta?.target_keyword ?? null,
    region: report.meta?.location ?? null,
    score: report.on_page_optimization?.score ?? null,
    scoreAlgorithm: report.on_page_optimization?.algorithm_version ?? null,
    comparedPages: matrix.domains.length,
    gaps,
    originalIdeas: {
      available: Array.isArray(ideas),
      items: Array.isArray(ideas) ? [...new Set(ideas.filter((s) => typeof s === "string" && s.trim()))] : [],
      uniqueDataPoints: gain?.unique_data_points ?? null,
    },
  };
}

For the saved report, the default rule returns 15 candidates. It excludes integration and retains testing. The report explorer shows the exact data behind those decisions.

Interpret the edge cases

Does missing prove the topic is absent from the entire page?

It describes the analyzed representation of the page. Synonyms, wording variants, extraction gaps, or content that changed after the scan can affect the result. Inspect the relevant visible content before presenting an absence as a confirmed editorial problem.

What if the entity is missing but its term count is positive?

The two signals disagree. The supplied helper excludes that row from the missing-term queue. Route it to review if needed; avoid instructing a writer to add wording already present.

Should every important entity become a heading?

No. Choose placement based on what the reader needs. Many concepts fit naturally in an existing paragraph or example. Preserve useful human-written headings and avoid creating a section for every row in the report.

Is a missing field equivalent to zero coverage?

No. Missing or unsupported data should remain unavailable. A valid empty queue only means no rows met your current rule. It does not certify that the page has no other SEO issues.

Which scan tiers support this guide?

Lite, Standard, and Deep include the natural-language entity and competitor-term fields used here. Additional category-related fields depend on the tier. See the report schema for the full section map.

Build recommendations people can understand

Keep the observation, the interpretation, and the proposed edit together. That makes entity coverage useful inside an editor, an audit, or a client dashboard. Continue with the content optimizer to build that review experience, or explore information gain when the page needs original evidence.