Skip to main content

Task format

A task is the unit of work in AfriAnnotate — one row in a project's dataset. It has input data (what the annotator sees), zero or more annotations (what humans produced), and optionally predictions (what a model produced ahead of time).

The same JSON shape is used for both imports (POST a task in) and exports (snapshot a project out). This page is the reference: read it once and you can write your own importer, exporter, or QA script.

Annotated task JSON​

What a fully-annotated task looks like when exported:

{
"id": 1,
"created_at": "2026-03-09T21:52:49.513742Z",
"updated_at": "2026-03-09T22:16:08.746926Z",
"project": 83,

"data": {
"image": "https://example.com/datasets/cars/1.jpg"
},

"annotations": [
{
"id": 1001,
"result": [
{
"from_name": "tag",
"id": "Dx_aB91ISN",
"source": "$image",
"to_name": "img",
"type": "rectanglelabels",
"value": {
"x": 50.8,
"y": 5.87,
"width": 12.4,
"height": 10.46,
"rotation": 0,
"rectanglelabels": ["Moonwalker"]
}
}
],
"was_cancelled": false,
"ground_truth": false,
"created_at": "2026-03-09T22:16:08.728353Z",
"updated_at": "2026-03-09T22:16:08.728378Z",
"lead_time": 4.288,
"task": 1,
"completed_by": 10
}
],

"predictions": [
{
"model_version": "model-v1",
"score": 0.92,
"result": [
{
"from_name": "tag",
"id": "t5sp3TyXPo",
"source": "$image",
"to_name": "img",
"type": "rectanglelabels",
"value": {
"x": 13.2,
"y": 34.7,
"width": 39.6,
"height": 11.61,
"rotation": 0,
"rectanglelabels": ["Moonwalker"]
}
}
]
}
]
}

Top-level fields​

FieldDescription
idTask identifier within the project. Auto-assigned on import.
dataThe task input. Keys here must match the $varname references in the project's labeling config (e.g. data.image matches <Image value="$image" />).
projectID of the project the task belongs to.
annotationsArray of human annotations on this task. Empty if no one has labeled it yet.
predictionsArray of model-produced annotations (pre-annotations). Same shape as annotations plus a score.
drafts(Export only.) Unsaved in-progress annotations, included when you snapshot via the UI or API.
created_at / updated_atISO-8601 timestamps.

Annotation object​

Each item in annotations[]:

FieldDescription
idIdentifier for this specific annotation. One task can have many annotations from different annotators.
resultThe actual labels — see Result format below.
lead_timeWall-clock seconds the annotator spent on this annotation, including breaks while the tab was open.
was_cancelledtrue when the annotator clicked Skip.
ground_truthtrue when this annotation has been promoted as the canonical reference for the task.
completed_byAnnotator user ID. On import, you can override this — see Specifying annotators on import.
created_at / updated_atTimestamps.
reviews(When the review workflow is enabled.) Array of review entries: { id, created_by, accepted }.

Prediction object​

Same shape as annotations[] with these additions:

FieldDescription
model_versionString tag identifying the model that produced the prediction. Useful when you A/B several model versions.
scoreOverall confidence, 0–1. Used for active-learning ordering.

completed_by, lead_time, ground_truth don't apply.

Result format​

Each item in annotations[i].result[] (or predictions[i].result[]) is one region — a labeled bit of the data:

  • Regions are the selected area: a text span, bounding box, audio segment, choice classification, etc.
  • Result entries carry the labels on that region.

Each region has a unique id per annotation, formed of [A-Za-z0-9_-]. Multiple result entries sharing the same id belong to the same region — that's how a single bounding box can carry both a kind label and a per-region text comment, for example.

Required keys on every result​

FieldDescription
idRegion identifier — shared across result entries that describe the same region.
from_nameName of the control tag that produced this entry (e.g. <Choices name="sentiment" .../>).
to_nameName of the object tag the region applies to (e.g. <Image name="img" .../>).
typeLowercase tag type, e.g. "choices", "rectanglelabels", "labels".
valueTag-specific payload. Shape depends on the tag — see the tag reference for each tag's value schema.

Optional:

  • source — the data variable the region is on (e.g. "$image").
  • parent_id — when a region nests inside another (used by some hierarchical tag types).

Predictions reuse the same region IDs​

When you pre-load predictions on a task and the annotator opens it to review, the predictions are surfaced as draft regions. If the annotator submits without changing them, the region IDs in the saved annotation stay the same as the prediction's IDs — so you can correlate "what the model said" against "what the human accepted" by matching IDs.

Common result shapes​

Image bounding box​

<Image name="img" value="$image"/>
<RectangleLabels name="tag" toName="img">
<Label value="Moonwalker"/>
</RectangleLabels>
{
"from_name": "tag",
"to_name": "img",
"type": "rectanglelabels",
"id": "Dx_aB91ISN",
"value": {
"x": 50.8, "y": 5.87,
"width": 12.4, "height": 10.46,
"rotation": 0,
"rectanglelabels": ["Moonwalker"]
}
}

Coordinates are percentages of the image dimensions, not pixels.

Text span (NER)​

<Text name="txt" value="$text"/>
<Labels name="ent" toName="txt">
<Label value="PER"/>
<Label value="LOC"/>
</Labels>
{
"from_name": "ent",
"to_name": "txt",
"type": "labels",
"id": "spZx2X",
"value": {
"start": 12,
"end": 24,
"text": "Nelson Mandela",
"labels": ["PER"]
}
}

start / end are character offsets into the source string.

Choice classification​

<Text name="txt" value="$text"/>
<Choices name="sentiment" toName="txt" choice="single">
<Choice value="positive"/>
<Choice value="negative"/>
</Choices>
{
"from_name": "sentiment",
"to_name": "txt",
"type": "choices",
"id": "auto_001",
"value": {
"choices": ["positive"]
}
}

Classification doesn't have a geometric region — it gets a synthetic ID so the rest of the schema stays uniform.

Relation between regions​

{
"type": "relation",
"from_id": "Dx_aB91ISN",
"to_id": "RQbW3Sj_Zr",
"direction": "right"
}

A relation refers to two existing region IDs and adds a directional arrow between them.

Per-region text (multiple results, same region ID)​

<Image name="img" value="$image"/>
<RectangleLabels name="product" toName="img">
<Label value="Coffee"/>
</RectangleLabels>
<TextArea name="note" toName="img" perRegion="true"/>

One bounding box, two result entries, same id:

[
{
"id": "X_12fGk",
"from_name": "product",
"to_name": "img",
"type": "rectanglelabels",
"value": {
"x": 10, "y": 20, "width": 30, "height": 40,
"rectanglelabels": ["Coffee"]
}
},
{
"id": "X_12fGk",
"from_name": "note",
"to_name": "img",
"type": "textarea",
"value": {
"text": ["Roasted beans"]
}
}
]

Find every result for one region by filtering result[] for matching id.

Specifying annotators on import​

By default, the user calling the import API becomes the completed_by for every imported annotation. You can override this to attribute imports to specific users — useful when migrating historical labels into AfriAnnotate:

{
"data": { "text": "..." },
"annotations": [
{
"result": [...],
"completed_by": null
},
{
"result": [...],
"completed_by": { "email": "annotator@example.com" }
},
{
"result": [...],
"completed_by": 42
}
]
}
FormBehaviour
Omitted or nullThe user calling the import API is set as the annotator.
Object with emailLooked up among your organisation's members. An unknown email fails the import with Unknown annotator's email … unless the server sets ALLOW_IMPORT_TASKS_WITH_UNKNOWN_EMAILS=true, in which case it falls back to the importing user.
NumberTreated as a user ID. Must be a member of the org.

This applies to imports via the UI, the REST API, and the Python SDK.

What's next​