Skip to main content

Documentation Index

Fetch the complete documentation index at: https://avala.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

Automate your entire annotation lifecycle through the Avala API. This guide walks through the complete flow from raw data to exported annotations.
This guide requires a PRO or ENTERPRISE organization plan. Contact your Avala account manager to upgrade.

Prerequisites

  • An Avala account with an API key (see Authentication)
  • API key with scopes: datasets.read, datasets.write, projects.read, projects.write, exports.create, work_batches.read
  • A template project set up by your Avala team (defines annotation types, task config, and quality settings)

Overview

1. Upload Data     POST /datasets/manual-upload/
        |
2. Clone Template  POST /projects/create-from-existing/
        |
3. Approve         POST /projects/{uid}/approve/
        |
4. Monitor         GET  /work-batches/  +  Webhooks
        |
5. Export           POST /exports/

Step 1: Upload Your Data

Upload a dataset with files via presigned S3 URLs.
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.avala.ai/api/v1"
headers = {"X-Avala-Api-Key": API_KEY}

# Create dataset and get upload URLs
response = requests.post(
    f"{BASE}/datasets/manual-upload/",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "name": "batch-2026-04-09",
        "data_type": "images",
        "files": [
            {"name": "frame_001.png", "size": 1024000},
            {"name": "frame_002.png", "size": 1024000},
        ],
    },
)
upload_data = response.json()
dataset_uid = upload_data["uid"]

# Upload each file to its presigned URL
for file_info in upload_data["files"]:
    with open(f"./data/{file_info['name']}", "rb") as f:
        requests.put(file_info["upload_url"], data=f)
For large uploads, use the Python SDK’s avala datasets upload CLI command which handles retries and parallelism automatically.

Step 2: Clone a Template Project

Create a new project by cloning a pre-configured template onto your dataset. The template defines the annotation types, task configuration, and quality settings.
# Clone template project onto your dataset
response = requests.post(
    f"{BASE}/projects/create-from-existing/",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "source_project_uid": "TEMPLATE_PROJECT_UID",
        "name": f"batch-2026-04-09",
        "image_dataset_uid": dataset_uid,
    },
)
project = response.json()
project_uid = project["uid"]
# Status is "pending-approval"
Your Avala team will provide the source_project_uid for your template. Each template is pre-configured for your annotation type (bounding boxes, polygons, cuboids, etc.).

Step 3: Approve the Project

Approving starts annotation work. This triggers the full pipeline: task creation, work batches, and coworker assignment.
response = requests.post(
    f"{BASE}/projects/{project_uid}/approve/",
    headers=headers,
)

if response.status_code == 200:
    project = response.json()
    print(f"Project approved: {project['status']}")  # "active"
elif response.status_code == 403:
    error = response.json()
    if error.get("code") == "plan_insufficient":
        print("Upgrade to PRO or ENTERPRISE plan")
    elif error.get("code") == "scope_required":
        print("API key needs 'projects.write' scope")
Approving a project starts billable annotation work. Use pause or cancel to stop work after approval.

Step 4: Monitor Progress

Polling

Check project metrics to track annotation progress:
response = requests.get(
    f"{BASE}/projects/{project_uid}/metrics/",
    headers=headers,
)
metrics = response.json()
print(f"Progress: {metrics.get('completed_tasks', 0)}/{metrics.get('total_tasks', 0)}")
Set up webhooks to receive real-time notifications instead of polling:
# Create a webhook subscription
requests.post(
    f"{BASE}/webhooks/",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "target_url": "https://your-app.example.com/avala-webhook",
        "events": [
            "result.submitted",   # Annotator submits work
            "result.accepted",    # QC approves annotation
            "export.completed",   # Export is ready to download
        ],
    },
)
See Webhooks for event types, signature verification, and retry behavior.

Step 5: Export Results

Once annotations are complete, export them:
# Create an export
response = requests.post(
    f"{BASE}/exports/",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "dataset_uid": dataset_uid,
        "format": "avala_lidar_labels",  # or "coco", "yolo", etc.
    },
)
export_uid = response.json()["uid"]

# Poll for completion (or use the export.completed webhook)
import time
while True:
    resp = requests.get(f"{BASE}/exports/{export_uid}/", headers=headers)
    export = resp.json()
    if export["status"] == "completed":
        print(f"Download: {export['download_url']}")
        break
    time.sleep(10)

Complete Example

Here is the full pipeline in one script:
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.avala.ai/api/v1"
TEMPLATE_UID = "YOUR_TEMPLATE_PROJECT_UID"
headers = {"X-Avala-Api-Key": API_KEY}

# 1. Upload
upload = requests.post(
    f"{BASE}/datasets/manual-upload/",
    headers={**headers, "Content-Type": "application/json"},
    json={"name": "batch-2026-04-09", "data_type": "images", "files": [{"name": "frame.png", "size": 1024}]},
).json()

# 2. Clone
project = requests.post(
    f"{BASE}/projects/create-from-existing/",
    headers={**headers, "Content-Type": "application/json"},
    json={"source_project_uid": TEMPLATE_UID, "name": "batch-2026-04-09", "image_dataset_uid": upload["uid"]},
).json()

# 3. Approve
resp = requests.post(f"{BASE}/projects/{project['uid']}/approve/", headers=headers)
resp.raise_for_status()

# 4. Monitor (poll every 60s using project metrics)
while True:
    metrics = requests.get(f"{BASE}/projects/{project['uid']}/metrics/", headers=headers).json()
    total = metrics.get("total_tasks", 0)
    completed = metrics.get("completed_tasks", 0)
    if total > 0 and completed >= total:
        break
    print(f"Progress: {completed}/{total} tasks complete...")
    time.sleep(60)

# 5. Export
export = requests.post(
    f"{BASE}/exports/",
    headers={**headers, "Content-Type": "application/json"},
    json={"dataset_uid": upload["uid"], "format": "avala_lidar_labels"},
).json()

print(f"Export started: {export['uid']}")

Error Handling

Your pipeline should handle these error codes:
CodeHTTPMeaningAction
plan_insufficient403Org needs PRO/ENTERPRISE planContact Avala to upgrade
scope_required403API key missing required scopeCreate new key with correct scopes
400Invalid state transitionCheck project status before approving
404Resource not foundVerify UIDs are correct
429Rate limitedRetry with exponential backoff
See Error Codes for the full reference.

Next Steps