Skip to content

Errors

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.

ErrorStatusWhen
CollectionNotFoundError404highlightId doesn’t exist
CollectionNotPublicError404Collection exists but is Draft and caller isn’t the owner
CollectionNotOwnedError403Caller authenticated but not the collection owner
CollectionNotDraftError400Attempting a draft-only operation on a live collection
CollectionContractNotFoundError404Referenced contractId doesn’t exist
CollectionContractNotOwnedError403Caller doesn’t own the referenced contract
InvalidCollectionInputError400Schema-level validation failure (e.g. missing logo)
InvalidTypeSpecificDetailsError400Edition/series/generative details don’t match the collection type
CollectionMediaNotFoundError404Referenced mediaId doesn’t exist
CollectionMediaNotOwnedError403Caller doesn’t own the referenced media
CollectionMediaUploadNotReadyError400Media isn’t Ready yet — wait for processing
CollectionMediaInvalidPurposeError400Media kind doesn’t fit the slot (e.g. Directory as logo)
CollectionMediaNotOnchainReadyError400Media not published to archive provider yet
BaseUriNotSetError400Cannot finalize metadata before base URI is computed
ErrorStatusWhen
SaleNotFoundError404saleId doesn’t exist or isn’t on this collection
SaleNotPublicError403Attempting to act on a gated sale without gate pass
SaleNotLiveError400Claim/bid against a non-live sale
SaleAlreadyExistsError409Draft collections allow one sale at a time
SaleCannotDeleteLiveError400Live sales cannot be deleted — pause or end-date
SaleTypeNotSupportedError400Combination not in the support matrix
InvalidSalePriceError400Price is zero, negative, or malformed
InvalidTypeConfigError400typeConfig fails schema validation for the sale type
ChooseTokenNotSupportedError400tokenIds passed for a non-series or non-fixed/dutch sale
AuctionNotEndedError400Cannot claim a ranked-auction sale before endAt
NoValidBidsError400Claiming a ranked auction but the caller has no valid winning bids
VectorIdRequiredError400Public Dutch auction claim needs a registered vectorId
SaleGateCheckFailedError403Caller’s wallet doesn’t satisfy the gate conditions
AccountContextRequiredError401Claim/bid without auth
AccountWalletRequiredError400Account has no wallet configured
ContractAddressRequiredError400Gated claim against an undeployed contract
SaleMechanicNotFoundError400Referenced mechanicId doesn’t exist
SaleMechanicDeploymentNotFoundError400Mechanic has no deployment on this chain
ErrorStatusWhen
GateNotFoundError404gateId doesn’t exist
GateNotOwnedError403Caller doesn’t own the gate
ConditionTypeNotSupportedError400Unknown type in a condition config
ErrorStatusWhen
MediaNotFoundError404mediaId doesn’t exist
MediaNotOwnedError403Caller doesn’t own the media
MediaUploadNotFoundError404No upload session on this media
MediaTooLargeError400fileSize exceeds per-kind limit
MediaKindMismatchError400Uploading bytes of the wrong MIME for the declared kind
MediaInUseError409Deleting media referenced by a deployed contract
MediaStorageUnavailableError502Upstream storage provider rejected the write
DirectoryNotFinalizedError400Using a Directory before extraction is Ready
LocationNotFoundError404No storage location record for this media
ProviderUploadFailedError502Upload to R2/Arweave failed; safe to retry
ErrorStatusWhen
ExtractionNotFoundError404No extraction record for this media
ExtractionAlreadyExistsError409Extraction already running
ExtractionNotRetryableError400Extraction is in a state that can’t be retried
ExtractionMediaNotZipError400Media kind isn’t Directory
ExtractionMediaNotUploadedError400Bytes never PUT to the upload URL
ExtractionMediaNotFoundError404Parent media record missing
ErrorStatusWhen
SiweInvalidMessageError400Message isn’t valid EIP-4361
SiweDomainMismatchError400Message domain doesn’t match the backend’s expected host
SiweChainNotAllowedError400chainId in the message isn’t on the allow-list
SiweNonceInvalidError400Nonce is expired, unknown, or reused
SiweInvalidSignatureError400Signature doesn’t recover to the message’s address
ErrorStatusWhen
ProviderVerificationFailedError400Privy token rejected
InsufficientCredentialsError400Sign-in request missing required fields
ApiKeyNotFoundError404apiKeyId doesn’t exist
ApiKeyNotOwnedError403Caller doesn’t own the key
ApiKeyRevokedError401Key was revoked
ApiKeyExpiredError401Key’s expiresAt has passed
InvalidApiKeyError401Key doesn’t match any record
ErrorStatusWhen
DeploymentNotFoundError404No deployment for this collection
DeploymentAlreadyExistsError409Deploy already initiated; poll status instead

These map to 500 because they indicate misconfiguration or upstream failure, not client error.

ErrorStatusWhen
SystemContractNotFoundError500No system-contract record for chain + type
RpcUrlNotConfiguredError500Chain has no RPC URL in config
EncryptionKeyNotConfiguredError500KMS key missing
ExecutorKeyNotConfiguredError500Platform executor key missing for this chain
ExecutorKeyDecryptionFailedError500KMS decrypt failed
SigningKeyNotConfiguredError500Sale-signing key missing
SiweConfigNotConfiguredError500SIWE domain/allow-list missing
ErrorStatusWhen
ChainNotFoundError400chainId isn’t supported
CurrencyNotSupportedError400currency 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.error) {
if (res.response?.status === 404) {
// CollectionNotFoundError or CollectionNotPublicError
return null;
}
if (res.response?.status === 403) {
// CollectionNotOwnedError
throw new Error("Not authorized to view this collection");
}
throw res.error;
}
return res.data;

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":
return null;
case "CollectionNotOwnedError":
throw new Error("Forbidden");
default:
throw res.error;
}
}