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

# Idempotency

> Safely retry requests without duplicate side effects

The Bota API supports idempotency for safely retrying requests without performing the same operation twice. This is critical for operations that create resources or trigger processing jobs.

***

## How It Works

Include an `Idempotency-Key` header with a unique value for each distinct operation:

```bash theme={null}
curl -X POST https://api.bota.dev/v1/recordings \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: rec_create_user123_1704067200" \
  -H "Content-Type: application/json" \
  -d '{
    "end_user_id": "eu_abc123",
    "device_id": "dev_xyz789"
  }'
```

If you send the same request with the same idempotency key:

* **First request** — Creates the resource and returns the response
* **Subsequent requests** — Returns the cached response from the first request

***

## Idempotency Key Requirements

| Requirement    | Details                                  |
| -------------- | ---------------------------------------- |
| **Format**     | String, 1-255 characters                 |
| **Characters** | Alphanumeric, hyphens, underscores       |
| **Uniqueness** | Must be unique per operation per project |
| **TTL**        | Keys are stored for 24 hours             |

### Recommended Key Patterns

```
# Pattern: {operation}_{entity}_{unique_id}_{timestamp}

rec_create_user123_1704067200
txn_start_rec456_1704067200
upload_complete_up789_1704067200
```

<Warning>
  Using the same idempotency key for different request bodies will return an error. The key is tied to both the endpoint and the original request payload.
</Warning>

***

## Supported Endpoints

Idempotency keys are supported on all `POST` endpoints that create resources or trigger jobs:

| Endpoint                               | Use Case                    |
| -------------------------------------- | --------------------------- |
| `POST /end-users`                      | Creating end users          |
| `POST /devices`                        | Registering devices         |
| `POST /devices/:id/bind`               | Binding devices to users    |
| `POST /recordings`                     | Creating recordings         |
| `POST /recordings/:id/upload-complete` | Completing uploads          |
| `POST /transcriptions`                 | Starting transcription jobs |
| `POST /summaries`                      | Starting summarization jobs |

***

## Response Behavior

### Successful Replay

When replaying a previously successful request:

```json theme={null}
{
  "id": "rec_abc123",
  "status": "created",
  "created_at": "2025-01-15T10:00:00Z"
}
```

The response includes the original resource, and no duplicate is created.

### Error Replay

If the original request failed with a client error (4xx), the error is returned on replay. Server errors (5xx) are not cached — you can safely retry with the same key.

### Key Conflict

If you reuse a key with a different request body:

```json theme={null}
{
  "error": {
    "code": "idempotency_key_conflict",
    "message": "Idempotency key was used with different request parameters",
    "param": "Idempotency-Key"
  }
}
```

***

## Implementation Example

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  async function createRecordingIdempotent(endUserId, deviceId) {
    // Generate a unique key for this operation
    const idempotencyKey = `rec_create_${endUserId}_${Date.now()}`;

    const response = await fetch('https://api.bota.dev/v1/recordings', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.BOTA_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey,
      },
      body: JSON.stringify({
        end_user_id: endUserId,
        device_id: deviceId,
      }),
    });

    return response.json();
  }

  // Safe to retry on network errors
  async function createRecordingWithRetry(endUserId, deviceId, maxRetries = 3) {
    const idempotencyKey = `rec_create_${endUserId}_${Date.now()}`;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const response = await fetch('https://api.bota.dev/v1/recordings', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.BOTA_API_KEY}`,
            'Content-Type': 'application/json',
            'Idempotency-Key': idempotencyKey,
          },
          body: JSON.stringify({
            end_user_id: endUserId,
            device_id: deviceId,
          }),
        });

        if (response.ok) {
          return response.json();
        }

        // Don't retry client errors
        if (response.status < 500) {
          throw new Error(`Client error: ${response.status}`);
        }
      } catch (error) {
        if (attempt === maxRetries) throw error;
        await new Promise(r => setTimeout(r, 1000 * attempt));
      }
    }
  }
  ```

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

  def create_recording_idempotent(end_user_id: str, device_id: str) -> dict:
      # Generate a unique key for this operation
      idempotency_key = f"rec_create_{end_user_id}_{int(time.time())}"

      response = requests.post(
          "https://api.bota.dev/v1/recordings",
          headers={
              "Authorization": f"Bearer {os.environ['BOTA_API_KEY']}",
              "Content-Type": "application/json",
              "Idempotency-Key": idempotency_key,
          },
          json={
              "end_user_id": end_user_id,
              "device_id": device_id,
          },
      )

      response.raise_for_status()
      return response.json()


  def create_recording_with_retry(end_user_id: str, device_id: str, max_retries: int = 3) -> dict:
      idempotency_key = f"rec_create_{end_user_id}_{int(time.time())}"

      for attempt in range(1, max_retries + 1):
          try:
              response = requests.post(
                  "https://api.bota.dev/v1/recordings",
                  headers={
                      "Authorization": f"Bearer {os.environ['BOTA_API_KEY']}",
                      "Content-Type": "application/json",
                      "Idempotency-Key": idempotency_key,
                  },
                  json={
                      "end_user_id": end_user_id,
                      "device_id": device_id,
                  },
              )

              if response.ok:
                  return response.json()

              # Don't retry client errors
              if response.status_code < 500:
                  response.raise_for_status()

          except requests.RequestException:
              if attempt == max_retries:
                  raise
              time.sleep(attempt)

      raise Exception("Max retries exceeded")
  ```
</CodeGroup>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Generate keys client-side">
    Always generate idempotency keys on the client before making the request. This ensures the same key is used across retries.
  </Accordion>

  <Accordion title="Include enough context">
    Include relevant identifiers in the key (user ID, timestamp) to make debugging easier and prevent accidental collisions.
  </Accordion>

  <Accordion title="Don't reuse keys across operations">
    Each distinct operation should have its own idempotency key. Reusing keys across different operations will cause conflicts.
  </Accordion>

  <Accordion title="Store keys for critical operations">
    For payment-like operations (e.g., triggering transcription jobs), store the idempotency key in your database so you can retry with the exact same key if needed.
  </Accordion>
</AccordionGroup>
