Skip to content

Instantly share code, notes, and snippets.

@duglin
Created August 13, 2026 20:24
Show Gist options
  • Select an option

  • Save duglin/97a23b768dcbce37be95e19fd2904047 to your computer and use it in GitHub Desktop.

Select an option

Save duglin/97a23b768dcbce37be95e19fd2904047 to your computer and use it in GitHub Desktop.
xReg 101

xRegistry 101

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).

1. What is xRegistry?

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 an endpoint).
  • 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.

2. Core HTTP Interactions

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.

2.1 Reading things

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.

2.2 Creating / updating things

  • POST on a collection creates (or upserts) one or more entities in it.
  • PUT/PATCH on 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; null deletes 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": "..."
}

2.3 Resources: metadata vs. the actual document

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 as contenttype, 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.

2.4 Versions

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.

2.5 Deleting

DELETE on any entity or collection member removes it, cascading to everything beneath it (delete a Group, its Resources go too).

2.6 Errors

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": "..."
}

3. Defining a Basic Model

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

3.1 Adding your own attributes (extensions)

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.

4. Where to go next

  • spec.md - the full core specification (concepts, entities, capabilities).
  • http.md - the full HTTP binding (every API, header, and query parameter in detail).
  • model.md - the full model specification (every model attribute and option).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment