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.dev

Les 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, et meta.sourceSince le 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éponse vies, 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 lire type suffise.
  • meta.checkedAt : sur une erreur VAT_NOT_REGISTERED uniquement : 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ètres
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ètres
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

Champs d’une entrée
Champ Type Description
dataobjectLa 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.
errorobjectÀ 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.
metaobjectD’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.sourcestringLe registre, dans meta : SIRENE, ARES, MF_WL, PEPPOL, GEMI, VIES… avec name à côté, tel que son éditeur l’écrit.
meta.kindenumbulk 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.callobjectDans 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.
historyarrayCe 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[]objectChaque 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

Champs de data
Champ Type Description
vatNumberstringNuméro canonique, préfixe pays inclus.
countryCodestringPréfixe TVA à deux lettres (EL pour la Grèce, XI pour l’Irlande du Nord).
nationalNumberstringLa partie nationale du numéro, sans le préfixe.
nationalIdstring | nullIdentifiant au registre national (SIREN, Business ID, registrikood…). Peut différer du numéro de TVA.
namestringRaison sociale telle que publiée par la source. Chaîne vide si la source ne la divulgue pas.
legalFormstring | nullForme 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.
statusenumactive, inactive ou unknown.
address.line1string | nullVoie, ou adresse entière quand la source ne la découpe pas.
address.line2string | nullComplément d’adresse.
address.postalCodestring | nullCode postal.
address.citystring | nullCommune. null en Lettonie, dont le registre publie un code de territoire.
address.countrystringCode 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énements
Événement Envoyé quand
company.changedUn registre a changé ce qu’il dit d’une société que vous suivez.
monitor.testVous avez cliqué « Envoyer un événement de test » dans la console. Même format, données inventées.
lookup.completedUn lookup en file a obtenu une réponse.
lookup.failedUn 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è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"
  }
}
Codes d’erreur
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 400 n’est jamais décomptée : le numéro est rejeté avant toute consultation.
  • Une 503 ne l’est pas davantage : une source n’a pas répondu, ce n’est pas à vous de le payer.
  • /v1/countries et /health ne demandent aucune clé et ne consomment aucun quota ; /v1/audit en demande une et n’en consomme pas non plus.
  • Quota épuisé : 429, avec la même enveloppe d’erreur. X-RateLimit-Reset indique 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'

Créer une clé prend une minute

100 requêtes par mois gratuitement, sans carte bancaire. 5 000 par mois pour 29 € HT quand cela ne suffit plus.