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 HTTP | Code stable |
|---|---|
400 | VALIDATION_FAILED |
401 | UNAUTHORIZED |
403 | FORBIDDEN |
404 | NOT_FOUND |
409 | CONFLICT |
429 | RATE_LIMITED |
| tout autre | RAPIDY_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édicat | Statuts | Ce qu'il veut dire |
|---|---|---|
isAuthError | 401 | Clé d'API absente, invalide ou révoquée. |
isValidationError | 400 | Entrée refusée — details dit quel champ. |
isNotFound | 404 | Ressource inconnue, ou hors de votre organisation. |
isConflict | 409 | État incompatible (envoi déjà enlevé, devis expiré…) — relire avant de réessayer. |
isRateLimited | 429 | Trop d'appels : temporiser avant de réessayer. |
isRetryable | 429 500 502 503 504 | Rejouer À L'IDENTIQUE est sûr. Un 409 n'est PAS ici : le rejouer ne le résoudra pas. |
Traiter les erreurs
# 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 voyez | Ce que ça veut dire | Ce qu'il faut faire |
|---|---|---|
401 | Clé absente, invalide ou révoquée. | Vérifier la configuration. Ne pas réessayer. |
400 | Une valeur est invalide. | Lire details, corriger l'appel. |
404 | La ressource n'existe pas pour cette clé. | Vérifier l'identifiant et l'organisation. |
409 | L'état courant interdit l'action. | Lire l'état dans details, changer de geste. |
429 | Quota dépassé. | Attendre, puis réessayer avec un délai croissant. |
5xx | Incident 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.