Stable identifiers
A page id looks like D0033 — one or two capitals then four digits — and is the stable handle for that page. Object identifiers elsewhere are a short prefix and an opaque remainder: ev_, fu_, ws_. Treat every one of them as a string to compare, never as a structure to read meaning out of. One identifier deliberately changes: the delivery attempt.
Page identifiers#
Every published page has an id matching ^[A-Z]{1,2}\d{4}$. It appears in the manifest, in every search result and in every fetched record, and it is the argument the MCP tool fetch_public_doc and the ref parameter both accept.
The id is stable across rewording, retitling and even a change of URL. That is the whole reason it exists: a URL is a location and can be improved, while a citation stored in somebody's database six months ago needs to keep resolving. If you are keeping a reference to a page, keep the id and treat the URL as display.
GET /api/public/docs/page?ref=D0033 HTTP/1.1
GET /api/public/docs/page?ref=/developers/entity-ids/ HTTP/1.1
GET /api/public/docs/page?ref=https://connectbyjbrh.com/developers/entity-ids/index.md HTTP/1.1
# all three resolve to the same pageThe reference parameter is forgiving on purpose. A full URL is reduced to a path, a query string and fragment are dropped, a trailing index.md is removed, and a missing leading or trailing slash is added. An id is uppercased before lookup, so d0033 works — but the canonical form in every response is uppercase, and that is the form to store.
Prefixed object identifiers#
| Form | Names | Stability |
|---|---|---|
ev_… | One event in the published webhook contract | Stable across retries — this is what you de-duplicate on |
fu_… | A follow-up, inside an event's data | Stable for that follow-up |
ws_… | A workspace | Stable. Always your own in an event you receive |
X-Connect-Delivery | One delivery attempt | Changes on every retry. Not an identity for the event |
key_id | An integration key record | Stable. Safe to log; the key itself is not |
The prefix tells you what kind of thing it is and nothing else. The remainder is opaque: no documented length, no documented alphabet, no embedded timestamp, no ordering guarantee. Sorting a set of ev_ identifiers does not order the events; occurredAt does that.
Store them as text with room to spare. A column sized to today's observed length is the most common way an opaque identifier stops being opaque — it becomes a constraint nobody agreed to.
URLs as identifiers#
A canonical page URL ends in a slash. /developers/entity-ids and /developers/entity-ids/ would otherwise be two URLs for one page, so the server redirects the first to the second — and deliberately leaves anything with a file extension alone, which is how index.md is served rather than bounced.
That makes the trailing slash part of the identity when you are comparing URLs. Normalise before you compare, or compare on id instead, which is why the id is there.
Absolute and relative forms both appear in one record: url and markdown are absolute, related entries are site-relative paths. Resolve related against the site root once, in one place in your client.
Five rules that survive contact with production#
- Compare identifiers with exact string equality. No trimming that changes case, no normalisation beyond what a response gave you.
- Never parse an identifier for a date, a sequence number, a workspace or a type. Read the field that carries the fact.
- De-duplicate on the event
id, never on the delivery header — one is designed to repeat, the other is designed to differ. - Keep the page
idas your durable reference and the URL as the thing you show a person. - Treat a key's plaintext as a secret and its
key_idas the identifier. They are not two names for one thing.
Questions#
If a page's URL changes, does its id change?
No — that separation is the reason both exist. The id follows the page; the URL describes where it currently lives.
Are page ids sequential in publication order?
Do not depend on it. They are allocated by the plan that produced the corpus, and a numeric neighbour is not a chronological one. If you want order, sort on url or on updated.
Can two pages share an id?
No. The build checks the identifier format and uniqueness across the whole corpus before anything is published, so an id resolves to exactly one page or to nothing.