Référence API · v1
Documentation de l’API Vatlas
Une API REST, trois points d’entrée, une seule enveloppe JSON. Tout est en GET, tout est en UTF-8, et toutes les dates sont en UTC au format ISO 8601.
URL de base
https://api.vatlas.devLes chemins sont versionnés (/v1/…). Une version ne change jamais de forme de façon incompatible : un nouveau champ peut apparaître, aucun ne disparaît.
Authentification
Chaque appel porte une clé en jeton bearer dans l’en-tête Authorization. Vous pouvez en créer autant que vous voulez : elles puisent toutes dans le même quota de compte, et servent à savoir laquelle de vos intégrations a appelé.
curl "https://api.vatlas.dev/v1/lookup/FR08000325175" \
-H "Authorization: Bearer vtl_live_xxxxxxxxxxxxxxxxxxxx"
Gardez la clé côté serveur. Elle ne doit jamais partir dans un bundle front : n’importe qui pourrait la lire et consommer votre quota. Proxifiez l’appel depuis votre backend.
Une clé absente, inconnue ou révoquée répond 401 avec l’enveloppe d’erreur standard, et n’est pas décomptée. Créez votre clé : le plan Free est gratuit et sans carte bancaire.
Enveloppe de réponse
Le succès porte toujours data et meta. L’erreur porte toujours error, et meta quand il y a quelque chose à y ajouter. La forme ne change pas selon le registre d’où vient la donnée : votre code n’a rien à brancher.
meta.source: le registre qui a répondu :SIRENE,MF_WL,PRH,RIK,UR_VID,ARES,VIES…meta.sourceDate: le dernier jour où ce registre l’a dit, etmeta.sourceSincele premier : date de publication du fichier pour un registre importé, jour de l’appel pour une source interrogée en direct.meta.sourceUpdatedAt: horodatage UTC de la donnée servie. Sur une réponsevies, il peut être antérieur à l’appel : quand ni VIES ni le registre ne répondent, la dernière réponse en notre possession est servie plutôt qu’un 503. Ce champ dit quand elle a été vérifiée.meta.proof: la preuve que la vérification a eu lieu :{ "type": "vies_consultation_number", "value": "…" }. Un numéro de consultation est ce qu’une administration fiscale accepte comme preuve. Présent sur toute réponse venue de VIES et sur aucune autre : un registre national n’en délivre pas, donc la clé est absente plutôt que nulle. C’est un objet et non une chaîne, pour que liretypesuffise.meta.checkedAt: sur une erreurVAT_NOT_REGISTEREDuniquement : l’instant où VIES a donné cette réponse quand nous l’avons interrogé pour cette requête, et le jour où il l’a dit quand la réponse vient de ce que nous détenions déjà.
Résoudre un numéro de TVA
GET /v1/lookup/{vatNumber}
Le cœur de l’API. L’entrée est normalisée avant validation : la casse, les espaces, les points et les tirets sont ignorés, et le préfixe ISO GR de la Grèce est accepté en plus de son préfixe TVA EL. data.vatNumber revient toujours sous forme canonique.
| Paramètre | Type | Description |
|---|---|---|
vatNumber |
chemin, requis | Numéro de TVA intracommunautaire, préfixe pays inclus. FR08000325175 et fr 08 000.325-175 sont équivalents. |
prefer |
query, optionnel | Ce qu’il faut privilégier quand les sources divergent : completeness, la valeur par défaut, prend la réponse la plus complète parmi toutes les sources du pays ; proof ne répond que si la réponse porte une preuve, en écartant l’historique stocké et toute source qui ne délivre aucun code de vérification. Toute autre valeur donne un 400. |
history |
query, drapeau optionnel | Un drapeau : écrivez ?history pour recevoir tout ce que la source qui a répondu a déjà dit, sous un nœud history à côté de data et meta. Désactivé par défaut : c’est une requête de plus, et la plupart des intégrations ne lisent que la réponse. |
Requête
curl "https://api.vatlas.dev/v1/lookup/FR08000325175" \
-H "Authorization: Bearer $VATLAS_KEY"
Réponse · 200 OK
{
"data": {
"vatNumber": "FR08000325175",
"countryCode": "FR",
"nationalNumber": "08000325175",
"nationalId": "000325175",
"name": "THIERRY JANOYER",
"legalForm": "Personne physique",
"status": "active",
"address": {
"line1": "51 RUE MARX DORMOY",
"line2": null,
"postalCode": "13004",
"city": "MARSEILLE",
"country": "FR"
}
},
"meta": {
"source": "SIRENE",
"sourceDate": "2026-09-01",
"sourceSince": "2026-01-05",
"sourceUpdatedAt": "2025-12-06T09:43:55.000Z"
}
}
Réponse · 200 OK, répondue par VIES
Lorsque la réponse vient de VIES et non d’un registre, meta porte un champ de plus. proof.value est le numéro de consultation émis par VIES pour cette vérification, l’identifiant qu’une administration fiscale accepte comme preuve que le numéro a été vérifié ce jour-là. Nous en demandons un à chaque appel ; quand VIES n’en émet aucun, la réponse est servie quand même, sans meta.proof. Conservez la valeur avec la facture qu’elle justifie.
"meta": {
"source": "VIES",
"sourceDate": "2026-08-28",
"sourceSince": "2026-08-28",
"sourceUpdatedAt": null,
"proof": {
"type": "vies_consultation_number",
"value": "908a3b38-e0dc-4502-8b06-d057eeac6861"
}
}
Préférer une preuve
Quelle source répond est normalement notre affaire, et par défaut nous prenons la réponse la plus complète parmi toutes les sources du pays, le plus souvent un registre national que nous avons déjà importé : riche, rapide, et sans rien pour le prouver. ?prefer=proof renverse cela : l’historique stocké et toute source qui ne délivre aucun code de vérification sont écartés, et vous n’êtes répondu que si la réponse porte meta.proof.
curl "https://api.vatlas.dev/v1/lookup/FR08000325175?prefer=proof" \
-H "Authorization: Bearer $VATLAS_KEY"
Cela coûte une requête, comme une recherche simple. Le vrai coût est le temps, puisqu’un appel VIES en direct part à chaque fois, et une façon d’échouer de plus : 503 PROOF_UNAVAILABLE, quand VIES répond sans émettre de numéro de consultation, ce qu’il fait pour la plupart des numéros qu’il déclare non assujettis. Comme tout 503, il n’est pas facturé. Les réponses sont conservées dans votre audit avec leur numéro de consultation.
Un nom vide n’est pas un bug. L’Allemagne et l’Espagne ne divulguent ni raison sociale ni adresse via VIES. L’Allemagne est résolue via l’annuaire de facturation électronique Peppol lorsque l’entreprise y figure ; le nom y étant auto-déclaré, status vaut unknown. Sinon, et pour l’Espagne, seul status est significatif.
La réponse de chaque source
GET /v1/lookup-all/{vatNumber}
Le point d’entrée ci-dessus répond « quel est ce numéro » et s’arrête à la première source qui nomme une société. Celui-ci répond « qu’est-ce que chaque registre a dit de ce numéro, et quand » : il publie l’historique complet de chaque source, appelle toute source interrogeable dont la dernière déclaration a vieilli, et rapporte l’échec de cet appel quand il échoue. Rien n’est écarté comme redondant : un rapport troué ne prouve rien. Chaque entrée est un lookup (data et meta, ou error et meta quand cette source n’a rien), si bien que le code qui lit le point d’entrée ci-dessus lit celui-ci sans apprendre une seconde forme.
| Paramètre | Type | Description |
|---|---|---|
vatNumber |
chemin, requis | Le même numéro, avec la même normalisation que la recherche simple. Seul le chemin change. |
history |
query, drapeau optionnel | Actif ici, contrairement au lookup simple : ?history=false retire le nœud history de chaque entrée, et économise la requête qu’il coûte. Reste la déclaration en vigueur de chaque source, qui est ce qu’on lit pour comparer des registres. |
Le rapport est le résultat
Le code de statut porte donc sur le rapport et non sur le numéro : 200 dès que le numéro est syntaxiquement valide, même si toutes les sources ont échoué. Les verdicts vivent un niveau plus bas, dans data.sources. Seul un numéro malformé donne un 400, et il ne coûte rien.
Requête
curl "https://api.vatlas.dev/v1/lookup-all/CZ27074358" \
-H "Authorization: Bearer $VATLAS_KEY"
Réponse · 200 OK
{
"data": {
"vatNumber": "CZ27074358",
"countryCode": "CZ",
"nationalNumber": "27074358",
"sources": [
{
"data": {
"vatNumber": "CZ27074358", "name": "ACME s.r.o.", /* the same company shape as above */
},
"meta": {
"source": "ARES", "name": "ARES (Ministerstvo financí)", "kind": "live",
"sourceDate": "2026-09-04", "sourceSince": "2026-09-04",
"call": { "status": "found", "checkedAt": "2026-09-04T22:03:00.505Z" }
},
"history": [
{ "from": "2026-06-14", "to": "2026-08-30", "status": "active", /* … */ },
{ "from": "2026-09-04", "to": "2026-09-04", "status": "active", /* … */ }
]
},
{
"error": {
"code": "SOURCE_UNAVAILABLE", "message": "The source did not answer.",
"fault": "MS_UNAVAILABLE", "attempts": 4
},
"meta": {
"source": "VIES", "name": "VIES (European Commission)", "kind": "live",
"call": { "status": "unavailable", /* … */ }
},
"history": []
}
]
}
}
Ce que porte chaque entrée
| Champ | Type | Description |
|---|---|---|
data | object | La société telle que cette source la décrit aujourd’hui, c’est-à-dire sa déclaration en vigueur, dans la forme exacte que publie le lookup simple. Absent quand cette source n’a rien à montrer : error prend alors sa place. |
error | object | À la place de data : VAT_NOT_REGISTERED quand cette source déclare le numéro non assujetti, SOURCE_UNAVAILABLE quand l’appel fait par cette requête n’est pas revenu (avec fault et attempts), COMPANY_NOT_FOUND quand la source n’a jamais rien dit du numéro. |
meta | object | D’où vient la réponse et de quand elle date, exactement comme sur le lookup simple, plus name et kind (le registre tel que son éditeur l’écrit, et la façon dont nous le lisons), et call quand cette requête a fait un appel en direct. |
meta.source | string | Le registre, dans meta : SIRENE, ARES, MF_WL, PEPPOL, GEMI, VIES… avec name à côté, tel que son éditeur l’écrit. |
meta.kind | enum | bulk pour un registre que nous importons sous forme de fichier, live pour un registre que nous appelons, both pour les quatre qui offrent les deux. Un registre qui est les deux n’est listé qu’une fois : il a un seul historique, lu de deux façons. |
meta.call | object | Dans meta, présent exactement quand cette requête a fait un appel en direct, c’est-à-dire quand l’historique stocké avait plus d’une semaine. Son status est le verdict de cet appel (found, not-registered, not-found ou unavailable), et un unavailable porte error.fault et error.attempts, qui distinguent une panne de source d’une limitation de débit. |
history | array | Ce que cette source a dit, du plus ancien au plus récent, et absent sous ?history=false. Une entrée par période ininterrompue pendant laquelle elle a dit la même chose, bornée par from et to, donc une société qui n’a pas bougé depuis 2019 tient en une entrée de sept ans plutôt qu’en quatre-vingts. Un trou entre deux entrées signifie qu’aucun fichier n’a été lu entre les deux. |
history[] | object | Chaque entrée porte from et to, la période pendant laquelle cette source l’a dit (dates de publication des fichiers pour un registre importé, jours des appels pour une source interrogée), puis la société dans la même forme que le lookup simple, file quand elle a été lue dans un fichier, et proof sur une entrée VIES positive. |
Le nombre d’entrées dépend du pays. Une source qui n’existe pas ici ne produit aucune entrée. La France n’a pas de registre national interrogeable ; la Bulgarie n’a pas de fichier à importer ; un registre dont ce déploiement n’a pas les identifiants est absent purement et simplement. Il n’y a rien à prouver au sujet d’un registre qui n’allait de toute façon pas être interrogé. Une source qui existe et n’a jamais rien dit de ce numéro est listée quand même, avec une erreur disant qu’elle ne tient rien.
Champs de la réponse
| Champ | Type | Description |
|---|---|---|
vatNumber | string | Numéro canonique, préfixe pays inclus. |
countryCode | string | Préfixe TVA à deux lettres (EL pour la Grèce, XI pour l’Irlande du Nord). |
nationalNumber | string | La partie nationale du numéro, sans le préfixe. |
nationalId | string | null | Identifiant au registre national (SIREN, Business ID, registrikood…). Peut différer du numéro de TVA. |
name | string | Raison sociale telle que publiée par la source. Chaîne vide si la source ne la divulgue pas. |
legalForm | string | null | Forme juridique telle que publiée par le registre d’origine, dans sa langue et non harmonisée entre pays. Quand le registre ne publie qu’un code (France, Tchéquie, Belgique), le libellé est résolu pour vous. |
status | enum | active, inactive ou unknown. |
address.line1 | string | null | Voie, ou adresse entière quand la source ne la découpe pas. |
address.line2 | string | null | Complément d’adresse. |
address.postalCode | string | null | Code postal. |
address.city | string | null | Commune. null en Lettonie, dont le registre publie un code de territoire. |
address.country | string | Code pays ISO 3166-1 alpha-2. |
Monitor
Suivez une société par son numéro de TVA et recevez un rappel quand un registre change son nom, son statut ou son adresse. Pratique pour tenir à jour un fichier fournisseurs ou clients sans revérifier chaque numéro vous-même.
Lister les sociétés suivies
GET /v1/monitor
Renvoie tous les monitors du compte. meta.limit indique combien votre formule en autorise.
curl "https://api.vatlas.dev/v1/monitor" \
-H "Authorization: Bearer $VATLAS_KEY"
Suivre une société
POST /v1/monitor/{vatNumber}
Répond 201. Suivre deux fois le même numéro répond 409.
curl -X POST "https://api.vatlas.dev/v1/monitor/FR95884831538" \
-H "Authorization: Bearer $VATLAS_KEY"
Les rappels partent vers l’URL par défaut définie dans votre console. Pour envoyer celui-ci ailleurs, passez un url dans le corps :
curl -X POST "https://api.vatlas.dev/v1/monitor/FR95884831538" \
-H "Authorization: Bearer $VATLAS_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/vatlas"}'
Consulter un monitor
GET /v1/monitor/{vatNumber}
Renvoie le monitor, son dernier état connu et ses dernières tentatives de livraison.
Ne plus suivre
DELETE /v1/monitor/{vatNumber}
Répond 204. L’historique de livraisons part avec.
L’événement reçu
data et meta sont la même enveloppe qu’un lookup simple : un client qui lit déjà GET /v1/lookup/{vatNumber} n’a rien de neuf à écrire. previous est ce qui vous avait été communiqué.
{
"event": "company.changed",
"monitor": "01a08018-e2b8-770c-a9e6-89c82d01bf53",
"occurredAt": "2026-09-08T03:00:41.563Z",
"vatNumber": "FR95884831538",
"data": { "name": "ACME SARL", "status": "active", … },
"meta": { "source": "SIRENE", "sourceDate": "2026-09-08", … },
"previous": { "data": { … }, "meta": { … } }
}
La première vérification après l’ajout n’envoie jamais d’événement : elle enregistre l’état courant, pour que le changement suivant ait un point de comparaison.
Les monitors ne sont pas décomptés de votre quota mensuel. Votre formule plafonne le nombre de sociétés suivies simultanément.
Lookup Async
Quand aucune source ne peut répondre tout de suite pour un numéro, mettez le lookup en file plutôt que de réessayer vous-même. Vatlas le retente à plusieurs moments dans la journée, puis les jours suivants, et vous envoie le résultat dès qu’il l’obtient.
Résoudre un numéro de TVA en asynchrone
POST /v1/lookup-async/{vatNumber}
Répond 202 : rien n’est encore résolu. Ajoutez ?prefer=proof pour demander un lookup avec preuve. Passez un url dans le corps pour remplacer votre URL de rappel par défaut.
curl -X POST "https://api.vatlas.dev/v1/lookup-async/FR95884831538" \
-H "Authorization: Bearer $VATLAS_KEY"
{
"data": {
"publicId": "01a0801b-0700-715f-b982-859f0d21da79",
"vatNumber": "FR95884831538",
"kind": "single",
"status": "pending",
"attempts": 0
}
}
Une requête prise sur votre quota mensuel, au moment de la mise en file. Si aucune source ne répond jamais, le lookup est abandonné et la requête vous est recréditée.
Toutes les sources, en asynchrone
POST /v1/lookup-async-all/{vatNumber}
La même chose pour toutes les sources à la fois, chacune répondant pour elle-même. Également 202, également une requête.
curl -X POST "https://api.vatlas.dev/v1/lookup-async-all/FR95884831538" \
-H "Authorization: Bearer $VATLAS_KEY"
Lister vos lookups
GET /v1/lookup-async
Du plus récent au plus ancien. Filtrez avec ?status=pending|success|failed, paginez avec ?limit= et ?before=. Ne consomme aucun quota.
Consulter un lookup
GET /v1/lookup-async/id/{publicId}
Renvoie le lookup, chaque tentative faite jusqu’ici et, dès qu’il y en a un, le résultat. Ne consomme aucun quota.
Annuler un lookup
DELETE /v1/lookup-async/id/{publicId}
Répond 204. La requête déjà dépensée n’est pas recréditée.
Le résultat reçu
data et meta sont exactement ce que l’appel synchrone aurait renvoyé : le même code de lecture fonctionne pour les deux.
{
"event": "lookup.completed",
"lookup": "01a0801b-0700-715f-b982-859f0d21da79",
"vatNumber": "FR95884831538",
"data": { … },
"meta": { … }
}
S’il est abandonné, vous recevez à la place lookup.failed avec un objet error. Vous n’êtes donc jamais laissé à attendre dans le silence.
Rappels
Monitor et Lookup Async livrent tous deux en envoyant du JSON en POST à une URL de votre choix. Chacun a son URL par défaut dans la console, et chaque monitor ou lookup peut la remplacer.
Ce que vous recevez
Répondez 2xx et nous considérons la livraison faite. Le corps est du JSON ; le nom de l’événement figure dans le corps et dans l’en-tête X-Vatlas-Event. X-Vatlas-Delivery est unique par tentative : vous pouvez vous en servir pour ignorer un doublon.
POST /hooks/vatlas
Content-Type: application/json
X-Vatlas-Event: company.changed
X-Vatlas-Delivery: f9b8936a-a499-489f-9099-6feb93f23bfb
X-Vatlas-Signature: t=1788855521,v1=6bc87dd5523b8a5b…
| Événement | Envoyé quand |
|---|---|
company.changed | Un registre a changé ce qu’il dit d’une société que vous suivez. |
monitor.test | Vous avez cliqué « Envoyer un événement de test » dans la console. Même format, données inventées. |
lookup.completed | Un lookup en file a obtenu une réponse. |
lookup.failed | Un lookup en file a été abandonné. |
Vérifier la signature d’un webhook (Monitor, Lookup Async)
Chaque requête que nous envoyons porte un en-tête X-Vatlas-Signature. Il a deux parties séparées par une virgule : t, l’heure unix de l’envoi, et v1, une empreinte de la requête calculée avec votre secret de signature. Voici ce qui arrive :
POST /hooks/vatlas
X-Vatlas-Event: company.changed
X-Vatlas-Signature: t=1788855521,v1=abb136a424dd0f7c1e5b90d2a3f84c67e10b9d5a2c8e73f4061b8ad9e2c4570f
{"event":"company.changed", … }
Prenez t, un point, puis le corps brut de la requête, exactement les octets reçus, avant de les analyser. Un corps passé par un analyseur JSON puis re-sérialisé n’est plus la même chaîne, et l’empreinte ne correspondrait plus. Vous obtenez ce qui a été signé :
1788855521.{"event":"company.changed", … }
Calculez le HMAC-SHA256 de cette chaîne avec votre secret de signature, et comparez-le à v1 :
HmacSHA256(t + '.' + rawBody, signingSecret) == v1
Avec de vraies valeurs, cela donne :
HmacSHA256('1788855521.{"event":"company.changed", … }', 'vtl_sign_xxx…')
== 'abb136a424dd0f7c1e5b90d2a3f84c67e10b9d5a2c8e73f4061b8ad9e2c4570f'
Comparez les deux en temps constant si votre langage le permet, et rejetez la requête s’ils diffèrent, ou si t date de plus de quelques minutes, ce qui empêche qu’on rejoue une requête capturée plus tôt.
Votre secret de signature est sur la page Monitor de la console, où vous pouvez aussi le renouveler. Un seul secret couvre les deux fonctionnalités.
Reprises
Une livraison qui ne répond pas 2xx est réessayée quelques fois dans les secondes qui suivent. Chaque tentative, réussie ou non, est listée dans la console avec le statut renvoyé par votre endpoint. C’est le premier endroit où regarder si quelque chose n’est jamais arrivé.
Avant même qu’une société suivie ne change, utilisez « Envoyer un événement de test » dans la console : une vraie requête signée vers votre vraie URL, portant des données inventées au format exact d’un vrai événement.
Historique d’audit
GET /v1/audit
Chaque réponse que nous vous avons donnée, telle qu’elle a été envoyée, pour pouvoir la reproduire des mois plus tard : ce que nous avons dit d’un numéro de TVA, de quel registre cela venait et à quelle date ce registre l’a dit. Le même historique que la console, en HTTP.
Il ne consomme aucun quota. Ces réponses ont été comptées au moment où elles ont été produites.
Sur l’offre Free, cet endpoint renvoie vos 3 dernières réponses, de quoi voir la fonctionnalité à l’œuvre. L’offre Pro conserve l’historique complet pendant 730 jours.
curl "https://api.vatlas.dev/v1/audit?vatNumber=FR95884831538" \
-H "Authorization: Bearer $VATLAS_KEY"
{
"data": [
{
"publicId": "01a0570f-5f96-77a0-9bf3-b990b0368c0c",
"vatNumber": "FR95884831538",
"fetchedAt": "2026-06-14T09:12:03.000Z",
"httpStatus": 200,
"errorCode": null,
"kind": "single",
"companyName": "SARL JULIEN MAULNY EURL",
"source": "SIRENE",
"sourceDate": "2026-09-01",
"sourceSince": "2026-06-01",
"sources": ["SIRENE"],
"response": { "data": { … }, "meta": { … } }
}
],
"meta": { "count": 1, "nextBefore": null }
}
Paramètres de requête
| Paramètre | Signification |
|---|---|
vatNumber |
L’historique d’un numéro. Normalisé comme partout ailleurs : les espaces et la casse n’ont pas d’importance. Omettez-le pour obtenir vos appels les plus récents, du plus récent au plus ancien. |
limit |
Taille de page, de 1 à 100. 25 par défaut. |
before |
Curseur : passez le meta.nextBefore de la page précédente, c’est-à-dire le publicId de sa dernière ligne, pour obtenir la suivante. null signifie qu’il n’y a pas de page suivante. Un curseur qui ne correspond à aucune de vos lignes donne un 400. |
publicId est un UUIDv7 : c’est la valeur à renvoyer dans before pour obtenir la page suivante.
response est l’enveloppe exactement telle qu’elle a été envoyée à l’époque, corps d’erreur d’un 404 ou d’un 503 compris : c’est la trace de ce qui vous a été répondu, et non une nouvelle réponse.
Lister tous les pays et leur mode de résolution
GET /v1/countries
Renvoie tous les préfixes servis, 39 en tout, avec la source de chacun et sa stratégie : bulk pour un registre importé, on-demand pour l’API du registre, both, ou vies pour un État membre sans source locale exploitable, qui reste résolu via le repli VIES.
{
"data": [
{ "countryCode": "FR", "name": "France", "source": "SIRENE", "strategy": "bulk" },
{ "countryCode": "PL", "name": "Poland", "source": "MF_WL", "strategy": "on-demand" },
{ "countryCode": "CZ", "name": "Czechia", "source": "ARES", "strategy": "on-demand" },
{ "countryCode": "FI", "name": "Finland", "source": "PRH", "strategy": "both" },
{ "countryCode": "DE", "name": "Germany", "source": "PEPPOL", "strategy": "both" }
]
}
Sonde de disponibilité
GET /health
Ne répond qu’une fois un aller-retour base de données réussi, donc utilisable tel quel comme readiness probe. Cet appel ne consomme pas de quota.
{ "status": "ok" }
Codes d’erreur
Toutes les erreurs partagent la même forme :
{
"error": {
"code": "INVALID_VAT_FORMAT",
"message": "Unknown VAT country prefix \"XX\"",
"reason": "UNKNOWN_COUNTRY"
}
}
| Code | HTTP | Signification |
|---|---|---|
INVALID_VAT_FORMAT |
400 | Syntaxe ou clé de contrôle invalide ; aucune source n’a été consultée. reason précise laquelle : EMPTY, UNKNOWN_COUNTRY, BAD_FORMAT, BAD_CHECKSUM. |
INVALID_QUERY |
400 | Un paramètre de requête est hors bornes ou malformé : prefer avec une valeur autre que completeness ou proof, history avec une valeur autre que true ou false, ou sur /v1/audit un limit hors de 1-100 ou un before qui n’est pas un curseur que nous avons émis. La requête est rejetée plutôt que corrigée en silence. |
INVALID_BODY |
400 | Le corps JSON n'est pas un objet, ou l'un de ses champs n'a pas le bon type. Un booléen a une orthographe en JSON, et « true » n'en est pas une. |
CALLBACK_URL_MISSING |
400 | Ni le monitor ni le compte n’indiquent d’URL de rappel : il n’y a nulle part où livrer. Refusé à la création plutôt que découvert des jours plus tard. |
INVALID_CALLBACK_URL |
400 | L'URL de rappel n'est pas absolue, n'est pas en http ou https, porte des identifiants, ou désigne une adresse que ce service n'appellera pas. |
MONITOR_LIMIT_REACHED |
403 | Votre formule suit un nombre fixe de sociétés à la fois, et il est atteint. Chaque liste indique ce plafond dans meta.limit. |
MONITOR_EXISTS |
409 | Ce compte suit déjà ce numéro. Le suivre deux fois livrerait deux fois le même événement. |
MISSING_SECRET_KEY |
401 | Aucune information d’authentification Authorization: Bearer n’a été envoyée, ou l’en-tête utilisait un autre schéma. Cet appel n’est pas décompté de votre quota. |
INVALID_SECRET_KEY |
401 | La clé est inconnue ou a été révoquée. Une révocation prend effet en moins d’une minute : une clé révoquée à l’instant peut donc encore répondre. |
QUOTA_EXCEEDED |
429 | Le quota mensuel du compte est épuisé. Il se renouvelle au début du mois suivant ; X-RateLimit-Reset en donne l’instant exact. |
VAT_NOT_REGISTERED |
404 | VIES a répondu de façon définitive : le numéro n’est pas enregistré pour les échanges intracommunautaires. Une société peut être active dans son registre national sans être enregistrée à la TVA intracommunautaire. La réponse porte meta.checkedAt et le même meta.proof qu’une réponse positive. |
COMPANY_NOT_FOUND |
404 | Plus aucune source à interroger : le numéro est valide, mais introuvable. |
SOURCE_UNAVAILABLE |
503 | Rien de stocké ne répond, et aucune source interrogeable n’a pu être jointe. Ce n’est pas un résultat négatif et rien n’est conservé : réessayez plus tard. |
PROOF_UNAVAILABLE |
503 | Uniquement avec ?prefer=proof : la vérification a eu lieu et VIES n’a émis aucun numéro de consultation, elle ne peut donc pas être prouvée. Ce n’est pas un verdict sur le numéro : il peut y avoir derrière une réponse valable, que demander une preuve revient à refuser. Réessayez, ou demandez ?prefer=completeness pour accepter la réponse sans preuve. |
SOURCE_ERROR |
Aucun | Une source a échoué d’une façon qui n’est pas une indisponibilité. N’apparaît que dans data.sources de /v1/lookup-all, jamais comme statut d’une requête. |
NOT_FOUND |
404 | Route inconnue. |
INTERNAL_ERROR |
500 | Défaillance inattendue de notre côté. |
Quotas & limites
Le quota est mensuel et appartient au compte, pas à une clé en particulier. Chaque réponse décomptée porte l’état courant du compteur, il n’y a donc rien à estimer :
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1788220800
- Une erreur de format
400n’est jamais décomptée : le numéro est rejeté avant toute consultation. - Une
503ne l’est pas davantage : une source n’a pas répondu, ce n’est pas à vous de le payer. /v1/countrieset/healthne demandent aucune clé et ne consomment aucun quota ;/v1/auditen demande une et n’en consomme pas non plus.- Quota épuisé :
429, avec la même enveloppe d’erreur.X-RateLimit-Resetindique le renouvellement.
Détail des offres sur la page tarifs.
Exemples de code
curl -s "https://api.vatlas.dev/v1/lookup/FR08000325175" \
-H "Authorization: Bearer $VATLAS_KEY" \
| jq '.data.name'
// Node 20+ / Deno / navigateur (via votre backend)
const lookupVat = async (vatNumber) => {
const res = await fetch(
`https://api.vatlas.dev/v1/lookup/${encodeURIComponent(vatNumber)}`,
{ headers: { 'Authorization': `Bearer ${process.env.VATLAS_KEY}` } },
)
const payload = await res.json()
if (!res.ok) throw new Error(`${payload.error.code}: ${payload.error.message}`)
return payload.data
}
const company = await lookupVat('FR08000325175')
console.log(company.name, '-', company.address.city)
import os
import requests
def lookup_vat(vat_number: str) -> dict:
res = requests.get(
f"https://api.vatlas.dev/v1/lookup/{vat_number}",
headers={"Authorization": f"Bearer {os.environ['VATLAS_KEY']}"},
timeout=10,
)
payload = res.json()
if res.status_code != 200:
raise RuntimeError(payload["error"]["code"])
return payload["data"]
company = lookup_vat("FR08000325175")
print(company["name"], company["address"]["city"])
<?php
function lookupVat(string $vatNumber): array
{
$context = stream_context_create([
'http' => ['header' => 'Authorization: Bearer ' . getenv('VATLAS_KEY')],
]);
$url = 'https://api.vatlas.dev/v1/lookup/' . rawurlencode($vatNumber);
$payload = json_decode(file_get_contents($url, false, $context), true);
if (isset($payload['error'])) {
throw new RuntimeException($payload['error']['code']);
}
return $payload['data'];
}
$company = lookupVat('FR08000325175');
echo $company['name'];