Todos los errores son RFC 9457 Problem Details, con Content-Type: application/problem+json. Sin wrapper error: {} ni details genérico.
Cada type resuelve a esta página: el slug final es el ancla de su sección, como #not-found-404.

La forma común

Seis campos, siempre presentes:
string
requerido
URI canónica y estable del tipo. Es el único campo que conviene comparar en código.
string
requerido
Texto estable por tipo. No describe la ocurrencia.
integer
requerido
El mismo código HTTP de la respuesta.
string
requerido
Describe esta ocurrencia. No lo parsees: no es una interfaz y puede cambiar sin aviso.
string
requerido
Alias corto en snake_case. Es un string abierto: pueden aparecer códigos nuevos sin romper un cliente viejo. El slug de type es el mismo código en kebab-case.
string
requerido
Identifica la ocurrencia y coincide con el header X-Request-ID. Es lo que va en un reporte de bug.
string
Opcional en RFC 9457, y la API no lo emite.
Todo error trae el header X-Request-ID. El 401 suma WWW-Authenticate: Bearer, y el 429, Retry-After.

El catálogo completo

Las extensiones van al nivel raíz del Problem, no anidadas.

invalid_request — 400

El cuerpo o los parámetros no cumplen el contrato: falta un campo requerido, sobra uno no declarado, o un valor no valida.
array
requerido
Al menos un elemento, con code y detail obligatorios y un pointer opcional: un JSON Pointer RFC 6901 al campo culpable, que empieza con /. Con él marcás el input equivocado en vez de mostrar un texto genérico.

unauthorized — 401

No mandaste Authorization, o la clave que mandaste no vale. Es el mismo error para las dos causas: distinguirlas le diría a un atacante si una clave existe.
Ver Claves y seguridad.

forbidden_connection — 403

La clave autenticó bien, pero el connection_id pedido no pertenece a esa tienda.
Es 403 y no 404 porque la conexión viaja en el path: el cliente ya sabe que la pidió. Para recursos de catálogo es al revés: ver not_found.

forbidden_scope — 403

La clave autenticó bien, pero no tiene el scope de la operación: recommendations:read, catalog:read o signals:write. Una clave no cambia de scopes: hace falta otra que lo tenga.

not_found — 404

El recurso no existe, o existe y es de otra tienda. Ningún detail dice cuál de los dos es.
Un 404 inesperado con una clave válida casi siempre es la clave de otra tienda, no un id mal escrito.

conflict — 409

El request choca con el estado actual: una regresión de source_version en la ingesta de catálogo. Una señal más vieja que la aplicada no pisa el catálogo.
No lo reintentes sin cambios: va a fallar igual. Es un error de orden de eventos, no uno transitorio.

cart_items_limit_exceeded — 422

El carrito de un pedido de recomendaciones supera el límite configurado.
integer
requerido
El límite vigente, 200 por defecto. Viene en la respuesta para que no lo hardcodees.
integer
requerido
Cuántos ítems mandaste.

signal_batch_limit_exceeded — 422

Un lote de señales del storefront trae más de las permitidas.
integer
requerido
El límite vigente, 50 por defecto. El SDK ya parte los lotes con este número.
integer
requerido
Cuántas señales mandaste.

rate_limited — 429

La clave gastó sus 60 pedidos por minuto; Retry-After dice cuántos segundos esperar. Ver Cuota y caché.

internal_error — 500

Algo se rompió del lado del servidor. El detail nunca trae el stack, el mensaje interno ni el nombre de un componente.
Acá el request_id es imprescindible: conecta lo que viste con el log del servidor.

signals_unavailable — 503

El almacén de señales del storefront no está disponible. Es transitorio: reintentá con espera. El SDK lo hace solo, con el mismo signal_id, y un reintento nunca cuenta dos veces.

Cómo manejarlos desde el SDK

El SDK traduce cualquiera de estos Problems a un CrossUpApiError tipado y preserva las extensiones que no conoce, para no romperse cuando la API sume campos.
Usá isCrossUpApiError(err) y no err instanceof CrossUpApiError: instanceof falla si hay dos copias del paquete en el árbol de módulos.
Tres reglas del SDK para cuando algo sale mal de verdad:
  1. No se mira el Content-Type, se mira la forma del cuerpo. Un proxy puede reescribir el header, y el error se tipa igual.
  2. El status del transporte manda. Si el cuerpo declara otro, la diferencia queda en extensions.status: es la prueba de que algo en el medio reescribe respuestas.
  3. errorFromResponse nunca lanza. Ante HTML, cuerpo vacío o JSON que no es un Problem devuelve un error sintético con code: "unexpected_response", el status real y el cuerpo crudo.