A quick-start guide for developers who just want to use xRegistry, not read the whole spec.
This document is a short, informal introduction to xRegistry. It intentionally
skips edge cases, admin-level configuration and formal MUST/SHOULD
language. If you need the full details later, see spec.md,
http.md and model.md. This guide assumes you
already know how typical REST APIs work (GET/POST/PUT/PATCH/DELETE,
JSON bodies, HTTP status codes).
xRegistry is a generic way to expose and manage collections of metadata (and optionally documents) over HTTP, using a consistent, predictable URL/JSON shape - so tooling written against one xRegistry-based API mostly works against any other one.
Think of it like a filesystem exposed over HTTP:
- A Registry is the root of the whole thing - like the root of a filesystem/drive.
- Groups are like folders - they organize related things together.
Example:
endpoints. - Resources are like files - the actual "things" you care about.
Example:
messages(inside anendpoint). - Versions are like a file's revision history - a Resource can have one or more Versions over time. If you don't care about versioning, just ignore Versions and treat the Resource as if it has one version.
A picture is worth it:
Registry
└── <GROUPS>/<GID> e.g. endpoints/ep1
└── <RESOURCES>/<RID> e.g. messages/msg1
└── versions/<VID> e.g. versions/1
Every Registry has a model that declares what Group and Resource types it supports (their names, and any extra attributes they carry). Clients can fetch this model at runtime, so tooling can be written generically without hard-coding knowledge of a specific Registry's shape.
Everything (Registries, Groups, Resources, Versions) shares a small set of common metadata attributes, the most important being:
| Attribute | Meaning |
|---|---|
<TYPE>id |
The unique ID of the entity, e.g. messageid |
name |
A human-friendly display name (optional) |
epoch |
A number that increments on every change (for concurrency) |
self |
The absolute URL of this entity |
createdat / modifiedat |
Timestamps |
You don't need to memorize these - they just show up automatically in responses.
xRegistry maps directly onto normal REST/HTTP verbs. The URL patterns are:
GET/PUT/PATCH / # the Registry itself
GET /model # the model definition
GET/PUT /modelsource # the model, as you defined it
GET/POST/PATCH /<GROUPS> # e.g. GET /endpoints
GET/PUT/PATCH/DELETE /<GROUPS>/<GID> # e.g. .../endpoints/ep1
GET/POST/PATCH /<GROUPS>/<GID>/<RESOURCES> # e.g. .../endpoints/ep1/messages
GET/PUT/PATCH/DELETE /<GROUPS>/<GID>/<RESOURCES>/<RID> # a Resource (its default Version)
GET/PUT/PATCH/DELETE /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID> # a specific Version
<GROUPS> and <RESOURCES> are whatever plural names your Registry's model
defines (e.g. endpoints, messages) - they're not literal.
A plain GET on any entity returns its metadata as JSON:
GET /endpoints/ep1
{
"endpointid": "ep1",
"self": "https://example.com/endpoints/ep1",
"epoch": 3,
"name": "My Endpoint",
"messagesurl": "https://example.com/endpoints/ep1/messages",
"messagescount": 5
}
Notice collections aren't inlined by default - you get a <COLLECTION>url
and <COLLECTION>count instead, and you GET that URL to see the actual
list. (You can ask for things to be inlined with ?inline, but that's an
advanced topic.)
GET-ing a collection (e.g. GET /endpoints) returns a map of id -> entity
for everything in it.
POSTon a collection creates (or upserts) one or more entities in it.PUT/PATCHon a specific entity creates it (if it doesn't exist) or updates it.PUT= full replacement (anything you omit gets reset/cleared).PATCH= partial update (only what you include is changed;nulldeletes an attribute).
Example - create a Group:
PUT /endpoints/ep1
Content-Type: application/json
{ "name": "My Endpoint" }
HTTP/1.1 201 Created
{
"endpointid": "ep1",
"self": "https://example.com/endpoints/ep1",
"epoch": 1,
"name": "My Endpoint",
"createdat": "...",
"modifiedat": "..."
}
This is the one twist compared to a plain REST API. A Resource (e.g. a
message or a schema) can have its own domain-specific "document" (its
actual content) in addition to its xRegistry metadata (epoch, name,
etc).
GET /endpoints/ep1/messages/msg1→ returns the Resource's document (its raw content) directly in the HTTP body.GET /endpoints/ep1/messages/msg1$details→ returns the Resource's metadata as JSON instead (with the document's metadata such ascontenttype, but not the document body itself).
Same idea applies for PUT: PUT .../msg1 with a raw body sets the
document content; PUT .../msg1$details with a JSON body sets/updates just
the metadata.
If a Resource type doesn't have its own separate document (just metadata),
then $details isn't needed - plain requests just return the metadata.
If you don't care about history, ignore versions entirely - just treat
/<GROUPS>/<GID>/<RESOURCES>/<RID> as "the Resource", and every update just
changes its one Version in place.
If you do want history, POST to the versions collection to add a new
Version, and GET .../versions to list them all. The most recent (or
explicitly-chosen "default") Version is always what you get back when you
address the Resource directly, without a specific versionid.
DELETE on any entity or collection member removes it, cascading to
everything beneath it (delete a Group, its Resources go too).
Errors come back as normal HTTP status codes with a JSON body describing what went wrong, e.g.:
{
"type": "https://...#not_found",
"title": "Not found",
"detail": "..."
}Before you can create Groups/Resources, the Registry needs a model telling it what types of Groups and Resources are allowed. At its simplest, this is just names:
{
"groups": {
"endpoints": {
"singular": "endpoint",
"resources": {
"messages": {
"singular": "message"
}
}
}
}
}This says: "there's a Group type called endpoints (singular endpoint),
and each endpoint can contain messages (singular message)." That's a
complete, valid model - you can have as many Group types as you like, and
each Group type can have as many Resource types as you like.
You load this via:
PUT /modelsource
Content-Type: application/json
{ ... model json above ... }
Once loaded, you can immediately do things like:
PUT /endpoints/ep1
PUT /endpoints/ep1/messages/msg1
If you want Resources/Groups to carry extra domain-specific fields, add
them under attributes:
{
"groups": {
"endpoints": {
"singular": "endpoint",
"resources": {
"messages": {
"singular": "message",
"attributes": {
"format": {
"type": "string",
"required": true
}
}
}
}
}
}
}Now every message Resource must include a format string attribute, e.g.:
{ "messageid": "msg1", "format": "avro" }Common type values you'll use most often: string, boolean, integer,
uinteger, decimal, timestamp, uri, array, map, object. Useful
attribute flags: required, default, readonly, enum (restrict to a
fixed set of values).
That's genuinely enough to get going. Everything else in the full model
spec (constraints, versioning policies, xid targets, ifvalues, etc.) is
there for more advanced scenarios - come back to model.md
when you need it.