> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chronicle-labs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and retries

> Timeouts, retries, idempotency, pagination, and what an error tells you.

The SDK handles connections, encoding, pagination cursors, and retry backoff.
You control timeouts, idempotency keys, and what to do when a call fails.

Use a [configured SDK client](/api-reference/languages/overview#use-the-guide-examples).
The examples share that client and the IDs returned by earlier steps.

## Timeouts and retries

Replace your client initialization with these defaults. Python uses seconds; TypeScript and
JavaScript use milliseconds; Go and Rust use durations.

<CodeGroup dropdown>
  ```python Python theme={null}
  from chronicle import Chronicle

  client = Chronicle(timeout=30.0, max_retries=2)
  ```

  ```typescript TypeScript theme={null}
  import { Chronicle } from "@chroniclelabs/sdk";

  const client = new Chronicle({ timeout: 30_000, maxRetries: 2 });
  ```

  ```javascript JavaScript theme={null}
  import { Chronicle } from "@chroniclelabs/sdk";

  const client = new Chronicle({ timeout: 30_000, maxRetries: 2 });
  ```

  ```go Go theme={null}
  client := chronicle.NewClient(
      chronicle.WithTimeout(30*time.Second),
      chronicle.WithMaxRetries(2),
  )
  ```

  ```rust Rust theme={null}
  let client = Chronicle::builder()
      .timeout(std::time::Duration::from_secs(30))
      .max_retries(2)
      .build()?;
  ```
</CodeGroup>

By default the SDK retries twice on network errors, `429`, and `5xx`, with
backoff, honoring `Retry-After`. It only retries writes that carry an
idempotency key. It never retries `400`, `401`, `403`, `404`, or `409`. Set
`max_retries=0` to turn retries off.

Launching a world can take longer than a normal request; the SDK uses a longer
timeout for that call automatically. `wait` helpers have their own deadline,
separate from the per-request timeout.

## Idempotency keys

`worlds.create` and `evaluations.create` take an `idempotency_key`. Pick
something that identifies the *intent*, like a build number, and store it with
the request.

* Same key, same inputs: you get the original world or run back. Safe to retry after a timeout or crash.
* Same key, different inputs: `409`.
* New key: a new world or run.

Once you have the ID, use it. Don't start a new run because you stopped waiting
for the old one.

## Errors

API failures expose the status, the server's message, and a request ID to quote
in support conversations. Transport errors may have no HTTP status or request
ID. Replace `<world-id>` below with the ID returned by your [world launch](/api-reference/worldsmith#launch-a-world).

<CodeGroup dropdown>
  ```python Python theme={null}
  from chronicle import APIError

  try:
      world = client.worlds.retrieve("<world-id>")
  except APIError as e:
      print(e.status_code, e.message, e.request_id)
      if e.status_code == 429:
          print("retry after", e.retry_after)
      raise
  ```

  ```typescript TypeScript theme={null}
  import { APIError } from "@chroniclelabs/sdk";

  try {
    const world = await client.worlds.retrieve("<world-id>");
  } catch (e) {
    if (e instanceof APIError) {
      console.error(e.statusCode, e.message, e.requestId);
      if (e.statusCode === 429) console.error("retry after", e.retryAfter);
    }
    throw e;
  }
  ```

  ```javascript JavaScript theme={null}
  import { APIError } from "@chroniclelabs/sdk";

  try {
    const world = await client.worlds.retrieve("<world-id>");
  } catch (e) {
    if (e instanceof APIError) {
      console.error(e.statusCode, e.message, e.requestId);
      if (e.statusCode === 429) console.error("retry after", e.retryAfter);
    }
    throw e;
  }
  ```

  ```go Go theme={null}
  _, err := client.Worlds.Retrieve(ctx, "<world-id>")
  if err != nil {
      var apiErr *chronicle.APIError
      if errors.As(err, &apiErr) {
          fmt.Println(apiErr.StatusCode, apiErr.Message, apiErr.RequestID)
          if apiErr.StatusCode == 429 {
              fmt.Println("retry after", apiErr.RetryAfter)
          }
      }
      return err
  }
  ```

  ```rust Rust theme={null}
  match client.worlds().retrieve("<world-id>").await {
      Ok(world) => println!("{}", world.world.id),
      Err(e) => {
          eprintln!("{:?} {} {:?}", e.status_code(), e, e.request_id());
          if e.status_code() == Some(429) {
              eprintln!("retry after {:?}", e.retry_after());
          }
          return Err(e.into());
      }
  }
  ```
</CodeGroup>

| Status | What it means                                                                                               | What to do                                               |
| ------ | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `400`  | Bad input: unknown ID, invalid blueprint, malformed key.                                                    | Read `message` and fix the request.                      |
| `401`  | Missing, invalid, or revoked API key.                                                                       | Check the key.                                           |
| `403`  | Missing scope, or your organization is out of capacity.                                                     | Add the scope, or free up twins.                         |
| `404`  | Not found in your organization.                                                                             | Check the ID.                                            |
| `409`  | Conflict: idempotency key reused with different inputs, or a version already exists with different content. | Use a new key or bump the version.                       |
| `429`  | Rate or concurrency limit.                                                                                  | Let the SDK retry, or wait `retry_after`.                |
| `5xx`  | Chronicle-side problem.                                                                                     | The SDK retries; if it persists, quote the `request_id`. |

Don't log full world or event payloads; they contain live twin tokens and your
own business data.

## Pagination

List methods return a page. These examples visit every task. Replace
`<task-suite-id>` with `world.evaluation.task_suite_id` from a ready world or
`suite.id` from [creating a suite](/api-reference/tasks#build-a-suite).

<CodeGroup dropdown>
  ```python Python theme={null}
  page = client.tasks.list(task_suite_id="<task-suite-id>", limit=100)

  for task in page.auto_paging_iter():        # all pages
      print(task.id, task.title)

  # Alternative: start again and process one page at a time.
  page = client.tasks.list(task_suite_id="<task-suite-id>", limit=100)
  while True:
      for task in page.items:
          print(task.id, task.title)
      if not page.has_next_page():
          break
      page = page.get_next_page()
  ```

  ```typescript TypeScript theme={null}
  const page = await client.tasks.list({ taskSuiteId: "<task-suite-id>", limit: 100 });

  for await (const task of page) console.log(task.id, task.title);   // all pages

  // Alternative: start again and process one page at a time.
  let current = await client.tasks.list({ taskSuiteId: "<task-suite-id>", limit: 100 });
  while (true) {
    for (const task of current.items) console.log(task.id, task.title);
    if (!current.hasNextPage()) break;
    current = await current.getNextPage();
  }
  ```

  ```javascript JavaScript theme={null}
  const page = await client.tasks.list({ taskSuiteId: "<task-suite-id>", limit: 100 });

  for await (const task of page) console.log(task.id, task.title);   // all pages

  // Alternative: start again and process one page at a time.
  let current = await client.tasks.list({ taskSuiteId: "<task-suite-id>", limit: 100 });
  while (true) {
    for (const task of current.items) console.log(task.id, task.title);
    if (!current.hasNextPage()) break;
    current = await current.getNextPage();
  }
  ```

  ```go Go theme={null}
  page, err := client.Tasks.List(ctx, chronicle.TaskListParams{TaskSuiteID: "<task-suite-id>", Limit: 100})
  if err != nil {
      return err
  }
  for {
      for _, task := range page.Items {
          fmt.Println(task.ID, task.Title)
      }
      if !page.HasNextPage() {
          break
      }
      if page, err = page.GetNextPage(ctx); err != nil {
          return err
      }
  }
  ```

  ```rust Rust theme={null}
  let mut page = client.tasks().list(TaskListParams {
      task_suite_id: "<task-suite-id>".into(),
      limit: Some(100),
      ..Default::default()
  }).await?;
  loop {
      for task in &page.items {
          println!("{} {}", task.id, task.title);
      }
      if !page.has_next_page() {
          break;
      }
      page = page.get_next_page().await?;
  }
  ```
</CodeGroup>

If you copy records into another system, checkpoint after each page succeeds
and make the destination tolerant of seeing a record twice.

## Success isn't always done

Some calls return before the work finishes. Check the object, not just the
absence of an error:

| After                           | Check                                                                                                          |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `worlds.compile`                | `problems` is empty.                                                                                           |
| `worlds.create` / `worlds.wait` | `status` is `ready`; otherwise read `launch_error` and each twin's `error`.                                    |
| `worlds.materialize`            | `evaluation.status` is `ready`.                                                                                |
| `scorers.test`                  | `error` is empty, then look at `score`.                                                                        |
| `evaluations.wait`              | `status` is `succeeded`, then read `results` for per-task pass/fail. A succeeded run can contain failed tasks. |

The `wait` helpers make the first part easy; the second part is yours.
