Gestion des erreurs

Interceptez les classes d’erreurs listingsapi-js, lisez les codes d’erreur des plateformes et créez une temporisation progressive adaptée à retryAfter.

Toutes les erreurs levées par le SDK dérivent de ListingsAPIError. Les échecs de l’API lèvent APIError ou l’une de ses sous-classes, que la plateforme les signale par un statut HTTP hors de la plage 2xx ou dans le corps d’une réponse HTTP 200. Les échecs réseau (erreurs DNS, connexions refusées, délais d’attente dépassés) lèvent APIConnectionError. Vous n’avez jamais à examiner les indicateurs success ni les données brutes : une promesse résolue signifie que l’appel a réussi.

Hiérarchie des erreurs

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

Toutes les classes sont exportées depuis la racine du package :

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

Propriétés de APIError

PropriétéTypeDescription
statusCodenumber | nullStatut HTTP de la réponse en échec : 401, 429, 500 ou 200 lorsque l’erreur figure dans le corps d’une réponse 200. null pour les échecs de validation côté client.
codestring | nullPremier code d’erreur de la plateforme, e.g. SY10005, lorsque l’API en a envoyé un.
errorsApiErrorEntry[]Toutes les entrées d’erreur, normalisées au format { code, message, context }.
responseBodystring | nullCorps brut de la réponse sous forme de chaîne.
retryAfternumber | nullRateLimitError uniquement. Nombre de secondes à attendre avant une nouvelle tentative, extrait de l’en-tête Retry-After, ou null si l’en-tête était absent.
messagestringRésumé lisible, suivi des entrées d’erreur (ou du corps brut).
namestringNom de la classe concrète, 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);}}

Intercepter les erreurs par type

Échecs d’authentification

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.');}}

Ressources manquantes

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

Limites de débit

RateLimitError.retryAfter contient la valeur Retry-After du serveur en secondes, ou null si l’en-tête était absent :

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');}}

Échecs de validation

Les échecs de validation côté serveur contiennent les entrées d’erreur de la plateforme :

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);  }}}

Erreurs dans les réponses HTTP 200

La plateforme signale certains échecs par un statut HTTP 200 et des données d’erreur dans le corps de la réponse. Le SDK examine chaque réponse et lève également une exception dans ces cas ; un statut 200 ne signifie donc jamais « vérifiez vous-même le corps de la réponse » :

Indicateur dans les donnéesException levée
errors[] au premier niveau avec le code SY90005 ou SY90001AuthenticationError
errors[] au premier niveau avec le code SY90003PermissionDeniedError
errors[] au premier niveau avec le code RATE_LIMITEDRateLimitError
Tout autre errors[] au premier niveauAPIError
Résultat d’écriture avec errors[] ou success: falseValidationError

Pour les écritures (fonctions utilitaires POST et DELETE), le SDK vérifie chaque objet de résultat dans la réponse ; si l’un d’eux signale success: false ou contient errors[], il lève ValidationError avec statusCode défini sur 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);}}

Validation côté client dans les fonctions utilitaires de publication

Les fonctions utilitaires de publication (createAnnouncement, createEvent, createOffer et bulkPublish) valident leurs données localement et lèvent ValidationError avant tout appel réseau :

  • name ne doit être ni vide ni composé uniquement d’espaces, et locationIds ne doit pas être vide.
  • sites doit être un sous-ensemble non vide de l’ensemble formé par GOOGLE et FACEBOOK.
  • message est obligatoire ; lorsqu’il s’agit d’une table de correspondance par site, chaque site ciblé doit avoir une entrée qui ne soit ni vide ni composée uniquement d’espaces.
  • ctaType et ctaUrl doivent être fournis ensemble, et ctaType doit avoir l’une des valeurs suivantes : BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER.
  • createEvent et createOffer nécessitent également title.
  • postType (sur bulkPublish) doit avoir l’une des valeurs suivantes : ANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT.

Ces erreurs locales ont statusCode: null, ce qui vous permet de les distinguer d’un rejet côté serveur. La méthode de bas niveau createPost(body) envoie son corps brut sans effectuer ces vérifications.

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}}

Nouvelles tentatives intégrées

Le client effectue lui-même de nouvelles tentatives pour 429 et les réponses 5xx (en respectant l’en-tête Retry-After), ainsi que pour les échecs réseau, à raison de deux nouvelles tentatives par défaut. Les erreurs ne sont transmises à votre code qu’une fois ces tentatives épuisées. Consultez Configuration pour les options maxRetries et timeout.

Exemple de temporisation tenant compte de retryAfter

Pour les tâches de longue durée qui doivent se poursuivre au-delà des nouvelles tentatives intégrées au client, placez les appels dans une boucle de temporisation qui utilise en priorité l’indication retryAfter du serveur et, à défaut, des délais exponentiels :

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 }));

NotFoundError, ValidationError, AuthenticationError et PermissionDeniedError ne donnent délibérément lieu à aucune nouvelle tentative : répéter ces requêtes ne peut pas réussir sans modifier les données d’entrée ou la clé.

Échecs réseau et dépassements du délai d’attente

Tout événement empêchant la réception d’une réponse (échec DNS, connexion refusée ou dépassement du délai d’attente de la requête) lève APIConnectionError. Le SDK ne laisse jamais se propager les exceptions natives TypeError ou AbortError de fetch.

APIConnectionError ne signifie pas que la requête n’a jamais atteint le serveur. Un échec DNS ou une connexion refusée a bien interrompu la requête avant qu’elle n’atteigne l’API, mais un dépassement du délai d’attente utilise la même classe, et la requête peut avoir été reçue et traitée après que le client a cessé d’attendre. Considérez que le résultat est inconnu : vous pouvez relancer une lecture sans restriction, et pour une écriture, relisez l’enregistrement avant de la répéter, car l’API ne détecte pas les écritures répétées.

Les délais d’attente sont appliqués en interne avec AbortSignal.timeout à chaque tentative ; leur valeur par défaut est de 240000 ms et peut être configurée au moyen de l’option timeout du constructeur. Une tentative dont le délai d’attente est dépassé est relancée comme pour tout autre échec de connexion ; seul l’échec final est levé :

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);}}