Skip to main content

API

The AfriAnnotate API exposes everything the web UI does β€” create projects, import tasks, manage members, export annotations. Use it to script bulk operations, integrate AfriAnnotate into a pipeline, or build a custom client.

Base URL: https://afriannotate.waraka.org/ β€” the URL your hoster gave you, e.g. https://afriannotate.waraka.org/ for the reference deployment or whatever your team's deployment uses.

All endpoints live under /api/. Most are JSON-in, JSON-out; file uploads are multipart/form-data.

Authentication​

API calls authenticate with a Personal Access Token (PAT) β€” a JWT issued from your user profile.

Get a token​

  1. Sign in at afriannotate.waraka.org.
  2. Open the user menu (your avatar, top right) and choose API tokens; it is also a tab on your profile.
  3. Click Make a token in the card's title row.
  4. Give it a name, choose what it may do (Full access or Read only), where it reaches (every project, or only the projects you choose), how long it lasts, and re-enter your account password.
  5. Copy the token immediately β€” AfriAnnotate shows it once.

A token confined to projects answers only those projects' endpoints (and /api/current-user/whoami); anything else gets 403 token_scope_project. A read-only token gets 403 token_scope_readonly on any write. You won't be able to retrieve it later (only regenerate or delete + replace). The list shows a masked stub afterwards.

The token is a JWT carrying the afriannotate_ prefix, so secret scanners recognise it. Use it exactly as copied: the Authorization header, the revoke endpoint (POST /api/token/blacklist/) and the rotate endpoint (POST /api/token/rotate/) all accept the prefixed form. You can hold one active token at a time; use Regenerate… in the same modal to swap it for a fresh one. Tokens don't expire on their own β€” rotate by regenerating.

tip

For scripts that only read data (exports, dashboards), use a Read-only token β€” any write request it makes is rejected. Rotate with Regenerate… + updating the env var.

See Your account β†’ API access tokens for the full management UI.

Use the token in HTTP requests​

Pass the token as a Bearer credential in the Authorization header:

curl https://{{platformHost}}/api/projects/ \
-H 'Authorization: Bearer <your-token>'

That's it. No session cookies, no CSRF dance β€” every authenticated request works with this header.

Use the token from the Python SDK​

The official Python SDK auto-handles the header for you:

from label_studio_sdk import LabelStudio

client = LabelStudio(
base_url="https://{{platformHost}}",
api_key="<your-token>",
)

projects = client.projects.list()
for p in projects:
print(p.id, p.title)

See Python SDK for the full client reference.

Common endpoints​

These are the API endpoints you'll reach for most often. The full endpoint reference is auto-generated from the backend schema:

  • OpenAPI schema: https://afriannotate.waraka.org/docs/api/schema/ (also /docs/api/schema/json/ and /docs/api/schema/yaml/)
  • Browsable docs: /docs/api/schema/swagger-ui/ and /docs/api/schema/redoc/

List your projects​

curl https://{{platformHost}}/api/projects/ \
-H 'Authorization: Bearer <token>'

Returns a paginated list of projects you have access to in your active organisation. Need a project's id (the pk) for most other endpoints.

Create a project​

curl -X POST https://{{platformHost}}/api/projects/ \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "Hausa sentiment v1",
"label_config": "<View><Choices name=\"sentiment\" toName=\"text\"><Choice value=\"positive\"/><Choice value=\"negative\"/></Choices><Text name=\"text\" value=\"$text\"/></View>"
}'

Returns the new project with id. Validate the labeling config ahead of time with POST /api/projects/<id>/validate/.

Import tasks​

JSON-in:

curl -X POST https://{{platformHost}}/api/projects/<id>/import \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '[
{"data": {"text": "Yana da kyau sosai"}},
{"data": {"text": "Ban so wannan ba"}}
]'

Each task's data keys must match the $varname references in the labeling config. See Task format.

List tasks​

curl 'https://{{platformHost}}/api/projects/<id>/tasks/?page=1&page_size=100' \
-H 'Authorization: Bearer <token>'

Export annotations​

# See available export formats for the project
curl https://{{platformHost}}/api/projects/<id>/export/formats \
-H 'Authorization: Bearer <token>'

# Export in JSON
curl 'https://{{platformHost}}/api/projects/<id>/export?exportType=JSON' \
-H 'Authorization: Bearer <token>' \
-o annotations.json

Supported formats include JSON (with full task + annotation structure), JSON_MIN, CSV (flat one-row-per-task), TSV, CoNLL2003, COCO, YOLO, Pascal VOC, brush masks as PNG, and ASR Manifest (for audio) β€” varies by task type.

Check a storage connection's files​

curl -X POST -H "Authorization: Bearer $TOKEN" \
"$HOST/api/storages/health/gcs/12"

gcs, s3 or azure, then the storage id. Returns the file check: status (good, warning, problem, unknown), a summary, the findings with what to do about each, and the files sampled. Project managers only.

Measure the media files of tasks​

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"selectedItems": {"all": true, "excluded": []}}' \
"$HOST/api/dm/actions?id=measure_files&project=42"

Fills meta.media (kind, size, duration, heavy) on each task and answers with how many were measured and how many are left; call it again until left is 0.

Where a bucket file is​

A task's bucket file is fetched through /tasks/<id>/resolve/?fileuri=<base64 of the address>, which redirects to a signed address at the provider. Add &location=1 to get {"url": "…"} instead of the redirect: use it when your request carries an Authorization header, so that header is not sent on to the provider.

Rate limits​

  • Authenticated endpoints: no explicit per-second cap; the practical ceiling is your deployment's concurrency limit. If you're bulk-loading thousands of tasks, batch into groups of ~500-1000 tasks per POST rather than one request per task.
  • Anonymous auth endpoints (/api/auth/forgot-password, /api/auth/account-status, /api/auth/resend-verification): exponential per-(IP, email) backoff. First retry 15-60s, doubles per attempt, caps at 30 min.

Error format​

4xx and 5xx responses return JSON of the form:

{
"id": "5f2a9c1e-…",
"status_code": 400,
"version": "0.1.0",
"detail": "Human-readable detail",
"exc_info": null
}

Validation failures add a validation_errors object keyed by field (with detail set to "Validation error"); some permission errors add a machine-readable error_code. id is a unique exception ID you can quote when reporting problems.

Exception: the anonymous auth endpoints' 429 responses use {"error", "code": "rate_limited", "retry_after", "attempts"} instead β€” see Rate limits.

What's next​