- 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 origindaawidusercontent.examplestands 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
createdAtis public anyway. - Storage: a native
uuidcolumn 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.
entnames 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:
- 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.examplestays possible later. - Derivation from the display name, applied in this order:
- Lowercase.
- Transliterate
ä→ae ö→oe ü→ue ß→ss. - Strip other diacritics (NFKD).
- Replace
&withund(German entries) orand. - 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. - Replace every other character with
-, collapse repeated hyphens, and trim.
- Collisions: first append the locality (
-wuppertal), then a counter (-2,-3, …). - 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. - 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.
- Erasure: after GDPR erasure, both the slug and
/id/{id}return410 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. - 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 Gonewith a minimal tombstone that contains no personal data. - Suspended entry:
200with 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.mdand/index.json/impressumand/datenschutzfor operators in Germany,/legaland/privacyfor 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
- 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" - HTML meta tag in the server-rendered
<head>ofhttps://{host}/:<meta name="daawid" content="t=q8xv3kf9m2d7rw4hzn6tp0y5ce; e=ent_01k8r2m5n7p9q3s4t6v8w0x2yz; v=1"> - Well-known file
https://{host}/.well-known/daawid, served astext/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.jsonthat 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:
tis 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
_daawidare 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 coverswww.{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
contactinstead (verification.md §2).
- A record at
- 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.jsonholds 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:
/v1is 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.