Rapidy Développeurs

Référence

Erreurs

Des codes stables, six prédicats, et une seule règle — ne réessayer que ce qui peut réussir.

La forme d'une erreur

{
  "code": "CONFLICT",
  "message": "Cet envoi est déjà pris en charge",
  "details": { "shipment_id": "shp_7c1f4a2e", "status": "PICKED_UP" }
}

Branchez-vous sur code, jamais sur message. Le code est stable et versionné ; le message est écrit pour un humain et peut être reformulé, traduit ou précisé à tout moment.

details porte le contexte utile au diagnostic — l'identifiant concerné, l'état courant, le champ fautif. Il est destiné à vos journaux, pas à l'écran d'un client.

Statut HTTP et code stable

Quand le corps ne porte pas de code, il est dérivé du statut :

Statut HTTPCode stable
400VALIDATION_FAILED
401UNAUTHORIZED
403FORBIDDEN
404NOT_FOUND
409CONFLICT
429RATE_LIMITED
tout autreRAPIDY_ERROR

Prédicats

Chaque SDK expose ces prédicats. Ils sont générés depuis le manifeste : un 429 est « réessayable » dans les huit SDK, ou dans aucun.

PrédicatStatutsCe qu'il veut dire
isAuthError401Clé d'API absente, invalide ou révoquée.
isValidationError400Entrée refusée — details dit quel champ.
isNotFound404Ressource inconnue, ou hors de votre organisation.
isConflict409État incompatible (envoi déjà enlevé, devis expiré…) — relire avant de réessayer.
isRateLimited429Trop d'appels : temporiser avant de réessayer.
isRetryable429 500 502 503 504Rejouer À L'IDENTIQUE est sûr. Un 409 n'est PAS ici : le rejouer ne le résoudra pas.

Traiter les erreurs

Lire un refus et décider quoi faire
# Le corps d'erreur porte un code STABLE. Ne parsez jamais le message.
{
  "code": "CONFLICT",
  "message": "Cet envoi est déjà pris en charge",
  "details": { "shipment_id": "shp_…", "status": "PICKED_UP" }
}
import { RapidyError } from '@rapidy/sdk';

try {
  await rapidy.cancelShipment(shipmentId, 'client rétracté', 'awa');
} catch (err) {
  if (err instanceof RapidyError) {
    if (err.isConflict) {
      // Déjà assigné ou récupéré : l'annulation n'est plus possible.
    }
    // Seul isRetryable vaut une nouvelle tentative. Rejouer un 400 ou un 409
    // ne changera rien : ce sont des refus, pas des incidents.
    if (err.isRetryable) await retryLater();
  }
}
from rapidy_sdk import RapidyError

try:
    rapidy.cancel_shipment(shipment_id=shipment_id, reason="client rétracté", by="awa")
except RapidyError as err:
    if err.is_conflict:
        ...  # déjà assigné ou récupéré : l'annulation n'est plus possible
    # Seul is_retryable vaut une nouvelle tentative.
    if err.is_retryable:
        retry_later()
use Rapidy\Sdk\RapidyException;

try {
    $rapidy->cancelShipment($shipmentId, reason: 'client rétracté', by: 'awa');
} catch (RapidyException $e) {
    if ($e->isConflict()) {
        // Déjà assigné ou récupéré : l'annulation n'est plus possible.
    }
    // Seul isRetryable() vaut une nouvelle tentative.
    if ($e->isRetryable()) {
        retryLater();
    }
}
begin
  rapidy.cancel_shipment(shipment_id: shipment_id, reason: 'client rétracté', by: 'awa')
rescue Rapidy::Error => e
  # Déjà assigné ou récupéré : l'annulation n'est plus possible.
  retry_later if e.retryable?
  raise unless e.conflict?
end
_, err := client.CancelShipment(ctx, shipmentID, "client rétracté", "awa")
if err != nil {
    var apiErr *rapidy.Error
    if errors.As(err, &apiErr) {
        if apiErr.IsConflict() {
            // Déjà assigné ou récupéré : l'annulation n'est plus possible.
        }
        // Seul IsRetryable vaut une nouvelle tentative.
        if apiErr.IsRetryable() {
            retryLater()
        }
    }
}
try {
    rapidy.cancelShipment(shipmentId, "client rétracté", "awa");
} catch (RapidyException e) {
    if (e.isConflict()) {
        // Déjà assigné ou récupéré : l'annulation n'est plus possible.
    }
    // Seul isRetryable() vaut une nouvelle tentative.
    if (e.isRetryable()) {
        retryLater();
    }
}
try
{
    await rapidy.CancelShipmentAsync(shipmentId, "client rétracté", "awa");
}
catch (RapidyException e)
{
    if (e.IsConflict)
    {
        // Déjà assigné ou récupéré : l'annulation n'est plus possible.
    }
    // Seul IsRetryable vaut une nouvelle tentative.
    if (e.IsRetryable) await RetryLater();
}
try {
  await rapidy.cancelShipment(
    shipmentId: shipmentId, reason: 'client rétracté', by: 'awa');
} on RapidyException catch (e) {
  if (e.isConflict) {
    // Déjà assigné ou récupéré : l'annulation n'est plus possible.
  }
  // Seul isRetryable vaut une nouvelle tentative.
  if (e.isRetryable) await retryLater();
}

La règle qui compte

Ne réessayez que ce qui peut réussir. Un 400 est une entrée invalide, un 409 est un état qui interdit l'action : les rejouer ne changera rien, et remplira vos journaux d'échecs identiques. Seul isRetryable vaut une nouvelle tentative — avec un délai croissant.

Vous voyezCe que ça veut direCe qu'il faut faire
401Clé absente, invalide ou révoquée.Vérifier la configuration. Ne pas réessayer.
400Une valeur est invalide.Lire details, corriger l'appel.
404La ressource n'existe pas pour cette clé.Vérifier l'identifiant et l'organisation.
409L'état courant interdit l'action.Lire l'état dans details, changer de geste.
429Quota dépassé.Attendre, puis réessayer avec un délai croissant.
5xxIncident côté Rapidy.Réessayer avec un délai croissant.

Erreurs réseau

Quand Rapidy est injoignable — DNS, TLS, délai dépassé — les SDK ne fabriquent pas un faux code d'API. Ils lèvent une erreur NETWORK_ERROR avec un statut 0.

C'est délibéré : présenter une panne réseau comme une erreur d'API enverrait l'intégrateur chercher dans la documentation un code qui n'existe pas.

Codes métier

Certaines opérations rendent, dans details.code, un code plus précis que le code HTTP. Exemple sur la chaîne monétaire : CASH_NOT_DEPOSITED signale qu'on tente de réconcilier une espèce qui n'a pas encore été remise. Traitez-les comme des refus explicites — ils vous disent quoi faire.