Identifiers, addresses and the pointer

As markdown

  • Status: version 0.1 of Daawid's open specification.
  • Change rule: every format in this document is append-only. New forms may be added. The meaning of a published one never changes, and it is never reused or removed.
  • Origins: daawid.example (an RFC 2606 reserved name) stands for the canonical origin daawid.com. The render origin daawidusercontent.example stands for daawidusercontent.com. Every name, number and address in the examples is fictional.
  • Licence: this text is CC BY 4.0. The reference schemas and validator are Apache 2.0, at /schemas/0.1/.

1. Entry ID

Format   ent_<26 chars: lowercase Crockford base32 of a UUIDv7>   (TypeID v0.3)
Regex    ^ent_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example  ent_01k8r2m5n7p9q3s4t6v8w0x2yz

Properties:

  • Opaque and immutable. The ID survives renames, slug changes, ownership transfers, legal-form changes (UG → GmbH) and domain changes. Nothing else about a business is that stable.
  • Never reused, including after deletion or GDPR erasure.
  • Lowercase only. That makes it safe in DNS TXT records, URLs and file names. The alphabet leaves out i l o u, so the ID survives being read aloud or retyped.
  • Time-ordered (UUIDv7). That gives good B-tree locality in Postgres. It reveals the creation time, which is acceptable because createdAt is public anyway.
  • Storage: a native uuid column in Postgres. The TypeID string form exists only at the API and rendering boundary.
  • Consumers MUST treat IDs as opaque strings. Daawid emits lowercase only, so consumers SHOULD lowercase input before comparing.
  • Brand-neutral prefix. ent names the object, not the company, so a rebrand never touches an ID.

Rejected alternatives:

  • Slugs or domains as identity: both change.
  • Bare UUIDs: no type safety, and they look noisy in DNS.
  • Integer IDs: they reveal registry size and let anyone enumerate entries.

TypeID gives a ULID-style compact form plus a type prefix, and the TypeScript library (typeid-js) is boring and well documented.

1.1 Item IDs

Sub-objects use the same format with their own prefixes. Item IDs are globally unique, stable across revisions, and never reused. Prefixes come from an append-only registry:

Prefix Object Public?
ent entry yes
loc location yes
off offering yes
act action yes
cp contact point yes (contact points marked private are never published)
pol policy yes
crd credential yes
faq FAQ item yes
med media asset yes
run action run only to whoever started it
snp render snapshot yes, behind an unguessable URL
org owner organization (tenant) no
clm claim no

1.2 Revisions

revision is a positive integer that increases by 1 on every publish. The pair (entryId, revision) identifies one immutable version. It is used as the HTTP ETag ("ent_…-r42") and appears in every projection.

The URL /id/{entryId}/r/{revision} is reserved but not served in v0.1.


2. Slugs

Normative rules:

  1. Length and characters: 3–63 characters from [a-z0-9-]. A slug must start with a letter, must end with a letter or digit, and must not contain --. Regex: ^[a-z](?:[a-z0-9]|-(?!-))*[a-z0-9]$. These rules match DNS labels, so {slug}.daawid.example stays possible later.
  2. Derivation from the display name, applied in this order:
    1. Lowercase.
    2. Transliterate ä→ae ö→oe ü→ue ß→ss.
    3. Strip other diacritics (NFKD).
    4. Replace & with und (German entries) or and.
    5. Drop legal-form tokens: gmbh mbh ug haftungsbeschraenkt ag kg ohg gbr ek ev eg co se kgaa partg mbb. Legal forms change; brands don't.
    6. Replace every other character with -, collapse repeated hyphens, and trim.
  3. Collisions: first append the locality (-wuppertal), then a counter (-2, -3, …).
  4. Reserved slugs can never be assigned. The list is append-only: api app www mcp auth media render static assets cdn docs help hilfe support status blog about contact kontakt impressum datenschutz privacy terms agb legal login logout signin signup register registrieren anmelden account admin dashboard claim verify confirm search suche sitemap sitemaps robots llms schemas taxonomy well-known security daawid pricing preise jobs karriere press presse partner developers entwickler test example null undefined daawid-support daawid-help daawid-team daawid-official daawid-admin daawid-security daawid-login daawid-account daawid-billing daawid-verify daawid-verified daawid-hilfe. Strings shorter than 3 characters are excluded by rule 1.
  5. Assignment and changes:
    • Daawid assigns the slug when the entry is created.
    • The owner may change it at most once every 30 days. A slug change is a sensitive operation (verification.md §6).
    • An old slug 301-redirects to the current slug forever and is never reassigned to a different entry.
    • The only exception is a documented legal transfer (trademark, or name rights under §12 BGB). Transfers are logged.
  6. Erasure: after GDPR erasure, both the slug and /id/{id} return 410 Gone. Retired slugs are kept only as keyed hashes (HMAC). That blocks reuse without storing the name, which matters because slugs of sole traders often contain a person's name.
  7. IDs only internally: nothing in the system references a slug. All references use IDs.

Example: "Holzwerk Krause GmbH" becomes holzwerk-krause. On a collision it becomes holzwerk-krause-wuppertal.

The namespace is flat and global, with no per-country prefix. People and organizations share it, and the ID space, the way domains do.


3. Public URL formats (v0.1)

3.1 Registry origin https://daawid.example

Pattern Response Notes
/{slug} HTML by default; markdown when Accept prefers text/markdown Canonical URL of the entry. Sends Vary: Accept.
/{slug}.md text/markdown; charset=utf-8 Always markdown. Follows the llms.txt ".md appended to the URL" convention.
/{slug}.json application/ld+json JSON-LD with all languages.
/{slug}/{lang} HTML or markdown (negotiated), language variant lang is a BCP 47 tag ll or ll-RR. Served only for languages the entry has.
/{slug}/{lang}.md markdown, language variant
/{slug}/offerings and .md full offering list Served only when an entry has more than 25 offerings.
/{slug}/impressum HTML legal notice (Impressum) For every entry with an operator block whose operator is in Germany (entry-schema §4.3). The block is optional. Linked from every HTML entry page, one click away.
/{slug}/legal HTML legal notice The same, for operators outside Germany.
/{slug}/datenschutz HTML privacy notice Only when the business uses a Daawid-hosted notice and its operator is in Germany.
/{slug}/privacy HTML privacy notice The same, for operators outside Germany.
/{slug}/actions/{actionId} HTML form Fallback so a human can run an action without an agent.
/{slug}/report HTML report form Where anyone reports the entry, with a code Daawid emails first (verification.md §2.1). Linked from every entry page's footer; never stored or indexed. Once its form is sent, the report's page is /{slug}/report?r={token}.
/confirm/{token} HTML confirmation page Where a human confirms an agent-initiated action run (data-contract §7.6).
/claim/{entryId} HTML claim start page Linked from unclaimed entries (data-contract §3.7).
/docs The documentation's index
/docs/{page} Public documentation The specification (entry-schema, identifiers, verification, data-contract, each also as .md), the ranking policy (ranking), the MCP server (mcp), the REST API (api), the verifier (verifier), and the guide for owners (your-website).
/terms Daawid's terms of use
/id/{entryId} and .md / .json 303 See Other → current slug URL Permanent identifier and the JSON-LD @id of the business. 303 because the target can change.
/llms.txt Registry index in llms.txt format Cheap to provide but rarely fetched; see data-contract §5.
/sitemap.xml → /sitemaps/entries-{n}.xml Sitemaps At most 50,000 URLs per file, with lastmod and hreflang alternates.
/robots.txt Includes Content-Signals.
/ns JSON-LD vocabulary for daawid: terms Namespace IRI https://daawid.example/ns#. Terms are append-only.
/schemas/{version}/{name}.json JSON Schema generated from Zod For example /schemas/0.1/public-entry.json.
/taxonomy/{version}.json Category codes Codes are never reused.
/assets/fonts/{family}/font.css A brand font's @font-face rules, next to its files Daawid's self-hosted fonts, which get_brand_kit names (data-contract §6). Other origins may load them.
/.well-known/ai-catalog.json AI Catalog (application/ai-catalog+json) Lists Daawid's MCP Server Card. The AI Catalog format is still a draft; Server Cards are final (SEP-2127).
/.well-known/security.txt RFC 9116

Path rule, permanent:

  • Sub-resource segments under /{slug}/ are 4 or more lowercase letters.
  • Language segments are 2–3 letters, optionally followed by -RR.

The two sets of paths can therefore never collide.

Status responses:

  • Removed entry: 410 Gone with a minimal tombstone that contains no personal data.
  • Suspended entry: 200 with a suspension notice and no owner content.
  • Owner left Daawid: /{slug} (301) and /id/{entryId} (303) redirect to the entry's new location for at least 12 months, unless the owner asked for erasure. Promised (data-contract §1, principle 8), and built when the first owner leaves with somewhere to go.

3.2 Other origins

Origin / pattern Purpose
https://api.daawid.example/v1/… REST API. /v1 is additive-only. Reads are public.
https://mcp.daawid.example/mcp MCP Streamable HTTP endpoint (single URL). This URL must never change, because directory installs are tied to it.
https://mcp.daawid.example/mcp/server-card MCP Server Card (application/mcp-server-card+json), at the location SEP-2127 recommends.
https://mcp.daawid.example/e/{entryId}/mcp and /server-card Reserved for per-entry MCP endpoints and cards, for example when a business's own AI Catalog points at us. Not served in v0.1.
https://auth.daawid.example OAuth 2.1 authorization server.
https://media.daawid.example/{sha256-prefix}.{ext} Immutable, content-addressed media. Cached forever.
https://daawidusercontent.example/r/{token} Ephemeral render, valid 1 hour, token of at least 128 bits. Reserved for v0.2.
https://daawidusercontent.example/s/{snapshotId} 30-day snapshot permalink (snp_ ID). Returns 410 after expiry. Reserved for v0.2.

The render host is a separate registrable domain on purpose. Its HTML is filled in by a model, so it must never share an origin, cookies or storage with the registry. This follows the same pattern as googleusercontent.com and githubusercontent.com.

3.3 Hosted-domain layout (reserved now, launch v0.2)

A business can point its own domain (or www) at Daawid. Daawid then serves the entry on that domain:

  • / (negotiated HTML or markdown)
  • /index.md and /index.json
  • /impressum and /datenschutz for operators in Germany, /legal and /privacy for everyone else
  • /actions/{actionId}
  • /llms.txt, /sitemap.xml, /robots.txt
  • /.well-known/daawid
  • /.well-known/ai-catalog.json, which points at the entry's server card

Canonical rule (locked now): while a hosted domain is active, the hosted domain's URL is canonical. https://daawid.example/{slug} then carries rel="canonical" pointing to it.

A business keeps its domain's authority, and Daawid never competes with its own customers in search results.


4. Pointer (company domain → entry)

4.1 Record

t=<token>; e=<entryId>; v=1
Key Value
t Claim token: 26 lowercase Crockford base32 characters (130 bits, at least 128 random). It comes first, as the IETF's domain verification draft requires.
e Entry ID (ent_…).
v Record version, currently 1.

Grammar:

record = pair *( *WSP ";" *WSP pair ) [ *WSP ";" ]
pair   = key "=" value
key    = 1*( %x61-7A / DIGIT / "-" )     ; lowercase
value  = 1*( %x21-3A / %x3C-7E )         ; printable, no ";" and no space

Parsing rules:

  • Unknown keys are ignored, so new keys can be added later.
  • A duplicate key makes the record invalid.
  • The order is fixed when we emit a record but not required when we parse one.

Example for the fictional carpenter:

t=q8xv3kf9m2d7rw4hzn6tp0y5ce; e=ent_01k8r2m5n7p9q3s4t6v8w0x2yz; v=1

4.2 Carriers: the owner publishes the same record in one of three places

  1. DNS TXT (preferred). This works without any website, which matters for businesses that keep their domain only for email.
    _daawid.holzwerk-krause.example.  3600  IN  TXT  "t=q8xv3kf9m2d7rw4hzn6tp0y5ce; e=ent_01k8r2m5n7p9q3s4t6v8w0x2yz; v=1"
    
  2. HTML meta tag in the server-rendered <head> of https://{host}/:
    <meta name="daawid" content="t=q8xv3kf9m2d7rw4hzn6tp0y5ce; e=ent_01k8r2m5n7p9q3s4t6v8w0x2yz; v=1">
    
  3. Well-known file https://{host}/.well-known/daawid, served as text/plain. It contains the record on one line and must be at most 1 KB. Both this path and /.well-known/daawid.json (§4.4) are to be registered with IANA under RFC 8615.

Optional extras (recommended, not required). These help ordinary crawlers find the entry; they verify nothing:

  • "sameAs": "https://daawid.example/id/ent_…" in the Organization JSON-LD on the company's site.
  • A visible link on the site.
  • An AI Catalog at https://{host}/.well-known/ai-catalog.json that references Daawid's server card: an emerging way for a domain to say "agents can reach this business here". Client support is still experimental, so this is offered but not relied on.

4.3 Semantics

  • A pointer says: "the business behind this host is described by entry e."
  • A pointer counts only if both directions agree:
    • t is a token that Daawid issued to the entry's current owner organization for this host, and
    • the entry lists this host as verified in verification.domains.
  • Consumers that cross-check MUST check both directions. A pointer on its own proves nothing.
  • Multiple records under _daawid are allowed, for example a holding company with several brands. Each one is evaluated independently.
  • Scope:
    • A record at _daawid.{host} covers {host}.
    • If {host} is a registrable domain (eTLD+1), the record also covers www.{host}.
    • Every other subdomain needs its own record.
    • Public suffixes, and hosts under private-section suffixes of the Public Suffix List (such as site-builder subdomains), cannot be verified in v0.1. Their owners publish at level contact instead (verification.md §2).
  • Hosts are stored and compared as lowercase A-labels (UTS #46). U-labels are used only for display.
  • Token:
    • Bound server-side to the tuple (owner organization, entry, host).
    • Public by design, but useless to any other organization, entry or host.
    • Rotated on ownership transfer.
    • Stays valid as long as the binding stands. The draft asks providers to say how long a token is valid: this is that statement.
  • Permanence: the record stays in place after verification. It is both the public pointer and the continuous proof of control (verification.md §5). The draft considers persistent records appropriate when they are bound to an account rather than validated by mere presence, as these are.

4.4 Self-hosted entries (reserved now, read from v0.2)

  • https://{host}/.well-known/daawid.json holds a complete entry in the open format. It's the strongest form of "owners can always leave": the entry lives on the owner's own domain.
  • From v0.2, Daawid's index reads it like any other entry, with the same validation (entry-schema §8) and trust labels. Serving it from the domain also proves control of that domain.
  • Not read in v0.1. The path is reserved now because URL formats are append-only.

5. Versioning policy

Append-only registries. New values can be added to each of these; existing values never change or disappear:

  • ID prefixes
  • URL patterns and reserved slugs
  • Pointer keys
  • Schema field names
  • Enum values
  • Action types
  • Error codes
  • Taxonomy codes
  • daawid: vocabulary terms
  • MCP tool names

Never:

  • Change the meaning of a URL, ID, field or enum value.
  • Reuse an ID or slug.
  • Remove a URL pattern. Deprecated patterns keep redirecting.

Tolerant readers. This is part of the consumer contract:

  • Consumers MUST ignore unknown fields.
  • Consumers MUST treat an unknown enum value as "other/unknown" rather than fail.

That is what makes additive changes safe.

schemaVersion uses MAJOR.MINOR and appears in every document.

  • From 1.0:
    • A MINOR version is additive only.
    • A MAJOR version is served in parallel, selected with Accept: …; profile="https://daawid.example/schemas/{major}" or ?v={major}.
    • The previous major version keeps being served for at least 12 months after deprecation, matching MCP's own 12-month deprecation window.

REST:

  • /v1 is additive only.
  • Breaking changes go to /v2, which runs alongside /v1.

MCP tools:

  • Names never change.
  • New input parameters must be optional.
  • Output changes are additive only.
  • A breaking change ships as a new tool (…_v2), and the old tool keeps working for at least 12 months.