Use InterfaceDelta in your enrichment pipeline

One Record API call per company domain tells you whether it runs agent-facing services, which protocols, when they last changed, and whether any change was breaking.

Many teams now replace boxed enrichment tools with their own scripts. The Record API’s domain lookup is a ready-made enrichment source for that pipeline: it is a closed projection of InterfaceDelta’s independently observed record, served as flat JSON with an evidence class per field.

What the lookup returns

GET /api/v1/record/lookup?domain=example.com accepts a domain or an HTTP(S) URL. It normalizes the host and matches that host and its subdomains. The response carries a summary for the whole domain plus per-service services entries, at most 10, sorted by atlas key. Every summary field has a sibling entry in an evidence map naming its evidence class.

servicesFound, atlasUrl
How many recorded services matched, and the public atlas page of the first one.
hasMcp, hasServerCard, hasOpenApi, hasA2a, hasArd, hasWebMcp
Whether any matched service currently exposes that agent-facing protocol, from the latest observation. Commerce flags hasUcp and hasAp2 appear only when commerce display is enabled.
asksForPayment
An MCP endpoint answered Payment Required. It exposes no payment terms.
toolsCount
Tools read across the matched services’ latest observations, or null if any matched service has an unknown count.
firstObservedAt, lastObservedAt
The earliest and latest retained observations. firstObservedAt is not a claim about when a service launched.
lastChangedAt, lastChangeClass
The most recent retained comparable change and its class, for example breaking or non_breaking.
breakingChanges30d, changes90d
Retained changes in those windows. Dates and change counts cover retained history only; boundedHistory stays true.

A domain with no recorded services is not an error: the lookup answers 200 with found: false, a zeroed summary and an empty services list, and still counts toward quota.

Authentication and the free quota

Create an API token in Account → API tokens with the record:read scope and send it as a Bearer token. The free plan includes 1,000 accepted Record API requests per month, shared by all tokens on the account and resetting at midnight UTC on the first day of the next month. Each token may send 60 requests per minute; a burst rejection does not consume monthly quota. Authentication and invalid-query errors do not count; every successfully constructed response does, including lookups with no matches.

curl -H "Authorization: Bearer $INTERFACEDELTA_API_TOKEN" \
  "https://interfacedelta.com/api/v1/record/lookup?domain=example.com"

Python waterfall

Look up each domain in turn; a miss marks the row not_observed and the loop continues; observed rows with breaking changes are flagged for review.

enrich.py
import json
import os
import urllib.error
import urllib.parse
import urllib.request

API = "https://interfacedelta.com/api/v1/record/lookup"
TOKEN = os.environ["INTERFACEDELTA_API_TOKEN"]

def lookup(domain):
    url = f"{API}?domain={urllib.parse.quote(domain)}"
    request = urllib.request.Request(
        url, headers={"Authorization": f"Bearer {TOKEN}"}
    )
    with urllib.request.urlopen(request) as response:
        return json.load(response)

def enrich(domains):
    for domain in domains:
        try:
            record = lookup(domain)
        except urllib.error.HTTPError as error:
            if error.code == 429:
                # Wait for Retry-After, or flag the row and continue.
                yield {"domain": domain, "status": "rate_limited", "review": True}
                continue
            raise
        if not record["found"]:
            # Not in the record is not a clean bill of health; keep going.
            yield {"domain": domain, "status": "not_observed"}
            continue
        summary = record["summary"]
        yield {
            "domain": domain,
            "status": "observed",
            "servicesFound": summary["servicesFound"],
            "hasMcp": summary["hasMcp"],
            "hasOpenApi": summary["hasOpenApi"],
            "hasA2a": summary["hasA2a"],
            "toolsCount": summary["toolsCount"],
            "asksForPayment": summary["asksForPayment"],
            "lastObservedAt": summary["lastObservedAt"],
            "lastChangedAt": summary["lastChangedAt"],
            "breakingChanges30d": summary["breakingChanges30d"],
            "atlasUrl": summary["atlasUrl"],
            "review": summary["breakingChanges30d"] > 0
            or summary["lastChangeClass"] == "breaking",
        }

for row in enrich(["example.com", "example.org"]):
    print(json.dumps(row))

JavaScript waterfall

The same waterfall in Node 18 or later, with the built-in fetch.

enrich.mjs
const API = "https://interfacedelta.com/api/v1/record/lookup";

async function lookup(domain, token) {
  const url = `${API}?domain=${encodeURIComponent(domain)}`;
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (response.status === 429) return { rateLimited: true };
  if (!response.ok) throw new Error(`lookup failed for ${domain}`);
  return { rateLimited: false, record: await response.json() };
}

async function enrich(domains, token) {
  const rows = [];
  for (const domain of domains) {
    const { rateLimited, record } = await lookup(domain, token);
    if (rateLimited) {
      // Honour Retry-After, or flag the row and continue.
      rows.push({ domain, status: "rate_limited", review: true });
      continue;
    }
    if (!record.found) {
      // Not in the record is not a clean bill of health; keep going.
      rows.push({ domain, status: "not_observed" });
      continue;
    }
    const summary = record.summary;
    rows.push({
      domain,
      status: "observed",
      servicesFound: summary.servicesFound,
      hasMcp: summary.hasMcp,
      hasOpenApi: summary.hasOpenApi,
      hasA2a: summary.hasA2a,
      toolsCount: summary.toolsCount,
      asksForPayment: summary.asksForPayment,
      lastObservedAt: summary.lastObservedAt,
      lastChangedAt: summary.lastChangedAt,
      breakingChanges30d: summary.breakingChanges30d,
      atlasUrl: summary.atlasUrl,
      review:
        summary.breakingChanges30d > 0 ||
        summary.lastChangeClass === "breaking",
    });
  }
  return rows;
}

const token = process.env.INTERFACEDELTA_API_TOKEN;
console.log(await enrich(["example.com", "example.org"], token));

Sample response

Computed by the same code path as the live lookup, over the fixtures used by the record tests: one service on example.com with a retained breaking change the day before the snapshot. Commerce fields are omitted, as they are when commerce display is off.

GET /api/v1/record/lookup?domain=example.com
Show all 136 lines
{
  "recordVersion": "record-v1",
  "generatedAt": "2026-11-02T00:00:00.000Z",
  "coverage": {
    "boundedHistory": true,
    "truncated": false
  },
  "provenance": {
    "manifestVersion": "provenance-v1",
    "publisher": "InterfaceDelta, Inc.",
    "collection": {
      "agent": "InterfaceDeltaBot/1.0 (+https://interfacedelta.com/bot)",
      "policy": "https://interfacedelta.com/bot",
      "method": "Passive observation of public agent interfaces: robots.txt honoured, no credentials sent, no tools called, nothing paid or signed."
    },
    "sources": [
      {
        "id": "official_registry",
        "name": "Official MCP Registry",
        "role": "listing",
        "licence": "CC0-1.0",
        "url": "https://registry.modelcontextprotocol.io"
      }
    ],
    "listingsAreLeads": "Listings only point InterfaceDelta at a service; every recorded fact comes from InterfaceDelta’s own observation of that service.",
    "evidenceClasses": [
      {
        "id": "DIRECTLY_OBSERVED",
        "meaning": "Recorded by InterfaceDelta from the service’s own public response."
      },
      {
        "id": "PUBLISHER_DECLARED",
        "meaning": "Stated by the publisher in its own interface or listing; not verified."
      },
      {
        "id": "INTERFACEDELTA_DERIVED",
        "meaning": "Computed by InterfaceDelta with fixed rules from recorded evidence."
      },
      {
        "id": "INTERFACEDELTA_INFERRED",
        "meaning": "Estimated by InterfaceDelta, including AI readings; labelled and checked against recorded evidence."
      }
    ],
    "rules": {
      "changeClassification": "compatibility-rules-v12"
    },
    "snapshot": {
      "generatedAt": "2026-11-02T00:00:00.000Z",
      "truncated": false
    },
    "noVerdicts": "Facts and evidence only. InterfaceDelta assigns no safety, trust or risk scores."
  },
  "found": true,
  "summary": {
    "domain": "example.com",
    "servicesFound": 1,
    "atlasUrl": "https://interfacedelta.com/atlas/sha256%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "hasMcp": true,
    "hasA2a": false,
    "hasOpenApi": false,
    "hasWebMcp": false,
    "hasArd": false,
    "hasServerCard": false,
    "asksForPayment": false,
    "toolsCount": 3,
    "firstObservedAt": "2026-11-01T00:00:00.000Z",
    "lastObservedAt": "2026-11-02T00:00:00.000Z",
    "lastChangedAt": "2026-11-01T00:00:00.000Z",
    "lastChangeClass": "breaking",
    "breakingChanges30d": 1,
    "changes90d": 1,
    "boundedHistory": true,
    "evidence": {
      "domain": "INTERFACEDELTA_DERIVED",
      "servicesFound": "INTERFACEDELTA_DERIVED",
      "atlasUrl": "INTERFACEDELTA_DERIVED",
      "hasMcp": "DIRECTLY_OBSERVED",
      "hasA2a": "DIRECTLY_OBSERVED",
      "hasOpenApi": "DIRECTLY_OBSERVED",
      "hasWebMcp": "DIRECTLY_OBSERVED",
      "hasArd": "DIRECTLY_OBSERVED",
      "hasServerCard": "DIRECTLY_OBSERVED",
      "asksForPayment": "INTERFACEDELTA_DERIVED",
      "toolsCount": "INTERFACEDELTA_DERIVED",
      "firstObservedAt": "DIRECTLY_OBSERVED",
      "lastObservedAt": "DIRECTLY_OBSERVED",
      "lastChangedAt": "INTERFACEDELTA_DERIVED",
      "lastChangeClass": "INTERFACEDELTA_DERIVED",
      "breakingChanges30d": "INTERFACEDELTA_DERIVED",
      "changes90d": "INTERFACEDELTA_DERIVED",
      "boundedHistory": "INTERFACEDELTA_DERIVED"
    }
  },
  "services": [
    {
      "domain": "example.com",
      "servicesFound": 1,
      "atlasUrl": "https://interfacedelta.com/atlas/sha256%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "hasMcp": true,
      "hasA2a": false,
      "hasOpenApi": false,
      "hasWebMcp": false,
      "hasArd": false,
      "hasServerCard": false,
      "asksForPayment": false,
      "toolsCount": 3,
      "firstObservedAt": "2026-11-01T00:00:00.000Z",
      "lastObservedAt": "2026-11-02T00:00:00.000Z",
      "lastChangedAt": "2026-11-01T00:00:00.000Z",
      "lastChangeClass": "breaking",
      "breakingChanges30d": 1,
      "changes90d": 1,
      "boundedHistory": true,
      "evidence": {
        "domain": "INTERFACEDELTA_DERIVED",
        "servicesFound": "INTERFACEDELTA_DERIVED",
        "atlasUrl": "INTERFACEDELTA_DERIVED",
        "hasMcp": "DIRECTLY_OBSERVED",
        "hasA2a": "DIRECTLY_OBSERVED",
        "hasOpenApi": "DIRECTLY_OBSERVED",
        "hasWebMcp": "DIRECTLY_OBSERVED",
        "hasArd": "DIRECTLY_OBSERVED",
        "hasServerCard": "DIRECTLY_OBSERVED",
        "asksForPayment": "INTERFACEDELTA_DERIVED",
        "toolsCount": "INTERFACEDELTA_DERIVED",
        "firstObservedAt": "DIRECTLY_OBSERVED",
        "lastObservedAt": "DIRECTLY_OBSERVED",
        "lastChangedAt": "INTERFACEDELTA_DERIVED",
        "lastChangeClass": "INTERFACEDELTA_DERIVED",
        "breakingChanges30d": "INTERFACEDELTA_DERIVED",
        "changes90d": "INTERFACEDELTA_DERIVED",
        "boundedHistory": "INTERFACEDELTA_DERIVED"
      }
    }
  ]
}

Reading the fields

Evidence classes are DIRECTLY_OBSERVED (recorded from the service’s own public response), PUBLISHER_DECLARED, INTERFACEDELTA_DERIVED and INTERFACEDELTA_INFERRED. In the lookup, a summary field whose evidence is DIRECTLY_OBSERVED was read from a single matched service; aggregated fields are derived. Every response also carries provenance: who collected the data, each listing source and licence, and the rule version that classified changes.

This is a bounded snapshot, not a durable archive: dates and change counts cover retained history only, and a null lastChangedAt does not establish that nothing ever changed. The full field list, including the per-service shape, is in the Record API OpenAPI document and the Record API reference.

Paid volumes and licensing

Higher volumes, and any licence to redistribute or republish record data inside a product, are by agreement; the included monthly quota is the one shown on pricing. InterfaceDelta publishes facts and evidence classes, never verdicts: keep it that way in your pipeline’s output.

Back to docs