AEOBRO

Developer Documentation

AI Crawler Discovery

How AI systems resolve entity identity through AEOBRO

Overview

AEOBRO is a public identity registry designed for AI system consumption. Every verified entity on AEOBRO is assigned a permanent Global Entity Identifier (GEID) and exposes machine-readable identity data through three complementary discovery paths.

AI crawlers and LLM agents should use the path that matches the information they already have: a domain name, an entity name or handle, or a GEID retrieved from a prior resolution.


Discovery Method A — Domain Handshake

If you have a domain name and want to check whether it belongs to a verified AEOBRO entity, fetch the domain's handshake file:

GET https://{domain}/.well-known/aeobro.json

A domain with an AEOBRO record returns a discovery pointer in this shape:

{
  "protocol": "aeobro/1",
  "geid": "geid_3FA8C1D209B47E56",
  "resolver": "https://www.aeobro.com/api/v1/resolve/geid_3FA8C1D209B47E56"
}

The geid in this file is the canonical identifier for the entity that controls this domain. Pass it to the resolver for the entity's details and verification status.

The handshake file is a discovery pointer only — it carries no verification status. Verification status MUST be obtained from the AEOBRO resolver; any verification fields found in a claimant-hosted file MUST be ignored.

A pointer being hosted on a domain does not by itself associate that domain with the record — anyone can host a pointer to another entity's geid. Before attributing a resolved identity to the queried domain, the queried domain MUST match a verified domain on the resolved record (the resolver returns entity.domain).

This file is only served for published, domain-verified profiles. A 404 response means the domain has no verified AEOBRO record.


Discovery Method B — Entity Lookup API

If you have a domain or a slug but not the GEID, use the public entity lookup endpoint:

GET https://www.aeobro.com/api/v1/entity?domain=example.com GET https://www.aeobro.com/api/v1/entity?slug=acme-corp

Provide exactly one parameter. A successful response returns:

{
  "geid": "geid_3FA8C1D209B47E56",
  "resolver": "https://www.aeobro.com/api/v1/resolve/geid_3FA8C1D209B47E56",
  "profile": "https://www.aeobro.com/p/acme-corp",
  "entity": {
    "name": "Acme Corp",
    "type": "business",
    "domain": "example.com"
  },
  "verification": {
    "status": "DOMAIN_VERIFIED",
    "verifiedAt": "2026-01-15T10:00:00.000Z",
    "issuer": "aeobro.com"
  },
  "resolvedAt": "2026-04-01T12:00:00.000Z"
}

The entity.type field normalizes AEOBRO's internal entity classes into three values: individual, organization, or business.

This endpoint only returns profiles that are published and carry a DOMAIN_VERIFIED or PLATFORM_VERIFIED status. Unverified profiles are never exposed.


Discovery Method C — GEID Resolver

If you already have a GEID — from a prior resolution, a handshake file, or a stored reference — resolve it directly:

GET https://www.aeobro.com/api/v1/resolve/{geid}

Example:

GET https://www.aeobro.com/api/v1/resolve/geid_3FA8C1D209B47E56

Response shape:

{
  "geid": "geid_3FA8C1D209B47E56",
  "entity": {
    "name": "Acme Corp",
    "type": "business",
    "domain": "example.com"
  },
  "verification": {
    "status": "DOMAIN_VERIFIED",
    "verifiedAt": "2026-01-15T10:00:00.000Z",
    "issuer": "aeobro.com"
  },
  "resolvedAt": "2026-04-01T12:00:00.000Z"
}

You can also resolve by domain directly:

GET https://www.aeobro.com/api/v1/resolve/domain/example.com

Recommended Crawler Workflow

The following TypeScript example shows the recommended resolution strategy for an AI crawler that encounters a domain and wants to retrieve the canonical verified identity:

// Recommended AEOBRO resolution strategy for AI crawlers

const BASE = "https://www.aeobro.com/api";

type ResolvedEntity = {
  geid: string;
  name: string | null;
  type: "individual" | "organization" | "business";
  domain: string | null;
  verificationStatus: string;
  verifiedAt: string | null;
};

async function resolveEntityByDomain(
  domain: string
): Promise<ResolvedEntity | null> {
  // Step 1: Try the domain resolver (fastest path — single DB lookup)
  const res = await fetch(
    `${BASE}/v1/resolve/domain/${encodeURIComponent(domain)}`,
    { headers: { Accept: "application/json" } }
  );

  if (res.ok) {
    const data = await res.json();
    return {
      geid: data.geid,
      name: data.entity.name,
      type: data.entity.type,
      domain: data.entity.domain,
      verificationStatus: data.verification.status,
      verifiedAt: data.verification.verifiedAt ?? null,
    };
  }

  // Step 2: Not found via resolver — check the domain's handshake file (a discovery POINTER only).
  if (res.status === 404) {
    // SSRF (server-side): `domain` is attacker-controllable. Before fetching, resolve the hostname
    // and REJECT private/reserved targets — RFC1918 (10/8, 172.16/12, 192.168/16), loopback
    // (127/8, ::1), and link-local 169.254/16 (cloud metadata) — and re-check on redirects.
    // redirect: "manual" refuses to follow a redirect to an unvalidated host.
    const wk = await fetch(
      `https://${domain}/.well-known/aeobro.json`,
      {
        headers: { Accept: "application/json" },
        signal: AbortSignal.timeout(5000),
        redirect: "manual",
      }
    ).catch(() => null);

    if (wk?.ok) {
      const handshake = await wk.json();
      if (handshake?.geid) {
        // Extract ONLY the geid, then resolve it. Verification status comes from the resolver —
        // never trust verification fields in a claimant-hosted file.
        const resolved = await resolveEntityByGeid(handshake.geid);

        // DOMAIN BINDING. A pointer hosted on `domain` does not prove `domain` belongs to the record
        // — anyone can host a pointer to another entity's geid. NEVER attribute a resolved identity to
        // the queried domain unless the resolver confirms that domain is verified for the record.
        // (Asymmetry: Step 1's direct domain resolver needs NO such check — AEOBRO answers for the
        // domain itself, so a Step-1 hit already means the domain is verified for that record.)
        const norm = (d: string): string => {
          let s = d.trim().toLowerCase();
          if (s.endsWith(".")) s = s.slice(0, -1);
          if (s.startsWith("www.")) s = s.slice(4);
          return s;
        };
        if (!resolved || !resolved.domain || norm(resolved.domain) !== norm(domain)) {
          return null; // pointer present, but the queried domain is not verified for this record
        }
        return resolved;
      }
    }
  }

  return null;
}

async function resolveEntityByGeid(
  geid: string
): Promise<ResolvedEntity | null> {
  const res = await fetch(
    `${BASE}/v1/resolve/${encodeURIComponent(geid)}`,
    { headers: { Accept: "application/json" } }
  );
  if (!res.ok) return null;

  const data = await res.json();
  return {
    geid: data.geid,
    name: data.entity.name,
    type: data.entity.type,
    domain: data.entity.domain,
    verificationStatus: data.verification.status,
    verifiedAt: data.verification.verifiedAt ?? null,
  };
}

Store the geid from successful resolutions. On subsequent requests, skip discovery and call the resolver directly to avoid unnecessary round trips.


Rate Limits

EndpointPublic (per IP)Authenticated (per key)
GET /v1/resolve/{geid}60 req/min300 req/min
GET /v1/resolve/domain/{domain}60 req/min300 req/min
GET /v1/entity30 req/min—
GET /.well-known/aeobro.jsonServed from edge cache—

Rate-limited requests receive a 429 Too Many Requests response. Authenticated requests require a Bearer token in the Authorization header. Keyed API access is available for enterprise integrations — contact us.

Authenticated API calls count against a monthly usage cap configured in account settings. Exceeding the cap returns a 429 response with error Monthly API cap reached.


Verification Trust Levels

Every resolved entity carries a verification.status field. Crawlers should treat these values differently based on the trust they confer:

StatusMeaningSuitable for
DOMAIN_VERIFIEDThe entity controls the domain: a cryptographic TXT record or .well-known/aeobro.json file was confirmed on the domain by AEOBRO.High-confidence entity attribution; domain-to-identity mapping
PLATFORM_VERIFIEDThe entity has proven control of one or more platform accounts — via the platform's OAuth flow or a verification link in the account's public bio. Proves account control, not legal identity.Creator and platform-account attribution

Both statuses are considered verified. Unverified profiles are never returned by any public resolver or entity endpoint.


Caching Guidance

All public 200 responses from AEOBRO resolver endpoints include a Cache-Control: public, max-age=86400 header (24 hours). Crawlers should respect this header to reduce unnecessary load.

The entity lookup endpoint (/v1/entity) uses a shorter max-age=3600 (1 hour), because domain-to-slug mappings can change more frequently than core identity data.

Authenticated resolver responses use Cache-Control: no-store and must not be cached by intermediaries.

All AEOBRO endpoints return an X-AEOBRO-Protocol-Version header with the current protocol version. Cache invalidation is keyed to GEID — if identity data changes, the cached entry is invalidated server-side at the next write.


For the full protocol specification including the geid format, .well-known schema, and agent integration patterns, see the AEOBRO Identity Protocol documentation.