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

# REST API

> Use the Avala API directly with HTTP requests

You can call the Avala API directly over HTTP from any programming language, shell script, or automation tool. No SDK required.

## Base URL

All API requests are made to:

```
https://api.avala.ai/api/v1
```

## Authentication

Every request must include your API key in the `X-Avala-Api-Key` header.

```
X-Avala-Api-Key: your-api-key
```

You can find your API key in the [Avala dashboard](https://avala.ai/settings?section=security) under **Settings > Security**.

## Making Requests

Below are examples of listing datasets across several languages.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.avala.ai/api/v1/datasets/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" \
    -H "Content-Type: application/json"
  ```

  ```python Python (requests) theme={null}
  import requests
  import os

  response = requests.get(
      "https://api.avala.ai/api/v1/datasets/",
      headers={
          "X-Avala-Api-Key": os.environ["AVALA_API_KEY"],
          "Content-Type": "application/json",
      },
  )

  data = response.json()
  for dataset in data["results"]:
      print(dataset["name"], dataset["uid"])
  ```

  ```go Go (net/http) theme={null}
  package main

  import (
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"
  )

  func main() {
  	req, _ := http.NewRequest("GET", "https://api.avala.ai/api/v1/datasets/", nil)
  	req.Header.Set("X-Avala-Api-Key", os.Getenv("AVALA_API_KEY"))
  	req.Header.Set("Content-Type", "application/json")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer resp.Body.Close()

  	var result map[string]interface{}
  	json.NewDecoder(resp.Body).Decode(&result)
  	fmt.Println(result)
  }
  ```

  ```ruby Ruby theme={null}
  require "net/http"
  require "json"

  uri = URI("https://api.avala.ai/api/v1/datasets/")

  request = Net::HTTP::Get.new(uri)
  request["X-Avala-Api-Key"] = ENV["AVALA_API_KEY"]
  request["Content-Type"] = "application/json"

  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
    http.request(request)
  end

  data = JSON.parse(response.body)
  data["results"].each do |dataset|
    puts "#{dataset["name"]} (#{dataset["uid"]})"
  end
  ```

  ```php PHP theme={null}
  <?php

  $ch = curl_init();

  curl_setopt_array($ch, [
      CURLOPT_URL => "https://api.avala.ai/api/v1/datasets/",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          "X-Avala-Api-Key: " . getenv("AVALA_API_KEY"),
          "Content-Type: application/json",
      ],
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  $data = json_decode($response, true);
  foreach ($data["results"] as $dataset) {
      echo $dataset["name"] . " (" . $dataset["uid"] . ")\n";
  }
  ```
</CodeGroup>

## Handling Pagination

List endpoints return paginated results using cursor-based pagination with `next`, `previous`, and `results` fields.

**Response structure:**

```json theme={null}
{
  "next": "https://api.avala.ai/api/v1/datasets/?cursor=cD0yMDI0...",
  "previous": null,
  "results": [...]
}
```

| Field      | Type           | Description                                                    |
| ---------- | -------------- | -------------------------------------------------------------- |
| `next`     | string \| null | URL for the next page, or `null` if this is the last page      |
| `previous` | string \| null | URL for the previous page, or `null` if this is the first page |
| `results`  | array          | Array of resource objects for the current page                 |

**Paginating through all results:**

<CodeGroup>
  ```bash cURL theme={null}
  # First page
  curl -s "https://api.avala.ai/api/v1/datasets/?limit=50" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq .

  # Next page (use the full `next` URL from previous response)
  curl -s "https://api.avala.ai/api/v1/datasets/?cursor=cD0yMDI0&limit=50" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq .
  ```

  ```python Python (requests) theme={null}
  import requests
  import os

  base_url = "https://api.avala.ai/api/v1"
  headers = {"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}

  all_datasets = []
  url = f"{base_url}/datasets/?limit=50"

  while url:
      response = requests.get(url, headers=headers)
      data = response.json()

      all_datasets.extend(data["results"])
      url = data.get("next")

  print(f"Fetched {len(all_datasets)} datasets")
  ```
</CodeGroup>

## Error Handling

The API returns standard HTTP status codes. Error responses include a JSON body with details.

**Error response format:**

```json theme={null}
{
  "detail": "Not found."
}
```

For validation errors, DRF returns field-level errors:

```json theme={null}
{
  "field_name": ["This field is required."]
}
```

| Status Code | Meaning                                                                            |
| ----------- | ---------------------------------------------------------------------------------- |
| `200`       | Success                                                                            |
| `201`       | Created                                                                            |
| `400`       | Bad request — check the `detail` field or field-level errors in the response body. |
| `401`       | Unauthorized — invalid or missing API key.                                         |
| `404`       | Not found — the resource does not exist.                                           |
| `429`       | Rate limited — too many requests. Respect the `Retry-After` header.                |
| `500`       | Internal server error — try again later.                                           |

### Handling Errors in Code

<CodeGroup>
  ```python Python (requests) theme={null}
  import requests
  import os
  import time

  headers = {"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}

  response = requests.get(
      "https://api.avala.ai/api/v1/datasets/nonexistent/",
      headers=headers,
  )

  if response.status_code == 200:
      data = response.json()
  elif response.status_code == 401:
      print("Invalid API key. Check your AVALA_API_KEY.")
  elif response.status_code == 404:
      print("Resource not found.")
  elif response.status_code == 429:
      retry_after = int(response.headers.get("Retry-After", 60))
      print(f"Rate limited. Retrying in {retry_after}s...")
      time.sleep(retry_after)
  else:
      print(f"Error {response.status_code}: {response.json().get('detail', 'Unknown error')}")
  ```

  ```bash cURL theme={null}
  # Check the HTTP status code
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    "https://api.avala.ai/api/v1/datasets/nonexistent/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY")

  if [ "$STATUS" = "429" ]; then
    echo "Rate limited. Check Retry-After header."
  elif [ "$STATUS" != "200" ]; then
    echo "Error: HTTP $STATUS"
  fi
  ```
</CodeGroup>

<Warning>
  When you receive a `429` response, always check the `Retry-After` header for the number of seconds to wait before retrying. The `X-RateLimit-Remaining` header on every response tells you how many requests you have left.
</Warning>

## File Uploads

File uploads use presigned URLs rather than direct multipart uploads to the API. The general flow is:

1. Request a presigned upload URL from the API
2. Upload the file directly to cloud storage using the presigned URL
3. Confirm the upload with the API

<CodeGroup>
  ```bash cURL theme={null}
  # Step 1: Request presigned URL
  UPLOAD_INFO=$(curl -s -X POST "https://api.avala.ai/api/v1/datasets/manual-upload/file-upload-url/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"file_path_in_dataset": "image001.jpg", "dataset_name": "my-dataset"}')

  PRESIGNED_URL=$(echo $UPLOAD_INFO | jq -r '.url')

  # Step 2: Upload to cloud storage using the presigned POST fields
  # All fields from the response must be included as form data
  curl -X POST "$PRESIGNED_URL" \
    $(echo $UPLOAD_INFO | jq -r '.fields | to_entries[] | "-F \(.key)=\(.value)"') \
    -F "file=@./image001.jpg"
  ```

  ```python Python (requests) theme={null}
  import requests
  import os

  headers = {"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}

  # Step 1: Request presigned URL
  response = requests.post(
      "https://api.avala.ai/api/v1/datasets/manual-upload/file-upload-url/",
      headers=headers,
      json={"file_path_in_dataset": "image001.jpg", "dataset_name": "my-dataset"},
  )
  upload_info = response.json()

  # Step 2: Upload to cloud storage using the presigned POST fields
  with open("image001.jpg", "rb") as f:
      requests.post(
          upload_info["url"],
          data=upload_info["fields"],
          files={"file": ("image001.jpg", f)},
      )
  ```
</CodeGroup>

## Common Patterns

### List All Items with Pagination

This pattern fetches every item in a dataset by following pagination URLs until all pages have been retrieved.

```python theme={null}
import requests
import os

base_url = "https://api.avala.ai/api/v1"
headers = {"X-Avala-Api-Key": os.environ["AVALA_API_KEY"]}

def get_all_items(owner, slug):
    items = []
    url = f"{base_url}/datasets/{owner}/{slug}/items/?limit=100"

    while url:
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        data = response.json()

        items.extend(data["results"])
        url = data.get("next")

    return items

all_items = get_all_items("acme-ai", "training-v2")
print(f"Total items: {len(all_items)}")
```

### Create and Wait for an Export

This pattern starts an export job and polls until it finishes, then retrieves the download URL.

```bash theme={null}
# Start the export
EXPORT=$(curl -s -X POST "https://api.avala.ai/api/v1/exports/" \
  -H "X-Avala-Api-Key: $AVALA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "project_abc123"}')

EXPORT_UID=$(echo $EXPORT | jq -r '.uid')
echo "Export started: $EXPORT_UID"

# Poll until completed
while true; do
  STATUS=$(curl -s "https://api.avala.ai/api/v1/exports/$EXPORT_UID/" \
    -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq -r '.status')

  echo "Status: $STATUS"

  if [ "$STATUS" = "completed" ]; then
    break
  fi

  sleep 2
done

# Get the download URL
DOWNLOAD_URL=$(curl -s "https://api.avala.ai/api/v1/exports/$EXPORT_UID/" \
  -H "X-Avala-Api-Key: $AVALA_API_KEY" | jq -r '.download_url')

echo "Download: $DOWNLOAD_URL"
```
