Skip to main content

Actors

In ActivityPub, an Actor is any entity that can perform activities. Most commonly actors represent users, but they can also represent bots, organizations, services, or groups.

Actor Types

ActivityStreams defines five actor types:

TypeDescriptionUse Case
PersonA human userIndividual accounts
ApplicationA software botAutomated accounts, bots
OrganizationA company or groupBusiness accounts
ServiceA software serviceRelay servers, bridges
GroupA shared actorCommunities, forums

Required Properties

Every actor MUST have:

{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Person",
"id": "https://example.com/users/alice",
"inbox": "https://example.com/users/alice/inbox",
"outbox": "https://example.com/users/alice/outbox"
}
PropertyTypeDescription
typeStringOne of the actor types
idURLUnique identifier (dereferenceable)
inboxURLEndpoint for receiving activities
outboxURLCollection of performed activities

Identity

{
"preferredUsername": "alice",
"name": "Alice Smith",
"summary": "<p>Software developer and Fediverse enthusiast</p>",
"url": "https://example.com/@alice"
}
PropertyDescription
preferredUsernameHandle/username (used in @mentions)
nameDisplay name
summaryBio/description (HTML allowed)
urlHuman-readable profile page

Media

{
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/avatars/alice.png"
},
"image": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://example.com/headers/alice.jpg"
}
}
PropertyDescription
iconAvatar/profile picture
imageHeader/banner image

Collections

{
"followers": "https://example.com/users/alice/followers",
"following": "https://example.com/users/alice/following",
"liked": "https://example.com/users/alice/liked"
}
PropertyDescription
followersCollection of followers
followingCollection of followed actors
likedCollection of liked objects

Endpoints

{
"endpoints": {
"sharedInbox": "https://example.com/inbox",
"proxyUrl": "https://example.com/proxy",
"oauthAuthorizationEndpoint": "https://example.com/oauth/authorize",
"oauthTokenEndpoint": "https://example.com/oauth/token"
}
}
EndpointDescription
sharedInboxServer-wide inbox for batch delivery
proxyUrlProxy for fetching remote objects
oauthAuthorizationEndpointOAuth 2.0 authorization
oauthTokenEndpointOAuth 2.0 token endpoint

Public Key

Actors MUST include a public key for HTTP Signature verification:

{
"publicKey": {
"id": "https://example.com/users/alice#main-key",
"owner": "https://example.com/users/alice",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2L...\n-----END PUBLIC KEY-----"
}
}
PropertyDescription
idURL of the key (usually {actor}#main-key)
ownerURL of the actor owning this key
publicKeyPemPEM-encoded RSA public key

Complete Actor Example

{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"type": "Person",
"id": "https://example.com/users/alice",
"inbox": "https://example.com/users/alice/inbox",
"outbox": "https://example.com/users/alice/outbox",
"followers": "https://example.com/users/alice/followers",
"following": "https://example.com/users/alice/following",
"preferredUsername": "alice",
"name": "Alice Smith",
"summary": "<p>Software developer and Fediverse enthusiast</p>",
"url": "https://example.com/@alice",
"manuallyApprovesFollowers": false,
"discoverable": true,
"published": "2023-01-15T10:00:00Z",
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/avatars/alice.png"
},
"image": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://example.com/headers/alice.jpg"
},
"attachment": [
{
"type": "PropertyValue",
"name": "Website",
"value": "<a href=\"https://alice.example\" rel=\"me\">alice.example</a>"
},
{
"type": "PropertyValue",
"name": "GitHub",
"value": "<a href=\"https://github.com/alice\" rel=\"me\">github.com/alice</a>"
}
],
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"publicKey": {
"id": "https://example.com/users/alice#main-key",
"owner": "https://example.com/users/alice",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}
}

Mastodon Extensions

Mastodon adds several properties to actors:

Profile Fields

{
"attachment": [
{
"type": "PropertyValue",
"name": "Website",
"value": "<a href=\"https://example.com\" rel=\"me\">example.com</a>"
}
]
}
{
"featured": "https://example.com/users/alice/collections/featured"
}
{
"featuredTags": "https://example.com/users/alice/collections/tags"
}

Account Settings

{
"manuallyApprovesFollowers": true,
"discoverable": true,
"memorial": false,
"indexable": true
}
PropertyDescription
manuallyApprovesFollowersRequires follow approval
discoverableListed in profile directory
memorialAccount is memorialized
indexablePosts can be indexed by search

Service and Application Actors

Server-level actors for relays, bridges, and bots:

{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Application",
"id": "https://example.com/actor",
"inbox": "https://example.com/inbox",
"outbox": "https://example.com/outbox",
"preferredUsername": "example.com",
"name": "Example.com Server Actor"
}

Group Actors

For communities (like Lemmy):

{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"lemmy": "https://join-lemmy.org/ns#"
}
],
"type": "Group",
"id": "https://lemmy.example/c/technology",
"name": "Technology",
"preferredUsername": "technology",
"inbox": "https://lemmy.example/c/technology/inbox",
"outbox": "https://lemmy.example/c/technology/outbox",
"followers": "https://lemmy.example/c/technology/followers",
"attributedTo": "https://lemmy.example/u/admin"
}

Actor Verification

When receiving activities, verify:

  1. Actor ID domain matches keyId domain
  2. Public key belongs to actor
  3. Actor is not blocked
function verifyActor(activity, signature) {
const actorUrl = new URL(activity.actor);
const keyUrl = new URL(signature.keyId);

// Key must be on same domain as actor
if (actorUrl.hostname !== keyUrl.hostname) {
throw new Error('Key domain mismatch');
}

// Fetch and verify actor owns the key
const actor = await fetchActor(activity.actor);
if (actor.publicKey.id !== signature.keyId) {
throw new Error('Key ID mismatch');
}

return true;
}

Caching Actors

Cache remote actors to reduce requests:

ApproachTTLNotes
Short cache1-24 hoursGood for active federation
Long cache1-7 daysReduces requests, staler data
On-demandFetch when neededMost current, more requests

Recommended: 24-hour cache with refresh on key operations.

Next Steps