AbstractMethods for creating, reading, 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.
AbstractpostCreates a new object.
An object to post. This object is statically type-checked against the JSON schema that can be optionally provided as the generic type parameter. It is recommended to a schema to ensure that the posted object matches subsequent get or discover methods.
An implementation-specific object with information to authenticate the
actor.
Returns the object that has been posted, complete with its
assigned url and
actor.
AbstractgetRetrieves an object from a given url matching
the provided schema.
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.
AbstractdeleteDeletes an object from a given url
that had previously been posted.
The deleting actor must be the same as the
actor that created the object.
The location of the object to delete.
An implementation-specific object with information to authenticate the
actor.
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.
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 querying 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.
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.
AbstractcontinueContinues a GraffitiObjectStream from a given
cursor string.
The continuation will return new objects that have been posted
that match the original stream, and also returns the
urls of objects that
have been deleted, as marked by a tombstone.
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.
AbstractpostUploads media data, such as an image or video.
Unlike structured objects,
media is not indexed for discovery and
must be retrieved by its exact URL using getMedia
The binary data of the media to be uploaded, along with its media type, formatted as a Blob.
An implementation-specific object with information to authenticate the
actor.
The URL that the media was posted to.
AbstractdeleteDeletes media previously posted to a given URL.
A globally unique identifier and locator for the media.
An implementation-specific object with information to authenticate the
actor.
GraffitiErrorNotFound if no media at that URL exists.
GraffitiErrorForbidden if the actor
provided in the session is not the same as the actor that posted
the media.
AbstractgetRetrieves media from the given media URL, adhering to the given requirements.
A globally unique identifier and locator for the media.
Optionalrequirements: { accept?: string; maxBytes?: number }An optional set of requirements the retrieved media must meet.
Optionalaccept?: stringA list of acceptable media types for the retrieved media, formatted as like an HTTP Accept header
OptionalmaxBytes?: numberThe maximum size, in bytes, of the media.
The URL of the retrieved media, as a Blob
and the actor that posted it.
GraffitiErrorNotFound if no media at that URL exists.
GraffitiErrorTooLarge if the media exceeds the given maxBytes.
GraffitiErrorNotAcceptable if the media does not match the given
accept specification.
AbstractloginBegins 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.
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.
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.
Abstract ReadonlysessionAn event target that can be used to listen for the following events and their corresponding event types:
login - GraffitiLoginEventlogout - GraffitiLogoutEventinitialized - GraffitiSessionInitializedEvent
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 interact with data objects using standard database operations (post, get, and delete), and a method to discover data objects created by others. 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 actors who are allowed to access the object (undefined for public objects).channels(string[]): An array of the contexts in which the object should appear.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.