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

# Error Responses

> Response envelope, HTTP status codes, error codes, and retry patterns.

### Response envelope

HydraDB core endpoints (`/databases`, `/context/*`, and `/query`) use the same top-level envelope for successful and failed requests. Webhook management endpoints (`/webhooks/indexing*`) return their documented response object directly and do not include this envelope.

```json theme={"dark"}
{
  "success": false,
  "data": null,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed"
  },
  "meta": {
    "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
    "latency_ms": 4.8
  }
}
```

| Field             | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `success`         | `false` for errors.                                     |
| `data`            | Always `null` for error responses.                      |
| `error.code`      | Machine-readable code for programmatic handling.        |
| `error.message`   | Human-readable explanation of what failed.              |
| `meta.request_id` | Request identifier. Include it when contacting support. |
| `meta.latency_ms` | Server-side processing time in milliseconds.            |

<Info>
  Use `error.code` for branching and log `meta.request_id` for every failed request. The HTTP status tells you the class of failure; the error code tells you what to do.
</Info>

## HTTP status codes

| Code  | Meaning                                                     | Retry?            |
| ----- | ----------------------------------------------------------- | ----------------- |
| `400` | Invalid parameters or malformed request                     | No                |
| `401` | Missing, expired, or invalid API key                        | No                |
| `403` | Authenticated, but not permitted for the resource           | No                |
| `404` | Database, source, memory, or related resource was not found | No                |
| `409` | Conflict, usually an existing database or context item ID   | Usually no        |
| `422` | Well-formed request that failed validation                  | No                |
| `429` | Rate limit exceeded                                         | Yes, with backoff |
| `500` | Internal server error                                       | Yes, with backoff |
| `503` | Temporary service unavailability                            | Yes, with backoff |

## Common error codes

| Code                      | Typical status | Meaning                                                                                                                    |
| ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_PARAMETERS`      | `400`          | A required parameter is missing, malformed, or mutually incompatible with another parameter.                               |
| `UNAUTHORIZED`            | `401`          | The `Authorization: Bearer <your_api_key>` header is missing or invalid.                                                   |
| `FORBIDDEN`               | `403`          | The API key is valid but does not have access to the requested resource, or the account/plan limit prevents the operation. |
| `DATABASE_ALREADY_EXISTS` | `409`          | `POST /databases` received a `database` (formerly `tenant_id`) that is already in use.                                     |
| `DATABASE_NOT_FOUND`      | `404`          | The requested database does not exist or is not visible to the current API key.                                            |
| `SOURCE_NOT_FOUND`        | `404`          | The requested source or memory ID does not exist in the selected database/collection.                                      |
| `VALIDATION_ERROR`        | `422`          | The request shape was valid JSON/form data, but one or more fields failed semantic validation.                             |
| `RATE_LIMITED`            | `429`          | The API key exceeded its current rate limit.                                                                               |
| `INTERNAL_ERROR`          | `500`          | HydraDB hit an unexpected server-side error.                                                                               |
| `SERVICE_UNAVAILABLE`     | `503`          | A dependency is temporarily unavailable or the service is under load.                                                      |

<Note>
  Endpoint pages list the most common codes for that operation. New codes may be added over time, so clients should handle unknown `error.code` values gracefully.
</Note>

<Note>
  **Missing database returns `DATABASE_NOT_FOUND`.** A request for a database that does not exist returns `DATABASE_NOT_FOUND` on both the canonical `/databases` routes and the deprecated `/tenants` routes. The deprecated `/tenants` routes otherwise keep their pre-rename error contract for backward compatibility: a duplicate on `POST /tenants` returns `INVALID_INPUT` (not `DATABASE_ALREADY_EXISTS`), whereas the canonical `POST /databases` returns `DATABASE_ALREADY_EXISTS` as shown above. The HTTP status is identical on both. See [Migrating from `tenant_id` and `sub_tenant_id`](/essentials/v2/multi-tenant#7-migrating-from-the-legacy-tenant-and-sub-tenant-fields).
</Note>

## Ingestion error codes

Asynchronous ingestion failures surface a numeric `E####` code in the `error_code` field of [`GET /context/status`](/api-reference/v2/endpoint/source-status) responses and `indexing.status_changed` [webhook](/essentials/v2/webhooks) payloads. Unlike the HTTP `error.code` values above (which describe why a *request* was rejected), these describe why a specific *item* failed to index.

Many storage- and capacity-related ingestion errors are **transient**: the pipeline retries them automatically with backoff, and they typically self-resolve within minutes. A code appearing in `error_code` does not by itself mean the item has failed permanently — only treat an item as a real failure once it reaches the terminal `errored` status.

| Code    | Meaning                                                                                                                                                                                                                                                                                                 | Severity                  |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `E6001` | Vector-store storage/indexing error while persisting processed data. The pipeline retries automatically and it usually clears within minutes. User message: *"Failed to store the processed data. Please try again. If the issue persists, contact [support@hydradb.com](mailto:support@hydradb.com)."* | **Transient** (retryable) |

<Note>
  `E6001` is **transient**, not terminal. If you observe it on an in-flight item, keep polling [`/context/status`](/api-reference/v2/endpoint/source-status) — the item normally advances to `graph_creation` / `completed` on a subsequent retry with no action on your part. Only contact support if the item is still reported as `errored` after retries are exhausted.
</Note>

## Retry pattern

Retry only transient failures: `429`, `500`, and `503`. Use exponential backoff with jitter and keep retries bounded.

<CodeGroup>
  ```typescript TypeScript SDK theme={"dark"}
  import { HydraDBError } from "@hydradb/sdk";

  async function withRetry<T>(
    operation: () => Promise<T>,
    maxRetries = 3
  ): Promise<T> {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        if (!(error instanceof HydraDBError)) throw error;

        const retryable = [429, 500, 503].includes(error.statusCode);
        if (!retryable || attempt === maxRetries) throw error;

        const baseDelayMs = 2 ** attempt * 1000;
        const jitterMs = Math.floor(Math.random() * 250);
        await new Promise((resolve) => setTimeout(resolve, baseDelayMs + jitterMs));
      }
    }

    throw new Error("unreachable");
  }

  const result = await withRetry(() =>
    client.query({
          database: "my_first_database",
          query: "What are the pricing tiers?",
          type: "knowledge",
    })
  );
  ```

  ```python Python SDK theme={"dark"}
  import random
  import time

  from hydra_db import HydraDBError

  def with_retry(operation, max_retries=3):
      for attempt in range(max_retries + 1):
          try:
              return operation()
          except HydraDBError as exc:
              retryable = exc.status_code in (429, 500, 503)
              if not retryable or attempt == max_retries:
                  raise

              delay = (2 ** attempt) + random.uniform(0, 0.25)
              time.sleep(delay)

  result = with_retry(lambda: client.query(
      database="my_first_database",
      query="What are the pricing tiers?",
      type="knowledge",
  ))
  ```

  ```typescript fetch theme={"dark"}
  async function hydraRequest(url: string, options: RequestInit, maxRetries = 3) {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const response = await fetch(url, options);
      if (response.ok) return response.json();

      const retryable = [429, 500, 503].includes(response.status);
      if (!retryable || attempt === maxRetries) {
        throw await response.json();
      }

      const baseDelayMs = 2 ** attempt * 1000;
      const jitterMs = Math.floor(Math.random() * 250);
      await new Promise((resolve) => setTimeout(resolve, baseDelayMs + jitterMs));
    }
  }
  ```
</CodeGroup>

## Handling errors

<CodeGroup>
  ```typescript TypeScript SDK theme={"dark"}
  import { HydraDBError } from "@hydradb/sdk";

  try {
    await client.context.ingest({
      type: "knowledge",
      database: "my_first_database",
      documents: [{ path: "policy.pdf", filename: "policy.pdf" }],
    });
  } catch (error) {
    if (error instanceof HydraDBError) {
      const code = error.body?.error?.code;
      const requestId = error.body?.meta?.request_id;

      if (code === "DATABASE_NOT_FOUND") {
        // Create or select a valid database before ingesting.
      } else if (error.statusCode === 429) {
        // Retry with backoff.
      } else {
        console.error({ code, requestId });
        throw error;
      }
    } else {
      throw error;
    }
  }
  ```

  ```python Python SDK theme={"dark"}
  from hydra_db import HydraDBError

  try:
      client.context.ingest(
      type="knowledge",
          database="my_first_database",
          documents=[("policy.pdf", open("policy.pdf", "rb"), "application/pdf")],
      )
  except HydraDBError as exc:
      if exc.error_code == "DATABASE_NOT_FOUND":
          # Create or select a valid database before ingesting.
          pass
      elif exc.status_code == 429:
          # Retry with backoff.
          pass
      else:
          raise
  ```
</CodeGroup>

## Troubleshooting

### Authentication failures

Send exactly one `Authorization` header:

```bash theme={"dark"}
Authorization: Bearer <your_api_key>
```

Also send `API-Version: 2` on raw HTTP requests. The official SDKs set the version header automatically.

### Database not found after creation

Database creation is asynchronous. After `POST /databases`, poll [`GET /databases/status`](/api-reference/v2/endpoint/tenant-status) until `infra.scheduler_status`, `infra.graph_status`, `infra.vectorstore_status.knowledge`, and `infra.vectorstore_status.memories` are all `true`.

### Ingestion validation errors

Common causes:

* `document_metadata` length does not match the `documents` array length.
* `app_knowledge`, `memories`, or `document_metadata` was sent as an object instead of a JSON-stringified multipart field.
* A memory item has neither `text` nor `user_assistant_pairs`.
* A typed `tenant_metadata` value does not match the database metadata schema.

### Empty query results

Empty results are not always errors. Check these first:

* Context status may still be `queued` or `processing`; poll [`GET /context/status`](/api-reference/v2/endpoint/source-status).
* `metadata_filters` may be too restrictive or may target the wrong metadata namespace.
* The query may be scoped to the wrong `database` or `collection` (formerly `tenant_id` / `sub_tenant_id`).
* The `type` value may exclude the collection you need. Use `type: "all"` when combining knowledge and memories in `POST /query`.

## Related sections

* [API Reference](/api-reference/v2) - endpoint inventory and conventions
* [Ingestion Status](/api-reference/v2/endpoint/source-status) - async ingestion state
* [Query](/api-reference/v2/endpoint/query) - retrieval parameters and response shape
