Fediverse and Bluesky
Websites
syndication-service
Connecting a website: its feed found, its domain proved, and a Website made of it, a profile whose posts are the feed's items (websites.md). Who may is WEBSITES_FROM, administrators unless the installation says otherwise.
Begin connecting a website
/websites/_connectThe site's feed is found: the address itself, or the feed the page names with <link rel="alternate">. It must be on the site's domain. The answer is the token, to be put in a TXT record or a file, with a name and a handle to begin with.
Who: Somebody signed in
Body application/json
site_urlstringrequiredThe site's address,example.comorhttps://example.com/blog/, or its feed's. At most 2000 characters.
Answers
- 201Createdsyndication.Connection application/json
- 400
not_a_domain,no_feed,unreadable,feed_not_on_domainorprivate_feed(behind a login, or a secret in its address: a website is public), aserror - 401the viewer is not signed in
- 403the viewer may not connect a website: see WEBSITES_FROM
- 409
feed_taken: the feed already has a Website - 429the viewer has begun too many lately
Request
curl -X POST "https://api.example.org/websites/_connect" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"site_url": "https://example.org/"
}'
const response = await fetch('https://api.example.org/websites/_connect', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"site_url": "https://example.org/"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/websites/_connect",
headers={"Authorization": f"Bearer {token}"},
json={
"site_url": "https://example.org/"
},
)
answer = response.json()
Answer · 201
{
"domain": "string",
"feed_description": "string",
"feed_title": "string",
"feed_url": "https://example.org/",
"file_url": "https://example.org/",
"host": "string",
"id": 42,
"record_name": "string",
"site_url": "https://example.org/",
"suggested_alias": "string",
"suggested_name": "string",
"token": "string"
}
Make the Website
/websites/_connect/{id}Once its domain is proved. Whoever proved it is its first administrator and its first follower; its feed's ten newest items are posted, the rest marked seen, and what is published afterwards is posted as it comes.
Who: Somebody signed in
Parameters
idintegerrequiredin pathThe connection's id. At least 1.
Body application/json
aliasstringrequiredIts handle:@example. Letters, digits,-and_. At least 3 characters. At most 30 characters.bodystringWhat it is, shown in its About card. At most 500 characters.feed_urlstringrequiredThe feed, as starting the connection found it. At most 2000 characters.namestringrequiredIts name, shown as a profile's is. At most 64 characters.
Answers
- 201Createdsyndication.Made application/json
- 400
no_feed,unreadable,feed_not_on_domainorprivate_feed, aserror - 401the viewer is not signed in
- 403the viewer may not connect a website: see WEBSITES_FROM
- 404there is no such connection begun by the viewer
- 409
not_proved,feed_takenorhandle_taken, aserror
Request
curl -X POST "https://api.example.org/websites/_connect/<id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"alias": "string",
"body": "string",
"feed_url": "https://example.org/",
"name": "string"
}'
const response = await fetch('https://api.example.org/websites/_connect/<id>', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"alias": "string",
"body": "string",
"feed_url": "https://example.org/",
"name": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/websites/_connect/<id>",
headers={"Authorization": f"Bearer {token}"},
json={
"alias": "string",
"body": "string",
"feed_url": "https://example.org/",
"name": "string"
},
)
answer = response.json()
Answer · 201
{
"alias": "string",
"id": 42
}
Look for the token
/websites/_connect/{id}/checkIn the TXT record at _anahita.<domain>, then in https://<host>/.well-known/anahita-verification.txt. Asked as often as the one connecting likes.
Who: Somebody signed in
Parameters
idintegerrequiredin pathThe connection's id, as starting it answered. At least 1.
Answers
- 200OKsyndication.Checked application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401the viewer is not signed in
- 403the viewer may not connect a website: see WEBSITES_FROM
- 404there is no such connection begun by the viewer
Request
curl -X POST "https://api.example.org/websites/_connect/<id>/check" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/websites/_connect/<id>/check', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/websites/_connect/<id>/check",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"method": "string",
"proved": false
}
A hub checks a subscription
/websites/_websub/{id}/{signature}WebSub: the hub a Website's feed names asks whether its subscription was asked for here, and is answered with its challenge.
Who: Anyone, signed in or not
Parameters
idintegerrequiredin pathThe website's id, and the signature that came with it.signaturestringrequiredin pathhub.modestringin queryWhat the hub checks:subscribe,unsubscribe, ordenied.hub.challengestringin queryWhat to answer with, to show the subscription was asked for here.hub.lease_secondsintegerin queryHow long, in seconds, the hub will call back.
Answers
- 200the challenge, as plain textstring application/json
- 400it is not a check this answers
- 404there is no such Website, or no subscription was asked for
Request
curl "https://api.example.org/websites/_websub/<id>/<signature>"
const response = await fetch('https://api.example.org/websites/_websub/<id>/<signature>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/websites/_websub/<id>/<signature>",
)
answer = response.json()
Answer · 200
"string"
A hub says a feed changed
/websites/_websub/{id}/{signature}The Website's feed is read from its site at once, at most once a minute; what the hub sends is not read.
Who: Anyone, signed in or not
Parameters
idintegerrequiredin pathThe website's id, and the signature that came with it.signaturestringrequiredin pathhub.modestringin queryWhat the hub checks:subscribe,unsubscribe, ordenied.hub.challengestringin queryWhat to answer with, to show the subscription was asked for here.hub.lease_secondsintegerin queryHow long, in seconds, the hub will call back.
Answers
- 202it will be read
- 404there is no such Website, or no subscription was asked for
Request
curl -X POST "https://api.example.org/websites/_websub/<id>/<signature>"
const response = await fetch('https://api.example.org/websites/_websub/<id>/<signature>', {
method: 'POST',
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/websites/_websub/<id>/<signature>",
)
answer = response.json()
Written by Claude, reviewed by Rastin Mehr · About this