Skip to main content

Command Palette

Search for a command to run...

How to Audit Publication Metadata for Entity Resolution

A parameter-controlled WordPress audit for detecting missing, fragmented, empty, and ambiguous organization records.

Updated
7 min readView as Markdown
J
Founder of AuthorityTech and creator of Machine Relations, the parent category for GEO, AEO, AI SEO, LLM Optimization, and AI PR. Machine Relations (MR) is the discipline I coined in 2024 to describe how brands become legible, retrievable, and citable inside AI-driven discovery systems. As search shifts from human-mediated discovery to machine-mediated answers, most of the market describes fragments of the same change using labels like GEO, AEO, AI SEO, LLMO, and AI PR. My position is that these are not separate categories. They are tactics or layers inside the broader system of Machine Relations (MR).

A publication can mention an organization repeatedly while maintaining no stable metadata record for it. The prose exists, but the entity anchor is missing, fragmented, empty, or captured by a different name.

That distinction matters whenever software must answer a basic question: which real-world entity does this article describe?

An anonymized September 2026 measurement across five publications and ten organizations found all four failure modes. Exact-match share ranged from 4% to 100%, and the densest tag namespace contained nearly twenty times as many tags per post as the sparsest one.

The useful result is not a leaderboard. It is a reproducible audit that developers can run against any public WordPress publication before treating coverage as machine-resolvable evidence.

The entity anchor

An entity anchor is a stable machine-readable record that connects a name to a specific organization and a set of documents.

The strongest versions use explicit identifiers. Wikidata assigns persistent entity IDs. Schema.org Organization can connect a page to external identities with sameAs.

Publication systems often provide something weaker but still useful: a tag or custom taxonomy term with a stable slug and an archive of attached posts.

That term is not a universal entity ID. It is a publication-local identifier. But it can still tell a retrieval system that several documents refer to the same thing.

The audit asks whether that local record is coherent.

Two measurements expose most failures

The first measurement is namespace density:

namespace_density = total_tags / total_posts

A high value does not prove a bad taxonomy. It does indicate that the publication creates many identifiers relative to its output, which raises the risk of duplicates, legal-suffix variants, event-specific variants, and editorial typos becoming permanent records.

The second measurement is canonical share:

canonical_share = exact_slug_count / sum(relevant_matching_slug_counts)

Suppose an organization uses the canonical slug acme, while the same namespace also contains acme-inc, acme-corp, and acme-summit. If the exact term holds 60 attached posts and the relevant variants hold 40, canonical share is 60%.

This is not a citation rate. It measures convergence inside the publisher's own metadata.

Discover every namespace before measuring one

Do not assume that post_tag is the only entity-like layer. A publication may expose custom taxonomies or custom post types that carry stronger identifiers.

Start with the WordPress discovery endpoints:

/wp-json/wp/v2/taxonomies
/wp-json/wp/v2/types

The WordPress REST API taxonomy reference describes the response fields. Inspect each public taxonomy's rest_base and attached object types.

This step prevents a common false conclusion: reporting that a publication has no entity framework because its ordinary tags are messy, while a separate company taxonomy exists beside them.

It also exposes legacy layers that still serve data but are attached unevenly.

A parameter-controlled Node.js audit

The following example measures the standard tag namespace. It also sends a bogus query as a control, because some WordPress installations silently ignore unsupported parameters and return plausible-looking unfiltered data.

const base = "https://publisher.example/wp-json/wp/v2";

async function request(path) {
  const response = await fetch(`\({base}\){path}`, {
    headers: { "user-agent": "entity-anchor-audit/1.0" }
  });

  if (!response.ok) {
    throw new Error(`\({response.status} \){response.statusText}: ${path}`);
  }

  return {
    data: await response.json(),
    total: Number(response.headers.get("x-wp-total") || 0)
  };
}

function normalizeSlug(value) {
  return value
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");
}

async function auditEntity(name) {
  const slug = normalizeSlug(name);

  const [taxonomies, posts, tags, candidates, control] = await Promise.all([
    request("/taxonomies"),
    request("/posts?per_page=1"),
    request("/tags?per_page=1"),
    request(`/tags?search=${encodeURIComponent(name)}&per_page=100`),
    request("/tags?slug=zzzz-entity-audit-control-zzzz")
  ]);

  if (control.data.length !== 0) {
    throw new Error("The slug filter failed its bogus-value control");
  }

  const relevant = candidates.data.filter(term =>
    term.slug.includes(slug)
  );

  const exact = relevant.find(term => term.slug === slug);
  const matchedCount = relevant.reduce((sum, term) => sum + term.count, 0);

  return {
    publicTaxonomies: Object.values(taxonomies.data)
      .filter(t => t.rest_base)
      .map(t => ({ name: t.name, restBase: t.rest_base, types: t.types })),
    posts: posts.total,
    tags: tags.total,
    namespaceDensity: posts.total ? tags.total / posts.total : null,
    exactSlug: slug,
    exactCount: exact?.count ?? 0,
    matchingCount: matchedCount,
    canonicalShare: matchedCount ? (exact?.count ?? 0) / matchedCount : null,
    candidates: relevant.map(t => ({ slug: t.slug, count: t.count }))
  };
}

console.log(await auditEntity("Example Company"));

This is a candidate generator, not a final adjudicator. Inspect every matching slug. Short names and common words will retrieve unrelated terms, and blindly summing them converts ambiguity into a precise-looking wrong number.

Run the same controlled procedure against any relevant custom taxonomy discovered in the first request.

Four defects require four different fixes

1. Homonym capture

The exact company term exists, but unrelated slugs containing the same string carry more attached posts.

This usually affects short names, dictionary words, acronyms, and names embedded inside larger brands or technical standards.

The fix is explicit disambiguation. The publisher needs a canonical organization term, stronger term descriptions or external identifiers, and consistent attachment of relevant articles.

2. Corporate-suffix fragmentation

The namespace splits one organization across variants such as company, company-inc, company-llc, and event-specific terms.

No competitor has captured the name. The publication is competing with itself.

The fix is consolidation: select one canonical term, redirect or merge variants where the CMS permits it, and reattach the affected archive.

3. Namespace exclusion

The publication maintains a clean, selective vocabulary, but an organization discussed in prose receives no term at all.

This is the precision-versus-recall failure. A tightly governed taxonomy can resolve admitted entities perfectly while making every excluded entity invisible at the metadata layer.

The fix is an admission rule tied to coverage rather than organizational fame or editor familiarity.

4. The empty anchor

A correct-looking term exists with a count of zero while the publication's search index contains many textual mentions.

This is more deceptive than a missing term because an existence check passes.

The fix is attachment, not creation. Treat count: 0 as a failed integrity check and reconcile the term against the article archive.

Controls matter more than clever scoring

A silent API failure is dangerous because the output still looks orderly.

If an installation ignores orderby=count, the first page may be mistaken for the most-used terms even though it is ordered by creation ID. If it ignores a custom taxonomy filter on the posts endpoint, the response may look like a valid archive while actually containing every post.

Use a control for each parameter that carries a conclusion:

  • Send a bogus slug and require an empty result.
  • Compare an ordered request with the default response and confirm the bytes or IDs actually change.
  • Test a known-present term and a known-absent term.
  • Verify pagination with x-wp-total and x-wp-totalpages.
  • Keep claims derived from named terms separate from claims derived from samples.

The rule is simple: HTTP 200 proves that the endpoint answered. It does not prove that the parameter worked.

Turn the audit into a release check

The audit becomes more useful when it runs repeatedly.

Store one record per publication, namespace, entity, and observation date:

{
  "publication": "publisher.example",
  "namespace": "tags",
  "entity": "example-company",
  "observedAt": "2026-09-10T12:00:00Z",
  "namespaceDensity": 0.48,
  "exactCount": 42,
  "matchingCount": 57,
  "canonicalShare": 0.7368,
  "status": "fragmented"
}

Then alert on state changes rather than raw totals:

  • exact term disappears;
  • exact count falls to zero;
  • a new competing variant appears;
  • canonical share drops materially;
  • a custom entity namespace is added or removed;
  • a query parameter stops passing its control.

That produces a metadata integrity monitor, not a one-time report.

What the audit can and cannot prove

It can prove that a publication exposes a stable term, how many posts the publication attaches to it, how fragmented the surrounding namespace is, and whether the API filters used in the measurement behave as claimed.

It cannot prove that a language model consumed the endpoint, that the metadata caused a citation, or that every textual mention was tagged correctly. Those require separate retrieval and citation tests.

The correct conclusion is narrower: coverage and entity resolution are separate system properties.

Machine Relations treats the gap between them as source-record quality. Before asking whether an answer engine cited an article, verify that the source itself maintains a coherent record of who the article is about.