Tratamento de erros

Capture as classes de erro de listingsapi-js, leia os códigos de erro da plataforma e implemente backoff que considere retryAfter.

Todo erro lançado pelo SDK deriva de ListingsAPIError. Falhas da API lançam APIError ou uma de suas subclasses, seja quando a plataforma as informa por meio de um status HTTP fora da faixa 2xx ou no corpo de uma resposta HTTP 200. Falhas de rede (erros de DNS, conexões recusadas, tempos limite excedidos) lançam APIConnectionError. Você nunca precisa inspecionar indicadores success ou payloads brutos: uma promise resolvida significa que a chamada foi bem-sucedida.

Hierarquia de erros

Code
ListingsAPIError                 base class for everything the SDK throws├── APIError                     any failure reported by the API│   ├── AuthenticationError      401, or invalid-token error payloads│   ├── PermissionDeniedError    403, or permission error payloads│   ├── NotFoundError            404, resource not found│   ├── ValidationError          400/422, mutations reporting success=false│   │                            or errors[], and client-side payload checks│   ├── RateLimitError           429, carries retryAfter│   └── InternalServerError      5xx, retry a read; read back a write first└── APIConnectionError           no response arrived: outcome unknown

Todas as classes são exportadas da raiz do pacote:

TypeScript
import {ListingsAPIError,APIError,AuthenticationError,PermissionDeniedError,NotFoundError,ValidationError,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js';

Propriedades de APIError

PropriedadeTipoDescrição
statusCodenumber | nullStatus HTTP da resposta com falha: 401, 429, 500 ou 200 quando o erro veio no corpo de uma resposta 200. null para falhas de validação no cliente.
codestring | nullPrimeiro código de erro da plataforma, e.g. SY10005, quando a API enviou um código.
errorsApiErrorEntry[]Todas as entradas de erro, normalizadas para { code, message, context }.
responseBodystring | nullCorpo bruto da resposta como string.
retryAfternumber | nullSomente RateLimitError. Segundos de espera antes de tentar novamente, extraídos do cabeçalho Retry-After, ou null quando o cabeçalho estava ausente.
messagestringResumo legível por pessoas, seguido das entradas de erro (ou do corpo bruto).
namestringO nome da classe concreta, e.g. "RateLimitError".
TypeScript
import { ListingsAPI, APIError, APIConnectionError } from 'listingsapi-js'; const client = new ListingsAPI(); try {await client.fetchAllLocations({ first: 10 });} catch (error) {if (error instanceof APIError) {  console.error(error.statusCode);   // 401, 429, 500, or 200 for payload errors  console.error(error.code);         // first platform code, e.g. 'SY10005'  console.error(error.errors);       // [{ code, message, context }, ...]  console.error(error.responseBody); // raw body text} else if (error instanceof APIConnectionError) {  // No response arrived. A read is safe to retry; for a write, read the  // record back before repeating it.  console.error('No response from the API:', error.message);}}

Captura de erros por tipo

Falhas de autenticação

TypeScript
import { AuthenticationError } from 'listingsapi-js'; try {await client.fetchAllLocations({ first: 1 });} catch (error) {if (error instanceof AuthenticationError) {  console.error('Invalid API key. Rotate it under API Keys in the dashboard.');}}

Recursos ausentes

TypeScript
import { NotFoundError } from 'listingsapi-js'; try {await client.fetchPremiumListings(99999999);} catch (error) {if (error instanceof NotFoundError) {  console.error('Location not found.');}}

Limites de requisições

RateLimitError.retryAfter contém o valor de Retry-After do servidor em segundos, ou null quando o cabeçalho estava ausente:

TypeScript
import { RateLimitError } from 'listingsapi-js'; try {await client.fetchAllLocations({ first: 100 });} catch (error) {if (error instanceof RateLimitError) {  const waitSeconds = error.retryAfter ?? 5;  console.error('Rate limited, retry in ' + waitSeconds + 's');}}

Falhas de validação

Falhas de validação no servidor contêm as entradas de erro da plataforma:

TypeScript
import { ValidationError } from 'listingsapi-js'; try {await client.updateLocation({ id: 16808, phone: 'not-a-phone-number' });} catch (error) {if (error instanceof ValidationError) {  for (const entry of error.errors) {    console.error((entry.code ?? 'unknown') + ': ' + entry.message);  }}}

Erros em respostas HTTP 200

A plataforma informa algumas falhas com HTTP 200 e um payload de erro no corpo. O SDK inspeciona todas as respostas e também lança erros nesses casos, portanto um status 200 nunca significa "verifique o corpo por conta própria":

Indicador no payloadLançado como
errors[] no nível superior com código SY90005 ou SY90001AuthenticationError
errors[] no nível superior com código SY90003PermissionDeniedError
errors[] no nível superior com código RATE_LIMITEDRateLimitError
Qualquer outro errors[] no nível superiorAPIError
Resultado de escrita com errors[] ou success: falseValidationError

Para operações de escrita (funções auxiliares POST e DELETE), o SDK verifica cada objeto de resultado na resposta; se algum deles informar success: false ou contiver errors[], ele lança ValidationError com statusCode definido como 200.

TypeScript
import { ValidationError } from 'listingsapi-js'; try {const result = await client.archiveLocations([16808]);console.log('Archived:', result);} catch (error) {if (error instanceof ValidationError) {  // The API answered 200 but rejected the mutation  console.error(error.statusCode); // 200  console.error(error.message);}}

Validação no cliente nas funções auxiliares de post

As funções auxiliares de post (createAnnouncement, createEvent, createOffer e bulkPublish) validam seu payload localmente e lançam ValidationError antes de qualquer chamada de rede:

  • name não pode estar em branco e locationIds não pode estar vazio.
  • sites deve ser um subconjunto não vazio de GOOGLE e FACEBOOK.
  • message é obrigatório; quando for um mapa por site, cada site de destino precisa de uma entrada que não esteja em branco.
  • ctaType e ctaUrl devem ser fornecidos juntos, e ctaType deve ser um dos seguintes valores: BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER.
  • createEvent e createOffer também exigem title.
  • postType (em bulkPublish) deve ser um dos seguintes valores: ANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT.

Esses erros locais têm statusCode: null, o que permite que você os diferencie de uma rejeição no servidor. O método de baixo nível createPost(body) envia seu corpo bruto sem essas verificações.

TypeScript
import { ListingsAPI, ValidationError } from 'listingsapi-js'; const client = new ListingsAPI(); try {await client.createAnnouncement({  name: 'Grand opening',  locationIds: [16808],  message: 'Acme Dental is now open in Midtown!',  ctaType: 'BOOK', // ctaUrl missing: throws before any request is sent});} catch (error) {if (error instanceof ValidationError && error.statusCode === null) {  console.error(error.message);  // Invalid post: ctaType and ctaUrl must be provided together}}

Novas tentativas integradas

O próprio cliente repete as tentativas em caso de respostas 429 e 5xx (respeitando o cabeçalho Retry-After) e de falhas de rede, duas vezes por padrão. Os erros chegam ao seu código somente depois que essas tentativas se esgotam. Consulte Configuração para ver as opções maxRetries e timeout.

Exemplo de espera progressiva que considera retryAfter

Para tarefas de longa duração que devem continuar além das novas tentativas integradas do cliente, coloque as chamadas em um laço de repetição com espera progressiva que priorize a indicação retryAfter do servidor e, na ausência dela, use intervalos exponenciais:

backoff.ts
import {ListingsAPI,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js'; const client = new ListingsAPI(); async function withBackoff<T>(fn: () => Promise<T>,maxAttempts = 5,): Promise<T> {for (let attempt = 1; attempt <= maxAttempts; attempt++) {  try {    return await fn();  } catch (err) {    const retryable =      err instanceof RateLimitError ||      err instanceof InternalServerError ||      err instanceof APIConnectionError;    if (!retryable || attempt === maxAttempts) throw err;     let delayMs = 500 * 2 ** (attempt - 1);    if (err instanceof RateLimitError && err.retryAfter !== null) {      delayMs = err.retryAfter * 1000;    }    await new Promise((resolve) => setTimeout(resolve, delayMs));  }}throw new Error('unreachable');} const page = await withBackoff(() => client.fetchAllLocations({ first: 25 }));

Por decisão de projeto, não há novas tentativas para NotFoundError, ValidationError, AuthenticationError e PermissionDeniedError: repetir essas requisições não pode ter sucesso sem alterar os dados de entrada ou a chave.

Falhas de rede e tempos limite

Qualquer situação que impeça a chegada de uma resposta (falha de DNS, conexão recusada ou esgotamento do tempo limite por requisição) lança APIConnectionError. O SDK nunca propaga os erros nativos TypeError ou AbortError de fetch.

APIConnectionError não significa que a requisição nunca chegou ao servidor. Uma falha de DNS ou uma conexão recusada de fato interrompeu a requisição antes de ela chegar à API, mas um tempo limite excedido usa a mesma classe, e a requisição pode ter chegado e sido concluída depois que o cliente parou de esperar. Trate esse caso como resultado desconhecido: repita uma leitura livremente e, para uma escrita, leia o registro novamente antes de repeti-la, pois a API não detecta escritas repetidas.

Os tempos limite são aplicados internamente com AbortSignal.timeout em cada tentativa, com valor padrão de 240000 ms, configurável pela opção timeout do construtor. Uma tentativa que excede o tempo limite é repetida como qualquer outra falha de conexão; somente a falha final é lançada:

TypeScript
import { ListingsAPI, APIConnectionError } from 'listingsapi-js'; const client = new ListingsAPI({ timeout: 15000 }); try {await client.fetchAllLocations({ first: 10 });} catch (error) {if (error instanceof APIConnectionError) {  console.error('Network failure or timeout:', error.message);}}