Un refus est une réponse comme une autre : il a une forme stable, un code que votre programme peut lire, et un identifiant que le support peut retrouver. Rien d'utile ne se cache dans le texte du message.
La forme d'un refus
{
"error": {
"code": "insufficient_credit",
"message": "This order needs 0.44 CAD of credit; the balance is 0.12 CAD.",
"details": { "required": "0.44", "balance": "0.12", "missing": "0.32", "currency": "CAD" },
"request_id": "req_8fh2kq4m1x0a"
}
}
Décidez sur code, jamais sur message : le message
est en anglais, destiné à un journal, et peut être reformulé. Le code, lui,
entre dans le contrat. details porte de quoi agir — ici, le
montant exact à recharger.
Chaque réponse, réussie ou non, porte un en-tête
X-Request-Id. Citez-le si vous nous écrivez : on retrouve
l'appel.
Les codes
De nouveaux codes peuvent apparaître. Traitez un code inconnu comme un échec ordinaire de sa classe HTTP.
| Code | HTTP |
|---|---|
missing_token | 401 |
invalid_api_key | 401 |
key_disabled | 401 |
key_expired | 401 |
insufficient_scope | 403 |
ip_not_allowed | 403 |
email_not_verified | 403 |
account_suspended | 403 |
forbidden | 403 |
validation_failed | 400 |
not_found | 404 |
request_refused | 422 |
insufficient_credit | 402 |
budget_cap_reached | 402 |
payment_required | 402 |
quota_reached | 409 |
service_suspended | 409 |
action_conflict | 409 |
idempotency_conflict | 409 |
hostname_taken | 409 |
order_in_progress | 409 |
too_many_pending_invoices | 409 |
coupon_not_found | 422 |
coupon_max_uses | 422 |
plan_unavailable_in_region | 422 |
image_unavailable_in_region | 422 |
image_incompatible_with_plan | 422 |
out_of_stock | 422 |
billing_mode_unavailable | 422 |
provisioning_failed | 500 |
rate_limited | 429 |
backend_unavailable | 503 |
internal_error | 500 |
Les seaux de débit
Par clé : 120 lectures par minute, 20 actions par minute, et 10 créations ou réinstallations par heure. Une réinstallation détruit un disque et reconstruit une machine : elle relève du même seau que les créations.
Chaque réponse porte X-RateLimit-Limit et
X-RateLimit-Remaining pour le seau concerné ; un refus rend
429 avec Retry-After. Un garde plus large s'applique
aussi par adresse, avant authentification : un client normal ne le rencontre
jamais, un script qui devine des jetons, si.
Rejouer sans dupliquer
POST /v1/vms exige un en-tête Idempotency-Key : une
valeur de votre choix, unique par commande. Si votre requête expire sans
réponse, vous ne savez pas si la machine a été créée — la rejouer avec la même
clé vous rend la première réponse, telle quelle, avec
Idempotency-Replayed: true.
Trois cas, un seul est un rejeu : même clé et même corps, on ressert ; même
clé et corps différent, c'est une erreur de votre côté et on refuse
(409 idempotency_conflict) ; même clé pendant que la première
tourne encore, on refuse aussi plutôt que d'exécuter les deux. Les clés sont
gardées 24 heures.
Les actions asynchrones
Provisionner, réinstaller, allumer : rien de tout cela n'est instantané. Ces
appels rendent 202 et un objet action, que l'on
sonde jusqu'à ce que son statut quitte running.
{ "id": 90112, "type": "create", "status": "error", "vm_id": 4312,
"source": "api",
"error": { "code": "provisioning_failed",
"message": "No IPv4 address left in the pool for this region." } }
Une action ne reste pas ouverte indéfiniment : passé six heures sans
conclusion — une tâche perdue, une machine supprimée avant qu'on relise son
sort — elle passe en error avec le code
internal_error. Une boucle de sondage finit donc toujours par
sortir.
L'historique d'une machine se lit sur
GET /v1/vms/{id}/actions, et il est complet : un clic dans la
console y figure aussi, avec source: console.