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 payantTout 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. Codeunauthorized. Chaque401du chemin de données porte aussi un défiWWW-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", avecerror="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 parcode: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 deRetry-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 parcode: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êteOriginabsent, 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 portecapability(legalLand,energy,agriculture,remote,miningouski),addonqui nomme le SKU le moins cher qui l'accorderait (addon-legal-land,addon-energy,addon-agriculture,addon-remote,addon-mining,addon-ski), et unmessageécrit pour une personne là oùerrorest é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 depuislocalhost,127.0.0.1,*.localhost,*.pages.devet*.workers.dev, où toutes les capacités sont gratuites. Elle porte les mêmes champscapability,addonetmessage, 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 champscapability,addonetmessage.
400: un paramètre de requête mal formé, codebad_requestsauf si un code plus précis s'applique. Coordonnées de tuile invalides,qmanquant sur un appel de géocodage,bboxmal formé,nearabsent ou non numérique, une longitude ou une latitude hors du WGS84 en géocodage ou pour les lieux,categoryinconnue,nearetbboxfournis 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/queryinconnue, etc. Le message nomme le paramètre.501:GET /data/querysur une couche tuilée du catalogue (sans table PostGIS). Codenot_implemented; le message liste les identifiants interrogeables. Voir Couches.404: style inconnu sur/styles/{style}.json, couche ou lieu inconnu,layer archive not configuredquand 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 utilisentnot_found, tout comme une ressource inconnue sur l'API de compte. Une clé absente est un401avant 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,/glyphsou/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 surcode.outside_coverage: un ancrage est hors du Canada. Le corps est{ "error": "outside Canadian coverage", "code": "outside_coverage" }. Une isochronemode=transitdont le centre n'a aucun transport en commun prévu à proximité utilise le même code, avec l'erreurno 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/nearbyet/places/nearbyl'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 un400bad_requestpour le géocodage et les lieux, et un400invalid_locationpour les itinéraires. Ce n'est pasoutside_coverage.no_route: le point est au Canada et le graphe n'a toujours pas de réponse. Sur/routele 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, sontno_route./matchutiliseno_routequand une trace canadienne ne peut être accrochée à aucune route./matrixgarde une paire que le moteur ne fait que rater comme une cellulenulldans un200, le reste de la grille étant intact, donc géreznullsur chaque cellule. Il répond422no_routequand 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 estoutside_coverageavant 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 unnulldans 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. Codeupstream_error: le conteneur de routage a échoué, ou/elevationa 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 drapeauretryableet unRetry-After:{ "error": "geocode temporarily unavailable", "code": "service_unavailable", "retryable": true }Le nom du service est
geocode,routing,landouenergy. 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-Aftervarie selon le service, et l'écart n'est pas cosmétique.geocodeetroutingsont gardés au chaud par une tâche planifiée qui les sollicite toutes les cinq minutes : leur503est un soubresaut et l'en-tête dit5.landetenergy, 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 dit60. Respectez l'en-tête. Réessayer un503delandtoutes 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 deretryable, 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 misconfiguredCache-Controlvautprivate, 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 un404basemap release not found, lui aussiprivate, no-store. Un identifiant qui n'a pas huit chiffres a le même corps404sans 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 froidUne 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.BadRequestpour les erreurs de validation : une entréefieldViolationspar erreur,fieldétant le chemin exact etdescriptionle message d'erreur suivi de son correctif.google.rpc.PreconditionFailurepour les envois obligatoires qu'aucun véhicule ne peut servir : une entréeviolationspar envoi (au plus 100),typeétant la raison d'exclusion etsubjectle chemin de l'envoi.google.rpc.RetryInfosur une erreur qu'on peut réessayer, qui concorde avec son en-têteRetry-After, etgoogle.rpc.ErrorInfoavec la raison, sur les refus de la couche des clés (une limite de débit et un quota mensuel sont tous deux des429et ne diffèrent que là).
Statuts HTTP
| HTTP | status | code | Cause | Correctif |
|---|---|---|---|---|
400 | INVALID_ARGUMENT | bad_request | Le 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. |
400 | INVALID_ARGUMENT | bad_request | Plus 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. |
400 | INVALID_ARGUMENT | bad_request | Le 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. |
400 | INVALID_ARGUMENT | bad_request | Plus 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. |
400 | INVALID_ARGUMENT | bad_request | Les 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. |
400 | FAILED_PRECONDITION | bad_request | Un 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. |
400 | FAILED_PRECONDITION | bad_request | Le 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. |
400 | FAILED_PRECONDITION | bad_request | Aucun 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. |
401 | UNAUTHENTICATED | unauthorized | Aucune clé API, ou une clé inconnue. | Envoyez une clé; voir Authentification. |
403 | PERMISSION_DENIED | origin_not_allowed | Les 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. |
404 | NOT_FOUND | not_found | Une 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. |
413 | INVALID_ARGUMENT | payload_too_large | Le corps dépasse 4 Mio. | Découpez la requête, ou retirez les champs dont vous n'avez pas besoin. |
429 | RESOURCE_EXHAUSTED | rate_limited | Toutes 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é. |
429 | RESOURCE_EXHAUSTED | rate_limited | Le 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. |
429 | RESOURCE_EXHAUSTED | quota_exceeded, spend_cap, billing_required | Les 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. |
500 | INTERNAL | internal_error | Un bogue d'Unmap, jamais votre faute. | Réessayez; signalez-le s'il se répète. |
503 | UNAVAILABLE | service_unavailable | Le 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. |
504 | DEADLINE_EXCEEDED | deadline_exceeded | Les 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.
| Code | Entier | Cause | Correctif |
|---|---|---|---|
INVALID_REQUEST | 1 | Le corps n'est pas un objet JSON. | Envoyez un objet JSON. |
UNKNOWN_FIELD | 2 | Un 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_NAMES | 3 | Un même champ envoyé deux fois, en snake_case et en camelCase. | Envoyez une seule graphie. |
INVALID_TYPE | 4 | Une valeur du mauvais type JSON. | Envoyez le type indiqué dans la table des champs. |
INVALID_ENUM_VALUE | 5 | Un 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_DURATION | 6 | Une durée qui n'est pas une chaîne comme "300s". | Envoyez des secondes suivies de s. |
INVALID_TIMESTAMP | 7 | Un horodatage qui n'est ni en RFC 3339 ni { seconds, nanos }. | Envoyez du RFC 3339, comme 2026-10-13T14:00:00Z. |
INVALID_INT64 | 8 | Un entier qui n'est ni une chaîne décimale ni un entier JSON sûr. | Envoyez une chaîne décimale. |
OUT_OF_RANGE | 9 | Un 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_VALUE | 10 | Une 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_FIELD | 11 | Un 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_FIELDS | 12 | Deux 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_WINDOW | 13 | Une 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_REFERENCE | 14 | Un 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_MISMATCH | 15 | Une 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_YET | 16 | Un 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_YET | 17 | Une 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_SUPPORTED | 18 | Un point de passage donné par un identifiant de lieu Google. | Géocodez avec /geocode/search et envoyez des coordonnées. |
GCS_URI_NOT_SUPPORTED | 19 | Une 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.
| Champ | Avertissement |
|---|---|
parent | parent is ignored: Unmap has no projects, so the request runs under the API key's account. |
considerRoadTraffic | considerRoadTraffic is ignored: Unmap travel times do not use live or historical traffic yet. |
populateTransitionPolylines | populateTransitionPolylines is ignored: Unmap returns one polyline per route, as routes[].routePolyline when populatePolylines is true. |
allowLargeDeadlineDespiteInterruptionRisk | allowLargeDeadlineDespiteInterruptionRisk is ignored: Unmap caps the timeout per plan and clamps anything longer. |
location.heading de tout point de passage | location.heading is ignored: Unmap snaps a location to the nearest road whatever the direction of travel. |
sideOfRoad de tout point de passage | sideOfRoad is ignored: Unmap travel times do not model which side of the road a stop is on. |
vehicleStopover de tout point de passage | vehicleStopover 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 :
| Code | Sens |
|---|---|
TIMEOUT_CLAMPED | timeout dépassait la résolution la plus longue du forfait et a été raccourci. Un en-tête Warning le dit aussi. |
ROAD_TRAVEL_DEGRADED | Des 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_UNCONFIRMED | Des 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_UNAVAILABLE | La polyligne n'a pas pu être tracée pour les véhicules nommés. Leurs tournées sont complètes, sans routePolyline. |
WARNINGS_TRUNCATED | L'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.
| Code | Quand | Cause | Correctif |
|---|---|---|---|
SHIPMENT_IGNORED | avant | L'envoi a ignore: true. Aucune pénalité, pas facturé. | Mettez ignore à false pour le planifier. |
NO_VEHICLE | avant | Le modèle n'a aucun véhicule. | Ajoutez un véhicule. |
DEMAND_EXCEEDS_VEHICLE_CAPACITY | les deux | La 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_WINDOWS | les deux | Partant à 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_LIMIT | les deux | Le 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_LIMIT | les deux | Le trajet dépasse travelDurationLimit.maxDuration. | Augmentez la limite ou utilisez un autre véhicule. |
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_DISTANCE_LIMIT | les deux | La distance dépasse routeDistanceLimit.maxMeters. | Augmentez la limite ou utilisez un autre véhicule. |
ZERO_PENALTY_COST | après | Rien 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 reasons | après | Rien 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).