欧盟 VAT 验证 API:正确使用 VIES,包括那些人人都会踩的坑

VIES 是欧盟 VAT 验证唯一的权威来源,而它有三种行为会悄悄搞坏天真的集成:它对什么都返回 200、两个成员国不披露身份信息、各国系统还会离线。本文讲清楚这三件事该怎么处理。
一句话总结
- •VIES 是欧盟委员会的增值税信息交换系统——唯一权威的校验途径。它会把每次查询实时转发到对应成员国自己的税务数据库。
- •VIES 对什么都返回 HTTP 200。真正的结论在 userError 字段里:VALID、INVALID、INVALID_INPUT,或若干「系统不可用」代码。把 200 当成成功,是最经典的那个 bug。
- •德国和西班牙从不通过 VIES 披露纳税主体的名称与地址。这是政策而非数据缺失——所以响应用 name_disclosed 标记出来,而不是返回空字符串。
- •希腊的增值税国家代码是 EL,不是它的 ISO 代码 GR。北爱尔兰是 XI。英国本土在脱欧后已退出 VIES,不在覆盖范围内。
- •当某个成员国系统离线时,答案是「稍后再试」,不是「无效」。这会返回 503 且从不计费——为比利时的一次维护窗口向客户收费是不可接受的。
一个数据源,三个陷阱
只要你做跨境 B2B 生意,就必须校验增值税号,而权威的地方只有一个:VIES,欧盟委员会的增值税信息交换系统。VIES 本身并不持有数据库。它会把每次查询实时转发给签发该税号的成员国税务机关。这正是它权威的原因——也正是它的行为与你集成过的任何其他 API 都不一样的原因。
它有三种行为会悄悄搞坏天真的集成。每一种产生的 bug 看起来都像数据问题而不是集成问题,这也是它们能在生产环境里存活那么久的原因。
陷阱一:什么都是 HTTP 200
无论税号有效、无效、格式错误,还是成员国那边已经烧起来了,VIES 都返回 200。真正的结论在 userError 字段里:VALID、INVALID、INVALID_INPUT,或若干不可用代码之一。任何把 200 当成成功、只读 isValid 的客户端,都会把一次服务中断读成「这个客户的税号是假的」。
欧盟 VAT 验证把这些翻译成常规的 HTTP 语义:真实答案是 200,输入格式错误是 400,成员国不可用是 503——且不计费,因为你不该为比利时的一次维护窗口买单。
curl "https://www.apipick.com/api/validate-vat?vat_number=IE6388047V" \
-H "x-api-key: $APIPICK_KEY"
{
"vat_number": "IE6388047V",
"country_code": "IE",
"number": "6388047V",
"valid": true,
"name": "GOOGLE IRELAND LIMITED",
"address": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
"name_disclosed": true,
"consultation_number": null,
"source": "European Commission VIES"
}陷阱二:德国和西班牙不给名称
校验一个德国税号,纳税主体名称会是空的。税号确实被确认为已注册——但德国和西班牙出于政策不通过 VIES 披露名称与地址。
{
"vat_number": "DE811907980",
"valid": true,
"name": null,
"address": null,
"name_disclosed": false
}实际后果是:不要把「必须有纳税主体名称」设成开户流程的前置条件。一个持有完全有效德国税号的客户,永远不会通过这条路径提供名称,而把他拦下来是你的 bug,不是他的。
陷阱三:希腊是 EL
在增值税语境下,希腊使用 EL,而不是它的 ISO 3166 代码 GR。如果你的注册表单拿 ISO 列表来校验国家前缀——这是最自然的做法——那么每一个希腊客户都会在请求离开你的服务器之前就被拒绝。北爱尔兰在《温莎框架》下是 XI,而英国本土在脱欧后退出了 VIES,因此 GB 税号在这里根本无法校验。
本接口接收带前缀的完整税号,会剥掉发票上常见的空格、点号与连字符,并对非成员国前缀返回 400,同时在错误信息中列出有效集合,而不是悄无声息地失败。
反向征收,以及为什么要留好凭证
这件事的商业理由在于:欧盟内部的跨境 B2B 交易中,你按零税率开票,由买方自行申报增值税。而「已核验买方注册状态」的举证责任落在你身上。如果税号最终被认定无效、而你又拿不出核验过的证据,税务机关可以向你追缴这笔未缴增值税。
所以要留存的是证据,而不只是那个布尔值。当 VIES 出具查询编号时,它会以 consultation_number 返回,而这正是税务机关认可的、证明你在某一日期核验过的凭证。请把它与发票一并保存。
import httpx, os
def verify_for_invoice(vat_number: str) -> dict:
r = httpx.get("https://www.apipick.com/api/validate-vat",
params={"vat_number": vat_number},
headers={"x-api-key": os.environ["APIPICK_KEY"]})
if r.status_code == 503:
raise RetryLater(r.json()["message"]) # outage, not an invalid number
if r.status_code == 400:
raise MalformedVat(r.json()["message"]) # fix the input, do not retry
r.raise_for_status()
d = r.json()
return {
"valid": d["valid"],
"legal_name": d["name"], # may be None by policy
"name_withheld": d["valid"] and not d["name_disclosed"],
"proof": d["consultation_number"], # keep with the invoice
"checked_at": d["request_date"],
}它在整个开户流程中的位置
一个 VAT 税号只能告诉你某家企业已注册可做跨境贸易。它不会告诉你背后的法人身份,而且只覆盖欧盟。若要核实交易对手身份——官方法定名称、司法辖区、本国登记号、股权结构——请搭配LEI 法人实体查询,它覆盖全球且以 CC0 发布。美国上市公司那一侧则由公司数据覆盖。
自己接,还是直接调
| 直连 VIES | API Pick | |
|---|---|---|
| 结果如何表达 | 什么都返回 200,得读 userError | 真正的 HTTP 状态码 |
| 宕机处理 | 你得自己分类 5 种不可用代码 | 503,且不计费 |
| 不予披露 | 字面量「---」字符串 | null + name_disclosed 标记 |
| 输入清洗 | 你自己剥空格、点号、连字符 | 已处理 |
| EL / XI / GB | 这些例外要你自己编码 | 校验并给出清晰错误 |
| 成本 | 免费,外加这些边界情况 | 3 积分/次,只在成功时计 |
VIES 本身免费,你完全可以直连——两条路走的都是同一个权威机构。你买到的是那层翻译,以及「欧洲税务基础设施午后打盹时不向你收费」的那份自觉。免费 key 附带 100 积分,无需绑卡。
常见问题
这是欧盟官方校验,还是某个数据库副本?
是官方校验。请求发往欧盟委员会的增值税信息交换系统 VIES,由它实时把查询转发到签发该税号的成员国税务数据库。中间不存在任何注册库副本——答案来自税务机关本身,这也是它与欧盟委员会官网表单结果一致、并可作为合规凭据的原因。
为什么德国和西班牙的税号查不到纳税主体名称?
德国和西班牙出于政策原因不通过 VIES 披露纳税主体的名称与地址。有效性依然会被明确确认,只是身份字段不予公开。响应会把 name_disclosed 设为 false,让你的代码能区分「政策性隐去」与「字段确实为空」。把空字符串当作该主体没有名称存进 CRM,正是这个问题悄悄污染数据的方式。
为什么希腊用 EL 而不是 GR?
在增值税语境下,希腊使用 EL,这一惯例早于其 ISO 3166 代码 GR 并与之不同。如果你的表单拿 ISO 列表来校验国家前缀,那么每一个希腊税号都会在抵达 VIES 之前就被拒绝。北爱尔兰在《温莎框架》下使用 XI,而英国本土在脱欧后退出了 VIES,因此 GB 税号在这里根本无法校验。
成员国系统宕机时应该怎么处理?
正确答案是「稍后再试」,绝不是「无效」。VIES 会把请求路由到各成员国自己的数据库,而这些系统会因维护而离线,并返回诸如 MS_UNAVAILABLE 的代码。把它当成税号无效,会在结账环节拦下一个完全合法的客户。本接口把这类代码映射为 HTTP 503 并附清晰说明,且不扣任何积分,因此重试不花钱。
能保留查询编号作为审计凭证吗?
可以,当 VIES 出具编号时,它会通过 consultation_number 字段返回。该编号正是税务机关认可的凭证,用以证明你在某个具体日期核验过交易对手的 VAT 状态——这在反向征收开票中尤为重要,因为「已核验」的举证责任在你这一方。请把它与发票一起留存,而不是只存一个布尔值。
本文涉及的 API
Sarah Choy 是 API Pick 的 CEO,专注于为 AI Agent 与 LLM 工作流构建可用于生产的 API。