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

# Error Codes

> Complete reference for all error codes returned by BlindCast packages. Every error has a code, human-readable message, and suggested remediation.

All BlindCast libraries return `Result<T, E>` instead of throwing exceptions. When `.ok` is `false`, `.error` has a `code` (a string literal) and a `message` (human-readable description).

The CLI uses Unix exit codes instead — see [CLI Error Codes](#cli-exit-codes) below.

See [Result Type Pattern](/reference/result-type-pattern) for how to consume results.

## Player — `PlayerErrorCode`

| Code                    | Cause                                                          | Remediation                                                                                            |
| ----------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `UNSUPPORTED_BROWSER`   | MSE or Web Crypto API not available                            | Check `isPlayerSupported()` before calling `createPlayer`; show a fallback for unsupported browsers    |
| `INVALID_VIDEO_ELEMENT` | `videoEl` is null or not an `HTMLVideoElement`                 | Ensure the element exists in the DOM before calling `createPlayer`                                     |
| `INVALID_OPTIONS`       | Required options are missing or malformed                      | `keyServerUrl` must be a valid URL string                                                              |
| `MANIFEST_LOAD_FAILED`  | Could not fetch or parse the HLS manifest                      | Check that the manifest URL is correct and the CDN is reachable; verify the `.m3u8` is valid HLS       |
| `KEY_FETCH_FAILED`      | Key server request failed (network error or non-200 response)  | Check key server availability and CORS configuration (`CORS_ORIGINS` must include the player's origin) |
| `KEY_AUTH_FAILED`       | Key server returned 401 or 403                                 | The `keyServerAuth` callback is returning an invalid or expired token                                  |
| `KEY_LEASE_EXPIRED`     | Lease was revoked or expired and the player could not renew it | The viewer's access has been terminated; show an appropriate message                                   |
| `DECRYPT_FAILED`        | Segment decryption failed                                      | Verify the manifest's `EXT-X-KEY IV=` attribute matches the IV used during encryption                  |
| `MEDIA_ERROR`           | hls.js media error (codec mismatch, corrupt segment)           | Check the encrypted segment data is valid; verify the upload completed successfully                    |
| `NETWORK_ERROR`         | Segment download failed (CDN error, offline)                   | Transient; hls.js will retry automatically within its configured retry limits                          |
| `PLAYER_DESTROYED`      | A method was called after `destroy()`                          | Check that your component isn't calling player methods after unmounting                                |

## Uploader — `UploaderErrorCode`

| Code                   | Cause                                                                                    | Remediation                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `ENCRYPT_FAILED`       | Segment encryption failed                                                                | Check the `contentKey` is an AES-128-CBC `CryptoKey` derived with `deriveContentKey` |
| `UPLOAD_FAILED`        | PUT request failed (network error, S3 permission error, presign expired)                 | Check S3 bucket policy; verify presigned URLs have sufficient TTL                    |
| `PRESIGN_FAILED`       | Presign endpoint returned an error or non-200 status                                     | Check the key server's presign endpoint is enabled and the `presignUrl` is correct   |
| `KEY_FETCH_FAILED`     | Could not fetch the content key from the key server                                      | Check key server availability and authentication                                     |
| `INVALID_INPUT`        | Segments array is empty, `contentId` has invalid characters, or a filename contains `..` | Use relative filenames (e.g., `seg-0.ts`); `contentId` must be `[a-zA-Z0-9_-]+`      |
| `MANIFEST_PARSE_ERROR` | The `.m3u8` manifest could not be parsed                                                 | Verify the manifest is valid HLS; check for encoding issues or truncated files       |
| `ABORTED`              | `AbortController.abort()` was called before or during the upload                         | Expected when user cancels; handle gracefully in your UI                             |

## Key Server — `KeyErrorCode`

These codes are returned by the key server's internal derivation logic. The HTTP layer maps them to status codes (400, 401, 403, 500).

| Code                     | Cause                                                                                        | Remediation                                                                                                                          |
| ------------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `INVALID_MASTER_KEY`     | `masterKeyBytes` is not 16 bytes (128-bit) or 32 bytes (256-bit)                             | Generate with `blindcast keygen`                                                                                                     |
| `INVALID_CONTENT_ID`     | `contentId` is empty, exceeds 256 characters, or contains characters outside `[a-zA-Z0-9_-]` | Use URL-safe alphanumeric strings with hyphens and underscores only                                                                  |
| `INVALID_EPOCH`          | Epoch number is negative or not an integer                                                   | Epochs are computed automatically; this indicates a client bug                                                                       |
| `INVALID_SALT`           | Salt is not exactly 32 bytes                                                                 | Generate with `blindcast keygen`                                                                                                     |
| `DERIVATION_FAILED`      | HKDF operation returned an error                                                             | Rare; usually indicates a runtime bug. Check that the master key and salt are valid                                                  |
| `KEY_EXPORT_FAILED`      | The derived key could not be exported                                                        | Rare; indicates an internal key state issue                                                                                          |
| `LEASE_REQUIRED`         | Leases are enabled but the request has no `X-Lease-Id` header                                | The player must be configured with a `lease` object (`{ leaseEndpoint }`) to send the lease ID. See [Player Leases](/player/leases). |
| `LEASE_INVALID`          | Lease ID does not match the expected format                                                  | Ensure the lease ID comes from the key server's `POST /keys/leases` endpoint                                                         |
| `LEASE_NOT_FOUND`        | Lease ID not found in the store                                                              | The lease may have been cleaned up after expiry; the viewer needs to re-acquire                                                      |
| `LEASE_EXPIRED`          | Lease TTL has passed                                                                         | The player should have renewed automatically; may indicate a network issue during renewal                                            |
| `LEASE_REVOKED`          | Lease was explicitly revoked                                                                 | Access has been intentionally terminated; show an appropriate message to the viewer                                                  |
| `LEASE_CONTENT_MISMATCH` | Lease was issued for a different `contentId` than the one being requested                    | The player's lease is scoped to a specific content ID; create a new lease for each content item                                      |
| `LEASE_STORE_ERROR`      | Backend failure (e.g., database timeout, KV unreachable)                                     | Check your database connection; this is a 5xx-class error                                                                            |

## Crypto (internal) — `CryptoErrorCode`

These codes come from `@blindcast/crypto`, which is used internally by the player, uploader, and key server. You'll see them if you use the crypto package directly.

| Code                     | Cause                                                                                | Remediation                                                                                                                                            |
| ------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `INVALID_KEY`            | Key type or algorithm does not match the operation                                   | Check that you're using an AES-GCM key with `encrypt`/`decrypt`, AES-CBC with `encryptSegment`/`decryptSegment`, and AES-KW with `wrapKey`/`unwrapKey` |
| `INVALID_IV`             | IV is the wrong length                                                               | AES-CBC requires exactly 16 bytes; AES-GCM requires exactly 12 bytes                                                                                   |
| `DECRYPT_FAILED`         | Ciphertext is corrupt, truncated, or the key/IV don't match the encrypted data       | Verify you're using the same key and IV that were used to encrypt                                                                                      |
| `UNSUPPORTED_ALGORITHM`  | Algorithm string is not `"AES-CBC"`, `"AES-GCM"`, or `"AES-KW"`                      | Pass a valid `AesAlgorithm` value                                                                                                                      |
| `KEY_IMPORT_FAILED`      | JWK is malformed or the requested algorithm/usage does not match the JWK `alg` field | Check that the JWK was exported with `exportKey` and imported with matching options                                                                    |
| `KEY_EXPORT_FAILED`      | Key was created as non-extractable                                                   | Keys derived server-side are non-extractable by design                                                                                                 |
| `KEY_WRAP_FAILED`        | The wrapping key is not an AES-KW key, or the target key is not extractable          | Use `generateKey("AES-KW")` for the wrapping key; ensure the key to wrap is extractable                                                                |
| `OPERATION_ERROR`        | Unexpected error from the Web Crypto API                                             | Check `.error.message` for the underlying browser error                                                                                                |
| `WEB_CRYPTO_UNAVAILABLE` | `globalThis.crypto.subtle` is not available                                          | Use HTTPS (crypto.subtle requires a secure context); check browser support                                                                             |

## CLI exit codes

The CLI uses Unix exit codes, not `Result` types:

| Exit code | Meaning       | Example                                                          |
| --------- | ------------- | ---------------------------------------------------------------- |
| `0`       | Success       | Encryption completed, keys generated                             |
| `1`       | User error    | Invalid flags, missing required arguments, bad content ID format |
| `2`       | Runtime error | Network failure, S3 permission denied, key server unreachable    |

Use `--json` to get structured JSON errors on stderr:

```bash theme={null}
blindcast encrypt ./segments --content-id "invalid content id!" --json 2>error.json
echo $?  # 1

cat error.json
# {"code":"INVALID_CONTENT_ID","message":"Content ID must match [a-zA-Z0-9_-]+"}
```
