Accounts and the graph
Data exports and imports
import-export-service
A person's copy of their own data: asking for one, seeing how it is getting on, fetching it and giving it up (data-export.md); and bringing their lists in from a file (moving.md (opens in a new tab)). Optional: an installation may not run it.
The viewer's exports
/exportsAnd how often they may ask, how long an archive is kept, and when they may next ask.
Who: Somebody signed in
Answers
- 200OKimportexport.ExportList application/json
- 401no token, or one that has expired
Request
curl "https://api.example.org/exports" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/exports', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/exports",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"created_at": "2026-10-27T09:30:00Z",
"download_url": "https://example.org/",
"error": "string",
"expires_at": "2026-10-27T09:30:00Z",
"id": "string",
"ready_at": "2026-10-27T09:30:00Z",
"size_bytes": 1,
"status": "string"
}
],
"every_days": 1,
"keep_days": 1,
"next_at": "2026-10-27T09:30:00Z"
}
Ask for a copy of the viewer's data
/exportsEverything they wrote, as a zip archive made in the background: GET /exports/:id says when it is ready, with where to fetch it. One at a time, and one a week unless the installation says otherwise (EXPORT_EVERY_DAYS).
Who: Somebody signed in, who has just proved who they are again
Answers
- 201Createdimportexport.ExportItem application/json
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 409
active: one is being madeRefusal application/json - 429
too_soon: it is not yet time to ask again, and it says whenimportexport.TooSoon application/json
Request
curl -X POST "https://api.example.org/exports" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/exports', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/exports",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 201
{
"created_at": "2026-10-27T09:30:00Z",
"download_url": "https://example.org/",
"error": "string",
"expires_at": "2026-10-27T09:30:00Z",
"id": "string",
"ready_at": "2026-10-27T09:30:00Z",
"size_bytes": 1,
"status": "string"
}
How an export is getting on
/exports/{id}Once it is ready, with an address to fetch it from, good for a few minutes and made afresh each time this is read.
Who: Somebody signed in
Parameters
idstringrequiredin pathThe export's id: a UUID.
Answers
- 200OKimportexport.ExportItem application/json
- 401no token, or one that has expired
- 404the viewer has no such export
Request
curl "https://api.example.org/exports/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/exports/<id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/exports/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"created_at": "2026-10-27T09:30:00Z",
"download_url": "https://example.org/",
"error": "string",
"expires_at": "2026-10-27T09:30:00Z",
"id": "string",
"ready_at": "2026-10-27T09:30:00Z",
"size_bytes": 1,
"status": "string"
}
Give an export up
/exports/{id}Stops it being made, or deletes its archive once it is.
Who: Somebody signed in
Parameters
idstringrequiredin pathThe export's id: a UUID.
Answers
- 204given up, or it was over already
- 401no token, or one that has expired
- 404the viewer has no such export
Request
curl -X DELETE "https://api.example.org/exports/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/exports/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/exports/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
The viewer's imports
/importsThe latest five, newest first, each with how far it has got.
Who: Somebody signed in
Answers
- 200OKimportexport.ImportList application/json
- 401no token, or one that has expired
Request
curl "https://api.example.org/imports" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/imports', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/imports",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"created_at": "2026-10-27T09:30:00Z",
"file_name": "string",
"finished_at": "2026-10-27T09:30:00Z",
"id": "string",
"kinds": [
null
],
"source": "string",
"started_at": "2026-10-27T09:30:00Z",
"status": "string"
}
]
}
Read a file to import
/importsAnother Anahita's download, as a zip, or a single CSV, Mastodon's or Misskey's: who the viewer follows, blocks and mutes, and what they saved. Read at once and kept, ready, with how many of each kind; nothing is done until it is started. One at a time; at most 20,000 lines and 20 MB.
Who: Somebody signed in, with socialgraph:write
Body multipart/form-data
filefilerequiredThe file: another Anahita's download (zip), or a CSV.kindstringWhat a CSV with no header is: "follow", "block", "mute" or "save". Asked of the person when the file says nothing (kind_unknown).
Answers
- 201Createdimportexport.ImportItem application/json
- 400the file could not be read:
empty,unreadable,too_large,too_many_lines,not_supported(lists, blocked servers), orkind_unknown: a CSV with no header, whose kind the viewer is to sayRefusal application/json - 401no token, or one that has expired
- 403
insufficient_scopeRefusal application/json - 409
active: one is waiting or runningRefusal application/json
Request
curl -X POST "https://api.example.org/imports" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg" \
-F "kind=string"
const form = new FormData();
form.append('file', file);
form.append('kind', "string");
const response = await fetch('https://api.example.org/imports', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/imports",
headers={"Authorization": f"Bearer {token}"},
data={"kind": "string"},
files={"file": open("photo.jpg", "rb")},
)
answer = response.json()
Answer · 201
{
"created_at": "2026-10-27T09:30:00Z",
"file_name": "string",
"finished_at": "2026-10-27T09:30:00Z",
"id": "string",
"kinds": [
{
"already": 1,
"done": 1,
"failed": 1,
"kind": "string",
"left": 1,
"skipped": 1,
"total": 1
}
],
"source": "string",
"started_at": "2026-10-27T09:30:00Z",
"status": "string"
}
How an import is getting on
/imports/{id}Who: Somebody signed in
Parameters
idstringrequiredin pathThe import's id.
Answers
- 200OKimportexport.ImportItem application/json
- 401no token, or one that has expired
- 404the viewer has no such import
Request
curl "https://api.example.org/imports/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/imports/<id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/imports/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"created_at": "2026-10-27T09:30:00Z",
"file_name": "string",
"finished_at": "2026-10-27T09:30:00Z",
"id": "string",
"kinds": [
{
"already": 1,
"done": 1,
"failed": 1,
"kind": "string",
"left": 1,
"skipped": 1,
"total": 1
}
],
"source": "string",
"started_at": "2026-10-27T09:30:00Z",
"status": "string"
}
Give an import up
/imports/{id}What it has not done is not done; what it did stays.
Who: Somebody signed in
Parameters
idstringrequiredin pathThe import's id.
Answers
- 204given up, or it was over already
- 401no token, or one that has expired
- 404the viewer has no such import
Request
curl -X DELETE "https://api.example.org/imports/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/imports/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/imports/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
What an import could not do
/imports/{id}/failedA CSV: the kind, who or what, the name, and why in a word.
Who: Somebody signed in
Parameters
idstringrequiredin pathThe import's id.
Answers
- 200the filefile text/csv
- 401no token, or one that has expired
- 404the viewer has no such import
Request
curl "https://api.example.org/imports/<id>/failed" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/imports/<id>/failed', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/imports/<id>/failed",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Start an import
/imports/{id}/startWith the kinds the viewer chose; the lines of the others are skipped. Each line is then done as the viewer would by hand, a line a second, in the background.
Who: Somebody signed in
Parameters
idstringrequiredin path
Body application/json
kindslist of stringThe kinds to bring in, of those the file has: "follow", "block", "mute", "save".
Answers
- 200OKimportexport.ImportItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 404the viewer has no such import
- 409
not_ready: started or given up alreadyRefusal application/json
Request
curl -X POST "https://api.example.org/imports/<id>/start" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kinds": [
"string"
]
}'
const response = await fetch('https://api.example.org/imports/<id>/start', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"kinds": [
"string"
]
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/imports/<id>/start",
headers={"Authorization": f"Bearer {token}"},
json={
"kinds": [
"string"
]
},
)
answer = response.json()
Answer · 200
{
"created_at": "2026-10-27T09:30:00Z",
"file_name": "string",
"finished_at": "2026-10-27T09:30:00Z",
"id": "string",
"kinds": [
{
"already": 1,
"done": 1,
"failed": 1,
"kind": "string",
"left": 1,
"skipped": 1,
"total": 1
}
],
"source": "string",
"started_at": "2026-10-27T09:30:00Z",
"status": "string"
}
Written by Claude, reviewed by Rastin Mehr · About this