> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.twelvelabs.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.twelvelabs.io/_mcp/server.

# Error handling

> Handle errors across the Jockey API with typed exceptions, retries, and status polling.

This page shows you how to handle errors across the [Jockey](/v1.3/agents/concepts/jockey) API: catch typed exceptions, retry transient failures, and poll for asynchronous status.

# Error response format

When a request fails, the SDK raises a typed exception: `ApiError` in Python and `TwelvelabsApiError` in Node.js. The exception includes the HTTP status code and the parsed error body, which follows the same shape across the API:

```json
{
  "code": "parameter_not_provided",
  "message": "The input parameter is required but was not provided."
}
```

Catch the exception and inspect its status code and body:

**`Python`**

```python Python maxlines=15
from twelvelabs import TwelveLabs
from twelvelabs.core.api_error import ApiError

client = TwelveLabs(api_key="<YOUR_API_KEY>")

try:
    store = client.knowledge_stores.retrieve(knowledge_store_id="<YOUR_KNOWLEDGE_STORE_ID>")
except ApiError as exc:
    print(f"Status: {exc.status_code}")
    print(f"Body: {exc.body}")
```

**`Node.js`**

```javascript Node.js maxlines=15
import { TwelveLabs, TwelvelabsApiError } from "twelvelabs-js";

const client = new TwelveLabs({ apiKey: "<YOUR_API_KEY>" });

try {
  const store = await client.knowledgeStores.retrieve("<YOUR_KNOWLEDGE_STORE_ID>");
} catch (err) {
  if (err instanceof TwelvelabsApiError) {
    console.log(`Status: ${err.statusCode}`);
    console.log(`Body: ${JSON.stringify(err.body)}`);
  }
}
```

# HTTP status codes

The status code is available on the exception (`status_code` in Python, `statusCode` in Node.js).

| Code  | Meaning      | Action                                          |
| ----- | ------------ | ----------------------------------------------- |
| `200` | Success      | Process response                                |
| `201` | Created      | Resource created successfully                   |
| `202` | Accepted     | Async operation started (knowledge store items) |
| `400` | Bad Request  | Fix request parameters                          |
| `401` | Unauthorized | Check API key                                   |
| `403` | Forbidden    | Check permissions                               |
| `404` | Not Found    | Check the resource identifier                   |
| `429` | Rate Limited | Back off and retry                              |
| `500` | Server Error | Retry with backoff                              |

# Retry strategy

Retry on transient errors (`429` rate limits and `5xx` server errors). Do not retry client errors (`400`, `401`, `403`, `404`). Fix the request instead.

**`Python`**

```python Python maxlines=28
import time
from twelvelabs import TwelveLabs
from twelvelabs.core.api_error import ApiError

client = TwelveLabs(api_key="<YOUR_API_KEY>")

def with_retry(call, max_retries=3, base_delay=1):
    """Call an SDK method with exponential backoff on 429 and 5xx errors."""
    for attempt in range(max_retries):
        try:
            return call()
        except ApiError as exc:
            status = exc.status_code
            if status == 429 or (status is not None and status >= 500):
                delay = base_delay * (2 ** attempt)
                print(f"Received {status}, retrying in {delay}s...")
                time.sleep(delay)
                continue
            raise  # Client errors are not retryable
    raise RuntimeError("Exhausted retries")

response = with_retry(lambda: client.responses.create(
    knowledge_store_id="<YOUR_KNOWLEDGE_STORE_ID>",
    input=[{"type": "message", "role": "user", "content": "<YOUR_PROMPT>"}],
))
```

**`Node.js`**

```javascript Node.js maxlines=28
import { TwelveLabs, TwelvelabsApiError } from "twelvelabs-js";

const client = new TwelveLabs({ apiKey: "<YOUR_API_KEY>" });

async function withRetry(call, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await call();
    } catch (err) {
      if (err instanceof TwelvelabsApiError && (err.statusCode === 429 || err.statusCode >= 500)) {
        const delay = baseDelay * 2 ** attempt;
        console.log(`Received ${err.statusCode}, retrying in ${delay / 1000}s...`);
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }
      throw err; // Client errors are not retryable
    }
  }
  throw new Error("Exhausted retries");
}

const response = await withRetry(() =>
  client.responses.create({
    knowledgeStoreId: "<YOUR_KNOWLEDGE_STORE_ID>",
    input: [{ type: "message", role: "user", content: "<YOUR_PROMPT>" }],
  })
);
```

# Common errors by endpoint

## Assets

| Error             | Cause                 | Fix                                       |
| ----------------- | --------------------- | ----------------------------------------- |
| `method` required | Missing upload method | Add `method: "direct"` or `method: "url"` |
| File too large    | Direct upload > 200MB | Use URL upload method instead             |
| Invalid URL       | URL not accessible    | Check URL is public and reachable         |

## Knowledge stores

| Error          | Cause                                      | Fix                                        |
| -------------- | ------------------------------------------ | ------------------------------------------ |
| Invalid schema | Bad JSON Schema in ingestion configuration | Validate against JSON Schema draft 2020-12 |
| Name required  | Missing `name` field                       | Add a name                                 |

## Knowledge store items

| Error           | Cause                        | Fix                                  |
| --------------- | ---------------------------- | ------------------------------------ |
| Asset not found | Invalid `asset_id`           | Check asset exists and is `ready`    |
| Store not found | Invalid `knowledge_store_id` | Check the knowledge store identifier |

## Responses

| Error                    | Cause                        | Fix                                     |
| ------------------------ | ---------------------------- | --------------------------------------- |
| Knowledge store required | Missing `knowledge_store_id` | Add `knowledge_store_id` to the request |
| Invalid session          | Bad `session_id`             | Start a new session (omit `session_id`) |
| Input required           | Missing `input` array        | Add at least one message                |

# Polling and async status

The platform processes content asynchronously. Any time you create an asset or add videos and images to a knowledge store, processing happens in the background. Poll the resource until it reaches the `ready` status before proceeding.

## Polling helper

**`Python`**

```python Python maxlines=22
import time

def wait_for_ready(fetch, interval=5, timeout=600):
    """Poll a resource until it is ready or failed.

    `fetch` returns the current resource, for example:
    lambda: client.assets.retrieve(asset_id=asset_id)
    """
    elapsed = 0
    while elapsed < timeout:
        resource = fetch()
        if resource.status == "ready":
            return resource
        elif resource.status == "failed":
            raise Exception(f"Resource failed: {resource.id}")
        print(f"  Status: {resource.status} ({elapsed}s elapsed)")
        time.sleep(interval)
        elapsed += interval
    raise TimeoutError(f"Not ready after {timeout}s")
```

**`Node.js`**

```javascript Node.js maxlines=22
async function waitForReady(fetch, intervalMs = 5000, timeoutMs = 600000) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const resource = await fetch();
    if (resource.status === "ready") return resource;
    if (resource.status === "failed") throw new Error(`Resource failed: ${resource.id}`);
    console.log(`  Status: ${resource.status}`);
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  throw new Error(`Not ready after ${timeoutMs}ms`);
}
```

## Usage

**`Python`**

```python Python maxlines=15
# Wait for an asset
asset = wait_for_ready(
    lambda: client.assets.retrieve(asset_id="<YOUR_ASSET_ID>"), interval=5
)

# Wait for a knowledge store item (longer interval - indexing takes more time)
item = wait_for_ready(
    lambda: client.knowledge_store_items.retrieve(
        knowledge_store_id="<YOUR_KNOWLEDGE_STORE_ID>", item_id="<YOUR_ITEM_ID>"
    ),
    interval=10,
    timeout=600,
)
```

**`Node.js`**

```javascript Node.js maxlines=15
// Wait for an asset
const asset = await waitForReady(() => client.assets.retrieve("<YOUR_ASSET_ID>"), 5000);

// Wait for a knowledge store item (longer interval - indexing takes more time)
const item = await waitForReady(
  () => client.knowledgeStoreItems.retrieve("<YOUR_KNOWLEDGE_STORE_ID>", "<YOUR_ITEM_ID>"),
  10000,
  600000
);
```

## Recommended intervals

| Resource             | Poll Interval | Typical Wait | Timeout |
| -------------------- | ------------- | ------------ | ------- |
| Asset (direct)       | 5s            | 10-60s       | 120s    |
| Asset (URL)          | 5s            | 10-120s      | 300s    |
| Knowledge store item | 10s           | 1-10 min     | 600s    |

## Batch polling

Wait for multiple resources in parallel:

**`Python`**

```python Python maxlines=22
import concurrent.futures

def wait_for_items(client, store_id, item_ids):
    """Wait for multiple knowledge store items in parallel."""
    def wait_one(item_id):
        return wait_for_ready(
            lambda: client.knowledge_store_items.retrieve(
                knowledge_store_id=store_id, item_id=item_id
            ),
            interval=10,
        )

    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = {executor.submit(wait_one, iid): iid for iid in item_ids}
        return {futures[f]: f.result() for f in concurrent.futures.as_completed(futures)}
```

**`Node.js`**

```javascript Node.js maxlines=22
async function waitForItems(client, storeId, itemIds) {
  // Wait for multiple knowledge store items in parallel.
  const results = await Promise.all(
    itemIds.map((itemId) =>
      waitForReady(() => client.knowledgeStoreItems.retrieve(storeId, itemId), 10000)
    )
  );
  return Object.fromEntries(itemIds.map((id, i) => [id, results[i]]));
}
```

## Polling guidance

* **Don't poll too aggressively**: 1-second intervals waste rate limit budget
* **Always handle `failed`**: failed resources don't recover
* **Set a timeout**: don't poll forever if something goes wrong
* **Rely on polling**: webhooks are not available during the research preview

# Jupyter notebook

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/twelvelabs-io/twelvelabs-developer-experience/blob/main/quickstarts/jockey/guides/error_handling.ipynb)