Every domain failure in the Highlight backend raises a named error with a stable name (the class name) and a typed payload. Route handlers map each error class to an HTTP status code — your client can either switch on the status code or pattern-match against the error payload shape.
Typed domain errors come back as JSON:
"name" : " CollectionNotFoundError " ,
"message" : " Collection 0x...abc not found " ,
"data" : { "highlightId" : " 0x...abc " }
name — the class name. Unique across the platform; safe to switch on.
message — human-readable; safe to log, not safe to parse.
data — structured payload (e.g. highlightId, chainId).
Untyped runtime failures (unhandled exceptions, 5xx from upstream services) come back as a generic 500 with a more minimal shape.
Error Status When CollectionNotFoundError404 highlightId doesn’t existCollectionNotPublicError404 Collection exists but is Draft and caller isn’t the owner CollectionNotOwnedError403 Caller authenticated but not the collection owner CollectionNotDraftError400 Attempting a draft-only operation on a live collection CollectionContractNotFoundError404 Referenced contractId doesn’t exist CollectionContractNotOwnedError403 Caller doesn’t own the referenced contract InvalidCollectionInputError400 Schema-level validation failure (e.g. missing logo) InvalidTypeSpecificDetailsError400 Edition/series/generative details don’t match the collection type CollectionMediaNotFoundError404 Referenced mediaId doesn’t exist CollectionMediaNotOwnedError403 Caller doesn’t own the referenced media CollectionMediaUploadNotReadyError400 Media isn’t Ready yet — wait for processing CollectionMediaInvalidPurposeError400 Media kind doesn’t fit the slot (e.g. Directory as logo) CollectionMediaNotOnchainReadyError400 Media not published to archive provider yet BaseUriNotSetError400 Cannot finalize metadata before base URI is computed
Error Status When SaleNotFoundError404 saleId doesn’t exist or isn’t on this collectionSaleNotPublicError403 Attempting to act on a gated sale without gate pass SaleNotLiveError400 Claim/bid against a non-live sale SaleAlreadyExistsError409 Draft collections allow one sale at a time SaleCannotDeleteLiveError400 Live sales cannot be deleted — pause or end-date SaleTypeNotSupportedError400 Combination not in the support matrix InvalidSalePriceError400 Price is zero, negative, or malformed InvalidTypeConfigError400 typeConfig fails schema validation for the sale typeChooseTokenNotSupportedError400 tokenIds passed for a non-series or non-fixed/dutch saleAuctionNotEndedError400 Cannot claim a ranked-auction sale before endAt NoValidBidsError400 Claiming a ranked auction but the caller has no valid winning bids VectorIdRequiredError400 Public Dutch auction claim needs a registered vectorId SaleGateCheckFailedError403 Caller’s wallet doesn’t satisfy the gate conditions AccountContextRequiredError401 Claim/bid without auth AccountWalletRequiredError400 Account has no wallet configured ContractAddressRequiredError400 Gated claim against an undeployed contract SaleMechanicNotFoundError400 Referenced mechanicId doesn’t exist SaleMechanicDeploymentNotFoundError400 Mechanic has no deployment on this chain
Error Status When GateNotFoundError404 gateId doesn’t existGateNotOwnedError403 Caller doesn’t own the gate ConditionTypeNotSupportedError400 Unknown type in a condition config
Error Status When MediaNotFoundError404 mediaId doesn’t existMediaNotOwnedError403 Caller doesn’t own the media MediaUploadNotFoundError404 No upload session on this media MediaTooLargeError400 fileSize exceeds per-kind limitMediaKindMismatchError400 Uploading bytes of the wrong MIME for the declared kind MediaInUseError409 Deleting media referenced by a deployed contract MediaStorageUnavailableError502 Upstream storage provider rejected the write DirectoryNotFinalizedError400 Using a Directory before extraction is Ready LocationNotFoundError404 No storage location record for this media ProviderUploadFailedError502 Upload to R2/Arweave failed; safe to retry
Error Status When ExtractionNotFoundError404 No extraction record for this media ExtractionAlreadyExistsError409 Extraction already running ExtractionNotRetryableError400 Extraction is in a state that can’t be retried ExtractionMediaNotZipError400 Media kind isn’t Directory ExtractionMediaNotUploadedError400 Bytes never PUT to the upload URL ExtractionMediaNotFoundError404 Parent media record missing
Error Status When SiweInvalidMessageError400 Message isn’t valid EIP-4361 SiweDomainMismatchError400 Message domain doesn’t match the backend’s expected host SiweChainNotAllowedError400 chainId in the message isn’t on the allow-listSiweNonceInvalidError400 Nonce is expired, unknown, or reused SiweInvalidSignatureError400 Signature doesn’t recover to the message’s address
Error Status When ProviderVerificationFailedError400 Privy token rejected InsufficientCredentialsError400 Sign-in request missing required fields ApiKeyNotFoundError404 apiKeyId doesn’t existApiKeyNotOwnedError403 Caller doesn’t own the key ApiKeyRevokedError401 Key was revoked ApiKeyExpiredError401 Key’s expiresAt has passed InvalidApiKeyError401 Key doesn’t match any record
Error Status When DeploymentNotFoundError404 No deployment for this collection DeploymentAlreadyExistsError409 Deploy already initiated; poll status instead
These map to 500 because they indicate misconfiguration or upstream failure, not client error.
Error Status When SystemContractNotFoundError500 No system-contract record for chain + type RpcUrlNotConfiguredError500 Chain has no RPC URL in config EncryptionKeyNotConfiguredError500 KMS key missing ExecutorKeyNotConfiguredError500 Platform executor key missing for this chain ExecutorKeyDecryptionFailedError500 KMS decrypt failed SigningKeyNotConfiguredError500 Sale-signing key missing SiweConfigNotConfiguredError500 SIWE domain/allow-list missing
Error Status When ChainNotFoundError400 chainId isn’t supportedCurrencyNotSupportedError400 currency not configured on this chain
The generated SDK returns a discriminated { data, error, response } shape — no exceptions on non-2xx. Example:
const res = await client . collection . get ( { highlightId } );
if (res . response ?. status === 404 ) {
// CollectionNotFoundError or CollectionNotPublicError
if (res . response ?. status === 403 ) {
// CollectionNotOwnedError
throw new Error ( " Not authorized to view this collection " );
For finer-grained dispatch, inspect res.error.name:
if (res . error && typeof res . error === " object " && " name " in res . error ) {
switch (res . error . name ) {
case " CollectionNotFoundError " :
case " CollectionNotPublicError " :
case " CollectionNotOwnedError " :
throw new Error ( " Forbidden " );