Aller au contenu

Erreurs

Forme

La plupart des points de terminaison JSON renvoient ce corps en cas d'échec :

{ "error": "message lisible", "code": "code-machine" }

Les deux champs sont toujours présents. error nomme en mots le paramètre ou la condition en cause; code est la valeur sur laquelle brancher, parce que les messages sont rédigés en anglais seulement et qu'un client ne devrait jamais avoir à analyser de la prose pour décider quoi faire.

Optimize utilise plutôt le format d'erreur structuré de Google : { "error": { "code": 400, "message": "...", "status": "INVALID_ARGUMENT", "details": [] }, "code": "bad_request" }. Lisez la chaîne code au premier niveau pour la catégorie d'erreur d'Unmap et error.status pour le statut de Google. La section Optimize plus bas donne ses statuts, ses codes de validation, ses avertissements et ses raisons d'exclusion.

Les tuiles, le terrain, les courbes de niveau, les glyphes et les sprites font exception. Leurs corps d'erreur sont du texte brut, pas du JSON : invalid tile coordinates, invalid fontstack, not found, etc. MapLibre GL ne lit jamais ces corps; il vérifie seulement le code HTTP et continue. Les styles sont aussi du JSON, sauf un cas : lorsque la publication active du fond de carte n'est pas un identifiant à huit chiffres, ou que cet objet n'est pas dans le stockage, le style et le chemin de tuile sans date répondent en texte brut 503 basemap release is misconfigured avec Cache-Control: private, no-store. Cette réponse ne sert pas l'archive précédente. Tous les autres points de terminaison de la passerelle (géocodage, itinéraires et l'API de compte) retournent du JSON, tout comme la seule erreur JSON que les chemins de terrain et de courbes de niveau émettent, le 404 d'une archive non configurée.

Union code

type ErrorCode =
  | "unauthorized"        // 401 : clé absente, mal formée ou inconnue
  | "rate_limited"        // 429 : au-delà de la limite de débit par clé
  | "quota_exceeded"      // 429 : forfait dev au bout de son volume mensuel inclus
  | "spend_cap"           // 429 : forfait payant au plafond de dépenses
  | "billing_required"    // 429 : paiement échoué sur un forfait payant
  | "origin_not_allowed"  // 403 : la liste d'origines de cette clé ne couvre pas la requête
  | "addon_required"      // 403 : forfait payant, la clé n'a pas la capacité nommée
  | "dev_only"            // 403 : forfait dev, capacité premium, origine de production
  | "usage_limit_reached" // 403 : forfait dev au-delà de son plafond d'appels premium
  | "bad_request"         // 400 : chemin de données lorsqu'aucun code plus précis ne s'applique, y
                          //       compris une coordonnée de géocodage ou de lieux hors du WGS84;
                          //       l'API de compte l'utilise aussi
  | "invalid_theme"       // 400 : un code `?theme=` qui ne se décode ou ne se valide pas
  | "invalid_coordinate"  // 400 : /timezone : lat ou lon manque, n'est pas un nombre, ou est hors limites
  | "invalid_timestamp"   // 400 : /timezone : `at` n'est pas un instant ISO 8601
  | "invalid_location"    // 400 : /route, /isochrone, /matrix, /match, /optimized-route : une
                          //       coordonnée manque, est mal formée, ou est hors du WGS84
  | "invalid_mode"        // 400 : /route, /isochrone : un mode de déplacement inconnu
  | "invalid_route_option" // 400 : une dimension de camion, avoid, use_highways, use_hills ou un plafond
                           //       de contour hors limites ou inconnu, ou envoyé sur un mode dont
                           //       le calcul de coût ne connaît pas cette option; aussi
                           //       avoid=highways envoyé avec use_highways
  | "invalid_body"        // 400 : /matrix, /match, /optimized-route : le corps manque, n'est pas du JSON, ou dépasse la limite de taille
  | "too_many_locations"  // 400 : /matrix, /match ou /optimized-route a dépassé son plafond d'origines, de destinations, de points ou d'arrêts
  | "outside_coverage"    // 422 : un ancrage hors du Canada. Le graphe et le géocodeur ne sont pas appelés
  | "no_route"            // 422 : /route, /matrix, /match, /optimized-route : une requête canadienne à laquelle le graphe ne répond pas
  | "service_unavailable" // 503 : un service porté par conteneur n'a pas répondu à temps, ou
                          //       n'est pas configuré sur ce déploiement (le corps porte alors `contract`)
  | "not_found"           // 404 : style, couche ou lieu inconnu; archive de terrain ou de courbes
                          //       de niveau non configurée; chemin que cette passerelle ne sert pas.
                          //       L'API de compte l'utilise aussi pour une ressource inconnue
  | "not_implemented"     // 501 : /data/query sur une couche cataloguée mais servie en tuiles seulement
  | "internal_error"      // 500 : échec inattendu d'Optimize
  | "deadline_exceeded"   // 504 : les trajets routiers ou la réponse du solveur d'Optimize ont dépassé leur budget de temps
  | "upstream_error"      // 502 : un service en amont a échoué : le conteneur de routage, ou
                          //       une tuile de terrain que /elevation ne peut pas décoder
  | "invalid_token"       // API de compte seulement : lien magique ou jeton de session invalide ou expiré
  | "conflict"            // API de compte seulement : l'état du traitement ne le permet pas
  | "payload_too_large"   // 413 : nombre de requêtes par lots, corps d'Optimize ou téléversement de compte au-delà de sa limite
  | "plan_required";      // API de compte seulement : exige un forfait payant

Tout jusqu'à upstream_error inclus est ce que le trafic par clé API de votre application peut rencontrer. Les quatre derniers appartiennent aux points de terminaison du tableau de bord (/account/*), qui s'authentifient par session navigateur plutôt que par clé API; payload_too_large et conflict sont partagés, puisque POST /geocode/batch lève le premier et que les traitements par lot hébergés lèvent les deux. Un échec d'itinéraire codé permet à un client de distinguer too_many_locations de invalid_location sans analyser le message, et invalid_theme porte aussi un tableau detail qui nomme ce qui clochait dans le code. Voir Itinéraires pour savoir quel point de terminaison émet quel code d'itinéraire, et ROUTING_ERROR_CODES dans apps/gateway/src/routing/errors.ts pour la liste source.

Sur le chemin de données, payload_too_large est le 413 de POST /geocode/batch lorsque queries a plus de 100 entrées, et d'Optimize lorsque le corps dépasse 4 Mio. Découpez la requête; ne réessayez pas le même corps trop volumineux. Optimize utilise aussi internal_error pour 500 et deadline_exceeded pour 504; voir Optimize plus bas. Les codes de compte conflict et plan_required appartiennent au géocodage par lots hébergé, l'exécuteur de traitements du tableau de bord authentifié par session, pas à POST /geocode/batch. Il n'y a pas d'API de traitements authentifiée par clé; voir Géocodage par lots.

En-têtes lisibles par un navigateur

Le JavaScript multi-origine ne voit que les en-têtes de réponse que le serveur expose explicitement, et un en-tête que votre code ne peut pas lire est un en-tête inexistant du point de vue de votre logique de reprise. L'API expose Retry-After, ETag, Content-Length, Content-Range et Accept-Ranges, ainsi que les en-têtes d'Optimize X-Unmap-Billable-Shipments, X-Unmap-Optimize-Warnings et Warning.

Retry-After est celui qui compte ici : il accompagne chaque 429 sauf billing_required, ainsi que le 503 réessayable, de sorte qu'un client navigateur peut le respecter au lieu de deviner un délai.

Codes HTTP

  • 401 : clé API absente, mal formée ou inconnue. Code unauthorized. Chaque 401 du chemin de données porte aussi un défi WWW-Authenticate (RFC 6750) qui nomme le schéma et pointe vers les métadonnées qui l'expliquent : Bearer realm="api.unmap.dev", resource_metadata="https://api.unmap.dev/.well-known/oauth-protected-resource", avec error="invalid_token" en plus lorsqu'une clé a été présentée mais n'a pas été reconnue.
  • 429 : l'une de quatre situations, distinguées par code :
    • rate_limited : au-delà de la limite de débit par clé (1 000 requêtes par 60 secondes par emplacement Cloudflare). Retry-After: 60.
    • quota_exceeded : une clé dev au-delà de ses 10 000 appels inclus pour le mois. Retry-After: 3600; le blocage se lève à la prochaine remise à zéro mensuelle.
    • spend_cap : une clé payante à son plafond de dépenses configuré. Retry-After: 3600; augmentez le plafond dans le tableau de bord.
    • billing_required : le dernier paiement d'un forfait payant a échoué. Pas de Retry-After, parce qu'aucune attente ne règle la situation; mettez à jour le mode de paiement dans le tableau de bord.
  • 403 : l'une de quatre situations, distinguées par code :
    • origin_not_allowed : la clé porte une liste d'origines autorisées non vide et cette requête n'y est pas couverte. Un en-tête Origin absent, un en-tête illisible et une origine bien formée mais simplement absente de la liste reçoivent tous la même réponse, et le corps ne dit jamais ce que contient la liste. Une clé dont la liste est vide n'a aucune restriction et ne voit jamais cette erreur. Voir Authentification.
    • addon_required : une clé sur forfait payant qui ne détient pas la capacité dont cette couche ou cette superposition a besoin. Le corps porte capability (legalLand, energy, agriculture, remote, mining ou ski), addon qui nomme le SKU le moins cher qui l'accorderait (addon-legal-land, addon-energy, addon-agriculture, addon-remote, addon-mining, addon-ski), et un message écrit pour une personne là où error est écrit pour un journal. Voir Couches. Les clés sans compte (fondateur et intégration) en sont exemptées.
    • dev_only : une clé sur forfait dev qui utilise une capacité premium depuis une origine de production. La même clé est acceptée depuis localhost, 127.0.0.1, *.localhost, *.pages.dev et *.workers.dev, où toutes les capacités sont gratuites. Elle porte les mêmes champs capability, addon et message, mais la correction diffère et c'est pourquoi le code diffère : un compte dev n'a aucun abonnement auquel rattacher un complément, donc l'étape est de souscrire. Voir Couches.
    • usage_limit_reached : une clé sur forfait dev au-delà de ses 5 000 appels premium du mois. Le trafic du noyau sur la même clé n'est pas touché et continue de fonctionner : seules les capacités restreintes refusent. Le plafond se lève au début du mois suivant, et un forfait payant le supprime. Porte les mêmes champs capability, addon et message.
  • 400 : un paramètre de requête mal formé, code bad_request sauf si un code plus précis s'applique. Coordonnées de tuile invalides, q manquant sur un appel de géocodage, bbox mal formé, near absent ou non numérique, une longitude ou une latitude hors du WGS84 en géocodage ou pour les lieux, category inconnue, near et bbox fournis ensemble ou ni l'un ni l'autre sur une recherche par catégorie, plus de quatre contours d'isochrone ou un contour de plus de 120 minutes, code de thème invalide, couche /data/query inconnue, etc. Le message nomme le paramètre.
  • 501 : GET /data/query sur une couche tuilée du catalogue (sans table PostGIS). Code not_implemented; le message liste les identifiants interrogeables. Voir Couches.
  • 404 : style inconnu sur /styles/{style}.json, couche ou lieu inconnu, layer archive not configured quand l'objet d'une couche du catalogue n'est pas dans le stockage, archive de terrain ou de courbes de niveau non configurée, ou chemin que la passerelle ne sert pas. Ces corps JSON utilisent not_found, tout comme une ressource inconnue sur l'API de compte. Une clé absente est un 401 avant tout cela, y compris sur un chemin inconnu. Une plage de glyphes ou une feuille de sprites manquante, et un chemin sans correspondance sous /tiles, /terrain, /contours, /layers, /glyphs ou /sprite, restent du texte brut.
  • 204 : une requête de tuile au-delà du zoom maximal de l'archive, ou pour une tuile qui ne contient aucune donnée. Une réponse vide, pas un échec : MapLibre surzoome depuis la tuile la plus profonde qu'il a déjà.
  • 422 : la requête a été comprise et refusée. Deux codes partagent ce statut; branchez donc sur code.
    • outside_coverage : un ancrage est hors du Canada. Le corps est { "error": "outside Canadian coverage", "code": "outside_coverage" }. Une isochrone mode=transit dont le centre n'a aucun transport en commun prévu à proximité utilise le même code, avec l'erreur no scheduled transit serves this point; use a road mode such as mode=pedestrian. Le graphe et le géocodeur ne sont pas appelés. /route, /isochrone, /matrix, /match, /optimized-route, /geocode/reverse, /geocode/nearby et /places/nearby l'utilisent tous. Une recherche textuelle ou une saisie semi-automatique sans résultat reste une liste vide. Une longitude ou une latitude hors du WGS84 est un 400 bad_request pour le géocodage et les lieux, et un 400 invalid_location pour les itinéraires. Ce n'est pas outside_coverage.
    • no_route : le point est au Canada et le graphe n'a toujours pas de réponse. Sur /route le corps est { "error": "no route", "code": "no_route" }. La pleine eau à l'intérieur du Canada, y compris la baie d'Hudson, et un trajet que les routes ne relient pas, y compris Iqaluit vers le continent, sont no_route. /match utilise no_route quand une trace canadienne ne peut être accrochée à aucune route. /matrix garde une paire que le moteur ne fait que rater comme une cellule null dans un 200, le reste de la grille étant intact, donc gérez null sur chaque cellule. Il répond 422 no_route quand le moteur refuse la requête d'emblée : un point sans route à proximité, des points qui ne sont pas tous dans une même région reliée, ou deux points au-delà de sa limite à vol d'oiseau. Un point hors du Canada est outside_coverage avant cette vérification. /optimized-route échoue en entier pour ces mêmes raisons de graphe, ce qui est confirmé contre la passerelle déployée, même si un null dans sa matrice interne ne suffit pas à le faire échouer; voir Itinéraires.
  • 502 : un service en amont a répondu par un échec qui lui est propre. Code upstream_error : le conteneur de routage a échoué, ou /elevation a rencontré une tuile de terrain qui ne se décode pas.
  • 503 : quatre situations différentes :
    • Le conteneur n'a pas répondu du tout, ou n'a pas répondu dans le délai imparti par la passerelle. C'est la seule que vous puissiez réellement voir en production, pendant un redémarrage de conteneur. Le corps porte un code, un drapeau retryable et un Retry-After :

      { "error": "geocode temporarily unavailable", "code": "service_unavailable", "retryable": true }

      Le nom du service est geocode, routing, land ou energy. Rien n'est divulgué sur la cause, délibérément : le texte d'erreur d'un conteneur peut nommer une étiquette d'image ou un message de base de données.

      Retry-After varie selon le service, et l'écart n'est pas cosmétique. geocode et routing sont gardés au chaud par une tâche planifiée qui les sollicite toutes les cinq minutes : leur 503 est un soubresaut et l'en-tête dit 5. land et energy, qui servent les surfaces de terres légales, d'énergie et de mines, ont le droit de s'endormir : leur première requête après une période d'inactivité démarre Postgres sur un répertoire de données précuit et prend environ une minute, donc l'en-tête dit 60. Respectez l'en-tête. Réessayer un 503 de land toutes les cinq secondes échoue une dizaine de fois avant de pouvoir réussir, ce qui se lit comme un produit cassé plutôt que comme un démarrage à froid.

    • Le service n'est pas branché du tout. C'est ce que vous voyez en exécutant la passerelle localement sans les conteneurs, pas en production. Le corps porte code: "service_unavailable" et, au lieu de retryable, répète la forme de requête attendue, pour qu'un appelant apprenne le contrat depuis l'échec :

      {
        "error": "routing is not deployed yet",
        "code": "service_unavailable",
        "contract": "?from=lng,lat&to=lng,lat&mode=auto|car|bicycle|pedestrian|truck|transit",
      }
    • La publication active du fond de carte n'est pas un identifiant à huit chiffres, ou cet objet n'est pas dans le stockage. Le style et le chemin de tuile sans date répondent en texte brut, pas en JSON :

      basemap release is misconfigured

      Cache-Control vaut private, no-store. L'archive précédente n'est pas servie à sa place, et une nouvelle requête ne la rétablit pas. Sur /tiles/{release}/{z}/{x}/{y}, un identifiant à huit chiffres qui n'a jamais été copié est un 404 basemap release not found, lui aussi private, no-store. Un identifiant qui n'a pas huit chiffres a le même corps 404 sans cet en-tête de cache.

Chaque page d'API liste ses propres messages 400 mot pour mot : l'API de cartes, Géocodage, Itinéraires.

Les corps, mot pour mot

Les six échecs que vous rencontrerez le plus souvent, exactement tels que la passerelle les envoie. Chacun est copié d'une vraie réponse, non paraphrasé.

Clé manquante, 401. Ni Authorization: Bearer, ni X-API-Key, ni X-Goog-Api-Key, ni ?key= :

{ "error": "missing API key", "code": "unauthorized" }

Clé invalide ou désactivée, 401. La clé est bien formée mais absente du registre, ou elle a été supprimée. Les deux cas sont délibérément indiscernables de l'extérieur :

{ "error": "invalid or disabled API key", "code": "unauthorized" }

Au-delà d'une limite, 429. Quatre situations différentes derrière un seul statut, que le code distingue. rate_limited est la limite de débit par clé et porte Retry-After: 60 :

{ "error": "rate limit exceeded", "code": "rate_limited" }

Les trois autres sont les drapeaux d'application mensuels, posés par la tâche planifiée de cinq minutes plutôt que comptés à chaque requête. quota_exceeded et spend_cap portent Retry-After: 3600; billing_required n'en porte aucun, parce qu'aucune attente ne règle un paiement échoué :

{
  "error": "monthly quota exceeded: raise your spend cap or upgrade at unmap.dev/dashboard/billing",
  "code": "quota_exceeded",
}
{ "error": "spend cap reached: raise it at unmap.dev/dashboard/billing", "code": "spend_cap" }
{
  "error": "payment failed: update your payment method at unmap.dev/dashboard/billing",
  "code": "billing_required",
}

Mode d'itinéraire non pris en charge, 400. /route et /isochrone valident mode au lieu de retomber sur une valeur par défaut, donc une faute de frappe est bruyante :

{ "error": "mode must be one of auto, car, bicycle, pedestrian, truck, transit", "code": "invalid_mode" }

Code de thème invalide, 400. Le seul 400 du plan de données qui porte un troisième champ à côté de error et code : un tableau detail qui dit ce qui n'allait pas. Un code qui n'est ni un document ut1. ni un préréglage court échoue d'une façon, et un code ut1. dont la charge utile ne se décompresse pas échoue d'une autre :

{
  "error": "invalid theme",
  "code": "invalid_theme",
  "detail": ["code must start with \"ut1.\" or be a short preset"],
}
{
  "error": "invalid theme",
  "code": "invalid_theme",
  "detail": ["code is not valid base64url deflate data"],
}

Style inconnu, 404. /styles/{style}.json énumère les noms qui existent, donc une erreur se corrige d'elle-même :

{
  "error": "unknown style",
  "code": "not_found",
  "styles": ["base", "muted", "outdoor", "blueprint", "blush", "orchid", "canopy", "lagoon", "tropic", "sunset", "bold", "pastel", "atlas", "swiss", "winter"],
}

Dans le SDK

Chaque client lève une exception sur une réponse non 2xx, et chaque erreur étend UnmapError, de sorte qu'un seul catch couvre toute la plateforme :

import { Unmap, UnmapError } from "@unmap/sdk";
 
try {
  await unmap.places.nearby("boulangerie", { near: [-73.57, 45.5] });
} catch (e) {
  if (e instanceof UnmapError) {
    console.error(e.status, e.code, e.message, e.retryAfterSeconds);
  }
}

Chaque produit a sa propre sous-classe, qui porte son propre nom, donc e instanceof GeocoderError cible un seul produit : GeocoderError, PlacesError, RouterError, ElevationError, RoadsError, TransitError et CoverageError. Chacune porte le status HTTP, le code lisible par machine quand le corps en avait un, retryAfterSeconds quand la réponse portait l'en-tête, et la chaîne error de la passerelle comme message, donc un appelant qui la journalise lit la raison et non seulement un nombre. Un corps qui n'est pas du JSON, comme le texte brut d'un chemin de tuiles, laisse simplement code indéfini. status vaut 0 quand la requête n'a jamais atteint le serveur.

e.retryable est vrai pour exactement les statuts qui valent une attente : 429, 502, 503 et un échec de transport.

Les tuiles, glyphes et sprites sont récupérés par MapLibre lui-même, qui signale les échecs par des événements error sur la carte plutôt que par des exceptions.

Les clients réessaient pour vous

Chaque lecture réessaie 429, 502, 503 et un échec de transport, trois tentatives par défaut, et respecte Retry-After quand la réponse en porte un plutôt que de deviner une temporisation. Rien d'autre n'est réessayé : un 400 ou un 403 décrit la requête ou le compte, et le répéter ne fait que dépenser du quota.

new Unmap({ key, retry: false })                       // désactivé
new Unmap({ key, retry: { attempts: 5 } })             // plus persistant
new Unmap({ key, retry: { maxDelayMs: 120_000 } })     // prêt à attendre un conteneur froid

Une attente plus longue que maxDelayMs (10 secondes par défaut) n'est pas subie. L'appel retourne l'échec, porteur de retryAfterSeconds, pour que vous puissiez vous organiser autour. Cela compte pour les conteneurs d'extension qui ont le droit de dormir : land et energy répondent Retry-After: 60, et immobiliser votre gestionnaire de requête une minute entière dans un seul await est une plus mauvaise surprise qu'une erreur qui vous dit combien de temps attendre.

Les signalements ne sont jamais réessayés. POST /feedback crée une fiche et ne porte pas de clé d'idempotence : un nouvel essai après une expiration risquerait de déposer deux fois le même signalement.

Réessayer

Seuls 429, 502 et 503 valent un nouvel essai, et seulement avec temporisation. Un 503 en texte brut basemap release is misconfigured est l'exception : il n'a pas de Retry-After, et le répéter ne rétablit pas l'archive précédente. Un 429 indique déjà combien de temps attendre via Retry-After, et un 429 avec code: "billing_required" ne se règle pas tant que le mode de paiement n'est pas corrigé. Un 503 avec code: "service_unavailable" annonce retryable: true dans le corps et porte un Retry-After dimensionné pour ce service : 5 pour geocode et routing, 60 pour land et energy, qui ont le droit de s'endormir. C'est le seul échec où réessayer est le bon geste, à l'intervalle que donne l'en-tête plutôt qu'à un plus court. Un 400, 401, 403, 404 ou 422 décrit quelque chose dans la requête ou le compte qu'un nouvel essai ne corrigera pas; corrigez la requête.

Optimize

Unmap Optimize signale les erreurs de requête dans le corps de réponse, les avertissements dans les en-têtes et les envois non servis dans skippedShipments. Cette section explique chaque statut et code et les mesures à prendre. Les messages d'erreur identifient les chemins de champs et les indices sans citer les coordonnées ni les étiquettes soumises. Les messages sont en anglais, tels que l'API les renvoie.

Le corps d'erreur

Sur les chemins d'Optimize, les erreurs suivent la forme d'erreur de Google : un client écrit pour l'API Route Optimization de Google Maps Platform les lit comme celles de Google. La chaîne code de la passerelle se trouve à côté de error, au premier niveau, comme sur tous les autres points de terminaison d'Unmap :

// 400
{
  "error": {
    "code": 400,
    "message": "The request has 1 validation error.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          { "field": "model.shipments[3].pickups[0].timeWindows[1]", "description": "Time windows must be in increasing order and must not overlap or touch." }
        ]
      }
    ]
  },
  "code": "bad_request"
}

details contient l'un de ces types de Google :

  • google.rpc.BadRequest pour les erreurs de validation : une entrée fieldViolations par erreur, field étant le chemin exact et description le message d'erreur suivi de son correctif.
  • google.rpc.PreconditionFailure pour les envois obligatoires qu'aucun véhicule ne peut servir : une entrée violations par envoi (au plus 100), type étant la raison d'exclusion et subject le chemin de l'envoi.
  • google.rpc.RetryInfo sur une erreur qu'on peut réessayer, qui concorde avec son en-tête Retry-After, et google.rpc.ErrorInfo avec la raison, sur les refus de la couche des clés (une limite de débit et un quota mensuel sont tous deux des 429 et ne diffèrent que là).

Statuts HTTP

HTTPstatuscodeCauseCorrectif
400INVALID_ARGUMENTbad_requestLe corps ne respecte pas le contrat : voir les erreurs de validation. Le message compte les erreurs et indique si la liste s'est arrêtée à maxValidationErrors.Corrigez chaque violation de champ. Envoyez solvingMode: VALIDATE_ONLY pour obtenir la liste complète sans résoudre.
400INVALID_ARGUMENTbad_requestPlus d'envois, de véhicules ou de lieux que le forfait de la clé ne le permet. Le message nomme la limite.Découpez la requête. Voir les limites par forfait.
400INVALID_ARGUMENTbad_requestLe total des cellules des matrices de véhicules dépasse la limite du forfait : 10 000 pour dev, 250 000 pour Indie ou 1 000 000 pour Growth.Découpez la requête, fusionnez les arrêts en double ou utilisez moins de matrices de véhicules distinctes.
400INVALID_ARGUMENTbad_requestPlus de 200 lieux distincts ont besoin du réseau routier, ou les trajets routiers exigeraient plus de requêtes au moteur d'itinéraires qu'une résolution n'en permet (en général parce que les arrêts s'étendent sur plus de 1 000 km).Découpez la requête par région.
400INVALID_ARGUMENTbad_requestLes coûts sont trop grands pour que le solveur les ramène à des nombres entiers, ou le problème traduit est trop gros pour être envoyé au solveur.Divisez tous les coûts par un même facteur; découpez la requête.
400FAILED_PRECONDITIONbad_requestUn envoi obligatoire, sans penaltyCost et non ignoré, ne peut être servi par aucun véhicule autorisé. Le message nomme le premier; un PreconditionFailure en liste au plus 100. Google peut renvoyer 200 avec des envois obligatoires écartés; Unmap refuse plutôt.Lisez la raison d'exclusion et corrigez le véhicule ou l'envoi, ou définissez penaltyCost pour permettre son exclusion.
400FAILED_PRECONDITIONbad_requestLe solveur n'a trouvé aucun plan servant tous les envois obligatoires dans son budget de temps. Réussir les vérifications avant la résolution ne garantit pas qu'un envoi est réalisable seul ou avec les autres.Ajoutez des véhicules, élargissez les fenêtres et les limites, rendez certains envois facultatifs ou augmentez timeout.
400FAILED_PRECONDITIONbad_requestAucun plan réalisable n'existe même en laissant tous les envois de côté : les véhicules eux-mêmes sont irréalisables, par exemple une pause qui n'entre dans aucune tournée.Vérifiez les fenêtres horaires, les pauses et les limites des véhicules.
401UNAUTHENTICATEDunauthorizedAucune clé API, ou une clé inconnue.Envoyez une clé; voir Authentification.
403PERMISSION_DENIEDorigin_not_allowedLes origines permises de la clé n'incluent pas celle de cette requête.Ajoutez l'origine à la clé, ou appelez depuis un serveur avec une clé sans restriction.
404NOT_FOUNDnot_foundUne méthode autre que :optimizeTours sous un chemin Google, ou une méthode HTTP autre que POST.Utilisez POST /optimize ou POST /v1/projects/{project}:optimizeTours.
413INVALID_ARGUMENTpayload_too_largeLe corps dépasse 4 Mio.Découpez la requête, ou retirez les champs dont vous n'avez pas besoin.
429RESOURCE_EXHAUSTEDrate_limitedToutes les résolutions simultanées du forfait sont en cours, toutes les instances de l'optimiseur sont occupées, ou la clé dépasse sa limite de débit. Retry-After indique quand réessayer.Attendez et réessayez, ou mettez les résolutions en file de votre côté.
429RESOURCE_EXHAUSTEDrate_limitedLe calcul des trajets routiers est à pleine capacité sur la plateforme : celui d'autres requêtes est en cours, et cette requête a attendu jusqu'à 20 secondes sans obtenir son tour. Le message commence par Road travel is at capacity right now. Rien n'est facturé.Réessayez après Retry-After, ou envoyez useGeodesicDistances ou vos propres durationDistanceMatrices, qui n'attendent jamais le calcul des trajets routiers.
429RESOURCE_EXHAUSTEDquota_exceeded, spend_cap, billing_requiredLes appels mensuels du forfait, le plafond de dépenses, ou un paiement refusé. Une résolution dont les envois dépassent les appels restants est refusée avant d'être résolue.Voir Forfaits et limites.
500INTERNALinternal_errorUn bogue d'Unmap, jamais votre faute.Réessayez; signalez-le s'il se répète.
503UNAVAILABLEservice_unavailableLe moteur d'itinéraires ou l'optimiseur n'a pas répondu, par exemple pendant le démarrage d'une instance de l'optimiseur. Retry-After indique quand réessayer.Réessayez après une courte attente.
504DEADLINE_EXCEEDEDdeadline_exceededLes trajets routiers n'ont pas fini dans leur part des 140 secondes de la requête, ou le solveur n'a pas répondu dans la limite de temps de la résolution plus 20 secondes.Découpez la requête, ou réduisez timeout.

L'analyse du corps, sa taille, l'authentification et les limites du nombre d'envois ou de véhicules s'appliquent même avec VALIDATE_ONLY et peuvent produire une réponse autre qu'un 200. La validation vérifie les champs et les contraintes; elle ne calcule pas les trajets routiers et ne prouve pas qu'un plan réalisable existe pour le parc de véhicules.

La limite de résolutions simultanées du forfait n'est vérifiée qu'après la validation de la requête, et seulement quand celle-ci a besoin du réseau routier ou d'une résolution. Une requête invalide reçoit donc son 400 même quand toutes les résolutions permises sont en cours, et VALIDATE_ONLY n'attend jamais.

Une réponse réussie ne laisse jamais de côté un envoi obligatoire. Les polylignes des tournées ne changent jamais le statut : une tournée que le moteur ne peut pas tracer revient sans routePolyline, avec un avertissement ROUTE_POLYLINE_UNAVAILABLE.

Erreurs de validation

La validation signale chaque problème trouvé, jusqu'à maxValidationErrors (100 par défaut, au plus 10 000). Avec solvingMode: VALIDATE_ONLY, ils reviennent dans un corps 200 sous validationErrors, avec le code entier ci-dessous, le code en chaîne comme displayName, une chaîne fields et errorMessage; rien n'est calculé ni résolu, et une requête valide répond {}. Dans tout autre mode, ils reviennent dans le 400 ci-dessus. Les entiers sont ceux d'Unmap et ne changent jamais; ils ne correspondent pas aux codes internes de Google.

CodeEntierCauseCorrectif
INVALID_REQUEST1Le corps n'est pas un objet JSON.Envoyez un objet JSON.
UNKNOWN_FIELD2Un champ que ni le format de Google ni Unmap ne définit, souvent une faute de frappe.Vérifiez le nom dans les champs pris en charge.
CONFLICTING_FIELD_NAMES3Un même champ envoyé deux fois, en snake_case et en camelCase.Envoyez une seule graphie.
INVALID_TYPE4Une valeur du mauvais type JSON.Envoyez le type indiqué dans la table des champs.
INVALID_ENUM_VALUE5Un nom ou un entier d'énumération qui n'existe pas, y compris SOLVING_MODE_UNSPECIFIED (la valeur par défaut est DEFAULT_SOLVE). Les champs énumérés acceptent le nom ou son entier proto.Utilisez un nom ou un entier listé.
INVALID_DURATION6Une durée qui n'est pas une chaîne comme "300s".Envoyez des secondes suivies de s.
INVALID_TIMESTAMP7Un horodatage qui n'est ni en RFC 3339 ni { seconds, nanos }.Envoyez du RFC 3339, comme 2026-10-13T14:00:00Z.
INVALID_INT648Un entier qui n'est ni une chaîne décimale ni un entier JSON sûr.Envoyez une chaîne décimale.
OUT_OF_RANGE9Un nombre hors de la plage du champ : un coût ou une durée négatifs, une latitude au-delà de 90, un geodesicMetersPerSecond inférieur à 1, une minDuration de pause qui n'est pas positive. Aussi une requête qui utilise plus de 16 noms de types de charge distincts, toutes les clés loadDemands et loadLimits confondues (l'erreur nomme le premier au-delà de la limite), les quantités d'un type de charge qui totalisent plus de 2^53 - 1 sur tous les ramassages et livraisons, un routeDistanceLimit.maxMeters supérieur à 1 000 000 000, ou un globalEndTime à plus de 999 999 999 secondes (environ 31 ans) de globalStartTime.Envoyez une valeur dans la plage; regroupez des types de charge pour en garder au plus 16; réduisez les quantités; raccourcissez la fenêtre globale.
INVALID_VALUE10Une valeur que le champ ne peut pas prendre : une balise en double ou vide, une matrice sans son propre vehicleStartTag quand il y en a plusieurs, un type de charge ou de visite qui porte le nom d'une propriété intégrée des objets (__proto__, constructor, toString, prototype et autres), une limite souple égale ou supérieure à sa limite stricte, une limite de charge souple avec un coût supérieur à 0 sur un type de charge que les ramassages et les livraisons utilisent tous les deux, ou tout routeDistanceLimit.costPerKilometerBelowSoftMax, que GMPRO ne prend pas en charge non plus.Rendez les balises uniques et non vides; balisez chaque matrice quand il y en a plusieurs; renommez le type; gardez une limite souple sous sa limite stricte; placez la limite de charge souple sur un type de charge que seuls les ramassages ou seules les livraisons utilisent; retirez costPerKilometerBelowSoftMax.
MISSING_REQUIRED_FIELD11Un champ requis est absent : un envoi sans ramassage ni livraison, une visite sans lieu (ou, avec des matrices, sans tags), un point de passage sans location.latLng, une pause sans bornes de début, geodesicMetersPerSecond avec useGeodesicDistances, des balises de matrices sans matrices ou l'inverse, un coût souple de fenêtre horaire, de durée ou de distance sans sa borne ou sa limite souple (un coût de charge seul vaut une charge maximale souple de 0).Ajoutez le champ.
MUTUALLY_EXCLUSIVE_FIELDS12Deux champs qui ne peuvent pas être présents ensemble : arrivalLocation et arrivalWaypoint (ou le lieu et le point de passage d'un véhicule); des coordonnées sur une visite ou un véhicule quand le modèle a des durationDistanceMatrices; useGeodesicDistances avec des matrices.Gardez-en un. Avec des matrices, placez les arrêts par balises seulement.
INVALID_TIME_WINDOW13Une fenêtre inversée, qui sort de la fenêtre globale ou qui est dans le désordre. Les fenêtres d'une même liste ne doivent ni se chevaucher ni se toucher; les endTimeWindows d'un véhicule ne doivent pas se fermer avant l'ouverture de ses startTimeWindows; le début au plus tôt d'une pause ne doit pas suivre son début au plus tard; globalEndTime doit suivre globalStartTime; une borne souple sur une liste de plusieurs fenêtres, un softStartTime avant startTime ou un softEndTime après endTime, ou une borne souple hors de la fenêtre globale.Triez et séparez les fenêtres, gardez-les dans la fenêtre globale, donnez au véhicule une fenêtre d'arrivée qu'il peut atteindre, et gardez une borne souple sur une fenêtre unique, avec le début souple au plus tôt à startTime, la fin souple au plus tard à endTime, et les deux dans la fenêtre globale.
INVALID_REFERENCE14Un indice ou une balise qui ne désigne rien : une entrée de allowedVehicleIndices au-delà du parc, des balises de visite ou de véhicule qui ne désignent pas exactement une balise d'origine et une de destination, ou des startTags de véhicule qui ne désignent pas exactement un vehicleStartTag dès qu'une matrice en a un.Désignez quelque chose qui existe, une seule fois.
MATRIX_SHAPE_MISMATCH15Une matrice dont les lignes ne correspondent pas à durationDistanceMatrixSrcTags, ou dont les durations ou les meters d'une ligne ne correspondent pas à durationDistanceMatrixDstTags.Une ligne par balise d'origine, une cellule par balise de destination.
FIELD_NOT_SUPPORTED_YET16Un champ qu'Unmap prévoit de prendre en charge, avec une valeur autre que celle par défaut. Les champs pris en charge donnent le message exact de chacun.Retirez le champ ou laissez-le à sa valeur par défaut.
VALUE_NOT_SUPPORTED_YET17Une valeur qu'Unmap prévoit de prendre en charge : solvingMode DETECT_SOME_INFEASIBLE_SHIPMENTS ou TRANSFORM_AND_RETURN_REQUEST, travelMode WALKING, unmapProfile truck ou pedestrian, plus d'un ramassage ou d'une livraison par envoi, plus d'une fenêtre de départ ou d'arrivée par véhicule.Utilisez une valeur prise en charge; répartissez les options dans des requêtes séparées.
PLACE_ID_NOT_SUPPORTED18Un point de passage donné par un identifiant de lieu Google.Géocodez avec /geocode/search et envoyez des coordonnées.
GCS_URI_NOT_SUPPORTED19Une URI Google Cloud Storage gs://. Aucun champ d'optimizeTours ne prend d'URI; le code est réservé aux méthodes par lots, qui ne sont pas encore offertes.Envoyez le corps directement.

Avertissements

Un champ qu'Unmap accepte mais ne peut pas respecter produit un avertissement FIELD_IGNORED quand il a une valeur autre que celle par défaut : un par champ, avec le premier chemin où il est apparu et un compte. Les avertissements ne vont jamais dans le corps de la réponse, qui garde la forme qu'attend un client Google : ils passent par l'en-tête de réponse X-Unmap-Optimize-Warnings, un tableau JSON, et reviennent aussi avec VALIDATE_ONLY. Le correctif est le même pour chacun : retirez le champ, ou acceptez qu'il soit sans effet.

ChampAvertissement
parentparent is ignored: Unmap has no projects, so the request runs under the API key's account.
considerRoadTrafficconsiderRoadTraffic is ignored: Unmap travel times do not use live or historical traffic yet.
populateTransitionPolylinespopulateTransitionPolylines is ignored: Unmap returns one polyline per route, as routes[].routePolyline when populatePolylines is true.
allowLargeDeadlineDespiteInterruptionRiskallowLargeDeadlineDespiteInterruptionRisk is ignored: Unmap caps the timeout per plan and clamps anything longer.
location.heading de tout point de passagelocation.heading is ignored: Unmap snaps a location to the nearest road whatever the direction of travel.
sideOfRoad de tout point de passagesideOfRoad is ignored: Unmap travel times do not model which side of the road a stop is on.
vehicleStopover de tout point de passagevehicleStopover is ignored: Unmap snaps every stop to the nearest road a vehicle can use.

Le même en-tête transporte les avertissements de la passerelle elle-même :

CodeSens
TIMEOUT_CLAMPEDtimeout dépassait la résolution la plus longue du forfait et a été raccourci. Un en-tête Warning le dit aussi.
ROAD_TRAVEL_DEGRADEDDes cellules de trajet routier ont été déclarées injoignables parce qu'un budget de calcul d'itinéraires par requête s'est épuisé, pas parce qu'aucune route n'a été trouvée. Un envoi laissé de côté comme injoignable pourrait être servi : découpez la requête par région.
ROAD_TRAVEL_UNCONFIRMEDDes cellules que le moteur d'itinéraires a laissées vides ont été déclarées injoignables sans la requête d'itinéraire de confirmation habituelle. Découpez la requête en requêtes plus petites.
ROUTE_POLYLINE_UNAVAILABLELa polyligne n'a pas pu être tracée pour les véhicules nommés. Leurs tournées sont complètes, sans routePolyline.
WARNINGS_TRUNCATEDL'en-tête a atteint sa limite de 4 Ko; count indique combien d'avertissements ont été omis.

Raisons d'exclusion

Un envoi qu'Unmap ne sert pas figure dans skippedShipments, avec un code de raison tiré de la liste publiée SkippedShipment.Reason.Code de Google. Un envoi facultatif laissé de côté ajoute son penaltyCost à metrics.totalCost; un envoi ignoré ne coûte rien. Un envoi laissé de côté n'est jamais facturé.

Avant la résolution, un envoi n'est laissé de côté que si aucun plan ne peut le servir : chaque véhicule qui peut le prendre est écarté même avec l'envoi seul sur une tournée vide. Un tel envoi n'atteint jamais le solveur. S'il est obligatoire, la requête échoue plutôt (400 FAILED_PRECONDITION, ci-dessus). Chaque raison nomme le premier véhicule auquel elle s'applique (exampleVehicleIndex). Après la résolution, les envois que le solveur a laissés de côté sont vérifiés de nouveau, contre la tournée de chaque véhicule, et listés avec les raisons qui écartent tous les véhicules.

Ces vérifications sont prudentes quant aux trajets. Le réseau routier et vos propres matrices ne respectent pas nécessairement l'inégalité triangulaire (un arrêt peut être plus vite atteint en passant par un autre que directement); les vérifications d'avant la résolution utilisent donc une borne inférieure valable pour toute matrice, et laissent de côté les pauses et l'attente. C'est pourquoi un envoi peut les passer et être quand même laissé de côté par le solveur.

CodeQuandCauseCorrectif
SHIPMENT_IGNOREDavantL'envoi a ignore: true. Aucune pénalité, pas facturé.Mettez ignore à false pour le planifier.
NO_VEHICLEavantLe modèle n'a aucun véhicule.Ajoutez un véhicule.
DEMAND_EXCEEDS_VEHICLE_CAPACITYles deuxLa demande de l'envoi pour exampleExceededCapacityType dépasse la maxLoad du véhicule; après la résolution, elle dépasse la place restante sur sa tournée.Augmentez loadLimits, divisez l'envoi ou permettez un plus gros véhicule.
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_TIME_WINDOWSles deuxPartant à son heure la plus hâtive, le véhicule ne peut pas atteindre une visite dans l'une de ses fenêtres, ou ne peut pas terminer et rejoindre son arrivée à temps. Sert aussi quand un arrêt est injoignable, par exemple une île sans route : la liste de Google n'a pas de code pour un arrêt injoignable.Élargissez les fenêtres de la visite ou du véhicule, partez plus tôt, ou vérifiez le lieu de l'arrêt.
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_DURATION_LIMITles deuxLe trajet plus le temps de service dépasse routeDurationLimit.maxDuration.Augmentez la limite ou utilisez un autre véhicule.
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_TRAVEL_DURATION_LIMITles deuxLe trajet dépasse travelDurationLimit.maxDuration.Augmentez la limite ou utilisez un autre véhicule.
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_DISTANCE_LIMITles deuxLa distance dépasse routeDistanceLimit.maxMeters.Augmentez la limite ou utilisez un autre véhicule.
ZERO_PENALTY_COSTaprèsRien n'empêchait l'envoi et son penaltyCost vaut 0 : le laisser de côté ne coûtait rien.Donnez-lui une pénalité qui reflète ce que coûte de le manquer.
pas de reasonsaprèsRien de ce que voient les vérifications ne l'a empêché : le servir coûterait plus que son penaltyCost, ou il entre en conflit avec d'autres envois d'une façon qu'une vérification par véhicule ne modélise pas (fenêtres le long d'une tournée chargée, pauses). Unmap n'envoie jamais CODE_UNSPECIFIED.Augmentez son penaltyCost, ajoutez de la capacité ou élargissez les fenêtres.

Codes qu'Unmap ne renvoie jamais : CODE_UNSPECIFIED (la liste de Google dit qu'il n'est jamais utilisé), VEHICLE_NOT_ALLOWED (Unmap renvoie plutôt la raison pour laquelle les véhicules permis échouent), VEHICLE_IGNORED (Vehicle.ignore n'est pas encore pris en charge), ainsi que SKIPPED_IN_INJECTED_SOLUTION_CONSTRAINT et VEHICLE_ROUTE_IS_FULLY_SEQUENCE_CONSTRAINED (les solutions injectées ne sont pas encore prises en charge).