Skip to content

Entities

Entities are the foundation of the Versia protocol. They are similar to ActivityStreams objects and Matrix events.

Definition

An entity is a simple JSON object that represents a data structure in Versia, such as a user. Extensions can define their own entity types. Every entity has a type and a created_at. Every entity also has an id that identifies it within its instance, except for Transient Entities and InstanceMetadata.

Objects that do not meet this definition, such as Media and Collection, are structures.

Fields not marked as required are optional.

Fields

id string Required

Unique identifier for the entity. MUST be unique within the instance, and from 1 to 13 characters long. MUST contain only the following characters:

  • a-z
  • 0-9
  • -, _

An id is opaque: receivers MUST NOT derive a time, an order or any other meaning from it. See Choosing IDs.

Required on every entity except Transient Entities and InstanceMetadata, which do not have it.

type string Required

Type of the entity. Custom types MUST follow Extension Naming.

Date and time when the entity was created. MUST be an RFC 3339 timestamp.

How to handle a well-formed date that is obviously incorrect (e.g. in the future) is left to the Implementation’s discretion. Sending a Note created on 1337-12-13T00:00:00Z is very funny, but don’t be surprised if it is rejected.

Date and time when the entity last changed. MUST be an RFC 3339 timestamp. See Changing an Entity.

Not present on entities without an id.

Extensions to the entity. Use this to add custom properties to the entity.

Each key MUST follow extension naming.

{
    "id": "033gs6adpp000",
    "type": "pub.versia:likes/Like",
    "created_at": "2021-01-01T00:00:00Z",
    "author": "example.com:033wv9k6n0007",
    "target": "example.org:033w19vsgr000"
}
{
    "id": "033qkb1gq0002",
    "type": "com.example:zlorbs/Zlorb",
    "created_at": "2023-04-13T08:00:00Z",
    "extensions": {
        "com.example:zlorbs": {
            "zlorb_type": "giant",
            "zlorb_size": "huge"
        },
        "com.example:location": {
            "latitude": 37.7749,
            "longitude": -122.4194
        }
    }
}

Choosing IDs

A reference carries the domain of the entity’s instance, so an id only has to be unique within that instance, not across the network. So it can be much shorter than a UUID, which at 36 characters is not a valid id. Short ids are also easier to read and type where they appear, such as in Versia Links and in the paths of the Federation API.

How an instance creates ids is up to it. For example, it can take the time the entity is created, in milliseconds since the Unix epoch, multiply it by 65 536, add a counter for entities created in the same millisecond, and write the result in lowercase Crockford base32, padded with 0 to 13 characters. Crockford base32 leaves out i, l, o and u, so an id cannot be misread as 1 or 0 when someone types it.

0341e147rp002

Ids made this way sort by creation time within one instance, which helps the instance store and page through them. Other instances still treat them as opaque.

Transient Entities

Some entities are transient. A Transient Entity has no id field, so it cannot be fetched using the Federation API or referenced by other entities. These entities are used for actions that do not require a permanent record, such as deletions or migrations.

Implementations do not need to store Transient Entities, and MUST NOT rely on other implementations to store them.

Changing an Entity

To change an entity, its instance sends the whole entity again, with the same id, to the same audience. The receiver replaces the copy it holds. An optional field that is left out is cleared.

id, type, author and created_at MUST NOT change. A receiver MUST discard a version that changes any of them. An extension MAY lock further fields of its own entities, and states which on its page.

Senders SHOULD set updated_at when sending a change. A receiver MUST discard a version whose updated_at is older than that of the copy it holds. Without an updated_at to compare, the last version received wins. The same applies to a version it fetched.

Reference

type Id = Entity["id"];

type Reference = `${Domain}:${Id}`;

A reference is a way to refer to any entity within the Versia network. It is a string composed of the following parts:

  • The domain of the entity’s instance.
    • Example: example.com, xn--bcher-kva.example.
  • A colon (:) separator.
  • The entity’s unique identifier. This is the id property of the entity.

The domain is always present, even when a reference points to an entity on the instance sending it. A string without a domain is not a valid reference. Like every domain, it has no port number.

Reference Types

A reference does not say what type of entity it points to, but fetching an entity needs its type. So every field holding a reference MUST state the entity type it points to. A field that can point to several types MUST carry the type next to the reference, as Delete and Report do.

Reference Example

{
    "type": "Subscribe",
    "created_at": "2021-01-01T00:00:00Z",
    "subscriber": "example.com:033wv9k6n0007",
    "target": "example.org:033qtdbdkj006",
    "target_type": "User"
}

Domain

Versia defines a domain as the hostname of an instance, which is used to identify the instance in the network. It MUST be a valid hostname as defined by RFC 1123, and MUST NOT include a port number (the HTTPS port is always 443). It MUST NOT be an IP address or a single label such as localhost. An internationalized domain name MUST be written in its ASCII xn-- form, as defined by IDNA.

A domain MUST be lowercase wherever it appears in the protocol, including References, Versia-Signed-By and the signed string.

example.com
test.localhost
sus.example.org
xn--bcher-kva.example
1.0.1.7.0.8.0.0.0.0.7.4.0.1.0.0.2.ip6.arpa # We know what you did
example.com:3000   # ❌️ Invalid: port number is not allowed
test..localhost    # ❌️ Invalid: double dot is not allowed
example.com.       # ❌️ Invalid: trailing dot is not allowed
example.org/       # ❌️ Invalid: trailing slash is not allowed
example.org/sus    # ❌️ Invalid: path is not allowed
bücher.example     # ❌️ Invalid: IDNs must be Punycode encoded
Example.com        # ❌️ Invalid: uppercase is not allowed
https://a.example  # ❌️ Invalid: protocol is not allowed
192.0.2.1          # ❌️ Invalid: IP address is not allowed
localhost          # ❌️ Invalid: single label is not allowed