AbstractMethods for creating, reading, updating, and deleting Graffiti objects.
Methods that retrieve or accumulate information about multiple Graffiti objects at a time.
Methods and properties for logging in and out.
AbstractdeleteDeletes an object from a given url.
The deleting actor must be the same as the
actor that created the object.
It is not possible to re-put an object that has been deleted
to ensure a person's right to be forgotten.
In cases where deleting and restoring an object is useful, an object's
allowed property can be set to
an empty list to hide it from all actors except the creator.
The location of the object to delete.
An implementation-specific object with information to authenticate the
actor.
Returns the object that was deleted with its
lastModified
property updated to the time of deletion.
GraffitiErrorNotFound if the object does not exist, has already been deleted,
or the actor is not allowed to access it.
GraffitiErrorForbidden if the actor
is not the same actor as the one who created the object.
AbstractgetRetrieves an object from a given url.
The retrieved object is type-checked against the provided JSON schema otherwise a GraffitiErrorSchemaMismatch is thrown.
If the retreiving actor is not
the object's actor,
the object's allowed and
channels properties are
not revealed, similar to a BCC email.
The location of the object to get.
The JSON schema to validate the retrieved object against.
Optionalsession: GraffitiSession | nullReturns the retrieved object.
GraffitiErrorNotFound if the object does not exist, has been deleted, or the actor is not
allowed to access it.
GraffitiErrorSchemaMismatch if the retrieved object does not match the provided schema.
AbstractpatchPatches an existing object at a given url.
The patching actor must be the same as the
actor that created the object.
A collection of JSON Patch operations to apply to the object. See GraffitiPatch for more information.
The location of the object to patch.
An implementation-specific object with information to authenticate the
actor.
Returns the original object prior to the patch with its
lastModified
property updated to the time of patching.
GraffitiErrorNotFound if the object does not exist, has already been deleted,
or the actor is not allowed to access it.
GraffitiErrorForbidden if the actor
is not the same actor as the one who created the object.
AbstractputCreates a new object or replaces an existing object.
An object can only be replaced by the same actor
that created it.
Replacement occurs when the url of
the replaced object exactly matches an existing object's URL.
The object to be put. This object is statically type-checked against the JSON schema that can be optionally provided as the generic type parameter. We highly recommend providing a schema to ensure that the put object matches subsequent get or discover methods.
An implementation-specific object with information to authenticate the
actor.
Returns the object that was replaced if one one exists, otherwise returns an object with
with an empty value,
channels, and allowed
list.
The lastModified property of the returned object
will be updated to the time of replacement/creation.
GraffitiErrorNotFound if a url
is provided that has not been created yet or the actor
is not allowed to see it.
GraffitiErrorForbidden if the actor
is not the same actor as the one who created the object.
AbstractchannelReturns statistics about all the channels
that an actor has posted to.
This method will not return statistics related to any other actor's channel usage.
Like recoverOrphans, this method is not useful for most applications, but necessary for getting a global view of all an actor's Graffiti data to implement something like Facebook's Activity Log or a debugging interface.
Like discover, objects are returned asynchronously as they are discovered and the stream will end once all leads have been exhausted.
An implementation-specific object with information to authenticate the
actor.
Returns a stream of statistics for each channel
that the actor has posted to.
AbstractcontinueContinues a GraffitiObjectStream from a given
cursor string.
The continuation will return new objects that have been created
that match the original stream, and also returns the
urls of objects that
have been deleted, as marked by a tombstone.
The continuation may also include duplicates of objects that were already returned by the original stream. This is dependent on how much state the underlying implementation maintains.
The cursor allows the client to
serialize the state of the stream and continue it later.
However this method loses any typing information that was
present in the original stream. For better type safety
and when serializing is not necessary, use the
continue method
instead, which is returned along with the cursor at the
end of the original stream.
Optionalsession: GraffitiSession | nullGraffitiErrorForbidden if the actor
provided in the session is not the same as the actor
that initiated the original stream.
AbstractdiscoverDiscovers objects created by any actor that are contained
in at least one of the given channels
and match the given JSON Schema.
Objects are returned asynchronously as they are discovered but the stream
will end once all leads have been exhausted.
The GraffitiObjectStream ends by returning a
continue method and a
cursor string,
each of which can be be used to poll for new objects.
The continue method preserves the type safety of the stream and the cursor
string can be serialized to continue the stream after an application is closed
and reopened.
discover will not return objects that the actor
is not allowed to access.
If the actor is not the creator of a discovered object,
the allowed list will be masked to only contain the querying actor if the
allowed list is not undefined (public). Additionally, if the actor is not the
creator of a discovered object, any channels
not specified by the discover method will not be revealed. This masking happens
before the object is validated against the supplied schema.
Since different implementations may fetch data from multiple sources there is
no guarentee on the order that objects are returned in.
It is also possible that duplicate objects are returned and their
lastModified fields must be used
to determine which object is the most recent.
The channels that objects must be associated with.
A JSON Schema that objects must satisfy.
Optionalsession: GraffitiSession | nullReturns a stream of objects that match the given channels
and JSON Schema.
AbstractrecoverDiscovers objects not contained in any
channels
that were created by the querying actor
and match the given JSON Schema.
Unlike discover, this method will not return objects created by other actors.
Like channelStats, this method is not useful for most applications, but necessary for getting a global view of all an actor's Graffiti data to implement something like Facebook's Activity Log or a debugging interface.
Like discover, objects are returned asynchronously as they are discovered,
the stream will end once all leads have been exhausted, and the stream
can be continued using the continue
method or cursor string.
A JSON Schema that orphaned objects must satisfy.
An implementation-specific object with information to authenticate the
actor.
Returns a stream of objects created by the querying actor
that do not belong to any channels
and match the given JSON Schema.
Abstract ReadonlysessionAn event target that can be used to listen for the following events and their corresponding event types:
login - GraffitiLoginEventlogout - GraffitiLogoutEventinitialized - GraffitiSessionInitializedEventAbstractloginBegins the login process. Depending on the implementation, this may involve redirecting to a login page or opening a popup, so it should always be called in response to a gesture, such as clicking a button, due to the feature-gating browser security feature.
The session object is returned
asynchronously via sessionEvents
as a GraffitiLoginEvent with event type login.
Optionalproposal: { actor?: string; scope?: {} }Suggestions for the permissions that the login process should grant. The login process may not provide the exact proposed permissions.
Optionalactor?: stringA suggested actor to login as. For example, if a user tries to edit a post but are not logged in, the interface can infer that they might want to log in as the actor who created the post they are attempting to edit.
Even if provided, the implementation should allow the user to log in as a different actor if they choose.
Optionalscope?: {}A yet to be defined permissions scope. An application may use this to indicate the minimum necessary scope needed to operate. For example, it may need to be able read private messages from a certain set of channels, or write messages that follow a particular schema.
The login process should make it clear what scope an application is requesting and allow the user to enhance or reduce that scope as necessary.
AbstractlogoutBegins the logout process for a particular session. Depending on the implementation, this may involve redirecting the user to a logout page or opening a popup, so it should always be called in response to a gesture, such as clicking a button, due to the feature-gating browser security feature.
A confirmation will be returned asynchronously via
sessionEvents
as a GraffitiLogoutEvent as event type logout.
The session object to logout.
This API describes a small but powerful set of methods that can be used to create many different kinds of social applications, from applications like Twitter, to Messenger, to Wikipedia, to many more new designs. See the Graffiti project website for links to example applications. Additionally, apps built on top of the API interoperate with each other so you can seamlessly switch between apps without losing your friends or data.
These API methods should satisfy all of an application's needs for the communication, storage, and access management of social data. The rest of the application can be built with standard client-side user interface tools to present and interact with that data—no server code necessary!
The Typescript code for this API is open source on Github.
There are several different implementations of this Graffiti API available, including a federated implementation, that lets people choose where their data is stored (you do not need to host your own server) and a local implementation that can be used for testing and development. Different implementations can be swapped-in in the future without changing the API or any of the apps built on top of it. In fact, we're working on an end-to-end encrypted version now! Follow Theia on BlueSky for updates.
On the other side of the stack, there is Vue plugin that wraps around this API to provide reactivity. Other plugin frameworks and high-level libraries will be available in the future.
API Overview
The Graffiti API provides applications with methods for login and logout, methods to store data objects using standard database operations (put, get, patch, and delete), and a method to discover data objects from other people. These data objects have a couple structured properties:
url(string): A globally unique identifier and locator for the object.actor(string): An unforgeable identifier for the creator of the object.allowed(string[] | undefined): An array of the identities who are allowed to access the object (undefined for public objects).channels(string[]): An array of the contexts in which the object should appear.revision(number): A number to compare different versions of an object.All other data is stored in the object's unstructured
valueproperty. This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows). For example, a post might have the value:a profile might have the value:
and a "Like" might have the value:
New social artifacts and activities can be easily created, simply by creating new objects with appropriate properties. Despite the lack of structure, we expect Graffiti object properties to adhere to a "folksonomy", similar to hashtags. Any string can be used as a hashtag on Twitter, but there is social value in using the same hashtags at other people and so a structure naturally emerges. Similarly, Graffiti objects can have arbitrary properties but if people use the same properties as each other, their apps will interoperate, which has social value.
For a more complete and detailed overview of Graffiti's design, please refer to this section of the Graffiti paper, published in ACM UIST 2025. The paper also overviews
channels, which are Graffiti's means of organizing data contextually, and a concept called "total reification", which handles explains how moderation, collaboration, and other interactions are managed.