> ## 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.

# Examples

> Practical code examples for common Avala workflows

Practical code examples for working with the Avala platform using the official SDKs and REST API. Covers basic operations, advanced patterns like retry logic and async batch processing, and production workflows.

## Setup

<CodeGroup>
  ```python Python theme={null}
  from avala import Client

  client = Client()  # reads AVALA_API_KEY env var
  ```

  ```typescript TypeScript theme={null}
  import Avala from "@avala-ai/sdk";

  const avala = new Avala();  // reads AVALA_API_KEY env var
  ```

  ```bash cURL theme={null}
  export AVALA_API_KEY="your-api-key-here"
  export BASE_URL="https://api.avala.ai/api/v1"
  ```
</CodeGroup>

***

## Dataset Management

### List Datasets

Retrieve all datasets accessible to your account.

<CodeGroup>
  ```python Python theme={null}
  page = client.datasets.list(limit=20)
  for dataset in page:
      print(f"{dataset.name} ({dataset.uid})")
  ```

  ```typescript TypeScript theme={null}
  const page = await avala.datasets.list({ limit: 20 });
  page.items.forEach(d => console.log(`${d.name} (${d.uid})`));
  ```

  ```bash cURL theme={null}
  curl -s "$BASE_URL/datasets/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.name) (\(.uid))"'
  ```
</CodeGroup>

### Get Dataset Details

Fetch details for a specific dataset by UID.

<CodeGroup>
  ```python Python theme={null}
  dataset = client.datasets.get("dataset-uid-here")
  print(f"Name: {dataset.name}")
  print(f"Slug: {dataset.slug}")
  ```

  ```typescript TypeScript theme={null}
  const dataset = await avala.datasets.get("dataset-uid-here");
  console.log(`Name: ${dataset.name}`);
  console.log(`Slug: ${dataset.slug}`);
  ```

  ```bash cURL theme={null}
  curl -s "$BASE_URL/datasets/acme-ai/training-v2/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.'
  ```
</CodeGroup>

***

## Project Workflows

### List Projects

Retrieve projects accessible to your account.

<CodeGroup>
  ```python Python theme={null}
  page = client.projects.list()
  for project in page:
      print(f"{project.name} ({project.uid})")
  ```

  ```typescript TypeScript theme={null}
  const page = await avala.projects.list();
  page.items.forEach(p => console.log(`${p.name} (${p.uid})`));
  ```

  ```bash cURL theme={null}
  curl -s "$BASE_URL/projects/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.name)"'
  ```
</CodeGroup>

### Get Project Metrics

Check the progress and quality metrics for a project.

<CodeGroup>
  ```python Python theme={null}
  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}%")
  ```

  ```typescript TypeScript theme={null}
  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)}%`);
  ```

  ```bash cURL theme={null}
  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)
    }'
  ```
</CodeGroup>

***

## Export Pipeline

### Create Export, Poll for Completion, and Download

A complete workflow for exporting annotation data from a project.

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  // 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));
  }
  ```

  ```bash cURL theme={null}
  # 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"
  ```
</CodeGroup>

***

## Annotation Retrieval

### List Tasks

Retrieve tasks for a project, optionally filtering by status.

<CodeGroup>
  ```python Python theme={null}
  page = client.tasks.list(project="proj-uuid-001", status="completed")
  for task in page:
      print(f"Task {task.uid}")
  ```

  ```typescript TypeScript theme={null}
  const page = await avala.tasks.list({ project: "proj-uuid-001", status: "completed" });
  page.items.forEach(t => console.log(`Task ${t.uid}`));
  ```

  ```bash cURL theme={null}
  curl -s "$BASE_URL/tasks/?project=proj-uuid-001&status=completed" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | .uid'
  ```
</CodeGroup>

***

## Organization Management

### List Members

Retrieve all members of your organization.

<CodeGroup>
  ```python Python theme={null}
  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']}")
  ```

  ```typescript TypeScript theme={null}
  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}`);
  }
  ```

  ```bash cURL theme={null}
  curl -s "$BASE_URL/organizations/acme-ai/members/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq '.results[] | "\(.user.username) - \(.role)"'
  ```
</CodeGroup>

### Send Invitation

Invite a new member to your organization.

<CodeGroup>
  ```python Python theme={null}
  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']}")
  ```

  ```typescript TypeScript theme={null}
  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}`);
  ```

  ```bash cURL theme={null}
  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 '.'
  ```
</CodeGroup>

***

## Error Handling

### Using SDK Error Classes

Handle errors using the SDK's built-in error classes.

<CodeGroup>
  ```python Python theme={null}
  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}")
  ```

  ```typescript TypeScript theme={null}
  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}`);
    }
  }
  ```

  ```bash cURL theme={null}
  # 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
  ```
</CodeGroup>

***

## Pagination

### Iterate Through All Results

Fetch all pages of a paginated endpoint.

<CodeGroup>
  ```python Python theme={null}
  # 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)}")
  ```

  ```typescript TypeScript theme={null}
  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}`);
  ```

  ```bash cURL theme={null}
  # 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
  ```
</CodeGroup>

***

## Advanced Patterns

### Retry with Exponential Backoff

The SDKs raise `RateLimitError` when you hit the rate limit. Build a retry wrapper that respects the `Retry-After` header.

<CodeGroup>
  ```python Python theme={null}
  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"))
  ```

  ```typescript TypeScript theme={null}
  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"));
  ```

  ```bash cURL theme={null}
  # 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/"
  ```
</CodeGroup>

### Async Batch Processing

Use the async client to process multiple items concurrently with controlled parallelism.

<CodeGroup>
  ```python Python theme={null}
  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())
  ```

  ```typescript TypeScript theme={null}
  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`);
  }
  ```
</CodeGroup>

### Export with Timeout and Error Recovery

A production-ready export workflow with a timeout, progress logging, and proper error handling.

<CodeGroup>
  ```python Python theme={null}
  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}")
  ```

  ```typescript TypeScript theme={null}
  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);
    }
  }
  ```
</CodeGroup>

### Upload Items via REST API

The SDKs focus on read operations. To upload items to a dataset, use the REST API directly.

<CodeGroup>
  ```python Python theme={null}
  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")
  ```

  ```typescript TypeScript theme={null}
  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`);
  ```

  ```bash cURL theme={null}
  # 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
  ```
</CodeGroup>

### Batch Export Multiple Projects

Export several projects in parallel and wait for all to complete.

<CodeGroup>
  ```python Python theme={null}
  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())
  ```

  ```typescript TypeScript theme={null}
  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}`);
    }
  }
  ```
</CodeGroup>

### Monitor Rate Limit Usage

Check your remaining rate limit budget before starting batch operations.

<CodeGroup>
  ```python Python theme={null}
  # 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.")
  ```

  ```typescript TypeScript theme={null}
  // 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.");
  }
  ```

  ```bash cURL theme={null}
  # 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
  ```
</CodeGroup>
