Setup
from avala import Client
client = Client() # reads AVALA_API_KEY env var
import Avala from "@avala-ai/sdk";
const avala = new Avala(); // reads AVALA_API_KEY env var
export AVALA_API_KEY="your-api-key-here"
export BASE_URL="https://api.avala.ai/api/v1"
Dataset Management
List Datasets
Retrieve all datasets accessible to your account.page = client.datasets.list(limit=20)
for dataset in page:
print(f"{dataset.name} ({dataset.uid})")
const page = await avala.datasets.list({ limit: 20 });
page.items.forEach(d => console.log(`${d.name} (${d.uid})`));
curl -s "$BASE_URL/datasets/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.name) (\(.uid))"'
Get Dataset Details
Fetch details for a specific dataset by UID.dataset = client.datasets.get("dataset-uid-here")
print(f"Name: {dataset.name}")
print(f"Slug: {dataset.slug}")
const dataset = await avala.datasets.get("dataset-uid-here");
console.log(`Name: ${dataset.name}`);
console.log(`Slug: ${dataset.slug}`);
curl -s "$BASE_URL/datasets/acme-ai/training-v2/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.'
Project Workflows
List Projects
Retrieve projects accessible to your account.page = client.projects.list()
for project in page:
print(f"{project.name} ({project.uid})")
const page = await avala.projects.list();
page.items.forEach(p => console.log(`${p.name} (${p.uid})`));
curl -s "$BASE_URL/projects/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.name)"'
Get Project Metrics
Check the progress and quality metrics for a project.import requests
import os
# Project metrics are available via the REST API
response = requests.get(
f"https://api.avala.ai/api/v1/projects/proj-uuid-001/metrics/",
headers={"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}
)
metrics = response.json()
completion = (metrics["completed_tasks"] / metrics["total_tasks"]) * 100
print(f"Progress: {completion:.1f}%")
print(f"Acceptance rate: {metrics['acceptance_rate'] * 100:.1f}%")
const response = await fetch(
`https://api.avala.ai/api/v1/projects/proj-uuid-001/metrics/`,
{ headers: { "X-Avala-Api-Key": process.env.AVALA_API_KEY! } }
);
const metrics = await response.json();
const completion = (metrics.completed_tasks / metrics.total_tasks) * 100;
console.log(`Progress: ${completion.toFixed(1)}%`);
console.log(`Acceptance rate: ${(metrics.acceptance_rate * 100).toFixed(1)}%`);
curl -s "$BASE_URL/projects/proj-uuid-001/metrics/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '{
completion: ((.completed_tasks / .total_tasks) * 100),
acceptance_rate: (.acceptance_rate * 100)
}'
Export Pipeline
Create Export, Poll for Completion, and Download
A complete workflow for exporting annotation data from a project.import time
# Create the export
export = client.exports.create(project="proj-uuid-001")
print(f"Export started: {export.uid}")
# Poll for completion
while True:
export = client.exports.get(export.uid)
if export.status == "completed":
print(f"Download URL: {export.download_url}")
break
elif export.status == "failed":
raise Exception("Export failed")
time.sleep(5)
// Create the export
let exp = await avala.exports.create({ project: "proj-uuid-001" });
console.log(`Export started: ${exp.uid}`);
// Poll for completion
while (true) {
exp = await avala.exports.get(exp.uid);
if (exp.status === "completed") {
console.log(`Download URL: ${exp.downloadUrl}`);
break;
} else if (exp.status === "failed") {
throw new Error("Export failed");
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
# Step 1: Create the export
EXPORT_RESPONSE=$(curl -s -X POST "$BASE_URL/exports/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": "proj-uuid-001"}')
EXPORT_UID=$(echo $EXPORT_RESPONSE | jq -r '.uid')
echo "Export created: $EXPORT_UID"
# Step 2: Poll for completion
while true; do
STATUS_RESPONSE=$(curl -s "$BASE_URL/exports/$EXPORT_UID/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY")
STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ]; then
DOWNLOAD_URL=$(echo $STATUS_RESPONSE | jq -r '.download_url')
echo "Download URL: $DOWNLOAD_URL"
break
elif [ "$STATUS" = "failed" ]; then
echo "Export failed"
exit 1
fi
sleep 5
done
# Step 3: Download the export
curl -L -o export.zip "$DOWNLOAD_URL"
echo "Export downloaded to export.zip"
Annotation Retrieval
List Tasks
Retrieve tasks for a project, optionally filtering by status.page = client.tasks.list(project="proj-uuid-001", status="completed")
for task in page:
print(f"Task {task.uid}")
const page = await avala.tasks.list({ project: "proj-uuid-001", status: "completed" });
page.items.forEach(t => console.log(`Task ${t.uid}`));
curl -s "$BASE_URL/tasks/?project=proj-uuid-001&status=completed" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | .uid'
Organization Management
List Members
Retrieve all members of your organization.import requests
import os
# Organization management is available via the REST API
response = requests.get(
"https://api.avala.ai/api/v1/organizations/acme-ai/members/",
headers={"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}
)
members = response.json()["results"]
for member in members:
print(f"{member['user']['username']} - {member['role']}")
const response = await fetch(
"https://api.avala.ai/api/v1/organizations/acme-ai/members/",
{ headers: { "X-Avala-Api-Key": process.env.AVALA_API_KEY! } }
);
const data = await response.json();
for (const member of data.results) {
console.log(`${member.user.username} - ${member.role}`);
}
curl -s "$BASE_URL/organizations/acme-ai/members/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.user.username) - \(.role)"'
Send Invitation
Invite a new member to your organization.import requests
import os
response = requests.post(
"https://api.avala.ai/api/v1/organizations/acme-ai/invitations/",
headers={
"X-Avala-Api-Key": os.environ["AVALA_API_KEY"],
"Content-Type": "application/json",
},
json={
"email": "newuser@example.com",
"role": "annotator"
}
)
invitation = response.json()
print(f"Invitation sent to {invitation['email']}")
const response = await fetch(
"https://api.avala.ai/api/v1/organizations/acme-ai/invitations/",
{
method: "POST",
headers: {
"X-Avala-Api-Key": process.env.AVALA_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "newuser@example.com",
role: "annotator",
}),
}
);
const invitation = await response.json();
console.log(`Invitation sent to ${invitation.email}`);
curl -s -X POST "$BASE_URL/organizations/acme-ai/invitations/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "newuser@example.com", "role": "annotator"}' | jq '.'
Error Handling
Using SDK Error Classes
Handle errors using the SDK’s built-in error classes.from avala import Client
from avala.errors import AvalaError, AuthenticationError, NotFoundError, RateLimitError
client = Client()
try:
dataset = client.datasets.get("nonexistent-uid")
except NotFoundError:
print("Dataset not found")
except RateLimitError:
print("Rate limited — try again later")
except AuthenticationError:
print("Invalid API key")
except AvalaError as e:
print(f"API error: {e}")
import Avala, { AvalaError, NotFoundError, RateLimitError } from "@avala-ai/sdk";
const avala = new Avala();
try {
const dataset = await avala.datasets.get("nonexistent-uid");
} catch (e) {
if (e instanceof NotFoundError) {
console.log("Dataset not found");
} else if (e instanceof RateLimitError) {
console.log("Rate limited — try again later");
} else if (e instanceof AvalaError) {
console.log(`API error: ${e.message}`);
}
}
# Check the HTTP status code for errors
HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" \
"$BASE_URL/datasets/nonexistent/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY")
if [ "$HTTP_CODE" -eq 404 ]; then
echo "Dataset not found"
elif [ "$HTTP_CODE" -eq 429 ]; then
echo "Rate limited"
else
cat response.json
fi
Pagination
Iterate Through All Results
Fetch all pages of a paginated endpoint.# CursorPage supports iteration and auto-pagination
page = client.datasets.list(limit=20)
all_datasets = []
while True:
for dataset in page:
all_datasets.append(dataset)
if not page.has_more:
break
page = client.datasets.list(cursor=page.next_cursor, limit=20)
print(f"Total datasets: {len(all_datasets)}")
const allDatasets: any[] = [];
let page = await avala.datasets.list({ limit: 20 });
while (true) {
allDatasets.push(...page.items);
if (!page.hasMore) break;
page = await avala.datasets.list({ cursor: page.nextCursor, limit: 20 });
}
console.log(`Total datasets: ${allDatasets.length}`);
# Using the REST API's cursor-based pagination
URL="$BASE_URL/datasets/"
while [ -n "$URL" ] && [ "$URL" != "null" ]; do
RESPONSE=$(curl -s "$URL" -H "X-Avala-Api-Key: $AVALA_API_KEY")
echo "$RESPONSE" | jq '.results'
URL=$(echo "$RESPONSE" | jq -r '.next')
done
Advanced Patterns
Retry with Exponential Backoff
The SDKs raiseRateLimitError when you hit the rate limit. Build a retry wrapper that respects the Retry-After header.
import time
import random
from avala.errors import RateLimitError, ServerError
def with_retry(fn, max_retries=5):
"""Call fn() with exponential backoff on rate limit or server errors."""
last_error = None
for attempt in range(max_retries):
try:
return fn()
except RateLimitError as e:
last_error = e
wait = e.retry_after or (2 ** attempt + random.random())
print(f"Rate limited. Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
time.sleep(wait)
except ServerError as e:
last_error = e
wait = 2 ** attempt + random.random()
print(f"Server error. Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
time.sleep(wait)
raise last_error or Exception(f"Failed after {max_retries} retries")
# Usage
dataset = with_retry(lambda: client.datasets.get("ds_abc123"))
import { RateLimitError, ServerError } from "@avala-ai/sdk";
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (e) {
if (e instanceof RateLimitError) {
const wait = e.retryAfter ?? 2 ** attempt + Math.random();
console.log(`Rate limited. Retrying in ${wait.toFixed(1)}s (attempt ${attempt + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, wait * 1000));
} else if (e instanceof ServerError) {
const wait = 2 ** attempt + Math.random();
console.log(`Server error. Retrying in ${wait.toFixed(1)}s (attempt ${attempt + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, wait * 1000));
} else {
throw e;
}
}
}
throw new Error(`Failed after ${maxRetries} retries`);
}
// Usage
const dataset = await withRetry(() => avala.datasets.get("ds_abc123"));
# Retry with exponential backoff
retry_request() {
local url="$1" attempt=0 max_retries=5
while [ $attempt -lt $max_retries ]; do
HTTP_CODE=$(curl -s -o /tmp/response.json -D /tmp/response_headers -w "%{http_code}" \
"$url" -H "X-Avala-Api-Key: $AVALA_API_KEY")
if [ "$HTTP_CODE" -eq 200 ]; then
cat /tmp/response.json
return 0
elif [ "$HTTP_CODE" -eq 429 ]; then
WAIT=$(grep -i "retry-after" /tmp/response_headers | awk '{print $2}' || echo $((2 ** attempt)))
echo "Rate limited. Retrying in ${WAIT}s..." >&2
sleep "$WAIT"
elif [ "$HTTP_CODE" -ge 500 ]; then
WAIT=$((2 ** attempt))
echo "Server error ($HTTP_CODE). Retrying in ${WAIT}s..." >&2
sleep "$WAIT"
else
cat /tmp/response.json
return 1
fi
attempt=$((attempt + 1))
done
echo "Failed after $max_retries retries" >&2
return 1
}
retry_request "$BASE_URL/datasets/ds_abc123/"
Async Batch Processing
Use the async client to process multiple items concurrently with controlled parallelism.import asyncio
from avala import AsyncClient
async def main():
async with AsyncClient() as client:
# Collect all dataset UIDs across pages
dataset_uids = []
page = await client.datasets.list(limit=50)
while True:
for ds in page:
dataset_uids.append(ds.uid)
if not page.has_more:
break
page = await client.datasets.list(cursor=page.next_cursor, limit=50)
# Process in batches of 10 to avoid rate limits
batch_size = 10
results = []
for i in range(0, len(dataset_uids), batch_size):
batch = dataset_uids[i : i + batch_size]
batch_results = await asyncio.gather(
*[client.datasets.get(uid) for uid in batch]
)
results.extend(batch_results)
for ds in results:
print(f"{ds.name}: {ds.item_count} items")
asyncio.run(main())
import Avala from "@avala-ai/sdk";
const avala = new Avala();
// Collect all dataset UIDs across pages
const datasetUids: string[] = [];
let page = await avala.datasets.list({ limit: 50 });
while (true) {
datasetUids.push(...page.items.map((d) => d.uid));
if (!page.hasMore) break;
page = await avala.datasets.list({ cursor: page.nextCursor!, limit: 50 });
}
// Process in batches of 10 to avoid rate limits
const batchSize = 10;
const results = [];
for (let i = 0; i < datasetUids.length; i += batchSize) {
const batch = datasetUids.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map((uid) => avala.datasets.get(uid))
);
results.push(...batchResults);
}
for (const ds of results) {
console.log(`${ds.name}: ${ds.itemCount} items`);
}
Export with Timeout and Error Recovery
A production-ready export workflow with a timeout, progress logging, and proper error handling.import time
from avala.errors import AvalaError
def export_project(client, project_uid, timeout_seconds=600, poll_interval=5):
"""Export a project and wait for completion.
Returns the download URL on success.
Raises TimeoutError if the export doesn't complete in time.
"""
export = client.exports.create(project=project_uid)
print(f"Export {export.uid} started")
deadline = time.time() + timeout_seconds
while time.time() < deadline:
export = client.exports.get(export.uid)
if export.status == "completed":
print(f"Export completed: {export.download_url}")
return export.download_url
elif export.status == "failed":
raise RuntimeError(f"Export {export.uid} failed")
elapsed = timeout_seconds - (deadline - time.time())
print(f" status={export.status} ({elapsed:.0f}s elapsed)")
time.sleep(poll_interval)
raise TimeoutError(f"Export {export.uid} did not complete within {timeout_seconds}s")
# Usage
try:
url = export_project(client, "proj_abc123", timeout_seconds=300)
except TimeoutError:
print("Export timed out — try again or contact support")
except AvalaError as e:
print(f"API error: {e}")
import Avala, { AvalaError } from "@avala-ai/sdk";
async function exportProject(
avala: Avala,
projectUid: string,
timeoutMs = 600_000,
pollIntervalMs = 5_000
): Promise<string> {
let exp = await avala.exports.create({ project: projectUid });
console.log(`Export ${exp.uid} started`);
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
exp = await avala.exports.get(exp.uid);
if (exp.status === "completed") {
console.log(`Export completed: ${exp.downloadUrl}`);
return exp.downloadUrl!;
} else if (exp.status === "failed") {
throw new Error(`Export ${exp.uid} failed`);
}
const elapsed = ((timeoutMs - (deadline - Date.now())) / 1000).toFixed(0);
console.log(` status=${exp.status} (${elapsed}s elapsed)`);
await new Promise((r) => setTimeout(r, pollIntervalMs));
}
throw new Error(`Export ${exp.uid} did not complete within ${timeoutMs / 1000}s`);
}
// Usage
const avala = new Avala();
try {
const url = await exportProject(avala, "proj_abc123", 300_000);
} catch (e) {
if (e instanceof AvalaError) {
console.error(`API error: ${e.message}`);
} else {
console.error(e);
}
}
Upload Items via REST API
The SDKs focus on read operations. To upload items to a dataset, use the REST API directly.import os
import requests
from pathlib import Path
API_KEY = os.environ["AVALA_API_KEY"]
BASE = "https://api.avala.ai/api/v1"
HEADERS = {"X-Avala-Api-Key": API_KEY}
def upload_items(dataset_uid, file_paths):
"""Upload a list of files to a dataset."""
uploaded = []
for path in file_paths:
path = Path(path)
with open(path, "rb") as f:
response = requests.post(
f"{BASE}/datasets/{dataset_uid}/items/",
headers=HEADERS,
files={"file": (path.name, f)},
)
response.raise_for_status()
item = response.json()
uploaded.append(item["uid"])
print(f"Uploaded {path.name} -> {item['uid']}")
return uploaded
# Upload all PNGs from a directory
images = sorted(Path("./training-data").glob("*.png"))
item_uids = upload_items("ds_abc123", images)
print(f"Uploaded {len(item_uids)} items")
import fs from "node:fs";
import path from "node:path";
const API_KEY = process.env.AVALA_API_KEY!;
const BASE = "https://api.avala.ai/api/v1";
async function uploadItems(datasetUid: string, filePaths: string[]) {
const uploaded: string[] = [];
for (const filePath of filePaths) {
const file = new Blob([fs.readFileSync(filePath)]);
const form = new FormData();
form.append("file", file, path.basename(filePath));
const response = await fetch(`${BASE}/datasets/${datasetUid}/items/`, {
method: "POST",
headers: { "X-Avala-Api-Key": API_KEY },
body: form,
});
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const item = await response.json();
uploaded.push(item.uid);
console.log(`Uploaded ${path.basename(filePath)} -> ${item.uid}`);
}
return uploaded;
}
// Upload files
const files = fs.readdirSync("./training-data")
.filter((f) => f.endsWith(".png"))
.map((f) => path.join("./training-data", f));
const itemUids = await uploadItems("ds_abc123", files);
console.log(`Uploaded ${itemUids.length} items`);
# Upload all PNG files in a directory
DATASET_UID="ds_abc123"
for FILE in ./training-data/*.png; do
RESPONSE=$(curl -s -X POST "$BASE_URL/datasets/$DATASET_UID/items/" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" \
-F "file=@$FILE")
UID=$(echo "$RESPONSE" | jq -r '.uid')
echo "Uploaded $(basename $FILE) -> $UID"
done
Batch Export Multiple Projects
Export several projects in parallel and wait for all to complete.import asyncio
import time
from avala import AsyncClient
async def export_and_wait(client, project_uid, timeout=600, poll_interval=5):
export = await client.exports.create(project=project_uid)
deadline = time.time() + timeout
while time.time() < deadline:
export = await client.exports.get(export.uid)
if export.status == "completed":
return {"project": project_uid, "url": export.download_url}
elif export.status == "failed":
return {"project": project_uid, "error": "Export failed"}
await asyncio.sleep(poll_interval)
return {"project": project_uid, "error": "Timed out"}
async def main():
project_uids = ["proj_001", "proj_002", "proj_003"]
async with AsyncClient() as client:
results = await asyncio.gather(
*[export_and_wait(client, uid) for uid in project_uids]
)
for result in results:
if "url" in result:
print(f"{result['project']}: {result['url']}")
else:
print(f"{result['project']}: FAILED — {result['error']}")
asyncio.run(main())
import Avala from "@avala-ai/sdk";
async function exportAndWait(avala: Avala, projectUid: string, timeoutMs = 600_000) {
let exp = await avala.exports.create({ project: projectUid });
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
exp = await avala.exports.get(exp.uid);
if (exp.status === "completed") return { project: projectUid, url: exp.downloadUrl! };
if (exp.status === "failed") return { project: projectUid, error: "Export failed" };
await new Promise((r) => setTimeout(r, 5000));
}
return { project: projectUid, error: "Timed out" };
}
const avala = new Avala();
const projectUids = ["proj_001", "proj_002", "proj_003"];
const results = await Promise.all(
projectUids.map((uid) => exportAndWait(avala, uid))
);
for (const result of results) {
if ("url" in result) {
console.log(`${result.project}: ${result.url}`);
} else {
console.log(`${result.project}: FAILED — ${result.error}`);
}
}
Monitor Rate Limit Usage
Check your remaining rate limit budget before starting batch operations.# Make any request to populate rate limit info
client.datasets.list(limit=1)
info = client.rate_limit_info
remaining = int(info.get("remaining") or 0)
limit = int(info.get("limit") or 0)
print(f"Rate limit: {remaining}/{limit} requests remaining")
if remaining < 50:
print("Warning: low rate limit budget. Consider slowing down requests.")
// Make any request to populate rate limit info
await avala.datasets.list({ limit: 1 });
const info = avala.rateLimitInfo;
console.log(`Rate limit: ${info.remaining ?? "?"}/${info.limit ?? "?"} requests remaining`);
if (info.remaining !== null && Number(info.remaining) < 50) {
console.log("Warning: low rate limit budget. Consider slowing down requests.");
}
# Inspect rate limit headers from any response
curl -s -D - "$BASE_URL/datasets/?limit=1" \
-H "X-Avala-Api-Key: $AVALA_API_KEY" -o /dev/null 2>&1 | \
grep -i "x-ratelimit"
# Example output:
# X-RateLimit-Limit: 100
# X-RateLimit-Remaining: 87
# X-RateLimit-Reset: 1708523460