Fediverse and Bluesky
The fediverse
activitypub-service
This installation on the fediverse, by ActivityPub: what other servers ask of it and send to it, a person's choice to be on it, finding somebody elsewhere, and the servers it refuses (federation.md). Optional: an installation may not run it.
Where WebFinger is
/.well-known/host-metaAn XRD document (RFC 6415), for servers that look for WebFinger this way.
Who: Anyone, signed in or not
Answers
- 200the XRD documentstring application/xrd+xml
Request
curl "https://api.example.org/.well-known/host-meta"
const response = await fetch('https://api.example.org/.well-known/host-meta', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/.well-known/host-meta",
)
answer = response.json()
Who an account is
/.well-known/webfingerWebFinger (RFC 7033): where another server finds somebody here by their handle.
Who: Anyone, signed in or not
Parameters
resourcestringin queryacct:ana@example.org, or the address of somebody's actor.
Answers
- 200OKactivitypub.JRD application/jrd+json
- 400no resource, or not one that is asked about
- 404nobody here by that name is on the fediverse
Request
curl "https://api.example.org/.well-known/webfinger"
const response = await fetch('https://api.example.org/.well-known/webfinger', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/.well-known/webfinger",
)
answer = response.json()
The servers refused
/activitypub/blocksWho: A super administrator
Parameters
qstringin queryWords the name or the reason holds.startintegerin queryHow many to pass over.limitintegerin queryThe most to send: 50 unless asked, 200 at most.
Answers
- 200OKobject application/json
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/activitypub/blocks" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/activitypub/blocks', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/activitypub/blocks",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"created_at": "2026-10-27T09:30:00Z",
"created_by": 1,
"domain": "string",
"reason": "string"
}
],
"pagination": {
"limit": 1,
"offset": 1,
"total": 1
}
}
Refuse a server, or change why
/activitypub/blocksNothing from it is taken, and nothing is sent to it.
Who: A super administrator
Body application/json
domainstringThe server's name, or an address or a handle on it.reasonstringWhy it is refused.
Answers
- 201CreatedDomainBlock application/json
- 400
domain_required: the request names no server;domain_unreadable: it is not a server's name;domain_is_here: it is this installationRefusal application/json - 401no token, or one that has expired
- 403the viewer may not do this
Request
curl -X POST "https://api.example.org/activitypub/blocks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": "string",
"reason": "string"
}'
const response = await fetch('https://api.example.org/activitypub/blocks', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"domain": "string",
"reason": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/activitypub/blocks",
headers={"Authorization": f"Bearer {token}"},
json={
"domain": "string",
"reason": "string"
},
)
answer = response.json()
Answer · 201
{
"created_at": "2026-10-27T09:30:00Z",
"created_by": 1,
"domain": "string",
"reason": "string"
}
Stop refusing a server
/activitypub/blocks/{domain}Who: A super administrator
Parameters
domainstringrequiredin pathIts name:spam.example.
Answers
- 204done
- 401no token, or one that has expired
- 403the viewer may not do this
- 404it is not refused
Request
curl -X DELETE "https://api.example.org/activitypub/blocks/<domain>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/activitypub/blocks/<domain>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/activitypub/blocks/<domain>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Find somebody on another server
/activitypub/lookupThey are kept here, so that they can be followed as anybody is. Thirty lookups in ten minutes.
Who: Somebody signed in
Parameters
qstringin queryTheir handle (@alice@mastodon.example) or the address of their profile there.
Answers
- 200OKactivitypub.RemotePerson application/json
- 400
lookup_unreadableRefusal application/json - 401no token, or one that has expired
- 404
lookup_not_found,lookup_is_here,lookup_not_a_personorlookup_refusedRefusal application/json - 409the installation is not on the fediverse:
site_privateorsite_unreachableRefusal application/json - 429
too_many_lookupsRefusal application/json - 502
lookup_failedRefusal application/json
Request
curl "https://api.example.org/activitypub/lookup" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/activitypub/lookup', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/activitypub/lookup",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"alias": "string",
"body": "string",
"id": 42,
"locked": false,
"name": "string",
"origin_domain": "string",
"type": "string",
"uri": "https://example.org/",
"url": "https://example.org/"
}
Copy a post's pictures here again
/activitypub/posts/{id}/picturesThe pictures of a post from another server that has been left alone a while are removed from storage, and the post says so (media_removed). Asked here, they are read again from where they came from. For anybody signed in who may see the post.
Who: Somebody signed in
Parameters
idintegerrequiredin pathThe post's id here.
Answers
- 200OKPicturesRestored application/json
- 401no token, or one that has expired
- 404
media_not_removed: there is no such post the viewer may see, or its pictures are hereRefusal application/json - 410
media_goneRefusal application/json - 429
too_many_requestsRefusal application/json - 502
media_failedRefusal application/json
Request
curl -X POST "https://api.example.org/activitypub/posts/<id>/pictures" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/activitypub/posts/<id>/pictures', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/activitypub/posts/<id>/pictures",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"restored": 1
}
Whether the viewer is on the fediverse, and could be
/activitypub/settingsWho: Somebody signed in
Parameters
person_idintegerin queryThe person's id, or a website's for one of its administrators. The viewer when left out.
Answers
- 200OKactivitypub.Settings application/json
- 400
person_unreadableRefusal application/json - 401no token, or one that has expired
- 403somebody else's, and the viewer is not a site administrator, nor an administrator of the website asked about
- 404there is no such person
Request
curl "https://api.example.org/activitypub/settings" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/activitypub/settings', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/activitypub/settings",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"actor": "string",
"available": false,
"enabled": false,
"handle": "string",
"reason": "string"
}
Go on the fediverse, or leave it
/activitypub/settingsIt is each person's own choice, off until they make it, and only for somebody whose profile is open to everybody. A website's is its administrators' (?person_id= with its id).
Who: Somebody signed in
Body application/json
enabledbooleanrequiredWhether the viewer is on the fediverse from now on. Leaving tells the servers that knew them, and some of those do not take them back (federation.md).
Answers
- 200OKactivitypub.Settings application/json
- 400
enabled_required,person_unreadableRefusal application/json - 401no token, or one that has expired
- 403somebody else's, and the viewer is not an administrator of the website asked about
- 409why they cannot be:
site_private,site_unreachableorprofile_not_publicRefusal application/json
Request
curl -X PUT "https://api.example.org/activitypub/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'
const response = await fetch('https://api.example.org/activitypub/settings', {
method: 'PUT',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"enabled": false
}),
});
const answer = await response.json();
import requests
response = requests.put(
"https://api.example.org/activitypub/settings",
headers={"Authorization": f"Bearer {token}"},
json={
"enabled": False
},
)
answer = response.json()
Answer · 200
{
"actor": "string",
"available": false,
"enabled": false,
"handle": "string",
"reason": "string"
}
The installation itself
/ap/actorFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). The actor that signs what the installation asks of other servers.
Who: Anyone, signed in or not
Answers
- 200OKactivitypub.Actor application/activity+json
- 404the installation is not on the fediverse
- 503its key cannot be read now
Request
curl "https://api.example.org/ap/actor"
const response = await fetch('https://api.example.org/ap/actor', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/actor",
)
answer = response.json()
A post, as other servers see it
/ap/notes/{uuid}For other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). A Note, of somebody on the fediverse, open to everybody.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathThe post's UUID.
Answers
- 200OKactivitypub.Note application/activity+json
- 404there is no such post that went out
Request
curl "https://api.example.org/ap/notes/<uuid>"
const response = await fetch('https://api.example.org/ap/notes/<uuid>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/notes/<uuid>",
)
answer = response.json()
A picture of a post
/ap/notes/{uuid}/pictures/{position}For a server that was sent the post: a redirect to the picture in storage.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathThe post's UUID.positionintegerrequiredin pathWhich picture, from 0.
Answers
- 302the picture is at the address in Location
- 404there is no such picture of a post that went out
Request
curl "https://api.example.org/ap/notes/<uuid>/pictures/<position>"
const response = await fetch('https://api.example.org/ap/notes/<uuid>/pictures/<position>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/notes/<uuid>/pictures/<position>",
)
answer = response.json()
The poster of a video post
/ap/notes/{uuid}/posterFor a server that was sent the post: a redirect to the picture in storage.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathThe post's UUID.
Answers
- 302the picture is at the address in Location
- 404there is no such video of a post that went out
Request
curl "https://api.example.org/ap/notes/<uuid>/poster"
const response = await fetch('https://api.example.org/ap/notes/<uuid>/poster', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/notes/<uuid>/poster",
)
answer = response.json()
The video of a post
/ap/notes/{uuid}/videoFor a server that was sent the post: a redirect to the file in storage.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathThe post's UUID.
Answers
- 302the file is at the address in Location
- 404there is no such video of a post that went out
Request
curl "https://api.example.org/ap/notes/<uuid>/video"
const response = await fetch('https://api.example.org/ap/notes/<uuid>/video', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/notes/<uuid>/video",
)
answer = response.json()
Somebody here, as other servers see them
/ap/users/{uuid}For other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). A Person, or an Organization for a Website, with their public key.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathTheir UUID, as other servers know them.
Answers
- 200OKactivitypub.Actor application/activity+json
- 404nobody here by that name is on the fediverse
- 503their key cannot be read now
Request
curl "https://api.example.org/ap/users/<uuid>"
const response = await fetch('https://api.example.org/ap/users/<uuid>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/users/<uuid>",
)
answer = response.json()
How many follow them
/ap/users/{uuid}/followersFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). A collection with its count and no items.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathTheir UUID, as other servers know them.
Answers
- 200OKactivitypub.Collection application/activity+json
- 404nobody here by that name is on the fediverse
Request
curl "https://api.example.org/ap/users/<uuid>/followers"
const response = await fetch('https://api.example.org/ap/users/<uuid>/followers', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/users/<uuid>/followers",
)
answer = response.json()
How many they follow
/ap/users/{uuid}/followingFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). A collection with its count and no items.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathTheir UUID, as other servers know them.
Answers
- 200OKactivitypub.Collection application/activity+json
- 404nobody here by that name is on the fediverse
Request
curl "https://api.example.org/ap/users/<uuid>/following"
const response = await fetch('https://api.example.org/ap/users/<uuid>/following', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/users/<uuid>/following",
)
answer = response.json()
Take an activity from another server
/ap/users/{uuid}/inboxFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). Signed with HTTP Signatures; what is not signed by whoever is said to have done it is not believed. Six hundred a minute from one address. Taken, whether or not anything is done with it.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathTheir UUID, as other servers know them.
Body application/activity+json
anythingAnswers
- 202taken
- 400the request is not well formed, or a value in it is not one it may be
- 401not signed, or the signature does not check, or not by whoever is said to have done it
- 403the server it came from is refused
- 404nobody here by that name is on the fediverse
- 413too large
- 429too many from one address
- 503it could not be kept now; send it again
Request
curl -X POST "https://api.example.org/ap/users/<uuid>/inbox"
const response = await fetch('https://api.example.org/ap/users/<uuid>/inbox', {
method: 'POST',
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/ap/users/<uuid>/inbox",
)
answer = response.json()
How many posts they have sent out
/ap/users/{uuid}/outboxFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). A collection with its count and no items.
Who: Anyone, signed in or not
Parameters
uuidstringrequiredin pathTheir UUID, as other servers know them.
Answers
- 200OKactivitypub.Collection application/activity+json
- 404nobody here by that name is on the fediverse
- 503it cannot be counted now
Request
curl "https://api.example.org/ap/users/<uuid>/outbox"
const response = await fetch('https://api.example.org/ap/users/<uuid>/outbox', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/ap/users/<uuid>/outbox",
)
answer = response.json()
Take an activity from another server
/inboxFor other servers, in ActivityStreams (https://www.w3.org/TR/activitystreams-vocabulary/ (opens in a new tab)). Signed with HTTP Signatures; what is not signed by whoever is said to have done it is not believed. Six hundred a minute from one address. Taken, whether or not anything is done with it.
Who: Anyone, signed in or not
Body application/activity+json
anythingAnswers
- 202taken
- 400the request is not well formed, or a value in it is not one it may be
- 401not signed, or the signature does not check, or not by whoever is said to have done it
- 403the server it came from is refused
- 404nobody here by that name is on the fediverse
- 413too large
- 429too many from one address
- 503it could not be kept now; send it again
Request
curl -X POST "https://api.example.org/inbox"
const response = await fetch('https://api.example.org/inbox', {
method: 'POST',
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/inbox",
)
answer = response.json()
Written by Claude, reviewed by Rastin Mehr · About this