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

# Errors

> Structured error responses, error codes, and field-level validation feedback.

The SDK returns one of two structured envelopes for every failure: a `ValidationError` for request-shape failures (HTTP 400) and a generic `Error` for everything else. Both shapes are declared in the [bundled OpenAPI spec](/api/anghami-sdk.openapi.yaml).

## Validation errors (HTTP 400)

When a request fails [`buf.validate`](https://github.com/bufbuild/protovalidate) checks or has malformed JSON, the response is:

```json theme={null}
{
  "violations": [
    { "field": "song_id.value", "description": "value is required" },
    { "field": "pageSize",      "description": "value must be between 1 and 100" }
  ]
}
```

| Field                      | Type                      | Notes                                                                                            |
| -------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------ |
| `violations`               | array of `FieldViolation` | One entry per failed field.                                                                      |
| `violations[].field`       | string                    | Dotted JSON path to the field. For header validation this is the header name (e.g. `X-API-Key`). |
| `violations[].description` | string                    | Human-readable description of what failed.                                                       |

Branch on the HTTP status (`400`) and surface `violations` to the developer. End users almost never see these — they indicate a client-side bug.

## Other errors (everything except 400)

For any non-validation failure, the response is a minimal envelope:

```json theme={null}
{ "message": "song not found" }
```

`message` is the only field. Treat it as developer-facing detail; it may include internal context.

The discriminator for branching is the **HTTP status code**, not a field inside the body:

| HTTP | Meaning           | When                                                                              |
| ---- | ----------------- | --------------------------------------------------------------------------------- |
| 400  | Validation error  | Malformed body or `buf.validate` failure — body is `ValidationError` (see above). |
| 401  | Unauthenticated   | Missing/invalid credential (no `x-api-key`, no/expired `Bearer` token).           |
| 403  | Permission denied | Valid credential but missing scope or unauthorized for the resource.              |
| 404  | Not found         | The targeted entity does not exist or is not visible to this caller.              |
| 409  | Conflict          | Request conflicts with current state (e.g. revoking an already-revoked key).      |
| 429  | Rate limited      | See [Rate Limits](/usage-rate-limits).                                            |
| 500  | Internal          | Unexpected server failure. Safe to retry with back-off.                           |
| 503  | Unavailable       | Transient — retry with exponential back-off and jitter.                           |

## Batch errors

`BatchGetSongs`, `BatchGetAlbums`, `BatchGetArtists`, `BatchGetShows`, `BatchGetMovies`, `BatchGetSeasons`, and `BatchGetEpisodes` return a **map** of ID → result, where each entry is a `oneof` of either the entity or a `BatchItemError`. Successful and failed items live side by side in the same response — the top-level call only fails if the request itself is malformed.

```json theme={null}
{
  "results": {
    "123": { "song":  { "id": { "value": "123" }, "title": { "value": "...", "originalValue": "..." } } },
    "456": { "error": { "code": "ERROR_CODE_NOT_FOUND", "message": "song not found" } }
  }
}
```

`BatchItemError` carries `code` (an `ErrorCode` enum string — typically `ERROR_CODE_NOT_FOUND` or `ERROR_CODE_PERMISSION_DENIED`) and `message`. Iterate the map, branch on which `oneof` arm is set, and only fail your overall flow if the failures violate your business rules.

## Idempotency and retries

* All `Get*`, `Batch*`, `Search`, and `Browse*` operations are idempotent — safe to retry on `INTERNAL` / `UNAVAILABLE`.
* `Acquire*Stream` is idempotent within the duration of a stream URL — retrying simply returns a fresh URL. Each acquire is the billable event, so don't retry past the back-off window unnecessarily.
* `CreateApiKey`, `RotateApiKey`, `RevokeApiKey` are **not** safely retryable on ambiguous failures. Use `ListApiKeys` to reconcile state before retrying.
