xRegistry Service - Version 1.0-rc1 🔗

Abstract 🔗

A Registry Service exposes Resources and their metadata, for the purpose of enabling discovery of those Resources for either end-user consumption or automation and tooling.

Table of Contents 🔗

Overview 🔗

A Registry Service is one that manages metadata about Resources. At its core, the management of an individual Resource is simply a REST-based interface for creating, modifying, and deleting the Resource. However, many Resource models share a common pattern of grouping Resources and can optionally support versioning of those Resources. This specification aims to provide a common interaction pattern for these types of services with the goal of providing an interoperable framework that will enable common tooling and automation to be created.

This document is meant to be a framework from which additional specifications can be defined that expose model-specific Resources and metadata.

As of today, this specification only specifies an HTTP-based interaction model. This is not meant to imply that other protocols cannot be supported, and other protocols will likely be added in the future. When that happens, this specification will be restructured to have clean separation between a protocol-agnostic core and protocol-specific requirements.

A Registry consists of two main types of entities: Resources and Groups.

Resources typically represent the main data of interest for users of the Registry, while Groups, as the name implies, is a mechanism by which related Resources are arranged together under a single collection.

This specification defines a set of common metadata that can appear on both Resources and Groups, and allows for domain-specific extensions to be added.

See the Registry Design section for a more complete discussion of the xRegistry concepts.

The following 3 diagrams show (from left to right):
1 - The core concepts of the Registry in its most abstract form.
2 - A Registry concept model with multiple types of Groups/Resources.
3 - A concrete sample usage of Registry that includes the use of an attribute on "Message Definition" that is a reference to a "Schema" document - all within the same Registry instance.

      

For easy reference, the JSON serialization of a Registry adheres to this form:

{
  "specversion": "<STRING>",
  "registryid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  "capabilities": {                     # Supported capabilities/options
    "apis": [ "/capabilities", "/export", "/model" ],
    "flags": [                          # Query parameters
      "collections",? "doc",? "epoch",? "filter",? "inline",?
      "nodefaultversionid",? "nodefaultversionsticky",? "noepoch",?
      "noreadonly",?  "offered",? "schema",? "setdefaultversionid",?
      "sort",? "specversion",?
      "<STRING>" *
    ],
    "mutable": [                        # What is mutable in the Registry
      "capabilities",? "entities",? "model",? "<STRING>"*
    ], ?
    "pagination": <BOOLEAN>, ?
    "schemas": [ "xRegistry-json/1.0-rc1", "<STRING>" * ], ?
    "shortself": <BOOLEAN>, ?
    "specversions": [ "1.0-rc1", "<STRING>"* ], ?
    "sticky": <BOOLEAN>, ?
    "versionmodes": [ "manual", "createdat",? "modifiedat",? "semver",
      "<STRING>"* ],

    "<STRING>": ... *                   # Extension capabilities
  }, ?

  "model": {                            # Full model. Only if inlined
    "description": "<STRING>", ?
    "documentation": "<URL>", ?
    "icon": "<URL>", ?
    "labels": { "<STRING>": "<STRING>" * }, ?
    "attributes": {                     # Registry level attributes/extensions
      "<STRING>": {                     # Attribute name (case-sensitive)
        "name": "<STRING>",             # Same as attribute's key
        "type": "<TYPE>",                 # string, decimal, array, object, ...
        "target": "<XIDTYPE>", ?        # If "type" is "xid" or "url"
        "namecharset": "<STRING>", ?    # If "type" is "object"
        "description": "<STRING>", ?
        "enum": [ <VALUE> * ], ?        # Array of scalars of type "<TYPE>"
        "strict": <BOOLEAN>, ?          # Just "enum" values? Default=true
        "readonly": <BOOLEAN>, ?        # From client's POV. Default=false
        "immutable": <BOOLEAN>, ?       # Once set, can't change. Default=false
        "required": <BOOLEAN>, ?        # Default=false
        "default": <VALUE>, ?           # Scalar attribute's default value

        "attributes": { ... }, ?        # If "type" above is object
        "item": {                       # If "type" above is map,array
          "type": "<TYPE>", ?           # map value type, or array type
          "target": "<XIDTYPE>", ?      # If this item "type" is xid/url
          "namecharset": "<STRING>", ?  # If this item "type" is object
          "attributes": { ... }, ?      # If this item "type" is object
          "item": { ... } ?             # If this item "type" is map,array
        } ?

        "ifvalues": {                   # If "type" is scalar
          "<VALUE>": {                  # Possible attribute value
            "siblingattributes": { ... } # See "attributes" above
          } *
        } ?
      } *
    },

    "groups": {
      "<STRING>": {                       # Key=plural name, e.g. "endpoints"
        "plural": "<STRING>",             # e.g. "endpoints"
        "singular": "<STRING>",           # e.g. "endpoint"
        "description": "<STRING>", ?
        "documentation": "<URL>", ?
        "icon": "<URL>", ?
        "labels": { "<STRING>": "<STRING>" * }, ?
        "modelversion": "<STRING>", ?     # Version of the group model
        "compatiblewith": "<URI>", ?      # Statement of compatibility with model spec
        "attributes": { ... }, ?        # Group level attributes/extensions
        "ximportresources": [ "<XIDTYPE>", * ], ?   # Include these Resources

        "resources": {
          "<STRING>": {                   # Key=plural name, e.g. "messages"
            "plural": "<STRING>",         # e.g. "messages"
            "singular": "<STRING>",       # e.g. "message"
            "description": "<STRING>", ?
            "documentation": "<URL>", ?
            "icon": "<URL>", ?
            "labels": { "<STRING>": "<STRING>" * }, ?
            "modelversion": "<STRING>", ? # Version of the resource model
            "compatiblewith": "<URI>", ?  # Statement of compatibility with model spec
            "maxversions": <UINTEGER>, ?  # Num Vers(>=0). Default=0(unlimited)
            "setversionid": <BOOLEAN>, ?  # vid settable? Default=true
            "setdefaultversionsticky": <BOOLEAN>, ? # sticky settable? Default=true
            "hasdocument": <BOOLEAN>, ?   # Has separate document. Default=true
            "versionmode": "<STRING>", ?  # 'ancestor' processing algorithm
            "singleversionroot": <BOOLEAN>, ? # Default=false"
            "typemap": <MAP>, ?           # contenttype mappings
            "attributes": { ... }, ?          # Version attributes/extensions
            "resourceattributes": { ... }, ?  # Resource attributes/extensions
            "metaattributes": { ... } ?       # Meta attributes/extensions
          } *
        } ?
      } *
    } ?
  }, ?
  "modelsource": { ... }, ?                        # Input model, if inlined

  # Repeat for each Group type
  "<GROUPS>url": "<URL>",                          # e.g. "endpointsurl"
  "<GROUPS>count": <UINTEGER>,                     # e.g. "endpointscount"
  "<GROUPS>": {                                    # Only if inlined
    "<KEY>": {                                     # Key=the Group id
      "<GROUP>id": "<STRING>",                     # The Group ID
      "self": "<URL>",
      "shortself": "<URL>", ?
      "xid": "<XID>",
      "epoch": <UINTEGER>,
      "name": "<STRING>", ?
      "description": "<STRING>", ?
      "documentation": "<URL>", ?
      "icon": "<URL>", ?
      "labels": { "<STRING>": "<STRING>" * }, ?
      "createdat": "<TIMESTAMP>",
      "modifiedat": "<TIMESTAMP>",

      # Repeat for each Resource type in the Group
      "<RESOURCES>url": "<URL>",                   # e.g. "messagesurl"
      "<RESOURCES>count": <UINTEGER>,              # e.g. "messagescount"
      "<RESOURCES>": {                             # Only if inlined
        "<KEY>": {                                 # The Resource id
          "<RESOURCE>id": "<STRING>",
          "versionid": "<STRING>",                 # Default Version's ID
          "self": "<URL>",                         # Resource URL, not Version
          "shortself": "<URL>", ?
          "xid": "<XID>",                          # Resource XID, not Version
          "epoch": <UINTEGER>,                     # Start of default Ver attrs
          "name": "<STRING>", ?
          "isdefault": true,
          "description": "<STRING>", ?
          "documentation": "<URL>", ?
          "icon": "<URL>", ?
          "labels": { "<STRING>": "<STRING>" * }, ?
          "createdat": "<TIMESTAMP>",
          "modifiedat": "<TIMESTAMP>",
          "ancestor": "<STRING>",                  # Ancestor's versionid
          "contenttype": "<STRING>, ?              # Add default Ver extensions

          "<RESOURCE>url": "<URL>", ?              # If not local
          "<RESOURCE>": ... Resource document ..., ? # If local & inlined & JSON
          "<RESOURCE>base64": "<STRING>", ?        # If local & inlined & ~JSON
                                                   # End of default Ver attrs
          # Resource level helper attributes
          "metaurl": "<URL>",
          "meta": {                                # Only if inlined
            "<RESOURCE>id": "<STRING>",
            "self": "<URL>",                       # URL to "meta" object
            "shortself": "<URL>", ?
            "xid": "<XID>",
            "xref": "<XID>", ?                     # xid of linked Resource
            "epoch": <UINTEGER>,                   # Resource's epoch
            "createdat": "<TIMESTAMP>",            # Resource's
            "modifiedat": "<TIMESTAMP>",           # Resource's
            "readonly": <BOOLEAN>,                 # Default=false
            "compatibility": "<STRING>",           # Default=none
            "compatibilityauthority": "<STRING>", ?  # Default=external
            "deprecated": {
              "effective": "<TIMESTAMP>", ?
              "removal": "<TIMESTAMP>", ?
              "alternative": "<URL>", ?
              "documentation": "<URL>"?
            }, ?

            "defaultversionid": "<STRING>",
            "defaultversionurl": "<URL>",
            "defaultversionsticky": <BOOLEAN>      # Default=false
          }, ?
          "versionsurl": "<URL>",
          "versionscount": <UINTEGER>,
          "versions": {                            # Only if inlined
            "<KEY>": {                             # The Version's versionid
              "<RESOURCE>id": "<STRING>",          # The Resource id
              "versionid": "<STRING>",             # The Version id
              "self": "<URL>",                     # Version URL
              "shortself": "<URL>", ?
              "xid": "<XID>",
              "epoch": <UINTEGER>,                 # Version's epoch
              "name": "<STRING>", ?
              "isdefault": <BOOLEAN>,              # Default=false
              "description": "<STRING>", ?
              "documentation": "<URL>", ?
              "icon": "<URL>", ?
              "labels": { "<STRING>": "<STRING>" * }, ?
              "createdat": "<TIMESTAMP>",
              "modifiedat": "<TIMESTAMP>",
              "ancestor": "<STRING>",              # Ancestor's versionid
              "contenttype": "<STRING>", ?

              "<RESOURCE>url": "<URL>", ?                # If not local
              "<RESOURCE>": ... Resource document ..., ? # If inlined & JSON
              "<RESOURCE>base64": "<STRING>" ?           # If inlined & ~JSON
            } *
          } ?
        } *
      } ?
    } *
  } ?
}

Notations and Terminology 🔗

Notational Conventions 🔗

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

For clarity, OPTIONAL attributes (specification-defined and extensions) are OPTIONAL for clients to use, but the servers' responsibility will vary. Server-unknown extension attributes MUST be silently stored in the backing datastore. Specification-defined, and server-known extension attributes MUST generate an error if the corresponding feature is not supported or enabled. However, as with all attributes, if accepting the attribute results in a bad state (such as exceeding a size limit, or results in a security issue), then the server MAY choose to reject the request.

In the pseudo JSON format snippets ? means the preceding item is OPTIONAL, * means the preceding item MAY appear zero or more times, and + means the preceding item MUST appear at least once. The presence of the # character means the remaining portion of the line is a comment. Whitespace characters in the JSON snippets are used for readability and are not normative.

Use of <...> the notation indicates a substitutable value where that is meant to be replaced with a runtime situational-specific value as defined by the word/phase in the angled brackets. For example <NAME> might be expected to be replaced by the "name" of the item being discussed.

When HTTP query parameters are discussed, they are presented as ?<NAME> where <NAME> is the name of the query parameter.

Use of <GROUP> and <RESOURCE> are meant to represent the singular name of a Group and Resource type being used. While <GROUPS> and <RESOURCES> are the plural name of those respective types. Use of <SINGULAR> represents the singular name of the entity being referenced. For example, for a "schema document" Resource type where its plural name is defined as schemas and its singular name is defined as schema, the <SINGULAR> value would be schema.

Additionally, the following acronyms are defined:

The following are used to denote an instance of one of the associated data types (see Attributes and Extensions for more information about each data type):

Terminology 🔗

This specification defines the following terms:

Groups, as the name implies, is a mechanism by which related Resources are arranged together under a single collection - the Group. The reason for the grouping is not defined by this specification, so the owners of the Registry can choose to define (or enforce) any pattern they wish. In this sense, a Group is similar to a "directory" on a filesystem.

An additional common use for Groups, aside from the contained Resources being related, is for access control. Managing access control on individual Resources, while possible, might be cumbersome, so moving it up to the Group could be a more manageable, and user-friendly, implementation choice.

An implementation of this specification. Typically, the implementation would include model-specific Groups, Resources and extension attributes.

Resources represent the main data of interest for the Registry. In the filesystem analogy, these would be the "files". All Resources exist under a single Group and, similar to Groups, have a set of Registry metadata. However, unlike a Group which only has Registry metadata, each Resource can also have a "document" associated with it. For example, a "schema" Resource might have a "schema document" as its "document". This specification places no restriction on the type of content stored in the Resource's document. Additionally, Resources (unlike Groups) MAY be versioned.

A Version is an instance of a Resource that represents a particular state of the Resource. Each Version of a Resource has its own set of xRegistry metadata and possibly a domain-specific document associated with it. Each Resource MUST have at least one Version associated with it. If versioning is not important for the use case in which the Resource is used, the default Version can be evolved without creating new ones.

This specification places no requirements on the lifecycle of Versions. Implementations, or users of the Registry, determine when new Versions are created, as opposed to updating existing Versions, and how many Versions are allowed per Resource type.

Registry Design 🔗

As discussed in the Overview section, an xRegistry consists of two main entities related to the data being managed: Groups and Resources. However, there are other concepts that make up the overall design and this section will cover them all in more detail.

Registry An xRegistry instance, or a "Registry", can be thought of as a single rooted tree of entities as shown in the "xRegistry Core Spec" diagram in the Overview section. At the root is the Registry entity itself. This entity is meant to serve a few key purposes:

Groups Traversing down the tree structure, below the Registry entity, there will be a set of Groups for the entities managed by the Registry. Each Group is meant to be a logical grouping of related Resources, much like a directory acts as a grouping of "files". Groups, like the Registry entity, does have similar high-level metadata that can be set and can have domain-specific extension attributes defined.

As hinted at with the "directory" analogy, a common use for Groups will be for them to be light-weight collections without much additional semantics associated with them. However, this is not a requirement. Because Groups allow for user-defined extension attributes to be defined, Groups might be quite rich with respect to managing domain-specific data. See the Endpoint as an example.

Resources Below, or within, each Group can be a set of Resources. Typically, Resources are the main pieces of data managed by the Registry. Like the Registry and Group entities, that have a set of xRegistry defined "common" metadata that can be set, and user-defined extension attributes can be defined. However, Resources also support the concept of a domain-specific "document" that can be associated with it. This document can be stored within the Registry itself (like another attribute on the Resource), or stored external to the Registry and a URL to the document will be stored within the xRegistry metadata. This allows for the definition of the model to support cases where domain-specific data needs to be managed, and exposed, as xRegistry metadata or the data needs to be completely separate from the Registry's metadata.

Typically, the domain-specific document will be used when a pre-existing document definition already exists and an xRegistry is being used as the mechanism to expose those documents in a consistent and interoperable way. For example, the Schema Registry only has a few xRegistry Resource extension attributes defined because most of the data of interest will be in the Schema Documents associated with the Resources.

Versions While "Resources" are presented as the most significant entities within a Registry, technically Resources themselves are very light-weight entities and are there to act as a grouping mechanism for another entity: Versions.

Often, as Resources change over time, it is desirable to maintain a historical set of instances (e.g. "versions") of those Resources so they can be referenced and accessed as independent (but related) pieces of data. Each Version of a Resource will have its own set of xRegistry metadata and instance of the domain-specific "document".

However, while each Version is independently accessible, the Resource itself has the concept of a "default Version" for which the Resource will act as a proxy (or alias). Meaning, accessing the Resource (or its document) will actually be accessing one of its Versions. This allows for end-users to not be concerned with keeping track of which Version is the "default" one as Versions are added or removed. This is sometimes configured to be the "latest" Version, however, xRegistry does not mandate that the "default" Version be the "latest".

While not a requirement, the collection of Versions within a Resource typically form a directed graph with respect to how they are related. Meaning, a Version is usually "derived" from another Version known as its "ancestor". For example, "version 2" might have "version 1" as its "ancestor". By default, xRegistry assumes that each new Version will have the current "newest" Version as its ancestor, but this is configurable. See the Version Mode section for more information.

Next Steps In summary, the xRegistry design itself is relatively simple and consists of 4 main concepts to form a tree of entities. However, with these, along with the extensibility of the xRegistry metadata model, a wide range of metadata can be categorized, managed, and exposed, in a consistent way, thus allowing for a dynamically discoverable, yet interoperable, programmatic access to what might otherwise be domain-specific set of APIs.

The following sections will define the technical details of those xRegistry entities and the APIs for access them.

Registry Attributes and APIs 🔗

This section defines common Registry metadata attributes and APIs. It is an explicit goal for this specification that metadata can be created and managed in files in a file system, for instance in a Git repository, and also managed in a Registry service that implements the API described here.

For instance, during development of a module, the metadata about the events raised by the modules will best be managed in a file that resides alongside the module's source code. When the module is ready to be deployed into a concrete system, the metadata about the events will be registered in a Registry service along with the endpoints where those events can be subscribed to or consumed from, and which allows discovery of the endpoints and all related metadata by other systems at runtime.

Therefore, the hierarchical structure of the Registry model is defined in such a way that it can be represented in a single file, including but not limited to JSON, or via the entity graph of a REST API.

If the processing of a request fails then an error MUST be generated and the entire request MUST be undone. See the Error Processing section for more information.

In the remainder of this specification, in particular when defining the attributes of the Registry entities, the terms "document view" or "API view" will be used to indicate whether the serialization of the entity in a response is meant for use as a stand-alone document or as part of a REST API message exchange. The most notable differences are that in document view:

Most of these differences are to make it easier for tooling to use the "stand-alone" document view of the Registry. For a complete list of the differences in "document view" see the Doc Flag flag and the Exporting section.

Note that "document view" only refers to response messages. There is no "document view" concept for requests. However, "document view" responses are designed such that they can be used in request messages as they still convey the same information as an "API view" response.

Implementation Customizations 🔗

This specification only defines the core APIs, and their semantics, of a Registry service. It does not address many of the details that would need to be added for a live instance of a service; as often times these aspects are very specific to the environment in which the service is running. For example, this specification does not address authentication or authorization levels of users, nor how to securely protect the APIs (aside from the implied use of https), clients or servers from attacks. Implementations of this specification are expected to add these various features as needed.

Additionally, implementation MAY choose to customize the data and behavior on a per-user basis as needed. For example, the following customizations might be implemented:

The goal of these customizations is not to allow for implementation to violate the specification, rather it is to allow for real-world requirements to be met while maintaining the interoperability goals of the specification.

Implementations are encouraged to contact the xRegistry community if it is unclear if certain customizations would violate the specification.

Attributes and Extensions 🔗

Unless otherwise noted, all attributes and extensions MUST be mutable and MUST be one of the following data types:

The 6 variants of URI/URL are provided to allow for strict type adherence when needed. However, for attributes that are simply "pointers" that might in practice be any of those 6 types it is RECOMMENDED that uri be used.

Attributes that are defined to be relative URIs or URLs MUST state what they are relative to and any constraints on their values, if any.

The root path of a Registry service MAY be at the root of a host or have a <PATH> portion in its URL (e.g. http://example.com/myregistry).

The "scalar" data types are: boolean, decimal, integer, string, timestamp, uinteger, uri, uriabsolute, urirelative, uritemplate, url, urlabsolute, urlrelative, xid, xidtype. Note that any is not a "scalar" type as its runtime value could be a complex type such as object.

All attributes (specification-defined and extensions) MUST adhere to the following rules:

Implementations of this specification MAY define additional (extension) attributes. However, they MUST adhere to the following rules:

Common Attributes 🔗

The following attributes are used by one or more entities defined by this specification. They are defined here once rather than repeating them throughout the specification.

For easy reference, the JSON serialization of these attributes adheres to this form:

The definition of each attribute is defined below:

<SINGULAR>id (id) Attribute 🔗

While <SINGULAR>id can be something like a UUID, when possible, it is RECOMMENDED that it be something human friendly as these values will often appear in user-facing situations such as URLs or as command-line parameters. And, in cases where name is absent, it might be used as the display name.

Note, since <SINGULAR>id is immutable, in order to change its value, a new entity would need to be created with the new <SINGULAR>id that is a deep-copy of the existing entity. Then the existing entity would be deleted.

self Attribute 🔗
shortself Attribute 🔗
xid Attribute 🔗
epoch Attribute 🔗
name Attribute 🔗
description Attribute 🔗
documentation Attribute 🔗
icon Attribute 🔗
labels Attribute 🔗
createdat Attribute 🔗
modifiedat Attribute 🔗

Registry APIs 🔗

This specification defines the following API patterns:

/                                                # Access the Registry
/capabilities                                    # Access available features
/model                                           # Access full model definitions
/modelsource                                     # Access model customizations
/export                                          # Retrieve Registry as a doc
/<GROUPS>                                        # Access a Group Type
/<GROUPS>/<GID>                                  # Access a Group
/<GROUPS>/<GID>/<RESOURCES>                      # Access a Resource Type
/<GROUPS>/<GID>/<RESOURCES>/<RID>                # Default Version of Resource
/<GROUPS>/<GID>/<RESOURCES>/<RID>/versions       # Versions of a Resource
/<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID> # Access Version of Resource

While these APIs are shown to be at the root path of a host, implementations MAY choose to prefix them as necessary. However, the same prefix MUST be used consistently for all APIs in the same Registry instance.

Support for any particular API defined by this specification is OPTIONAL, however, it is STRONGLY RECOMMENDED that server-side implementations support at least the "read" (e.g. HTTP GET) operations. Implementations MAY choose to incorporate authentication and/or authorization mechanisms for the APIs.

If an OPTIONAL HTTP path is not supported by an implementation, then any use of that API MUST generate an error (api_not_found).

If an HTTP method is not supported for a supported HTTP path, then an error (method_not_allowed) MUST be generated.

Implementations MAY support extension APIs, however, the following rules MUST apply:

For example, a new API with an HTTP path of /my-api is allowed, but APIs with /model/my-api or /name HTTP paths are not.

This specification attempts to follow a standard REST/HTTP processing model. The following key aspects are called out to help understand the overall pattern of the APIs:

In general, if a server is unable to retrieve all of the data intended to be sent in a response, then an error (data_retrieval_error) MUST be generated and the request rejected without any changes being made. However, it is permissible for a server to attempt some creative processing. For example, if while processing a GET the server can only retrieve half of the entities to be returned at the current point in time, then it could return those with an indication of there being more (via the pagination specification). Then during the next GET request it could return the remainder of the data - or an error if it is still not available. Note that if an entity is to be sent, then it MUST be serialized in its entirety (all attributes, and requested child entities) or an error MUST be generated.

There might be situations where someone will do a GET to retrieve data from a Registry, and then do an update operation to a Registry with that data. Depending on the use case, they might not want some of the retrieved data to be applied during the update - for example, they might not want the epoch validation checking to occur. Rather than forcing the user to edit the data to remove the potentially problematic attributes, the following query parameters MAY be included on write operations to control certain aspects of the processing:

Any JSON xRegistry metadata message that represents a single entity (i.e. not a map) MAY include a top-level "$schema" attribute that points to a JSON Schema document that describes the message contents. These notations can be used or ignored by receivers of these messages. There is no requirement for implementation of this specification to persist these values, to include them in responses or to use this information.

No-Code Servers 🔗

One of the goals of xRegistry is to be as broadly supported as possible. Requiring all xRegistry endpoints to support the full range of APIs defined in this specification might not be feasible in all cases. In particular, there might be cases where someone wishes to host a read-only xRegistry server to only expose their documents (and metadata) and therefore the write operations or advanced features (such as inlining or filtering) might not be needed. In those cases, simple file serving HTTP servers, such as blob stores, ought to be sufficient, and in those cases requiring support for query parameters and other advanced features (that could require code) might not always be possible.

To support these simple (no-code) scenarios, this specification is written such that all of the APIs are OPTIONAL, and all of the query parameters on the read operations are OPTIONAL (typically specified by saying that they SHOULD be supported). However, it is STRONGLY RECOMMENDED that full API servers support the query parameters when possible to enable a better user experience, and increase interoperability.

Note that simple file servers SHOULD support exposing Resources where the HTTP body response contains the Resource's associated "document" as well as the case where the HTTP response body contains a JSON serialization of the Resource via the $details suffix on the URL path. This can be achieved by creating a secondary sibling file on disk with $details at the end of its filename.


The remainder of this specification mainly focuses on the successful interaction patterns of the APIs. For example, most examples will show an HTTP "200 OK" as the response. Each implementation MAY choose to return a more appropriate response based on the specific situation. For example, in the case of an authentication error the server could return 401 Unauthorized.

The following sections define the APIs in more detail.


Registry Collections 🔗

Registry collections (<GROUPS>, <RESOURCES> and versions) that are defined by the Registry Model MUST be serialized according to the rules defined below.

The serialization of a collection is done as 3 attributes and they MUST adhere to their respective forms as follows:

"<COLLECTION>url": "<URL>",
"<COLLECTION>count": <UINTEGER>,
"<COLLECTION>": {
  # Map of entities in the collection, key is the "<SINGULAR>id" of the entity
}

Where:

When the <COLLECTION> attribute is expected to be present in the serialization, but the number of entities in the collection is zero, it MUST still be included as an empty map (e.g. {}).

The set of entities that are part of the <COLLECTION> attribute is a point-in-time view of the Registry. There is no guarantee that a future GET to the <COLLECTION>url will return the exact same collection since the contents of the Registry might have changed. This specification makes no statement as to whether a subsequent GET that is missing previously returned entities is an indication of those entities being deleted or not.

Since collections could be too large to retrieve in one request, when retrieving a collection, the client MAY request a subset by using the pagination specification. Likewise, the server MAY choose to return a subset of the collection using the same mechanism defined in that specification even if the request didn't ask for pagination. The pagination specification MUST only be used when the request is directed at a collection, not at its owning entity (such as the root of the Registry, or at an individual Group or Resource).

In the remainder of the specification, the presence of the Link HTTP header indicates the use of the pagination specification MAY be used for that API.

The requirements on the presence of the 3 <COLLECTION> attributes varies between document and API views, and is defined below:

Collections in Document View 🔗

In document view:

Collections in API View 🔗

In API view:

Updating Nested Registry Collections 🔗

When updating an entity that can contain Registry collections, the request MAY contain the 3 collection attributes. The <COLLECTION>url and <COLLECTION>count attributes MUST be silently ignored by the server.

If the <COLLECTION> attribute is present, the server MUST process each entity in the collection map as a request to create or update that entity according to the semantics of the HTTP method used. An entry in the map that isn't a valid entity (e.g. is null) MUST generate an error (bad_request).

For example:

PUT https://example.com/endpoints/ep1

{
  "endpointid": "ep1",
  "name": "A cool endpoint",

  "messages": {
    "mymsg1": { ... },
    "mymsg2:" { ... }
  }
}

will not only create/update an endpoint Group with an endpointid of ep1 but will also create/update its message Resources (mymsg1 and mymsg2).

Any error while processing a nested collection entity MUST result in the entire request being rejected.

An absent <COLLECTION> attribute MUST be interpreted as a request to not modify the collection at all.

If a client wishes to replace an entire collection, rather than just add new entities, the client MUST use one of the DELETE operations on the collection first.

In cases where an update operation includes attributes meant to be applied to the "default" Version of a Resource, and the incoming inlined versions collections includes that "default" Version, the Resource's default Version attributes MUST be silently ignored. This is to avoid any possible conflicting data between the two sets of data for that Version. In other words, the Version attributes in the incoming versions collection wins.

To better understand this scenario, consider the following HTTP request to update a Message where the defaultversionid is v1:

PUT http://example.com/endpoints/ep1/messages/msg1

{
  "messageid": "msg1",
  "versionid": "v1",
  "name": "Blob Created"

  "versions": {
    "v1": {
      "messageid": "msg1",
      "versionid": "v1",
      "name": "Blob Created Message Definition"
    }
  }
}

If the versions collection were not present with the v1 entity then the top-level attributes would be used to update the default Version (v1 in this case). However, because it is present, the request to update v1 becomes ambiguous because it is not clear if the server is meant to use the top-level attributes or if it is to use the attributes under the v1 entity of the versions collection. When both sets of attributes are the same, then it does not matter. However, in these cases the name attributes have different values. The paragraph above mandates that in these potentially ambiguous cases the entity in the versions collection is to be used and the top-level attributes are to be ignored - for the purposes of updating the "default" Version's attributes. So, in this case the name of the default (v1) Version will be Blob Created Message Definition.


Entity Processing Rules 🔗

Rather than repeating the processing rules for each type of xRegistry entity or Registry collection, the overall pattern is defined once in this section and any entity-, or collection-specific rules will be detailed in the appropriate section in the specification.

Creating or Updating Entities 🔗

This defines the general rules for how to update entities.

Creating or updating entities MAY be done using HTTP PUT, PATCH or POST methods:

Based on the entity being processed, the OPTIONS available will vary.

The PUT variant MUST adhere to the following:

The POST variant MUST adhere to the following:

The PATCH variant when directed at a single entity, MUST adhere to the PUT semantics defined above with the following exceptions:

The PATCH variant when directed at an xRegistry collection, MUST adhere to the following:

The processing of each individual entity follows the same set of rules:

A successful response MUST return the same response as a GET to the entity (or entities) processed, showing their current representation, with the following exceptions:

Otherwise an HTTP 200 OK without an HTTP Location header MUST be returned.

Note that the response MUST be generated applying the semantics of any query parameters specified on the request URL (e.g. ?inline). If an error occurs while generating the response (e.g. invalid ?filter), then an error MUST be generated and the entire operation MUST be undone.

Retrieving a Registry Collection 🔗

To retrieve a Registry collection, an HTTP GET MAY be used. The request MUST be of the form:

GET <PATH-TO-COLLECTION>

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <URL>;rel=next;count=<UINTEGER> ?

{
  "<KEY>": {                                         # <SINGULAR>id value
    "<SINGULAR>id": "<STRING>",
    ... remaining entity attributes ...
  } *
}
Retrieving an Entity from a Registry Collection 🔗

To retrieve an entity, an HTTP GET MAY be used. The request MUST be of the form:

GET <PATH-TO-COLLECTION>/<ID-OF-ENTITY>

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "<SINGULAR>id": "<STRING>",
  ... remaining entity attributes ...
}
Deleting Entities in a Registry Collection 🔗

There are two ways to delete entities from a Registry collection:

  1. to delete a single entity, an HTTP DELETE MAY be used. The request MUST be of the form:
DELETE <PATH-TO-COLLECTION>/<ID-OF-ENTITY>[?epoch=<UINTEGER>]

Where:

The following query parameter SHOULD be supported by servers:

  1. to delete multiple entities within a Registry collection, the request MUST be in one of two forms:

For non-Resource entities:

DELETE <PATH-TO-COLLECTION>

{
  "<KEY>": {                                          # <SINGULAR>id of entity
    "epoch": <UINTEGER> ?
  } *
} ?

or

For Resource entities (see below for more details):

DELETE <PATH-TO-COLLECTION>

{
  "<KEY>": {                                          # <SINGULAR>id of entity
    "meta": {
      "epoch": <UINTEGER> ?
    } ?
  } *
} ?

Where:

Whether the request is to delete a single entity or multiple, deleting an entity MUST delete all children entities as well - meaning, any entities within any nested Registry collections.

Any error MUST result in the entire request being rejected.

A successful response MUST return either:

HTTP/1.1 204 No Content

with an empty HTTP body, or:

HTTP/1.1 200 OK

if, as an extension, the server chooses to return additional data in the HTTP body.


Registry Root APIs 🔗

The Registry entity represents the root of a Registry and is the main entry-point for traversal and discovery.

The serialization of the Registry entity adheres to this form:

{
  "specversion": "<STRING>",
  "registryid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  "capabilities": { Registry capabilities }, ?   # Only if inlined
  "model": { Registry model }, ?                 # Only if inlined
  "modelsource": { Registry model }, ?           # Only if inlined

  # Repeat for each Group type
  "<GROUPS>url": "<URL>",                        # e.g. "endpointsurl"
  "<GROUPS>count": <UINTEGER>,                   # e.g. "endpointscount"
  "<GROUPS>": { Groups collection } ?            # Only if inlined
}

The Registry entity includes the following common attributes:

and the following Registry level attributes:

specversion Attribute 🔗
model Attribute 🔗
modelsource Attribute 🔗
<GROUPS> Collections 🔗

Retrieving the Registry 🔗

To retrieve the Registry, its metadata attributes, and Groups, an HTTP GET MAY be used.

The request MUST be of the form:

GET /[?specversion=...]

The following query parameter SHOULD be supported by servers:

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "specversion": "<STRING>",
  "registryid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  "capabilities": { Registry capabilities }, ?   # Only if inlined
  "model": { Registry model }, ?                 # Only if inlined
  "modelsource": { Registry model }, ?           # Only if inlined

  # Repeat for each Group type
  "<GROUPS>url": "<URL>",               # e.g. "endpointsurl"
  "<GROUPS>count": <UINTEGER>,          # e.g. "endpointscount"
  "<GROUPS>": { Groups collection } ?   # Only if inlined
}

Examples:

Retrieve a Registry that has 2 types of Groups (endpoints and schemagroups):

GET /
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "specversion": "1.0-rc1",
  "registryid": "myRegistry",
  "self": "https://example.com/",
  "xid": "/",
  "epoch": 1,
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",

  "endpointsurl": "https://example.com/endpoints",
  "endpointscount": 42,

  "schemagroupsurl": "https://example.com/schemagroups",
  "schemagroupscount": 1
}

Another example where:

GET /?inline=schemagroups,model

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "specversion": "1.0-rc1",
  "registryid": "myRegistry",
  "self": "https://example.com/",
  "xid": "/",
  "epoch": 1,
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",

  "model": {
    ... xRegistry spec-defined attributes excluded for brevity ...
    "groups": {
      "endpoints": {
        "plural": "endpoints",
        "singular": "endpoint",
        "attributes": {
          ... xRegistry spec-defined attributes excluded for brevity ...
          "shared": {
            "name": "shared",
            "type": "boolean"
          }
        },

        "resources": {
          "messages": {
            "plural": "messages",
            "singular": "message",
            "attributes": {
              ... xRegistry spec-defined attributes excluded for brevity ...
              "*": {
                type: "any"
              }
            },
            "maxversions": 1
          }
        }
      },
      "schemagroups": {
        "plural": "schemagroups",
        "singular": "schemagroup",
        ... xRegistry spec-defined attributes excluded for brevity ...

        "resources": {
          "schemas": {
            "plural": "schemas",
            "singular": "schema",
            ... xRegistry spec-defined attributes excluded for brevity ...
            "maxversions": 1
          }
        }
      }
    }
  },

  "endpointsurl": "https://example.com/endpoints",
  "endpointscount": 42,

  "schemagroupsurl": "https://example.com/schemagroups",
  "schemagroupscount": 1,
  "schemagroups": {
    "mySchemas": {
      "schemaid": "mySchemas",
      # Remainder of schemagroup is excluded for brevity
    }
  }
}

Updating the Registry Entity 🔗

To update the Registry entity, an HTTP PUT or PATCH MAY be used.

The request MUST be of the form:

PUT /
or
PATCH /
Content-Type: application/json; charset=utf-8
If-Match: "<UINTEGER>|*" ?

{
  "registryid": "<STRING>", ?
  "epoch": <UINTEGER>, ?
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>", ?
  "modifiedat": "<TIMESTAMP>", ?

  "capabilities": { Registry capabilities }, ?
  "modelsource": { Registry model }, ?

  # Repeat for each Group type
  "<GROUPS>": { Groups collection } ?
}

Where:

A successful response MUST include the same content that an HTTP GET on the Registry would return, and be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "specversion": "<STRING>",
  "registryid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  # Repeat for each Group type
  "<GROUPS>url": "<URL>",
  "<GROUPS>count": <UINTEGER>
}

Note that the response MUST NOT include the model* attributes, nor any inlined Groups collections.

Examples:

Updating a Registry's metadata

PUT /
Content-Type: application/json; charset=utf-8

{
  "registryid": "myRegistry",
  "name": "My Registry",
  "description": "An even cooler registry!"
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "specversion": "1.0-rc1",
  "registryid": "myRegistry",
  "self": "https://example.com/",
  "xid": "/",
  "epoch": 2,
  "name": "My Registry",
  "description": "An even cooler registry!",
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",

  "endpointsurl": "https://example.com/endpoints",
  "endpointscount": 42,

  "schemagroupsurl": "https://example.com/schemagroups",
  "schemagroupscount": 1
}

Registry Capabilities 🔗

In order to programmatically discover which capabilities are supported by an implementation, servers MUST support exposing this information via a "capabilities" map that lists each supported feature along with any related configuration detail that will help in successful usage of that feature.

The "key" of the capabilities-map is the "name" of each feature, and the "value" is a feature specific set of configuration values. With the most basic being a <BOOLEAN> value of true to indicate support for the feature.

The capabilities-map MAY be retrieved via two mechanisms:

Regardless of the retrieval mechanism, the format of the capabilities-map MUST be of the form:

{
  "apis": [ "<STRING>" * ], ?
  "flags": [ "<STRING>" * ], ?
  "mutable": [ "<STRING>" * ], ?
  "pagination": <BOOLEAN>, ?
  "schemas": [ "<STRING>" * ], ?
  "shortself": <BOOLEAN>, ?
  "specversions": [ "<STRING>" ], ?
  "sticky": <BOOLEAN>, ?
  "versionmodes": [ "<STRING>" ], ?

  "<STRING>": ... capability configuration ... *   // Extension capabilities
}

Where:

All capability values, including extensions, MUST be defined as one of the following:

Absence of a capability in the capability map is an indication of that feature not being supported. All supported extensions MUST be included in the list.

Absence, presence, or configuration values of a feature in the map MAY vary based on the authorization level of the client making the request.

The following defines the specification-defined capabilities:

apis 🔗

flags 🔗

mutable 🔗

pagination 🔗

schemas 🔗

shortself 🔗

specversions 🔗

sticky 🔗

The list of values for the arrays MUST be case-insensitive and MAY include extension values.

For clarity, servers MUST include all known capabilities in the serialization, even if they are set to their default values or have empty lists.

Updating the Capabilities of a Server 🔗

If supported, updates to the server's capabilities MAY be done via an HTTP PUT, or PATCH, to the /capabilities API, or by updating the capabilities attribute on the root of the Registry. As with other APIs, a PUT MUST be interpreted as a request to update the entire set of capabilities and any missing capability MUST be interpreted as a request to reset it to its default value. If a PATCH is used then each capability included MUST be fully specified, and fully replaced by the incoming value. In other words, PATCH is done at a capability level not any deeper within the JSON structure.

The request to the /capabilities API MUST be of the form:

PUT /capabilities
Content-Type: application/json; charset=utf-8

{ ... Capabilities map ...  }

or

PATCH /capabilities
Content-Type: application/json; charset=utf-8

{ ... Capabilities map ...  }

Where:

A successful response MUST include a full representation of all of the capabilities of the Registry and be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{ ... Capabilities map ... }

Updates via the capabilities attribute follows the same attribute update semantics as the other Registry level attributes. Note that using an HTTP PATCH to update the Registry's attributes MAY include the capabilities attribute, however, it MUST be processed with the PATCH semantics as well.

During the processing of a request to update the capabilities, the semantic change MUST NOT take effect until after the processing of the current request. Note that if the response includes the serialization of the Registry's capabilities, then the changes MUST appear in that serialization.

For any capability that is an array of strings, a value of "*" MAY be used to indicate that the server MUST replace "*" with the full set of items that are available. An error (capability_error) MUST be generated if "*" appears with any other value in the list. "*" MUST NOT appear in the serialization in any server's response.

Regardless of the mechanism used to update the capabilities, the Registry's epoch value MUST be incremented.

In order for a client to discover the list of available values for each capability, an HTTP GET MAY be sent to the /capabilities API with the ?offered query parameter and the response MUST adhere to the following (which borrows much of the same structure from the model definition language):

GET /capabilities?offered

{
  "<STRING>": {
    "type": "<TYPE>",
    "item": {
      "type": "<TYPE>"
    }, ?
    "enum": [ <VALUE>, * ], ?
    "min": <VALUE>, ?
    "max": <VALUE>, ?
    "documentation": "<URL>" ?
  }, *
}

Where:

For example:

GET /capabilities?offered

{
  "apis": {
    "type": "string",
    "enum": [ "/capabilities", "/export", "/model", /"modelsource" ]
  },
  "flags": {
    "type": "string",
    "enum": [ "collections", "doc", "epoch", "filter", "inline",
      "nodefaultversionid", "nodefaultversionsticky", "noepoch", "noreadonly",
      "offered", "schema", "setdefaultversionid", "sort", "specversion" ]
  },
  "pagination": {
    "type": "boolean",
    "enum": [ false, true ]
  },
  "schemas": {
    "type": "string",
    "enum": [ "xRegistry-json/1.0-rc1" ]
  },
  "shortself": {
    "type": "boolean",
    "enum": [ false, true ]
  },
  "specversions": {
    "type": "string",
    "enum": [ "xRegistry-json/1.0-rc1" ]
  },
  "sticky": {
    "type": "boolean",
    "enum": [ true ]
  },
  "versionmodes": [ "manual" ]
}

The enum of values allows for some special cases:

A request to update a capability with a value that is compliant with the output of the /capabilities?offered MAY still generate an error (capability_error) if the server determines it cannot support the request. For example, due to authorization concerns or the value, while syntactically valid, isn't allowed in certain situations.

For clarity, even in cases where there is no variability allowed with certain capabilities they SHOULD still be listed in both the /capabilities API and the /capabilities?offered API to maximize discoverability. For example, if pagination is not supported, then a server SHOULD still include:

  "pagination": false

in the /capabilities output, and

  "pagination": {
    "type": "boolean",
    "enum": [ false ]
  }

in the /capabilities?offered output (assuming both APIs are supported).

Registry Model 🔗

The Registry model defines the Groups, Resources, attributes and changes to specification-defined attributes that define what a Registry instance supports. This information is intended to be used by tooling that does not have knowledge of the structure of the Registry in advance and therefore will need to dynamically discover it.

The following sections will go into the details of how to create, retrieve and edit the model of a Registry, but the overall format of a model definition is as follows:

{
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "attributes": {                      # Registry level extensions
    "<STRING>": {                      # Attribute name
      "name": "<STRING>",              # Same as attribute's key
      "type": "<TYPE>",                # boolean, string, array, object, ...
      "target": "<XIDTYPE>", ?         # If "type" is "xid" or "url"
      "namecharset": "<STRING>", ?     # If "type" is "object"
      "description": "<STRING>",
      "enum": [ <VALUE> * ], ?         # Array of scalars of type "<TYPE>"
      "strict": <BOOLEAN>, ?           # Just "enum" values or not. Default=true
      "readonly": <BOOLEAN>, ?         # From client's POV. Default=false
      "immutable": <BOOLEAN>, ?        # Once set, can't change. Default=false
      "required": <BOOLEAN>, ?         # Default=false
      "default": <VALUE>, ?            # Scalar attribute's default value

      "attributes": { ... }, ?         # If "type" above is object
      "item": {                        # If "type" above is map,array
        "type": "<TYPE>", ?            # map value type, or array type
        "target": "<XIDTYPE>", ?       # If this item "type" is xid/url
        "namecharset": "<STRING>", ?   # If this item "type" is object
        "attributes": { ... }, ?       # If this item "type" is object
        "item": { ... } ?              # If this item "type" is map,array
      } ?

      "ifvalues": {                    # If "type" is scalar
        "<VALUE>": {
          "siblingattributes": { ... } # Siblings to this "attribute"
        } *
      } ?
    } *
  },

  "groups": {
    "<STRING>": {                      # Key=plural name, e.g. "endpoints"
      "plural": "<STRING>",            # e.g. "endpoints"
      "singular": "<STRING>",          # e.g. "endpoint"
      "description": "<STRING>", ?
      "documentation": "<URL>", ?
      "icon": "<URL>", ?
      "labels": { "<STRING>": "<STRING>" * }, ?
      "modelversion": "<STRING>", ?    # Version of the group model
      "compatiblewith": "<URI>", ?     # Statement of compatibility with model spec
      "attributes": { ... }, ?         # See "attributes" above
      "ximportresources": [ "<XIDTYPE>", * ], ?   # Include these Resources

      "resources": {
        "<STRING>": {                  # Key=plural name, e.g. "messages"
          "plural": "<STRING>",        # e.g. "messages"
          "singular": "<STRING>",      # e.g. "message"
          "description": "<STRING>", ?
          "documentation": "<URL>", ?
          "icon": "<URL>", ?
          "labels": { "<STRING>": "<STRING>" * }, ?
          "modelversion": "<STRING>", ?  # Version of the resource model
          "compatiblewith": "<URI>"`, ?  # Statement of compatibility with model spec
          "maxversions": <UINTEGER>, ? # Num Vers(>=0). Default=0, 0=unlimited
          "setversionid": <BOOLEAN>, ? # vid settable? Default=true
          "setdefaultversionsticky": <BOOLEAN>, ? # sticky settable? Default=true
          "hasdocument": <BOOLEAN>, ?    # Has separate document. Default=true
          "versionmode": "<STRING>", ?   # 'ancestor' processing algorithm
          "singleversionroot": <BOOLEAN>, ? # enforce single root. Default=false
          "typemap": <MAP>, ?            # contenttype mappings
          "attributes": { ... }, ?          # Version attributes/extensions
          "resourceattributes": { ... }, ?  # Resource attributes/extensions
          "metaattributes": { ... } ?       # Meta attributes/extensions
        } *
      } ?
    } *
  } ?
}

The following describes the attributes of Registry model:

- Model: description 🔗
- Model: icon 🔗
- Model: labels 🔗
- Model: attributes 🔗
- Model: attributes."<STRING>" 🔗
- Model: attributes."<STRING>".name 🔗
- Model: attributes."<STRING>".type 🔗
- Model: attributes."<STRING>".target 🔗
- Model: attributes."<STRING>".namecharset 🔗
- Model: attributes."<STRING>".description 🔗
- Model: attributes."<STRING>".enum 🔗
- Model: attributes."<STRING>".strict 🔗
- Model: attributes."<STRING>".readonly 🔗
- Model: attributes."<STRING>".immutable 🔗
- Model: attributes."<STRING>".required 🔗
- Model: attributes."<STRING>".default 🔗
- Model: attributes."<STRING>".attributes 🔗
- Model: attributes."<STRING>".item 🔗
- Model: attributes."<STRING>".item.type 🔗
- Model: attributes."<STRING>".item.target 🔗
- Model: attributes."<STRING>".item.namecharset 🔗
- Model: attributes."<STRING>".item.attributes 🔗
- Model: attributes."<STRING>".item.item 🔗
- Model: attributes."<STRING>".ifvalues 🔗
- Model: groups 🔗
- Model: groups."<STRING>" 🔗
- Model: groups."<STRING>".plural 🔗
- Model: groups."<STRING>".singular 🔗
- Model: groups."<STRING>".description 🔗
- Model: groups."<STRING>".icon 🔗
- Model: groups."<STRING>".labels 🔗
- Model: groups."<STRING>".modelversion 🔗
- Model: compatiblewith 🔗
- Model: groups."<STRING>".attributes 🔗
- Model: groups."<STRING>".ximportresources 🔗
- Model: groups."<STRING>".resources 🔗
- Model: groups."<STRING>".resources.""` 🔗
- Model: groups."<STRING>".resources."<STRING>".plural 🔗
- Model: groups."<STRING>".resources."<STRING>".singular 🔗
- Model: groups."<STRING>".resources."<STRING>".description 🔗
- Model: groups."<STRING>".resources."<STRING>".icon 🔗
- Model: groups."<STRING>".resources."<STRING>".labels 🔗
- Model: groups."<STRING>".resources."<STRING>".modelversion 🔗
- Model: groups."<STRING>".resources."<STRING>".compatiblewith 🔗
- Model: groups."<STRING>".resources."<STRING>".maxversions 🔗
- Model: groups."<STRING>".resources."<STRING>".setversionid 🔗
- Model: groups."<STRING>".resources."<STRING>".setdefaultversionsticky 🔗
- Model: groups."<STRING>".resources."<STRING>".hasdocument 🔗
- Model: groups."<STRING>".resources."<STRING>".versionmode 🔗
- Type: String
- OPTIONAL.
- Indicates the algorithm that MUST be used when determining how Versions
  are managed with respect to aspects such as:
  - Which Version is the "latest"?
  - Which Version is the "oldest"?
  - How a Version's `ancestor` attribute will be populated when not
    provided during a create or when its current ancestor is deleted.
- Implementations MAY defined additional algorithms and MAY defined
  additional aspects that they control as long as those aspects do not
  conflict with specification defined semantics.
- Regardless of the algorithm used, implementations MUST ensure that
  the `ancestor` attribute of all Versions of a Resource accurately
  represent the relationship of the Versions prior to the completion of
  any operation. For example, when the `createdat` algorithm is used and
  the `createdat` timestamp of a Version is modified, this might cause a
  reordering of the Versions and the `ancestor` attributes might need to
  be changed accordingly. Similarly, the `defaultversionid` of the
  Resource might change if its `defaultversionsticky` attribute is `false`.
- When not specified the default value MUST be `manual`.
- This specification defines the following `versionmode` algorithms:
  - `manual`
    - Latest Version: MUST be determined by finding all Versions that are
      not referenced as an `ancestor` of another Version, then
      finding the one with the newest `createdat` timestamp. If there is
      more than one, then the one with the highest alphabetically
      case-insensitive `versionid` value MUST be chosen.
    - Oldest Version: MUST be determined by finding all root Versions (ones
      that have an `ancestor` value that points to itself), then finding
      the one with the oldest `createdat` timestamp. If there is more than
      one, then the one with the lowest alphabetically case-insensitive
      `versionid` MUST be chosen.
    - Ancestor Processing: typically provided by clients. During a "create"
      operation, all Versions that do not have an `ancestor` value
      provided MUST be sorted/processed by `versionid` (in case-insensitive
      ascending order) and the `ancestor` value of each MUST be set to the
      current "latest version" per the above semantics. Note that as
      each new Version is created, it MUST become the "latest". If there
      is no existing Version then the new Version becomes a root and its
      `ancestor` value MUST be its `versionid` attribute value.
    - Invalid Ancestor: if a Version's `ancestor` value is no longer
      valid (i.e. the ancestor Version was deleted), then this Version
      MUST become a root, and it's `ancestor` value MUST is its `versionid`
      attribute value.

  - `createdat`
    - Latest Version: MUST be determined by finding the Version with the
      newest `createdat` timestamp. If there is more than one, then the
      one with the highest alphabetically case-insensitive `versionid`
      value MUST be chosen.
    - Oldest Version: MUST be determined by finding the Version with the
      oldest `createdat` timestamp. If there is more than one, then the
      one with the lowest alphabetically case-insensitive `versionid`
      value MUST be chosen. Note that this MUST also be the one and only
      "root" Version.
    - Ancestor Processing: The `ancestor` value of each Version MUST be
      determined via examination of the `createdat` timestamp of each
      Version and the Versions sorted in ascending order, where the first
      one will be the "root" (oldest) Version and its `ancestor` value
      MUST be its `versionid`. If there is more than one Version with the
      same `createdat` timestamp then those MUST be ordered in ascending
      case-insensitive ordered based on their `versionid` values.
    - Invalid Ancestor: When a Version is deleted then the "ancestor
      processing" logic is as stated above MUST be applied.
    - When this `versionmode` is used, the `singleversionroot` aspect
      MUST be set to `true`.

  - `modifiedat`
    - This is the same as the `createdat` algorithm except that the
      `modifiedat` attribute of each Version MUST be used instead of the
      `createdat` attribute.

  - `semver`
    - Latest Version: MUST be the Version with the highest `versionid`
      value per the [Semantic Versioning](https://semver.org/)
      specification's "precedence" ordering rules.
    - Oldest Version: MUST be the Version with the lowest `versionid`
      value per the [Semantic Versioning](https://semver.org/)
      specification's "precedence" ordering rules. Note that this MUST also
      be the one and only "root" Version.
    - Ancestor Processing: The `ancestor` value of each Version MUST either
      be its `versionid` value (if it it the oldest Version), or the
      `versionid` of the next oldest Version per the
      [Semantic Versioning](https://semver.org/) specification's
      "precedence" ordering rules.
    - Invalid Ancestor: When a Version is deleted then the "ancestor
      processing" logic is as stated above MUST be applied.
    - When this `versionmode` is used, the `singleversionroot` aspect
      MUST be set to `true`.
- Model: groups."<STRING>".resources."<STRING>".singleversionroot 🔗
- Type: Boolean (`true` or `false`, case-sensitive).
- OPTIONAL.
- Indicates whether Resources of this type can have multiple Versions
  that represent roots of an ancestor tree, as indicated by the
  Version's `ancestor` attribute value being the same as its `versionid`
  attribute.
- When not specified, the default value MUST be `false`.
- A value of `true` indicates that only one Version of the Resource can
  be a root. This is useful to avoid creating multiple roots. When this
  attribute is set to `true`, the server MUST generate an error
  ([multiple_roots](#multiple_roots)) if any
  request results in a state where more than one Version of a Resource
  is a root of an ancestor tree.
- Note that if the Resource's `versionmode` value might influence
  the permissible values of this aspect.
- Model: groups."<STRING>".resources."<STRING>".typemap 🔗
- Model: groups."<STRING>".resources."<STRING>".attributes 🔗
- Model: groups."<STRING>".resources."<STRING>".resourceattributes 🔗
- Model: groups."<STRING>".resources."<STRING>".metaattributes 🔗

Clarifying the usage of the attributes, resourceattributes and metaattributes:

Retrieving the Registry Model 🔗

The Registry model is available in two forms:

The full "model" view can be thought of as a full schema definition of what the messages exchanges with the server might look like. As such, it includes the Groups, Resources, model-specific attributes, extension attributes, specification defined attributes and overrides to those specification defined attributes.

The "modelsource" view of the model is just what was provided by the user when the model was defined, or last edited. It is expected that this view of the model is much smaller than the full model and only includes domain-specific information. While specification defined attributes MAY appear in this document, they are NOT RECOMMENDED since the server will automatically add them and so users do not need to concern themselves with those details.

The modelsource document is always a semantic subset of the full model document.

To retrieve either of the model views as a stand-alone entity, an HTTP GET MAY be used. In the case of retrieving the full model, the result MUST include the full Registry model - meaning all specification-defined attributes, extension attributes, Group types, and Resource types.

Registries MAY support exposing the model in a variety of well-defined schema formats. The schemas capabilities attribute MUST expose the set of schema formats available.

For the sake of brevity, this specification doesn't include the full definition of the specification-defined attributes as part of the snippets of output. However, an example a full model definition of a sample Registry can be can be found in this sample sample-model-full.json.

The full model MAY be retrieved via:

Where a successful response MUST include the full model definition, adhering to the model format specified above.

The modelsource MAY be retrieved via:

Where a successful response MUST include the model definition last used when updating the model, adhering to the model format specified above.

Additionally:

Implementations of this specification MUST support a schema value of xRegistry-json/1.0-rc1, if they support the schema flag.

In the case of using the /model and /modelsource APIs, the response MUST adhere to:

HTTP/1.1 200 OK
Content-Type: ...

... xRegistry model in a schema specific format ...

Where:

If the specified schema format is not supported then an error (invalid_data) MUST be generated.

When the schema is xRegistry-json/1.0-rc1 then the response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "labels": { "<STRING>": "<STRING>" * }, ?
  "attributes": {
    "<STRING>": {
      "name": "<STRING>",
      "type": "<TYPE>",
      "target": "<XIDTYPE>", ?
      "namecharset": "<STRING>", ?
      "description": "<STRING>", ?
      "enum": [ <VALUE> * ], ?
      "strict": <BOOLEAN>, ?
      "readonly": <BOOLEAN>, ?
      "immutable": <BOOLEAN>, ?
      "required": <BOOLEAN>, ?
      "default": <VALUE>, ?

      "attributes": { ... }, ?
      "item": { ... }, ?

      "ifvalues": {
        "<VALUE>": {
          "siblingattributes": { ... }
        } *
      } ?
    } *
  },

  "groups": {
    "<STRING>": {
      "plural": "<STRING>",
      "singular": "<STRING>",
      "description": "<STRING>", ?
      "documentation": "<URL>", ?
      "icon": "<URL>", ?
      "labels": { "<STRING>": "<STRING>" * }, ?
      "modelversion": "<STRING>", ?
      "compatiblewith": "<URI>", ?
      "attributes": { ... }, ?
      "ximportresources": [ "<XIDTYPE>", * ], ?

      "resources": {
        "<STRING>": {
          "plural": "<STRING>",
          "singular": "<STRING>",
          "description": "<STRING>", ?
          "documentation": "<URL>", ?
          "icon": "<URL>", ?
          "labels": { "<STRING>": "<STRING>" * }, ?
          "modelversion": "<STRING>", ?
          "compatiblewith": "<URI>", ?
          "maxversions": <UINTEGER>, ?
          "setversionid": <BOOLEAN>, ?
          "setdefaultversionsticky": <BOOLEAN>, ?
          "hasdocument": <BOOLEAN>, ?
          "versionmode": "<STRING>", ?
          "singleversionroot": <BOOLEAN>, ?
          "typemap": <MAP>, ?
          "attributes": { ... }, ?
          "resourceattributes": { ... }, ?
          "metaattributes": { ... } ?
        } *
      } ?
    } *
  } ?
}

Examples:

Retrieve a Registry model that has one extension attribute on the endpoints Group, and supports returning the schema of the Registry as JSON Schema:

GET /model
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "attributes": {
    ... xRegistry spec-defined attributes excluded for brevity ...
  },
  "groups": {
    "endpoints": {
      "plural": "endpoints",
      "singular": "endpoint",
      "attributes": {
        ... xRegistry spec-defined attributes excluded for brevity ...
        "shared": {
          "name": "shared",
          "type": "boolean"
        }
      },

      "resources": {
        "messages": {
          "plural": "messages",
          "singular": "message",
            ... xRegistry spec-defined attributes excluded for brevity ...
            "*": {
              type: "any"
            }
          },
          "metaattributes": {
            ... xRegistry spec-defined attributes excluded for brevity ...
            "*": {
              type: "any"
            }
          }
        }
      }
    }
  }
}

Creating or Updating the Registry Model 🔗

The create, or update, the model a Registry, the new model definition MAY be provided to the server via one of two mechanisms:

The input JSON object in both cases MUST align with the pseudo JSON format of the model as specific above. It MAY include as much of the full model as the user would like to specify, but as stated previously, it is RECOMMENDED that it only include the domain-specific aspects needed to be added to the specification defined features. This will keep the input small and more easily managed if updates are ever needed to be made.

The response of a successful PUT /modelsource MUST be the same as the result of a GET /modelsource API call.

The following sample model definition defines one Group type (dirs) that contains one Resource type (files), which also has one attribute called owner (a string):

"modelsource": {
  "groups": {
    "dirs": {
      "singular": "dir",
      "resources": {
        "files": {
          "singular": "file"
          "attributes": {
            "owner": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}

To enable support for a wide range of use cases, but to also ensure interoperability across implementations, the following rules have been defined with respect to how models are defined or updated:

Any specification attributes not included in a request to define, or update, a model MUST be included in the resulting full model. In other words, the full Registry's model consists of the specification-defined attributes overlaid with the attributes that are explicitly-defined as part of a "modelsource" update request.

Note: there is no mechanism defined to delete specification-defined attributes from the model.

Registries MAY support extension attributes to the model language (meaning, new attributes within the model definitions themselves), but only if the server supports them. Servers MUST generate an error (model_error) if a model definition includes unknown model language attributes.

Once a Registry has been created, changes to the model MAY be supported by server implementations. This specification makes no statement as to what types of changes are allowed beyond the following requirements:

Any request to update the model that does not adhere to those requirements MUST generate an error (model_compliance_error).

How the server guarantees that all entities in the Registry are compliant with the model is an implementation detail. For example, while it is NOT RECOMMENDED, it is valid for an implementation to modify (or even delete) existing entities to ensure model compliance. Instead, it is RECOMMENDED that the model update requests generate an error (model_compliance_error) if existing entities are not compliant.

For the purposes of validating that the existing entities in the Registry are compliant with the model, the mechanisms used to define the model (e.g. $include vs ximportresources vs defined locally) MUST NOT impact that analysis. In other words, model updates that have no semantic changes but rather switch between one of those 3 mechanisms MUST NOT invalidate any existing entities in the Registry.

Additionally, is it STRONGLY RECOMMENDED that model updates be limited to backwards compatible changes.

Implementations MAY choose to limit the types of changes made to the model, or not support model updates at all.

The xRegistry schema (model definition) used to create a sample xRegistry can be found here, while the resulting "full" model (with all of the system defined aspects added) can be found here.

Reuse of Resource Definitions 🔗

When a Resource type definition is to be shared between Groups, rather than creating a duplicate Resource definition, the ximportresources mechanism MAY be used instead. The ximportresources attribute on a Group definition allows for a list of references to other Resource types that are to be included within this Group.

For example, the following abbreviated model definition defines one Resource type (messages) under the messagegroups Group, that is also used by the endpoints Group.

"modelsource": {
  "groups": {
    "messagegroups": {
      "plural": "messagegroups",
      "singular": "messagegroup",
      "resources": {
        "messages": {
          "plural": "messages",
          "singular": "message"
        }
      }
    },
    "endpoints": {
      "plural": "endpoints",
      "singular": "endpoint",
      "ximportresources": [ "/messagegroups/messages" ]
    }
  }
}

The format of the ximportresources specification is:

"ximportresources": [ "<XIDTYPE>", * ]

where:

Locally defined Resources MAY be defined within a Group that uses the ximportresources feature, however, Resource plural and singular values MUST be unique across all imported and locally defined Resources.

See Cross Referencing Resources for more additional information.

Includes in the xRegistry Model Data 🔗

There might be times when it is necessary for an xRegistry model to reuse portions of another xRegistry model defined elsewhere. Rather than forcing the duplication of the model definitions, an "include" type of JSON directive MAY be used.

The general formats of the include are:

"$include": "<PATH-TO-DOCUMENT>#<JSON-POINTER-IN-DOC>"

or

"$includes": [ "<PATH-TO-DOCUMENT>#<JSON-POINTER-IN-DOC>" * ]

where the first form specifies a single reference to be included, and the second form specifies multiple. The fragment (#...) portion is OPTIONAL.

For example:

"$include": "http://example.com/xreg-model.json#/groups/mygroup/attributes"

is asking for the attributes of a Group called mygroup to be included at this location of the current model definition.

These directives MAY be used in any JSON Object or Map entity in an xRegistry model definition. The following rules apply for how to process the include directive:

When the directives are used in a request to update the model, the server MUST resolve all includes prior to updating the model. The original (source) model definition, with any "include" directives, MUST be available via the modelsource attribute and the expanded model (after the resolution of any includes, and after all specification defined attribute have been added) MUST be available via the model attribute. The directives MUST only be processed once. In order to have them re-evaluated, a subsequent model update request (with those directive) MUST be sent via the modelsource attribute.

When there is tooling used outside of the server, e.g. in an xRegistry client, if that tooling resolves the "include" directives prior to sending the model to the server, then the directives will not appear in the modelsource view of the the model. Ideally, tooling SHOULD allow users to choose whether the resolution of the directives are done locally or by the server.

Examples:

A model definition that includes xRegistry attributes from a file on a remote server, and adds the definition of one attribute to a Group named mygroups from an external Group named group1 in another xRegistry.

{
  "attributes": {
    "$include": "http://example.com/someattributes",
    "myattribute": {
      "name": "myattribute",
      "type": "string"
    }
  }
  "groups": {
    "mygroups": {
      "plural": "mygroups",
      "singular": "mygroup",
      "attributes": {
        "attr1": {
          "$include": "http://example.com/model#/groups/group1/attributes/attr1"
        }
        ... remainder of model excluded for brevity ...
      }
    }
  }
}

where http://example.com/someattributes might look like:

{
  "myattr": {
    "name": "myattr",
    "type": "string"
  }
}

and the second include target might look like:

{
  "name": "attr1",
  "type": "string"
}

Exporting 🔗

The /export API MUST be an alias for GET /?doc&inline=*,model,capabilities". If supported, it MUST only support the GET` HTTP method. This API was created:

Query parameters MAY be included on the request and any ?inline flag specified MUST override the default value defined above.


Groups APIs 🔗

Groups represent entities that typically act as a collection mechanism for related Resources. However, it is worth noting that Groups do not have to have Resources associated with them. It is possible to have Groups be the main (or only) entity of a Registry. Each Group type MAY have any number of Resource types within it. This specification does not define how the Resources within a Group type are related to each other.

The serialization of a Group entity adheres to this form:

{
  "<GROUP>id": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  # Repeat for each Resource type in the Group
  "<RESOURCES>url": "<URL>",                  # e.g. "messagesurl"
  "<RESOURCES>count": <UINTEGER>,             # e.g. "messagescount"
  "<RESOURCES>": { Resources collection } ?   # If inlined
}

Groups include the following common attributes:

and the following Group level attributes:

<RESOURCES> Collections 🔗

Retrieving a Group Collection 🔗

To retrieve a Group collection, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <URL>;rel=next;count=<UINTEGER> ?

{
  "<KEY>": {                                     # <GROUP>id
    "<GROUP>id": "<STRING>",
    "self": "<URL>",
    "shortself": "<URL>", ?
    "xid": "<XID>",
    "epoch": <UINTEGER>,
    "name": "<STRING>", ?
    "description": "<STRING>", ?
    "documentation": "<URL>", ?
    "icon": "<URL>", ?
    "labels": { "<STRING>": "<STRING>" * }, ?
    "createdat": "<TIMESTAMP>",
    "modifiedat": "<TIMESTAMP>",

    # Repeat for each Resource type in the Group
    "<RESOURCES>url": "<URL>",                  # e.g. "messagesurl"
    "<RESOURCES>count": <UINTEGER>,             # e.g. "messagescount"
    "<RESOURCES>": { Resources collection } ?   # If inlined
  } *
}

Examples:

Retrieve all entities in the endpoints Group:

GET /endpoints
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <https://example.com/endpoints&page=2>;rel=next;count=100

{
  "ep1": {
    "endpointid": "ep1",
    "self": "https://example.com/endpoints/ep1",
    "xid": "/endpoints/ep1",
    "epoch": 1,
    "name": "A cool endpoint",
    "createdat": "2024-04-30T12:00:00Z",
    "modifiedat": "2024-04-30T12:00:01Z",

    "messagesurl": "https://example.com/endpoints/ep1/messages",
    "messagescount": 5
  },
  "ep2": {
    "endpointid": "ep2",
    "self": "https://example.com/endpoints/ep2",
    "xid": "/endpoints/ep2",
    "epoch": 3,
    "name": "Redis Queue",
    "createdat": "2024-04-30T12:00:00Z",
    "modifiedat": "2024-04-30T12:00:01Z",

    "messagesurl": "https://example.com/endpoints/ep2/messages",
    "messagescount": 1
  }
}

Notice that the Link HTTP header is present, indicating that there is a second page of results that can be retrieved via the specified URL, and that there are total of 100 items in this collection.

Creating or Updating Groups 🔗

Creating or updating Groups via HTTP MAY be done by using the HTTP PUT, PATCH or POST methods:

The processing of the above APIs is defined in the Creating or Updating Entities section.

This API is very similar to the POST /<GROUPS> above except that the HTTP body MUST be a map of Group types as shown below:

{
  "endpoints": {
    "endpoint1": { ... Group endpoint1's xRegistry metadata ... },
    "endpoint2": { ... Group endpoint2's xRegistry metadata ... }
  },
  "schemagroups": {
    "schemagroup1": { ... Group schemagroup1's xRegistry metadata ... },
    "schemagroup2": { ... Group schemagroup2's xRegistry metadata ... }
  }
}

Notice the format is almost the same as what a PUT / would look like if the request wanted to update the Registry's attributes and define a set of Groups, but without the Registry's attributes. This allows for an update of the specified Groups without modifying the Registry's attributes.

The response in this case MUST be a map of the Group types with just the Groups that were processed as part of the request.

Each individual Group definition MUST adhere to the following:

{
  "<GROUP>id": "<STRING>", ?
  "self": "<URL>", ?
  "shortself": "<URL>", ?
  "xid": "<XID>", ?
  "epoch": <UINTEGER>, ?
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>", ?
  "modifiedat": "<TIMESTAMP>", ?

  # Repeat for each Resource type in the Group
  "<RESOURCES>url": "<URL>",                     # e.g. "messagesurl"
  "<RESOURCES>count": <UINTEGER>,                # e.g. "messagescount"
  "<RESOURCES>": { Resources collection } ?
}

Each individual Group in a successful response MUST adhere to the following:

{
  "<GROUP>id": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  # Repeat for each Resource type in the Group
  "<RESOURCES>url": "<URL>",                    # e.g. "messagesurl"
  "<RESOURCES>count": <UINTEGER>                # e.g. "messagescount"
}

Examples:

Targeted request to create a specific Group by <GROUP>id:

PUT /endpoints/ep1
Content-Type: application/json; charset=utf-8

{
  "endpointid": "ep1",
  ... remainder of Endpoint 'ep1' definition ...
}
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: https://example.com/endpoints/ep1

{
  "endpointid": "ep1",
  ... remainder of Endpoint 'ep1' definition ...
}

Multiple Groups specified in the HTTP body:

POST /endpoints
Content-Type: application/json; charset=utf-8

{
  "ep1": {
    "endpointid": "ep1",
    ... remainder of ep1 definition ...
  },
  "ep2": {
    "endpointid": "ep2",
    ... remainder of ep2 definition ...
  }
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "ep1": {
    "endpointid": "ep1",
    ... remainder of ep1 definition ...
  },
  "ep2": {
    "endpointid": "ep2",
    ... remainder of ep2 definition ...
  }
}

Retrieving a Group 🔗

To retrieve a Group, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "<GROUP>id": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",

  # Repeat for each Resource type in the Group
  "<RESOURCES>url": "<URL>",                   # e.g. "messagesurl"
  "<RESOURCES>count": <UINTEGER>,              # e.g. "messagescount"
  "<RESOURCES>": { Resources collection } ?    # If inlined
}

Examples:

Retrieve a single endpoints Group:

GET /endpoints/ep1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "<GROUP>id": "ep1",
  "self": "https://example.com/endpoints/ep1",
  "xid": "/endpoints/ep1",
  "epoch": 1,
  "name": "myEndpoint",
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",

  "messagesurl": "https://example.com/endpoints/ep1/messages",
  "messagescount": 5
}

Deleting Groups 🔗

To delete one or more Groups, an HTTP DELETE MAY be used:

The processing of these two APIs is defined in the Deleting Entities in a Registry Collection section.


Resources APIs 🔗

Resources typically represent the main entity that the Registry is managing. Each Resource is associated with a Group to aid in their discovery and to show a relationship with Resources in that same Group. Resources appear within the Group's <RESOURCES> collection.

Resources, like all entities in the Registry, can be modified but Resources can also have a version history associated with them, allowing for users to retrieve previous Versions of the Resource. In this respect, Resources have a 2-layered definition. The first layer is the Resource entity itself, and the second layer is its versions collection - the version history of the Resource.

The Resource entity serves three purposes: 1 - It represents the collection for the historical Versions of the data being managed. This is true even if the Resource type is defined to not use versioning, meaning the number of Versions allowed is just one. The Versions will appear as a nested entity under the versions attribute.
2 - It is an alias for the "default" Version of the Resource. And most operations directed at the URL of the Resource will act upon that Version, not the Resource itself. See Default Version of a Resource and Versions for more details.
3 - It has a set of attributes for Resource level metadata - data that is not specific to one Version of the Resource but instead applies to the Resource in general. These attributes appear under a meta attribute/sub-object so as to keep them separate from any Version level attributes. Note that these attributes do not appear on the Versions.

The URL of a Resource can be thought of as an alias for the "default" Version of the Resource, and as such, most of the attributes shown when processing the Resource will be mapped from the "default" Version.

However, there a few exceptions:

The remainder of this section discusses the processing rules for Resources and Versions. While it mainly uses the term "Resource" for ease of reading, in most cases it can be assumed that the same applies for "Versions". When this is not the case, it will be explicitly called out.

Resource Metadata vs Resource Document 🔗

Unlike Groups, which consist entirely of xRegistry managed metadata, Resources typically have their own domain-specific data and document format that needs to be kept distinct from the xRegistry Resource metadata. As discussed previously, the model definition for Resource has a hasdocument attribute indicating whether a Resource type defines its own separate document or not.

This specification does not define any requirements for the contents of this separate document, and it doesn't even need to be stored within the Registry. The Resource MAY choose to simply store a URL reference to the externally managed document instead. When the document is stored within the Registry, it can be managed as an opaque array of bytes.

When a Resource does have a separate document, HTTP interactions to the URL for the Resource MUST include this document in the HTTP body as it is typically the data of interest for end users. As a convenance, the simple (mainly scalar) xRegistry metadata of the Resource will appear as HTTP headers.

To change this view such that the xRegistry metadata becomes the data of interest, the request URLs MUST have $details appended to them. In these cases, the HTTP body of the requests and responses MUST have a JSON serialization of the entity's xRegistry metadata, and the separate document MAY appear as an attribute within that metadata based on the specific operation being done.

For example:

GET https://example.com/schemagroups/mygroup/schemas/myschema

will retrieve the schema document associated with the myschema Resource, while:

GET https://example.com/schemagroups/mygroup/schemas/myschema$details

will retrieve the xRegistry metadata information for the myschema Resource.

When the Resource's path is appended with $details, the Resource's document becomes available via a set of <RESOURCE>* attributes within that metadata:

When accessing a Resource's metadata with $details, often it is to view or update the xRegistry metadata and not the document, as such, including the potentially large amount of data from the Resource's document in request and response messages could be cumbersome. To address this, the <RESOURCE> and <RESOURCE>base64 attributes do not appear by default as part of the serialization of the Resource. Rather, they MUST only appear in responses when the ?inline=<RESOURCE> query parameter is used. Likewise, in requests, these attributes are OPTIONAL and would only need to be used when a change to the document's content is needed at the same time as updates to the Resource's metadata. However, the <RESOURCE>url attribute MUST always appear if it has a value.

Note that the serialization of a Resource MUST only use at most one of these 3 attributes at a time.

Resource Attributes 🔗

Resource attributes are non-versioned attributes associated with a Resource. In a sense they can be considered to be global to the Resource and its Versions, but they are not part of, or serialized in, any Version. Instead, they are serialized in two different ways:

1 - some will appear within a meta attribute/sub-object to the Resource. This keeps them separate from the default Version attributes that might appear. However, the meta attribute itself will appear as a sibling to the default Version attributes. Note that meta will only be serialized when requested by the client.

2 - some will appear as siblings to the default Version attributes within the Resource serialization. These appear here, rather than under meta, because they are specifically designed to help with the traversal of the Resource's hierarchy and putting them "one level down" would reduce their usefulness.

When the Resource is serialized as a JSON object, the serialization of the Resource attribute MUST adhere to the following:

{
  "<RESOURCE>id": "<STRING>",
  "versionid": "<STRING>",
  "self": "<URL>",                           # URL to Resource, not Version
  "shortself": "<URL>", ?
  "xid": "<XID>",                            # Relative URI to Resource
  # Default Version attributes appear here

  "metaurl": "<URL>",
  "meta": {                                  # Only if inlined
    "<RESOURCE>id": "<STRING>",
    "self": "<URL>",                         # Absolute Meta URL, not Version
    "shortself": "<URL>", ?
    "xid": "<XID>",                          # Relative Meta URI, not Version
    "xref": "<XID>", ?                       # Ptr to linked Resource
    "epoch": <UINTEGER>,                     # Resource's epoch
    "createdat": "<TIMESTAMP>",              # Resource's
    "modifiedat": "<TIMESTAMP>",             # Resource's
    "readonly": <BOOLEAN>,                   # Default=false
    "compatibility": "<STRING>",             # Default=none
    "compatibilityauthority": "<STRING>", ?  # Default=external
    "deprecated": { ... }, ?

    "defaultversionid": "<STRING>",
    "defaultversionurl": "<URL>",
    "defaultversionsticky": <BOOLEAN>        # Default=false
  }, ?
  "versionsurl": "<URL>",
  "versionscount": <UINTEGER>,
  "versions": { map of Versions }            # Only if inlined
}

Note that the meta and versions attributes MUST only appear when requested by the client - for example, via the ?inline flag.

When the Resource is serialized with its domain-specific document in the HTTP body, then Resource level attributes SHOULD appear as HTTP headers and adhere to the following:

xRegistry-<RESOURCE>id: <STRING>
xRegistry-versionid: <STRING>
xRegistry-self: <URL>
xRegistry-xid: <XID>
# Default Version attributes, and other HTTP headers, appear here
xRegistry-metaurl: <URL>
xRegistry-versionsurl: <URL>
xRegistry-versionscount: <UINTEGER>

Notice the meta and versions attributes are not included since they are not complex data types.

The Resource level attributes include the following common attributes:

and the following Resource level attributes:

xref Attribute 🔗
readonly Attribute 🔗
compatibility Attribute 🔗

compatibilityauthority Attribute 🔗

deprecated 🔗

defaultversionid Attribute 🔗
defaultversionurl Attribute 🔗
defaultversionsticky Attribute 🔗
metaurl Attribute 🔗
meta Attribute/Sub-Object 🔗

Note: doing a PUT to a Resource, or a POST to an xRegistry Collection, as a mechanism to update the meta sub-object MUST include the Resource default Version attributes in the request. When not specified, the server will interpret it as a request to delete the default Version attributes. If possible, an update request to the metaurl directly would be a better choice, or use PATCH instead and only include the meta sub-object.

During a write operation, the absence of the meta attribute indicates that no changes are to be made to the meta sub-object.

versions Collection 🔗

Serializing Resources 🔗

Serializing Resources requires some special processing due to Resources not representing just a single set of data. In particular, the following aspects need to be taken into account:

To address these aspects, the serialization of a Resource will vary based on whether it is defined to have a domain-specific document and whether the client wishes to focus on managing its xRegistry metadata or that secondary document.

As discussed above, there are two ways to serialize a Resource in an HTTP message's body:

Which variant is used is controlled by the use of $details on the URL path. The following sections go into more details about these two serialization options.

Serializing Resource Documents 🔗

When a Resource is serialized as its underlying domain-specific document, in other words $details is not appended to its URL path, the HTTP body of requests and responses MUST be the exact bytes of the document. If the document is empty, or there is no document, then the HTTP body MUST be empty (zero length).

In this serialization mode, it might be useful for clients to have access to Resource's xRegistry metadata. To support this, some of the Resource's xRegistry metadata will appear as HTTP headers in response messages.

On responses, unless otherwise stated, all top-level scalar attributes of the Resource SHOULD appear as HTTP headers where the header name is the name of the attribute prefixed with xRegistry-. Note, the optionality of this requirement is not to allow for servers to decide whether or not to do so, rather it is to allow for No-Code Servers servers than might not be able to control the HTTP response headers.

Certain attributes do not follow this rule if a standard HTTP header name is to be used instead (e.g. contenttype MUST use Content-Type, not xRegistry-contenttype). Each attribute that falls into this category will be identified as part of its definition.

Top-level map attributes whose values are of scalar types MUST also appear as HTTP headers (each key having its own HTTP header) and in those cases the HTTP header names will be of the form: xRegistry-<ATTRIBUTENAME>-<KEYNAME>. Note that map keys MAY contain the - character, so any - after the 2nd - is part of the key name. See HTTP Header Values for additional information and labels for an example of one such attribute.

Complex top-level attributes (e.g. arrays, objects, non-scalar maps) MUST NOT appear as HTTP headers.

On update requests, similar serialization rules apply. However, rather than these headers being REQUIRED, the client would only need to include those top-level attributes that they would like to change. But, including unchanged attributes MAY be done. Any attributes not included in request messages MUST be interpreted as a request to leave their values unchanged. Using a value of null (case-sensitive) indicates a request to delete that attribute.

Any top-level map attributes that appear as HTTP headers MUST be included in their entirety and any missing keys MUST be interpreted as a request to delete those keys from the map.

Since only some types of attributes can appear as HTTP headers, $details MUST be used to manage the others. See the next section for more details.

When a Resource (not a Version) is serialized with the Resource document in the HTTP body, it MUST adhere to this form:

Content-Type: <STRING> ?
xRegistry-<RESOURCE>id: <STRING>           # ID of Resource, not default Version
xRegistry-versionid: <STRING>              # ID of the default Version
xRegistry-self: <URL>                      # Resource URL, not default Version
xRegistry-xid: <URI>                       # Relative Resource URI
xRegistry-epoch: <UINTEGER>                # Start default Version's attributes
xRegistry-name: <STRING> ?
xRegistry-isdefault: true
xRegistry-description: <STRING> ?
xRegistry-documentation: <URL> ?
xRegistry-icon: <URL> ?
xRegistry-labels-<KEY>: <STRING> *
xRegistry-createdat: <TIMESTAMP>
xRegistry-modifiedat: <TIMESTAMP>
xRegistry-ancestor: <STRING>
xRegistry-<RESOURCE>url: <URL> ?           # End of default Version attributes
xRegistry-metaurl: <URL>                   # Resource level attributes
xRegistry-versionsurl: <URL>
xRegistry-versionscount: <UINTEGER>
Location: <URL>
Content-Location: <URL> ?
Content-Disposition: <STRING> ?

... Resource document ... ?

Where:

Version serialization will look similar, but the set of xRegistry HTTP headers will be slightly different (to exclude Resource level attributes). See the next sections for more information.

Scalar default Version extension attributes MUST also appear as xRegistry- HTTP headers.

Serializing Resource Metadata 🔗

Appending $details to a Resource or Version's URL path modifies the serialization of the entity such that rather than the HTTP body containing the entity's domain-specific "document" and the xRegistry metadata being in HTTP headers, all of them are instead within the HTTP body as one JSON object. If the entity's "document" is included within the object then it appears under a <RESOURCE>* attribute (as discussed above).

The advantage of this format is that the HTTP body will contain all of the xRegistry metadata and not just the scalar values - as is the case when they appear as HTTP headers. This allows for management of all metadata as well as any possible domain-specific document at one time.

Note that in the case of a reference to a Resource (not a Version), the metadata will be from the default Version, plus the extra meta and versions related attributes.

When serialized as a JSON object, a Resource (not a Version) MUST adhere to this form:

{
  "<RESOURCE>id": "<STRING>",              # ID of Resource, not default Version
  "versionid": "<STRING>",                 # ID of default Version
  "self": "<URL>",                         # URL of Resource,not default Version
  "shortself": "<URL>", ?
  "xid": "<XID>",                          # Relative URI of Resource
  # These are inherited from the default Version
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "isdefault": true,
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",
  "ancestor": "<STRING>",
  "contenttype": "<STRING>", ?

  "<RESOURCE>url": "<URL>", ?                # If not local
  "<RESOURCE>": ... Resource document ..., ? # If inlined & JSON
  "<RESOURCE>base64": "<STRING>", ?          # If inlined & ~JSON

  # Resource level helper attributes
  "metaurl": "<URL>",
  "meta": {                                  # If inlined
    "<RESOURCE>id": "<STRING>",
    "self": "<URL>",                         # URL to "meta"
    "shortself": "<URL>", ?
    "xid": "<XID>",                          # Relative URI to "meta"
    "xref": "<XID>", ?                       # Ptr to linked Resource
    "epoch": <UINTEGER>,                     # Resource's epoch
    "createdat": "<TIMESTAMP>",              # Resource's
    "modifiedat": "<TIMESTAMP>",             # Resource's
    "readonly": <BOOLEAN>,                   # Default=false
    "compatibility": "<STRING>",             # Default=none
    "compatibilityauthority": "<STRING>", ?  # Default=external
    "deprecated": { ... },

    "defaultversionid": "<STRING>",
    "defaultversionurl": "<URL>",
    "defaultversionsticky": <BOOLEAN>        # Default=false
  }, ?
  "versionsurl": "<URL>",
  "versionscount": <UINTEGER>,
  "versions": { Versions collection } ?      # If inlined
}

The serialization of a Version will look similar except the meta and versions related Resource level attributes MUST NOT be present. More on this in the next sections.

Cross Referencing Resources 🔗

Typically, Resources exist within the scope of a single Group, however there might be situations where a Resource needs to be related to multiple Groups. In these cases, there are two options. First, a copy of the Resource could be made into the second Group. The obvious downside to this is that there's no relationship between the two Resources and any changes to one would need to be done in the other - running the risk of them getting out of sync.

The second, and better, option is to create a cross-reference from one (the "source" Resource) to the other ("target" Resource). This is done by setting the xref attribute on the source Resource to be the xid of the target Resource.

The xref attribute is defined in the model as:

"xref": {
  "name": "xref",
  "type": "xid",
  "target": "/<GROUPS>/<RESOURCES>"
}

where /<GROUPS>/<RESOURCES> will be the actual Group and Resource plural names of this Resource.

Looking at a specific example, a Group/Resource model definition of:

{
  "groups" : {
    "schemagroups": {
      "plural": "schemagroups",
      "singular": "schemagroup",

      "resources": {
        "plural": "schemas",
        "singular": "schema",
        "attributes": {
          "xref": {
            "name": "xref",
            "type": "xid",
            "target": "/schemagroups/schemas"
          }
        }
      }
    }
  }
}

Means that schemas can be cross-referenced to other /schemagroups/schema Resource. Notice that target is a xid template to itself.

For example: a schema Resource instance defined as (HTTP body of PUT /schemagroups/group1/schemas/mySchema$details):

{
  "schemaid": "mySchema",
  "meta": {
    "xref": "/schemagroups/group2/schemas/sharedSchema"
  }
}

means that mySchema references sharedSchema, which exists in group2. When this source Resource (mySchema) is retrieved, all of the target Resource's attributes (except its <RESOURCE>id) will appear as if they were locally defined.

So, if the target Resource (sharedSchema) is defined as:

{
  "resourceid": "sharedSchema",
  "versionid": "v1",
  "self": "http://example.com/schemagroups/group2/schemas/sharedSchema",
  "xid": "/schemagroups/group2/schemas/sharedSchema",
  "epoch": 2,
  "isdefault": true,
  "createdat": "2024-01-01-T12:00:00Z",
  "modifiedat": "2024-01-01-T12:01:00Z",
  "ancestor": "v1",

  "metaurl": "http://example.com/schemagroups/group2/schemas/sharedSchema/meta",
  "versionscount": 1,
  "versionsurl": "http://example.com/schemagroups/group2/schemas/sharedSchema/versions"
}

then the resulting serialization of the source Resource would be:

{
  "resourceid": "mySchema",
  "versionid": "v1",
  "self": "http://example.com/schemagroups/group1/schemas/mySchema",
  "xid": "/schemagroups/group1/schemas/mySchema",
  "epoch": 2,
  "isdefault": true,
  "createdat": "2024-01-01-T12:00:00Z",
  "modifiedat": "2024-01-01-T12:01:00Z",
  "ancestor": "v1",

  "metaurl": "http://example.com/schemagroups/group1/schemas/mySchema/meta",
  "meta": {
    "resourceid": "mySchema",
    "self": "http://example.com/schemagroups/group1/schemas/mySchema/meta",
    "xid": "/schemagroups/group1/schemas/mySchema/meta",
    "xref": "/schemagroups/group2/schemas/sharedSchema",
    "createdat": "2024-01-01-T12:00:00Z",
    "modifiedat": "2024-01-01-T12:01:00Z",
    "readonly": false,
    "compatibility": "none"
  },
  "versionscount": 1,
  "versionsurl": "http://example.com/schemagroups/group1/schemas/mySchema/versions"
}

Note:

From a consumption (read) perspective, aside from the presence of the xref attribute, the Resource appears to be a normal Resource that exists within group1. All of the specification-defined features (e.g. ?inline, ?filter) MAY be used when retrieving the Resource.

However, from a write perspective it is quite different. In order to update the target Resource's attributes (or nested entities), a write operation MUST be done on the appropriate target Resource entity directly. Write operations on the source MAY be done, however, the changes are limited to converting it from a "cross-reference" Resource back into a "normal" Resource. See the following for more information:

When converting a "normal" Resource into a cross-reference Resource (adding an xref value), or creating a new Resource that will be a cross-reference Resource, the following MUST be adhered to:

When converting a cross-reference Resource back into a "normal" Resource, the following MUST be adhered to:

If the target Resource itself is a cross-reference Resource, then including the target Resource's attributes MUST NOT be done when serializing the source Resource. Recursive, or transitively, following of xref XIDs is not done.

Both the source and target Resources MUST be of the same Resource model type, simply having similar Resource type definitions is not sufficient. This implies that use of the ximportresources feature in the model to reference a Resource type from another Group type definition MUST be used. See ximportresources for more information.

An xref value that points to a non-existing Resource, either because it was deleted or never existed, is not an error and is not a condition that a server is REQUIRED to detect. In these "dangling xref" situations, the serialization of the source Resource will not include any target Resource attributes or nested collections. Rather, it will only show the <RESOURCE>id and xref attributes.


Resource and Version APIs 🔗

For convenience, the Resource and Version create, update and delete APIs can be summarized as:

POST /<GROUPS>/<GID>

POST /<GROUPS>/<GID>/<RESOURCES>
PATCH /<GROUPS>/<GID>/<RESOURCES>

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]

POST /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>/meta
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/meta

POST /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>[$details]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>[$details]

And the delete APIs are summarized as:

DELETE /<GROUPS>/<GID>/<RESOURCES>

DELETE /<GROUPS>/<GID>/<RESOURCES>/<RID>

DELETE /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions

DELETE /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>

The following sections go into more detail about each API.


Retrieving a Resource Collection 🔗

To retrieve all Resources in a Resource Collection, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <URL>;rel=next;count=<UINTEGER> ?

{
  "<KEY>": {                                    # The Resource ID
    "<RESOURCE>id": "<STRING>",                 # The Resource ID
    "versionid": "<STRING>",                    # Default Version ID
    "self": "<URL>",                            # URL to the Resource
    "shortself": "<URL>", ?
    "xid": "<XID>",                             # Relative URI to the Resource
    "epoch": <UINTEGER>,                        # Start of Default Ver attribs
    "name": "<STRING>", ?
    "isdefault": true,
    "description": "<STRING>", ?
    "documentation": "<URL>", ?
    "icon": "<URL>", ?
    "labels": { "<STRING>": "<STRING>" * }, ?
    "createdat": "<TIMESTAMP>",
    "modifiedat": "<TIMESTAMP>",
    "ancestor": "<STRING>",
    "contenttype": "<STRING>", ?

    "<RESOURCE>url": "<URL>", ?                 # If not local
    "<RESOURCE>": ... Resource document ..., ?  # If inlined & JSON
    "<RESOURCE>base64": "<STRING>", ?           # If inlined & ~JSON

    # Resource level helper attributes
    "metaurl": "<URL>",
    "meta": {                                   # If inlined
      "<RESOURCE>id": "<STRING>",               # Resource ID
      "self": "<URL>",                          # URL to "meta"
      "shortself": "<URL>", ?
      "xid": "<XID>",                           # Relative URI to "meta"
      "xref": "<XID>", ?                        # Ptr to linked Resource
      "epoch": <UINTEGER>,                      # Resource's epoch
      "createdat": "<TIMESTAMP>",               # Resource's
      "modifiedat": "<TIMESTAMP>",              # Resource's
      "readonly": <BOOLEAN>,                    # Default=false
      "compatibility": "<STRING>",              # Default=none
      "compatibilityauthority": "<STRING>", ?   # Default=external
      "deprecated": { ... }, ?

      "defaultversionid": "<STRING>",
      "defaultversionurl": "<URL>",
      "defaultversionsticky": <BOOLEAN>
    }
    "versionsurl": "<URL>",
    "versionscount": <UINTEGER>,
    "versions": { Versions collection } ?       # If inlined
  } *
}

Where:

Examples:

Retrieve all messages of an endpoint whose <RESOURCE>id is ep1:

GET /endpoints/ep1/messages
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <https://example.com/endpoints/ep1/messages&page=2>;rel=next;count=100

{
  "msg1": {
    "messageid": "msg1",
    "versionid": "1.0",
    "self": "https://example.com/endpoints/ep1/messages/msg1",
    "xid": "/endpoints/ep1/messages/msg1",
    "epoch": 1,
    "name": "Blob Created",
    "isdefault": true,
    "createdat": "2024-04-30T12:00:00Z",
    "modifiedat": "2024-04-30T12:00:01Z",
    "ancestor": "1.0",

    "metaurl": "https://example.com/endpoints/ep1/messages/msg1/meta",
    "versionsurl": "https://example.com/endpoints/ep1/messages/msg1/versions",
    "versionscount": 1
  }
}

Creating or Updating Resources and Versions 🔗

These APIs follow the overall pattern described in the Creating or Updating Entities section. Any variations will be called out.

Creating and updating of Resources via HTTP MAY be done using the HTTP POST, PUT or PATCH methods as described below:

POST /<GROUPS>/<GID>

Where:

For example:

{
  "schemas": {
    "schema1": { ... Resource schema1's xRegistry metadata ... },
    "schema2": { ... Resource schema1's xRegistry metadata ... }
  },
  "messages": {
    "message1": { ... Resource message1's xRegistry metadata ... },
    "message2": { ... Resource message2's xRegistry metadata ... }
  }
}

Notice the format is almost the same as what a PUT /<GROUPS>/<GID> would look like if the request wanted to update the Group's attributes and define a set of Resources, but without the Group's attributes. This allows for an update of the specified Resources without modifying the Group's attributes.

The response in this case MUST be a map of the Resource types with just the Resources that were processed as part of the request.

POST /<GROUPS>/<GID>/<RESOURCES>
PATCH /<GROUPS>/<GID>/<RESOURCES>

Where:

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]

Where:

POST /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details][?setdefaultversionid=<VID>]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details][?setdefaultversionid=<VID>]

Where:

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>/meta
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/meta Where:

POST /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions[?setdefaultversionid=<VID>]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions[?setdefaultversionid=<VID>]

Where:

See Default Version of a Resource for more information about the ?setdefaultversionid query parameter.

PUT /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>[$details][?setdefaultversionid=<VID>]
PATCH /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>[$details][?setdefaultversionid=<VID>]

Where:

See Default Version of a Resource for more information about the ?setdefaultversionid query parameter.


To reduce the number of interactions needed, these APIs are designed to allow for the implicit creation of all parent entities specified in the . And each entity not already present with the specified <SINGULAR>id MUST be created with that value. Note: if any of those entities have REQUIRED attributes, then they cannot be implicitly created, and would need to be created directly.

When specified as an xRegistry JSON object, each individual Resource or Version in the request MUST adhere to the following:

{
  "<RESOURCE>id": "<STRING>", ?
  "versionid": "<STRING>", ?
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?                      # Version-level attributes
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>", ?
  "modifiedat": "<TIMESTAMP>", ?
  "ancestor": "<STRING>",
  "contenttype": "<STRING>", ?

  "<RESOURCE>url": "<URL>", ?                  # If not local
  "<RESOURCE>": ... Resource document ..., ? # If inlined & JSON
  "<RESOURCE>base64": "<STRING>", ?            # If inlined & ~JSON

  "meta": {                                # Resource-only attributes
    "<RESOURCE>id": "<STRING>", ?
    "xref": "<XID>", ?
    "epoch": <UINTEGER>, ?
    "createdat": "<TIMESTAMP>", ?
    "modifiedat": "<TIMESTAMP>", ?
    "compatibility": "<STRING>", ?
    "compatibilityauthority": "<STRING>", ?
    "deprecated": { ... }, ?

    "defaultversionid": "<STRING>",
    "defaultversionsticky": <BOOLEAN>
  }, ?
  "versions": { Versions collection } ?
}

When the HTTP body contains the Resource's (or Version's) document, then any xRegistry scalar metadata MUST appear as HTTP headers and the request MUST adhere to the following:

<METHOD> <PATH>
Content-Type: <STRING> ?
xRegistry-<RESOURCE>id: <STRING> ?
xRegistry-versionid: <STRING> ?
xRegistry-epoch: <UINTEGER> ?
xRegistry-name: <STRING> ?
xRegistry-description: <STRING> ?
xRegistry-documentation: <URL> ?
xRegistry-icon: <URL> ?
xRegistry-labels-<KEY>: <STRING> *
xRegistry-createdat: <TIMESTAMP> ?
xRegistry-modifiedat: <TIMESTAMP> ?
xRegistry-ancestor: <STRING> ?
xRegistry-<RESOURCE>url: <URL> ?

... entity document ... ?

Where:

A successful response MUST include the current representation of the entities created or updated and be in the same format ($details variant or not) as the request.

If the request used the PUT or PATCH variants directed at a single entity, and a new Version was created, then a successful response MUST include a Content-Location HTTP header to the newly created Version entity, and if present, it MUST be the same as the Version's self attribute.

Note that the response MUST NOT include any inlinable attributes (such as <RESOURCE>, <RESOURCE>base64 or nested objects/collections).

Examples:

Create a new Resource:

PUT /endpoints/ep1/messages/msg1
Content-Type: application/json; charset=utf-8
xRegistry-name: Blob Created

{
  # Definition of a "Blob Created" event (document) excluded for brevity
}
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
xRegistry-messageid: msg1
xRegistry-versionid: 1.0
xRegistry-self: https://example.com/endpoints/ep1/messages/msg1
xRegistry-xid: /endpoints/ep1/messages/msg1
xRegistry-epoch: 1
xRegistry-name: Blob Created
xRegistry-isdefault: true
xRegistry-metaurl: https://example.com/endpoints/ep1/messages/msg1/meta
xRegistry-versionsurl: https://example.com/endpoints/ep1/messages/msg1/versions
xRegistry-versionscount: 1
Location: https://example.com/endpoints/ep1/messages/msg1
Content-Location: https://example.com/endpoints/ep1/messages/msg1/versions/1.0
Content-Disposition: msg1

{
  # Definition of a "Blob Created" event (document) excluded for brevity
}

Updates the default Version of a Resource as xRegistry metadata:

PUT /endpoints/ep1/messages/msg1$details
Content-Type: application/json; charset=utf-8

{
  "epoch": 1,
  "name": "Blob Created",
  "description": "a cool event",

  "message": {
    # Updated definition of a "Blob Created" event excluded for brevity
  }
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Location: https://example.com/endpoints/ep1/messages/msg1/versions/1.0

{
  "messageid": "msg1",
  "versionid": "1.0",
  "self": "https://example.com/endpoints/ep1/messages/msg1",
  "xid": "/endpoints/ep1/messages/msg1",
  "epoch": 2,
  "name": "Blob Created",
  "isdefault": true,
  "description": "a cool event",
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",
  "ancestor": "1.0",

  "message": {
    # Updated definition of a "Blob Created" event excluded for brevity
  },

  "metaurl": "https://example.com/endpoints/ep1/messages/msg1/meta",
  "versionsurl": "https://example.com/endpoints/ep1/messages/msg1/versions",
  "versionscount": 1
}

Update several Versions (adding a label):

PATCH /endpoints/ep1/messages/msg1/versions
Content-Type: application/json; charset=utf-8

{
  "1.0": {
    "labels": { "customer": "abc" },
  },
  "2.0": {
    "labels": { "customer": "abc" },
  },
  "3.0": {
    "labels": { "customer": "abc" },
  }
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "1.0": {
    "messageid": "msg1",
    "versionid": "1.0",
    "labels": { "customer": "abc" },
    # Remainder of xRegistry metadata excluded for brevity
  },
  "2.0": {
    "messageid": "msg1",
    "versionid": "2.0",
    "labels": { "customer": "abc" },
    # Remainder of xRegistry metadata excluded for brevity
  },
  "3.0": {
    "messageid": "msg1",
    "versionid": "3.0",
    "labels": { "customer": "abc" },
    # Remainder of xRegistry metadata excluded for brevity
  }
]

Note that in this case, the new "label" replaces all existing labels, it is not a "merge" operation because all attributes need to be specified in their entirety.

Retrieving a Resource 🔗

To retrieve a Resource, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>/<RID>

This MUST retrieve the default Version of a Resource. Note that <RID> will be the <SINGULAR>id of the Resource and not the versionid of the underlying Version (see Resources API).

A successful response MUST either be:

In both cases the Resource's default Version attributes, along with the meta and versions related scalar attributes, MUST be serialized as HTTP xRegistry- headers when the Resource's hasdocument model attribute has a value of true.

Note that if the Resource's hasdocument model attribute has a value of false then the "Resource document" will be the xRegistry metadata for the default Version - same as in the Retrieving a Resource as Metadata section but without the explicit usage of $details.

When hasdocument is true, the response MUST be of the form:

HTTP/1.1 200 OK|303 See Other
Content-Type: <STRING> ?
xRegistry-<RESOURCE>id: <STRING>
xRegistry-versionid: <STRING>
xRegistry-self: <URL>
xRegistry-xid: <XID>
xRegistry-epoch: <UINTEGER>
xRegistry-name: <STRING> ?
xRegistry-description: <STRING> ?
xRegistry-documentation: <URL> ?
xRegistry-icon: <URL> ?
xRegistry-labels-<KEY>: <STRING> *
xRegistry-createdat: <TIMESTAMP>
xRegistry-modifiedat: <TIMESTAMP>
xRegistry-ancestor: <STRING>
xRegistry-<RESOURCE>url: <URL> ?  # If Resource is not in body
xRegistry-metaurl: <URL>
xRegistry-versionsurl: <URL>
xRegistry-versionscount: <UINTEGER>
Location: <URL> ?                 # If Resource is not in body
Content-Location: <URL> ?
Content-Disposition: <STRING>

... Resource document ...         # If <RESOURCE>url is not set

Where:

Retrieving a Resource as Metadata 🔗

When a Resource has the hasdocument model attribute set to true, to retrieve a Resource's metadata (Resource attributes) as a JSON object, an HTTP GET with $details appended to its URL path MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>/<RID>[$details]

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Location: <URL> ?

{
  "<RESOURCE>id": "<STRING>",
  "versionid": "<STRING>",
  "self": "<URL>",                           # URL to Resource, not default Ver
  "shortself": "<URL>", ?
  "xid": "<XID>",                            # Relative URI to Resource
  "epoch": <UINTEGER>,                       # Start of Default Ver attribs
  "name": "<STRING>", ?
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",
  "ancestor": "<STRING>",
  "contenttype": "<STRING>", ?

  "<RESOURCE>url": "<URL>", ?                # If not local
  "<RESOURCE>": ... Resource document ..., ? # If inlined & JSON
  "<RESOURCE>base64": "<STRING>", ?          # If inlined & ~JSON

  "metaurl": "<URL>",
  "meta": {
    "<RESOURCE>id": "<STRING>", ?
    "self": "<URL>",                         # URL to "meta" sub-object
    "shortself": "<URL>", ?
    "xid": "<XID>",                          # XID of "meta" sub-object
    "xref": "<XID>", ?
    "epoch": <UINTEGER>,
    "createdat": "<TIMESTAMP>",
    "modifiedat": "<TIMESTAMP>",
    "readonly": <BOOLEAN>,
    "compatibility": "<STRING>",
    "compatibilityauthority": "<STRING>", ?
    "deprecated": { ... }, ?

    "defaultversionid": "<STRING>",
    "defaultversionurl": "<URL>",
    "defaultversionsticky": <BOOLEAN>
  },
  "versionsurl": "<URL>",
  "versionscount": <UINTEGER>,
  "versions": { Versions collection } ?      # If inlined
}

Where:

Examples:

Retrieve a message Resource as xRegistry metadata:

GET /endpoints/ep1/messages/msg1$details
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Location: https://example.com/endpoints/ep1/messages/msg1/versions/1.0

{
  "messageid": "msg1",
  "versionid": "1.0",
  "self": "https://example.com/endpoints/ep1/messages/msg1","
  "xid": "/endpoints/ep1/messages/msg1",
  "epoch": 1,
  "name": "Blob Created",
  "isdefault": true,
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",
  "ancestor": "1.0",

  "metaurl": "https://example.com/endpoints/ep1/messages/msg1/meta",
  "versionsurl": "https://example.com/endpoints/ep1/messages/msg1/versions",
  "versionscount": 1
}

Deleting Resources 🔗

To delete one or more Resources, and all of their Versions, an HTTP DELETE MAY be used:

The processing of these two APIs is defined in the Deleting Entities in a Registry Collection section.

Deleting a Resource MUST delete all Versions within the Resource.


Versions APIs 🔗

Versions represent historical instances of a Resource. When a Resource is updated, there are two actions that might take place. First, the update can completely replace an existing Version of the Resource. This is most typically done when the previous state of the Resource is no longer needed, and there is no reason to allow people to reference it. The second situation is when both the old and new Versions of a Resource are meaningful and both might need to be referenced. In this case, the update will cause a new Version of the Resource to be created and will have a unique versionid within the scope of the owning Resource.

For example, updating the data of Resource without creating a new Version would make sense if there is a typo in the description field. But, adding additional data to the document of a Resource might require a new Version and a new versionid (e.g. changing it from "1.0" to "1.1").

This specification does not mandate a particular versioning algorithm or Version identification (versionid) scheme.

When serialized as a JSON object, the Version entity adheres to this form:

{
  "<RESOURCE>id": "<STRING>",                  # <SINGULAR>id of Resource
  "versionid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "isdefault": <BOOLEAN>,
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",
  "ancestor": "<STRING>",
  "contenttype": "<STRING>", ?

  "<RESOURCE>url": "<URL>", ?                  # If not local
  "<RESOURCE>": ... Resource document ..., ?   # If inlined & JSON
  "<RESOURCE>base64": "<STRING>" ?             # If inlined & ~JSON
}

Version extension attributes would also appear as additional top-level JSON attributes.

Versions include the following common attributes:

and the following Version level attributes:

as defined below:

versionid Attribute 🔗
isdefault Attribute 🔗
ancestor Attribute 🔗
contenttype Attribute 🔗
<RESOURCE>url Attribute 🔗
<RESOURCE> Attribute 🔗
<RESOURCE>base64 Attribute 🔗

Version IDs 🔗

If a server does not support client-side specification of the versionid of a new Version (see the setversionid attribute in the Registry Model), or if a client chooses to not specify the versionid, then the server MUST assign new Version an versionid that is unique within the scope of its owning Resource.

Servers MAY have their own algorithm for the creation of new Version versionid values, but the default algorithm is as follows:

With this default versioning algorithm, when semantic versioning is needed, it is RECOMMENDED to include a major version identifier in the Resource <RESOURCE>id, like "com.example.event.v1" or "com.example.event.2024-02", so that incompatible, but historically related Resources can be more easily identified by users and developers. The Version's versionid then functions as the semantic minor version identifier.

Default Version of a Resource 🔗

As Versions of a Resource are added or removed, there needs to be a mechanism by which the "default" one is determined. There are two options for how this might be done:

  1. Newest = Default. The newest Version (based on the Resource's versionmode algorithm) MUST be the "default" Version. This is the default choice.

  2. Client explicitly chooses the "default". In this option, a client has explicitly chosen which Version is the "default" and it will not change until a client chooses another Version, or that Version is deleted (in which case the server MUST revert back to option 1 (newest = default), if the client did not use ?setdefaultversionid to choose the next "default" Version - see below). This is referred to as the default Version being "sticky" as it will not change until explicitly requested by a client.

If supported (as determined by the setdefaultversionsticky model aspect), a client MAY choose the "default" Version two ways:

  1. Via the Resource defaultversionsticky and defaultversionid attributes in its meta sub-object. See Resource Attributes for more information about these attributes.
  2. Via the ?setdefaultversionid query parameter that is available on certain APIs, as defined below.

The ?setdefaultversionid query parameter is defined as:

...?setdefaultversionid=<VID>

Where:

Any use of this query parameter on a Resource that has the setdefaultversionsticky aspect set to false MUST generate an error (bad_flag).

Updating a Resource's defaultversionid, regardless of the mechanism used to do so, MUST adhere to the following rules:

Retrieving all Versions 🔗

To retrieve all Versions of a Resource, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <URL>;rel=next;count=<UINTEGER> ?

{
  "<KEY>": {                                    # The versionid
    "<RESOURCE>id": "<STRING>",                 # ID of Resource
    "versionid": "<STRING>",
    "self": "<URL>",
    "shortself": "<URL>", ?
    "xid": "<XID>",
    "epoch": <UINTEGER>,
    "name": "<STRING>", ?
    "isdefault": <BOOLEAN>,
    "description": "<STRING>", ?
    "documentation": "<URL>", ?
    "icon": "<URL>", ?
    "labels": { "<STRING>": "<STRING>" * }, ?
    "createdat": "<TIMESTAMP>",
    "modifiedat": "<TIMESTAMP>",
    "ancestor": "<STRING>",
    "contenttype": "<STRING>", ?

    "<RESOURCE>url": "<URL>", ?                 # If not local
    "<RESOURCE>": ... Resource document ..., ?  # If inlined & JSON
    "<RESOURCE>base64": "<STRING>" ?            # If inlined & ~JSON
  } *
}

Where:

Examples:

Retrieve all Version of a message Resource:

GET /endpoints/ep1/messages/msg1/versions
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <https://example.com/endpoints/ep1/messages/msg1/versions&page=2>;rel=next;count=100

{
  "1.0": {
    "messageid": "msg1",
    "versionid": "1.0",
    "self": "https://example.com/endpoints/ep1/messages/msg1",
    "xid": "/endpoints/ep1/messages/msg1",
    "epoch": 1,
    "name": "Blob Created",
    "isdefault": true,
    "createdat": "2024-04-30T12:00:00Z",
    "modifiedat": "2024-04-30T12:00:01Z",
    "ancestor": "1.0"
  }
}

Creating or Updating Versions 🔗

See Creating or Updating Resources and Versions.

Retrieving a Version 🔗

To retrieve a particular Version of a Resource, an HTTP GET MAY be used.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>

A successful response MUST either return the Version or an HTTP redirect to the <RESOURCE>url value if set.

In the case of returning the Version's document, the response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: <STRING> ?
xRegistry-<RESOURCE>id: <STRING>
xRegistry-versionid: <STRING>
xRegistry-self: <URL>
xRegistry-xid: <XID>
xRegistry-epoch: <UINTEGER>
xRegistry-name: <STRING> ?
xRegistry-isdefault: <BOOLEAN> ?
xRegistry-description: <STRING> ?
xRegistry-documentation: <URL> ?
xRegistry-icon: <URL> ?
xRegistry-labels-<KEY>: <STRING> *
xRegistry-createdat: <TIMESTAMP>
xRegistry-modifiedat: <TIMESTAMP>
xRegistry-ancestor: <STRING>
Content-Disposition: <STRING>

... Version document ...

Where:

In the case of a redirect, the response MUST be of the form:

HTTP/1.1 303 See Other
Content-Type: <STRING> ?
xRegistry-<RESOURCE>id: <STRING>
xRegistry-versionid: <STRING>
xRegistry-self: <URL>
xRegistry-xid: <URI>
xRegistry-epoch: <UINTEGER>
xRegistry-name: <STRING> ?
xRegistry-isdefault: <BOOLEAN> ?
xRegistry-description: <STRING> ?
xRegistry-documentation: <URL> ?
xRegistry-icon: <URL> ?
xRegistry-labels-<KEY>: <STRING> *
xRegistry-createdat: <TIMESTAMP>
xRegistry-modifiedat: <TIMESTAMP>
xRegistry-ancestor: <STRING>
xRegistry-<RESOURCE>url: <URL>
Location: <URL>
Content-Disposition: <STRING>

Where:

Examples:

Retrieve a specific Version (1.0) of a message Resource:

GET /endpoints/ep1/messages/msg1/versions/1.0
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
xRegistry-messageid: msg1
xRegistry-versionid: 1.0
xRegistry-self: https://example.com/endpoints/ep1/messages/msg1/versions/1.0
xRegistry-xid: /endpoints/ep1/messages/msg1/versions/1.0
xRegistry-epoch: 2
xRegistry-name: Blob Created
xRegistry-isdefault: true
xRegistry-createdat: <TIMESTAMP>
xRegistry-modifiedat: <TIMESTAMP>
xRegistry-ancestor: 1.0
Content-Disposition: msg1

{
  # Definition of a "Blob Created" event excluded for brevity
}

Retrieving a Version as Metadata 🔗

To retrieve a particular Version's metadata, an HTTP GET with $details appended to its <RESOURCE>id MAY be used. Note that in cases where the Resource's hasdocument is false then the $details suffix is OPTIONAL.

The request MUST be of the form:

GET /<GROUPS>/<GID>/<RESOURCES>/<RID>/versions/<VID>[$details]

A successful response MUST be of the form:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "<RESOURCE>id": "<STRING>",
  "versionid": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",
  "epoch": <UINTEGER>,
  "name": "<STRING>", ?
  "isdefault": <BOOLEAN>,
  "description": "<STRING>", ?
  "documentation": "<URL>", ?
  "icon": "<URL>", ?
  "labels": { "<STRING>": "<STRING>" * }, ?
  "createdat": "<TIMESTAMP>",
  "modifiedat": "<TIMESTAMP>",
  "ancestor": "<STRING>",
  "contenttype": "<STRING>", ?

  "<RESOURCE>url": "<URL>", ?                # If not local
  "<RESOURCE>": ... Resource document ..., ? # If inlined & JSON
  "<RESOURCE>base64": "<STRING>" ?           # If inlined & ~JSON
}

Where:

Examples:

Retrieve a specific Version of a message Resource as xRegistry metadata:

GET /endpoints/ep1/messages/msg1/versions/1.0$details
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "messageid": "msg1",
  "versionid": "1.0",
  "self": "https://example.com/endpoints/ep1/messages/msg1/versions/1.0",
  "xid": "/endpoints/ep1/messages/msg1/versions/1.0",
  "epoch": 2,
  "name": "Blob Created",
  "isdefault": true,
  "createdat": "2024-04-30T12:00:00Z",
  "modifiedat": "2024-04-30T12:00:01Z",
  "ancestor": "1.0"
}

Deleting Versions 🔗

To delete one or more Versions of a Resource, an HTTP DELETE MAY be used:

The processing of these two APIs is defined in the Deleting Entities in a Registry Collection section. For more information about the ?setdefaultversionid query parameter see the Default Version of a Resource section.

If as a result of one of these operations a Resource has no Versions, then the Resource MUST also be deleted.

A successful response MUST return either:

HTTP/1.1 204 No Content

with an empty HTTP body, or:

HTTP/1.1 200 OK

If, as an extension, the server chooses to return additional data in the HTTP body.

Examples:

Delete a single Version of a message Resource:

DELETE /endpoints/ep1/messages/msg1/versions/1.0
HTTP/1.1 204 No Content

Configuring Responses 🔗

Any request MAY include a set of query parameters (flags) to control how the response is to be generated. The following sections will define the following flags:

Implementations of this specification SHOULD support all 3 flags.

Note: If the Registry cannot return all expected data in one response because it is too large then it MUST generate an error (too_large). In those cases, the client will need to query the individual inlinable attributes in isolation so the Registry can leverage pagination of the response.

Collections Flag 🔗

The ?collections query parameter (flag) MAY be used on requests directed to the Registry itself or to Group instances to indicate that the response message MUST NOT include any attributes from the top-level entity (Registry or Group), but instead MUST include only all of the nested Collection maps that are defined at that level. Specifying it on a request directed to some other part of the Registry MUST generate an error (bad_flag). Use of this flag MUST implicitly turn on inlining - ?inline=*.

Servers MAY choose include, or exclude, the sibling <COLLECTION>url and <COLLECTION>count attributes for those top-level collections.

Note that this feature only applies to the root entity of the response and not to any nested entities/collections.

This feature is meant to be used when the Collections of the Registry, or Group, are of interest but not the top-level metadata. For example, this could be used to export one or more Group types from a Registry where the resulting JSON document is then used to import them into another Registry. If the Registry-level attributes were present in the output then they would need to be removed prior to the import, otherwise they would override the target Registry's values.

The resulting JSON, when using this feature, is designed to be used on a future POST / operation to either a Registry entity or to a Group instance as appropriate.

Doc Flag 🔗

The ?doc query parameter (flag) MAY be used to indicate that the response MUST use "document view" when serializing entities and MUST be modified to do the following:

All of the relative URLs mentioned in the last bullet MUST begin with # and be followed by a JSON Pointer reference to the entity within the response, e.g. #/endpoints/e1. This means that they are relative to the root of the document (response) generated, and not necessarily relative to the root of the Registry. Additionally, when those URLs are relative and reference a Resource or Version, the $details suffix MUST NOT be present despite the semantics of the suffix being applied (as noted below).

For clarity, if a Registry has a Schema Resource at /schemagroups/g1/schemas/s1, then this entity's self URL (when serialized in document view) would change based on the path specified on the GET request:

GET Path self URL
http://example.com/myreg #/schemagroups/g1/schemas/s1
http://example.com/myreg/schemagroups #/g1/schemas/s1
http://example.com/myreg/schemagroups/g1/ #/schemas/s1
http://example.com/myreg/schemagroups/g1/schemas #/s1
http://example.com/myreg/schemagroups/g1/schemas/s1 #/

This feature is useful when a client wants to minimize the amount of data returned by a server because the duplication of that data (typically used for human readability purposes) isn't necessary. For example, if tooling would ignore the duplication, or if the data will be used to populate a new Registry, then this feature might be used. It also makes the output more of a "stand-alone" document that minimizes external references.

For clarity, the serialization of a Resource in document view will adhere to the following:

{
  "<RESOURCE>id": "<STRING>",
  "self": "<URL>",
  "shortself": "<URL>", ?
  "xid": "<XID>",

  "metaurl": "<URL>",
  "meta": {
    "<RESOURCE>id": "<STRING>",
    "self": "<URL>",
    "shortself": "<URL>", ?
    "xid": "<XID>",
    "xref": "<XID>" ?
    # The following attributes are absent if 'xref' is set
    "epoch": <UINTEGER>,
    "createdat": "<TIMESTAMP>",
    "modifiedat": "<TIMESTAMP>",
    "ancestor": "<STRING>",
    "readonly": <BOOLEAN>,
    "compatibility": "<STRING>",
    "compatibilityauthority": "<STRING>", ?
    "deprecated": { ... }, ?

    "defaultversionid": "<STRING>",
    "defaultversionurl": "<URL>"
    "defaultversionsticky": <BOOLEAN>
  }
}

Note that the attributes epoch through defaultversionsticky MUST be excluded if xref is set because those would be picked-up from the target Resource's meta sub-object.

If ?doc is used on a request directed to a Resource, or Version, that has the hasdocument model aspect set to true, then the processing of the request MUST take place as if the $details suffix was specified in the URL. Meaning, the response MUST be the xRegistry metadata view of the Resource and not the Resource's "document".

If ?doc is used on a request directed to a Resource's versions collection, or to one of its Versions, but the Resource is defined as an xref to another Resource, then the server MUST generate an error (cannot_doc_xref) and SHOULD indicate that using ?doc on this part of the hierarchy is not valid - due to it not technically existing in document view.

Filter Flag 🔗

The ?filter query parameter (flag) on a request indicates that the response MUST include only those entities that match the specified filter criteria. This means that any Registry Collection's attributes MUST be modified to match the resulting subset. In particular:

The format of the ?filter query parameter is:

filter=<EXPRESSION>[,<EXPRESSION>]

Where:

The abstract processing logic would be:

The format of <EXPRESSION> is one of:

[<PATH>.]<ATTRIBUTE>
[<PATH>.]<ATTRIBUTE>=null
[<PATH>.]<ATTRIBUTE>=[<VALUE>]
[<PATH>.]<ATTRIBUTE>< <VALUE>
[<PATH>.]<ATTRIBUTE><=<VALUE>
[<PATH>.]<ATTRIBUTE>> <VALUE>
[<PATH>.]<ATTRIBUTE>>=<VALUE>
[<PATH>.]<ATTRIBUTE>!=<VALUE>
[<PATH>.]<ATTRIBUTE><> <VALUE>

Where:

For comparing an <ATTRIBUTE> to the specified <VALUE>, and for purposes of sorting (see the ?sort flag), the type of the attribute impacts how the comparisons are done:

Additionally, wildcards (*) MAY be used in string <VALUE>s with the following constraints:

If the request references an entity (not a collection), and the <EXPRESSION> references an attribute in that entity (i.e. there is no <PATH>), then if the <EXPRESSION> does not match the entity, that entity MUST NOT be returned. In other words, a 404 Not Found would be generated in the HTTP protocol case.

Examples:

Request Path Filter query Commentary
/ ?filter=endpoints.description=*cool* Only endpoints with the word cool in the description
/endpoints ?filter=description=*CooL* Similar results as previous, with a different request URL
/ ?filter=endpoints.messages.versions.versionid=1.0 Only versions (and their owning parents) that have a versionid of 1.0
/ ?filter=endpoints.name=myendpoint,endpoints.description=*cool*& filter=schemagroups.labels.stage=dev Only endpoints whose name is myendpoint and whose description contains the word cool, as well as any schemagroups with a label name/value pair of stage/dev
/ ?filter=description=no-match Returns a 404 if the Registry's description doesn't equal no-match
/ ?filter=endpoints.messages.meta.readonly=true Only messages that are readonly

Specifying a filter does not imply inlining. However, inlining can be used at the same time but MUST NOT result in additional entities being included in the results unless they are children of a matching leaf entity.

For example, in the following entity URL paths representing a Registry:

mygroups/g1/myresources/r1/versions/v1
mygroups/g1/myresources/r1/versions/v2
mygroups/g1/myresources/r2/versions/v1
mygroups/g2/myresources/r3/versions/v1

This request:

GET /?filter=mygroups.myresources.myresourceid=r1&inline=*

would result in the following entities (and their parents along the specified paths) being returned:

mygroups/g1/myresources/r1/versions/v1  # versions are due to inlining
mygroups/g1/myresources/r1/versions/v2

However, this request:

GET /?filter=mygroups.mygroupid=g2&filter=mygroups.myresources.myresourceid=r1&inline=*

would result in the following returned:

mygroups/g1/myresources/r1/versions/v1   # from 2nd ?filter
mygroups/g1/myresources/r1/versions/v2   # from 2nd ?filter
mygroups/g2/myresources/r3/versions/v1   # from 1nd ?filter

And, this request:

GET /?filter=mygroups.mygroupid=g1&filter=mygroups.myresources.myresourceid=r1&inline=*

would result in the following being returned:

mygroups/g1/myresources/r1/versions/v1   # from 2nd ?filter
mygroups/g1/myresources/r1/versions/v2   # from 2nd ?filter
mygroups/g1/myresources/r2/versions/v1   # from 1st ?filter

And, finally this request:

GET /?filter=mygroups.mygroupid=g1,mygroups.myresources.myresourceid=r1&inline=*

would result in the following being returned:

mygroups/g1/myresources/r1/versions/v1
mygroups/g1/myresources/r1/versions/v2

Notice the first part of the ?filter expression (to the left of the "and" (,)) has no impact on the results because the list of resulting leaves in that subtree is not changed by that search criteria.

Inline Flag 🔗

The ?inline query parameter (flag) MAY be used on requests to indicate whether nested collections/objects, or certain (potentially large) attributes, are to be included in the response message.

The ?inline query parameter on a request indicates that the response MUST include the contents of all specified inlinable attributes. Inlinable attributes include:

While the <RESOURCE> and <RESOURCE>base64 attributes are defined as two separate attributes, they are technically two separate "views" of the same underlying data. As such, the usage of each will be based on the content type of the Resource, specifying <RESOURCE> in the ?inline query parameter MUST be interpreted as a request for the appropriate attribute. In other words, <RESOURCE>base64 is not a valid inlinable attribute name.

Use of this feature is useful for cases where the contents of the Registry are to be represented as a single (self-contained) document.

Some examples:

The format of the ?inline query parameter is:

?inline[=<PATH>[,...]]

Where <PATH> is a string indicating which inlinable attributes to show in the response. References to nested attributes are represented using a dot (.) notation where the xRegistry collections names along the hierarchy are concatenated. For example: endpoints.messages.versions will inline all Versions of Messages. Non-leaf parts of the <PATH> MUST only reference xRegistry collection names and not any specific entity IDs since <PATH> is meant to be an abstract traversal of the model.

To reference an attribute with a dot as part of its name, the JSONPath escaping mechanism MUST be used: ['my.name']. For example, prop1.my.name.prop2 would be specified as prop1['my.name'].prop2 if my.name is the name of an attribute.

There MAY be multiple <PATH>s specified, either as comma separated values on a single ?inline query parameter or via multiple ?inline query parameters.

The * value MAY be used to indicate that all nested inlinable attributes at that level in the hierarchy (and below) MUST be inlined - except model, modelsource and capabilities at the root of the Registry. These three are excluded since the data associated with them are configuration related. To include their data the request MUST include <PATH> values of model, modelsource or capabilities. Use of * MUST only be used as the last part of the <PATH> (in its entirety). For example, foo* and *.foo are not valid <PATH> values, but * and endpoints.* are.

An ?inline query parameter without any value MAY be supported and if so it MUST have the same semantic meaning as ?inline=*.

The specific value of <PATH> will vary based on where the request is directed. For example, a request to the root of the Registry MUST start with a <GROUPS> name, while a request directed at a Group would start with a <RESOURCES> name.

For example, given a Registry with a model that has endpoints as a Group and messages as a Resource within endpoints, the table below shows some <PATH> values and a description of the result:

HTTP GET Path Example ?inline= values Comment
/ ?inline=endpoints Inlines the endpoints collection, but just one level of it, not any nested inlinable attributes
/ ?inline=endpoints.messages.versions Inlines the versions collection of all messages. Note that this implicitly means the parent attributes (messages and endpoints would also be inlined - however any other <GROUPS> or <RESOURCE>s types would not be
/endpoints ?inline=messages Inlines just messages and not any nested attributes. Note we don't need to specify the parent <GROUP> since the URL already included it
/endpoints/ep1 ?inline=messages.versions Similar to the previous endpoints.messages.version example
/endpoints/ep1 ?inline=messages.message Inline the Resource itself
/endpoints/ep1 ?inline=endpoints Invalid, already in endpoints and there is no <RESOURCE> called endpoints
/ ?inline=endpoints.messages.meta Inlines the meta attributes/sub-object of each message returned.
/ ?inline=endpoints.* Inlines everything for all endpoints.

Note that asking for an attribute to be inlined will implicitly cause all of its parents to be inlined as well, but just the parent's collections needed to show the child. In other words, just the collection in the parent in which the child appears, not all collections in the parent.

When specifying a collection to be inlined, it MUST be specified using the plural name for the collection in its defined case.

A request to inline an unknown, or non-inlinable, attribute MUST generate an error (invalid_data).

Sort Flag 🔗

When a request is directed at a collection of Groups, Resources or Versions, the ?sort query parameter (flag) MAY be used to indicate the order in which the entities of that collection are to be returned (i.e. sorted).

The format of the ?sort query parameter is:

?sort=<ATTRIBUTE>[=asc|desc]

Where:

If a server supports sorting and the attribute specified is valid, but the the server is unable to support sorting over it, then an error (unsupported_sort_attribute) MUST be generated. If the specified attribute is not valid then an error (invalid_sort_attribute) MUST be generated.

When pagination is used to return the results, but the ?sort flag is not specified, then the server MUST sort the results on the entities' <SINGULAR>id value in ascending order.

See the inline Flag section for how the various data types are compared for sorting purposes.

Entities that do not have a value for the specified attribute MUST be treated the same as if they had the "lowest" possible value for that attribute. If more than one entity shares the same attribute value then the <SINGULAR>id MUST be used as a secondary sorting key, using the same asc/desc value specified for the primary sorting key.

Some examples:

HTTP Header Values 🔗

Some attributes can contain arbitrary UTF-8 string content, and per RFC7230, section 3, HTTP headers MUST only use printable characters from the US-ASCII character set, and are terminated by a CRLF sequence with OPTIONAL whitespace around the header value.

When encoding an attribute's value as an HTTP header, it MUST be percent-encoded as described below. This is compatible with RFC3986, section 2.1 but is more specific about what needs encoding. The resulting string SHOULD NOT be further encoded. (Rationale: quoted string escaping is unnecessary when every space and double-quote character is already percent-encoded.)

When decoding an HTTP header into an attribute's value, any HTTP header value MUST first be unescaped with respect to double-quoted strings, as described in RFC7230, section 3.2.6. A single round of percent-decoding MUST then be performed as described below. HTTP headers for attribute values do not support parenthetical comments, so the initial unescaping only needs to handle double-quoted values, including processing backslash escapes within double-quoted values. Header values produced via the percent-encoding described here will never include double-quoted values, but they MUST be supported when receiving events, for compatibility with older versions of this specification which did not require double-quote and space characters to be percent-encoded.

Percent encoding is performed by considering each Unicode character within the attribute's canonical string representation. Any character represented in memory as a [Unicode surrogate pair][surrogate-pair] MUST be treated as a single Unicode character. The following characters MUST be percent-encoded:

Space and double-quote are encoded to avoid requiring any further quoting. Percent is encoded to avoid ambiguity with percent-encoding itself.

Steps to encode a Unicode character:

Percent-encoding SHOULD be performed using upper-case for values A-F, but decoding MUST accept lowercase values.

When performing percent-decoding, values that have been unnecessarily percent-encoded MUST be accepted, but encoded byte sequences which are invalid in UTF-8 MUST generate an error (header_decoding_error). For example, "%C0%A0" is an overlong encoding of U+0020, and would be rejected.

Example: a header value of "Euro € 😀" SHOULD be encoded as follows:

Error Processing 🔗

If an error occurs during the processing of a request, even if the error was during the creation of the response (e.g. an invalid ?inline value was provided), then an error MUST be generated and the entire request MUST be undone.

In general, when an error is generated, it SHOULD be sent back to the client. However, this MAY not happen if the server determines there is a good reason to not do so - such as due to security concerns.

Most of the error conditions mentioned in this specification will include a reference to one of the errors listed in this section. While it is RECOMMENDED that implementations use those errors (for consistency), they MAY choose to use a more appropriate one (or a custom one).

When an error is transmitted back to clients, it SHOULD adhere to the format specified in this section - which references the Problem Details for HTTP APIs specification, and when used MUST be of the following form:

HTTP/1.1 <CODE>
Content-Type: application/json; charset=utf-8

{
  "type": "<URI>",
  "instance": "<URL>",
  "title": "<STRING>",
  "detail": "<STRING>", ?
  ... error specific fields ...
}

Where:

<CODE>, "type", "instance" and title fields are REQUIRED. All other fields are OPTIONAL unless overwise stated as part of the error definition. Any substitutable information (as denoted by the <...> syntax) defined as part of an error MUST be populated appropriately.

HTTP response codes and status text are defined in the HTTP Semantics specification.

In the following list of errors, the Code, Type and Instance values MUST be as specified. The other field values are recommendations and MAY be modified as appropriate, including being specified in a language other than English.

ancestor_circular_reference 🔗

api_not_found 🔗

bad_flag 🔗

bad_request 🔗

This error is purposely generic and can be used when there isn't a more condition-specific error that would be more appropriate. Implementations SHOULD attempt to use a more specific error when possible.

cannot_doc_xref 🔗

capability_error 🔗

compatibility_violation 🔗

data_retrieval_error 🔗

details_required 🔗

extra_xregistry_headers 🔗

header_decoding_error 🔗

invalid_character 🔗

invalid_data 🔗

invalid_data_type 🔗

invalid_sort_attribute 🔗

method_not_allowed 🔗

mismatched_epoch 🔗

mismatched_id 🔗

misplaced_epoch 🔗

missing_versions 🔗

model_compliance_error 🔗

model_error 🔗

multiple_roots 🔗

not_found 🔗

readonly 🔗

required_attribute_missing 🔗

server_error 🔗

This error MAY be used when it appears that the incoming request was valid but something unexpected happened in the server that caused an error condition.

too_large 🔗

too_many_versions 🔗

unknown_attribute 🔗

unknown_id 🔗

unsupported_sort_attribute 🔗

unsupported_specversion 🔗