# CDN.MN Domains — integration guide for developers and AI agents

CDN.MN Domains is a shared domain-membership pre-check. A registrar, a
domain investor's tooling, or an AI agent consults a timestamped index first,
then decides whether a name needs a live query to the registry. It is not a
registry, not a registration checkout, not a WHOIS or RDAP replacement, and not
an authoritative availability database.

**Status: private access by request.** No Domains endpoint, API key, dataset or
download is public today. Every API route and file format below is proposed or
illustrative until it is agreed during onboarding. The parent CDN.MN product's
CLI and SDK do not cover Domains; there is no Domains SDK or CLI. Approved
accounts get their endpoint, keys and agent-access details on the Domains page
of their CDN.MN account.

Also on this host: the [overview page](https://domains.cdn.mn/), the
[short index](https://domains.cdn.mn/llms.txt) and the
[illustrative API contract](https://domains.cdn.mn/api-contract.example.json).

## Requesting access

Access is requested from a CDN.MN account or by email — keys are issued only
after the team approves the request.

1. Open https://app.cdn.mn/app/domains?audience=registrar (registrars, domain
   investors, AI-tool developers) or https://app.cdn.mn/app/domains?audience=registry
   (registry operators), choose your role, fill in the short form and confirm
   with an email sign-in code (this creates your CDN.MN account) — or email
   domains@cdn.mn. Registrars, domain investors and AI-tool developers:
   subject "Private access request — CDN.MN Domains". Registry operators: subject
   "Registry participation — CDN.MN Domains".
2. Describe your organization, use case, the TLDs or registration zones you
   need, your approximate daily check volume, and the access you prefer (API,
   hashed files, Bloom files).
3. Scope, permitted use, limits and the access method are agreed during
   onboarding. A scoped credential is issued only after access is granted.
   Keep it server-side — never in a frontend bundle, a URL, or a log.

Please don't email source files, credentials, or database exports. Start with a short email; we'll agree on a secure transfer method afterwards.

## What a result means

| Machine state | Label | What it means | Safe next step |
| --- | --- | --- | --- |
| `taken` | Taken in snapshot | Exact source membership, or a strong full-length hash match, supports a historical observation. | The caller may use its caching policy to deprioritize the name; recheck when current availability matters. |
| `possibly_taken` | Possible match | A Bloom filter reports possible membership; there is no independent exact confirmation. | Confirm before making a definitive taken/unavailable claim. |
| `not_in_snapshot` | Not found in this snapshot | No match in the specified usable dataset. | Check the authoritative registry; this is not an availability guarantee. |
| `unknown` | No usable coverage | The dataset is missing, unusable, incompatible, unauthorized, or its scope does not cover this query. | Use the caller's normal authorized registry workflow. |

- A Bloom-only positive is a **possible match** (`possibly_taken`), never an
  exact `taken` — a Bloom filter can report a name that is not in the source.
- `bloom_false_positive_probability`: The filter's declared target false-positive rate (a number) when match_method is bloom; null otherwise. A filter parameter, not the probability that this answer is wrong.
- “Not found” means not found in that dataset. It does not mean available to register.
- `current_availability` is always `not_checked`: a pre-check never reports
  whether a name can be registered now. For what a live registry check response
  means, see the EPP reference below.
- `evidence` values:
  - `dns_delegation`: The result rests on DNS-delegation data from a DNS zone snapshot.
  - `registry_registered`: The result rests on a registry's registered-name data: a list, hashes, or a Bloom file built from it.
  - `none`: No source entry matched: see state for a valid miss (not_in_snapshot) or no usable coverage (unknown).
- Decoding rule: evidence names the kind of source, not certainty: certainty comes from state and match_method, so registry_registered with match_method bloom is a possible match only.
- `match_method` values:
  - `exact`: Exact match in the source list.
  - `sha256`: Full-length SHA-256 hash match (negligible collision risk, not zero).
  - `bloom`: Bloom filter: possible membership, not an exact confirmation.
  - `none`: No match was made: see state for a valid miss (not_in_snapshot) or no usable coverage (unknown).
- If independently verified sources conflict, the result is never resolved silently to the convenient answer: it will keep each source's provenance and be marked for authoritative confirmation. The fields that carry that mark and the per-source provenance are proposed and defined during onboarding; this example contract has no field for them yet.

## Timestamps and freshness

- `as_of` — the **source snapshot** time, ISO 8601 in UTC. It is `null` when
  the source time is unknown (`timestamp_basis: "unknown"`); a receipt,
  download or response time is never substituted for it.
- `ingested_at` — when CDN.MN ingested that snapshot.
- `checked_at` — when the check ran.
- Whenever present: `as_of` ≤ `ingested_at` ≤ `checked_at`. Each result in a
  batch carries its own source and timestamps; one timestamp is never applied
  to unrelated TLD feeds.
- `freshness` (`fresh`, `stale`, `unknown`) is a separate dimension from
  the state. It is computed from the age of `as_of` against a documented
  policy — the examples use 36 hours — never from the
  service answering. A result can be `taken` and `stale` at once. Apply your
  own freshness policy to `as_of`.

We aim to refresh supported datasets at least once every 24 hours, subject to source publication and access. Each response shows the source snapshot time, where available, so you can apply your own freshness policy.

## Normalization

Send names in one canonical form, agreed and versioned per source:

- The full registrable domain name, not just the label: `example.com` and
  `example.net` are different names.
- Internationalized names as lowercase ASCII A-labels, produced by maintained
  IDNA code under a declared UTS #46 policy and version — never ad hoc Unicode
  transformations.
- No trailing root dot.
- Only domain names: no schemes, paths, email addresses, ports or wildcards. A
  malformed name is a validation error, not a membership state.
- Never shorten a subdomain into a registrable name. Multi-level registration
  namespaces (common under ccTLDs) follow explicit registry-specific scope
  rules.

## Batching, deduplication and limits

- Normalize names to one canonical form, drop duplicates, and send authorized automated pre-checks in batches within agreed access and usage limits.
- Keep retries bounded, with backoff. Missing coverage is a normal `unknown`
  result — do not retry it as if it were an error, and never treat an error or
  an `unknown` as `not_in_snapshot`.
- Rate limits and batch sizes are agreed per customer during onboarding; none
  is published.
- A remote pre-check adds a network hop for names it does not match; a local
  file check avoids that hop. Real savings depend on hit rates, batching,
  coverage and your recheck policy.

## Checking is separate from registration

- A pre-check never registers, reserves, or buys a name. Use an authorized live check and your registrar's workflow for that decision.
- A `not_in_snapshot` result is never permission to spend money or register a
  name.
- Registrars may skip a separate EPP check before an authorized registration attempt where their integration and registry rules permit. CDN.MN does not submit registrations or authorize purchases. The registry still decides whether a registration succeeds.

## Illustrative API contract (proposed)

Illustrative API contract — final endpoints and fields confirmed during onboarding. The route below is proposed and illustrative; it is not
operational, and no anonymous API endpoint exists.

```bash
# Illustrative server-side request, not a public demo endpoint.
# Use a real scoped token only after access has been granted.
curl 'https://domains.cdn.mn/api/v1/check' \
  --request POST \
  --header "Authorization: Bearer ${CDNMN_DOMAIN_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data '{"domains":["registered.example"]}'
```

Illustrative example response (an exact historical result):

```json
{
  "schema_version": "1.0-example",
  "example": true,
  "results": [
    {
      "domain": "registered.example",
      "state": "taken",
      "evidence": "registry_registered",
      "match_method": "exact",
      "source_type": "registry_list",
      "dataset_id": "example-registry-demo",
      "snapshot_id": "demo-2026-10-12",
      "as_of": "2026-10-12T00:00:00Z",
      "timestamp_basis": "source_snapshot",
      "ingested_at": "2026-10-12T00:20:00Z",
      "checked_at": "2026-10-12T02:00:00Z",
      "freshness": "fresh",
      "current_availability": "not_checked",
      "bloom_false_positive_probability": null
    }
  ]
}
```

The full illustrative contract, with an example for every state, is at
[api-contract.example.json](https://domains.cdn.mn/api-contract.example.json).

## Local files (future requirements)

Hashed lists and Bloom files are offered by request, only where each source's
permitted use allows that delivery mode. Neither is a readable name list, but a
recipient can test guessed names, so neither is anonymous or secret. Hash
exports reveal their entry count unless padded; Bloom files can reveal an
approximate dataset size.

When downloads are agreed, each artifact is expected to come with a versioned
manifest stating, as applicable:

- namespace and evidence scope;
- source snapshot time, generation time and snapshot ID;
- normalization and file-format versions;
- hash algorithm, or Bloom hash derivation, bit ordering, size and parameters;
- an integrity checksum, plus authenticity protection through an approved
  signed manifest or equivalent — a checksum alone is not authentication, and
  no file is described as signed until signature verification exists;
- the declared target Bloom false-positive rate and capacity assumptions;
- permitted usage and distribution class;
- refresh and stale-data handling instructions.

Bloom files have no universal serialization format: a registry-produced file
needs an agreed format and compatibility tests. Never load pickles or any other
executable object serialization.

Theoretical sizing only — not observed service statistics. Raw size per million
distinct entries, decimal MB, excluding manifests, implementation overhead and
padding; hex-text hashes are larger than raw binary digests:

| Representation | Approximate raw size |
| --- | --- |
| Full SHA-256 digests, binary, 32 bytes each | 32 MB |
| Bloom filter, target false-positive rate `1e-6` | 3.6 MB |
| Bloom filter, target false-positive rate `1e-9` | 5.4 MB |

## Future production requirements (proposed)

Recommended requirements for an eventual production service. None is
implemented or promised today; the actual terms are agreed during onboarding.

- Permitted use is enforced per source and per delivery mode: the API, hashed
  downloads and Bloom downloads are each enabled only where that source
  allows it.
- A file is checked before use for truncation, corruption, an incompatible
  format, a stale snapshot or an over-capacity filter, and is not used if a
  check fails.
- A new snapshot replaces the previous one atomically, and retention follows
  each source agreement.
- Coverage is stated per source; no result implies that every registration
  is indexed.
- Missing sources, bad inputs, network failures and valid negative matches are
  four different outcomes and are handled separately.
- API-query logs are minimized, and candidate names checked locally against a
  downloaded file stay local.
- A policy for protecting commercially sensitive searched-name lists is agreed
  explicitly. This guide makes no commitment about the logging or use of
  searches.
- Participating registries may receive aggregate operational feedback only
  where that is approved; customer search lists are never shared with them.
- A standard Bloom filter cannot delete a name by clearing shared bits, so the
  first integration replaces whole snapshots. Incremental update feeds need a
  separately agreed design.

## References

- [ICANN — Centralized Zone Data Service overview](https://www.icann.org/en/contracted-parties/registry-operators/services/centralized-zone-data-service)
- [RFC 5731 — EPP Domain Name Mapping, section 3.1.1 (what a live check response means)](https://www.rfc-editor.org/rfc/rfc5731.html#section-3.1.1)
- [Unicode UTS #46 — Unicode IDNA Compatibility Processing](https://www.unicode.org/reports/tr46/)

## Contact

domains@cdn.mn
