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 below.
See 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. |
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, notResult 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 |
--json to get structured JSON errors on stderr: