Todos os erros são RFC 9457 Problem Details, com Content-Type: application/problem+json. Sem wrapper error: {} nem details genérico.
Cada type resolve para esta página: o slug final é a âncora da sua seção, como #not-found-404.

A forma comum

Seis campos, sempre presentes:
string
obrigatório
URI canônica e estável do tipo. É o único campo que convém comparar em código.
string
obrigatório
Texto estável por tipo. Não descreve a ocorrência.
integer
obrigatório
O mesmo código HTTP da resposta.
string
obrigatório
Descreve esta ocorrência. Não faça parse dele: não é uma interface e pode mudar sem aviso.
string
obrigatório
Alias curto em snake_case. É uma string aberta: podem aparecer códigos novos sem quebrar um cliente antigo. O slug de type é o mesmo código em kebab-case.
string
obrigatório
Identifica a ocorrência e coincide com o header X-Request-ID. É o que vai em um relatório de bug.
string
Opcional na RFC 9457, e a API não o emite.
Todo erro traz o header X-Request-ID. O 401 soma WWW-Authenticate: Bearer, e o 429, Retry-After.

O catálogo completo

As extensões vão no nível raiz do Problem, não aninhadas.

invalid_request — 400

O corpo ou os parâmetros não cumprem o contrato: falta um campo obrigatório, sobra um não declarado, ou um valor não valida.
array
obrigatório
Pelo menos um elemento, com code e detail obrigatórios e um pointer opcional: um JSON Pointer RFC 6901 para o campo culpado, que começa com /. Com ele você marca o input errado em vez de mostrar um texto genérico.

unauthorized — 401

Você não mandou Authorization, ou a chave que mandou não vale. É o mesmo erro para as duas causas: distingui-las diria a um atacante se uma chave existe.
Ver Chaves e segurança.

forbidden_connection — 403

A chave autenticou bem, mas o connection_id pedido não pertence a essa loja.
É 403 e não 404 porque a conexão viaja no path: o cliente já sabe que a pediu. Para recursos de catálogo é o contrário: ver not_found.

forbidden_scope — 403

A chave autenticou bem, mas não tem o scope da operação: recommendations:read, catalog:read ou signals:write. Uma chave não muda de scopes: é preciso outra que o tenha.

forbidden_origin — 403

A chave autenticou bem e tem o scope, mas a página que a está usando não é uma das origens com as quais foi emitida. Cada chave pública (spk_) nomeia os domínios https:// de onde vale; escolhem-se ao emiti-la em API & Agentes. Um pedido sem cabeçalho Origin (um servidor, curl, Postman) e um desde http://localhost passam só com a chave: a regra é apenas para páginas https. Resolve-se emitindo uma chave que liste o domínio da loja, não mexendo no código. Uma chave sem domínios é de servidor: não serve desde nenhum storefront.

not_found — 404

O recurso não existe, ou existe e é de outra loja. Nenhum detail diz qual dos dois é.
Um 404 inesperado com uma chave válida quase sempre é a chave de outra loja, não um id errado.

conflict — 409

A requisição colide com o estado atual: uma regressão de source_version na ingestão de catálogo. Um sinal mais velho que o aplicado não sobrescreve o catálogo.
Não repita sem mudanças: vai falhar do mesmo jeito. É um erro de ordem de eventos, não um transitório.

cart_items_limit_exceeded — 422

O carrinho de uma requisição de recomendações supera o limite configurado.
integer
obrigatório
O limite vigente, 200 por padrão. Vem na resposta para que você não o fixe no código.
integer
obrigatório
Quantos itens você mandou.

signal_batch_limit_exceeded — 422

Um lote de sinais do storefront traz mais do que o permitido.
integer
obrigatório
O limite vigente, 50 por padrão. O SDK já divide os lotes com este número.
integer
obrigatório
Quantos sinais você mandou.

rate_limited — 429

A chave gastou suas 60 requisições por minuto; Retry-After diz quantos segundos esperar. Ver Cota e cache.

internal_error — 500

Algo quebrou do lado do servidor. O detail nunca traz o stack, a mensagem interna nem o nome de um componente.
Aqui o request_id é imprescindível: conecta o que você viu com o log do servidor.

signals_unavailable — 503

O armazenamento de sinais do storefront não está disponível. É transitório: tente de novo com espera. O SDK faz isso sozinho, com o mesmo signal_id, e uma nova tentativa nunca conta duas vezes.

Como tratá-los a partir do SDK

O SDK traduz qualquer um destes Problems para um CrossUpApiError tipado e preserva as extensões que não conhece, para não quebrar quando a API somar campos.
Use isCrossUpApiError(err) e não err instanceof CrossUpApiError: instanceof falha se há duas cópias do pacote na árvore de módulos.
Três regras do SDK para quando algo dá errado de verdade:
  1. Não se olha o Content-Type, olha-se a forma do corpo. Um proxy pode reescrever o header, e o erro é tipado mesmo assim.
  2. O status do transporte manda. Se o corpo declara outro, a diferença fica em extensions.status: é a prova de que algo no meio reescreve respostas.
  3. errorFromResponse nunca lança. Diante de HTML, corpo vazio ou JSON que não é um Problem, devolve um erro sintético com code: "unexpected_response", o status real e o corpo cru.