增值税发票在线查验 API 技术接入指南

1. 背景与适用场景
在企业财务、费用报销、电子发票归档等场景中,经常需要对纸质或电子发票进行真伪核验与票面信息提取。传统人工核对方式效率低、易出错,而通过 API 将发票图片传入服务端,由服务端完成 OCR 识别并联网核验,是一种常见的工程化方案。
本接口适合以下场景:
- 报销系统:员工上传发票后自动提取发票代码、号码、金额等字段。
- 财务对账:批量核对销方、购方、价税合计等信息。
- 发票归档:将纸质发票扫描件结构化存储,便于检索与审计。
说明:本接口为阿里云市场入驻服务商提供的数据服务,正文示例中的鉴权头与调用路径均按阿里云 API 网关标准接入。
2. 接口概览
- 调用方式:POST
- 路径:
/ocrCheckVAT - 协议:HTTPS
- 返回格式:JSON
- 鉴权方式:阿里云 API 网关 APPCODE 简单身份认证,请求头:
Authorization: APPCODE <appcode>
请求体中传入发票图片的 Base64 编码或图片 URL,服务端完成识别与核验后返回票面结构化字段。完整调用地址请在阿里云控制台查看。

3. 请求参数
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| imgBase64 | string | 否 | 发票图片的 Base64 字符串,与 imgUrl 二选一 |
| imgUrl | string | 否 | 发票图片的可访问下载地址,与 imgBase64 二选一 |
注意:imgBase64 与 imgUrl 必须且只能传一个。建议优先使用 Base64,避免图片外链失效或被限制访问。
4. 返回结构
顶层返回结构统一如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| showapi_res_code | int | 接口调用是否成功,0 为成功,其他为失败 |
| showapi_res_error | string | 错误提示信息 |
| showapi_res_id | string | 请求唯一标识 |
| showapi_res_body | object | 业务数据主体 |
showapi_res_body 内部字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| ret_code | int | 业务调用结果,0 为成功 |
| remark | string | 提示信息 |
| invoiceType | string | 发票类型 |
| invoiceCode | string | 发票代码 |
| invoiceNo | string | 发票号码 |
| invoiceDate | string | 开票日期 |
| buyerName | string | 购方名称 |
| buyerTaxNo | string | 购方税号 |
| buyerContact | string | 购方地址电话 |
| buyerBank | string | 购方开户行账户 |
| salerName | string | 销方名称 |
| salerTaxNo | string | 销方税号 |
| salerContact | string | 销方地址电话 |
| salerBank | string | 销方开户行账户 |
| invoiceAmt | string | 合计金额 |
| totalTaxAmt | string | 合计税额 |
| totalAmt | string | 价税合计 |
| notes | string | 发票备注 |
| status | int | 发票状态:1 未作废、2 作废、3 红冲 |
| itemList | array | 商品明细列表 |
itemList 每项字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| name | string | 商品名称 |
| amount | string | 数量 |
| spec | string | 规格 |
| unit | string | 单位 |
| taxAmt | string | 税额 |
| taxRate | string | 税率 |
| priceAmt | string | 金额 |
| priceUnit | string | 单价 |
成功响应示例:
{
"showapi_res_code": 0,
"showapi_res_error": "",
"showapi_res_id": "",
"showapi_res_body": {
"ret_code": 0,
"remark": "核验成功",
"invoiceType": "增值税普通发票(电子)",
"invoiceCode": "031*****411",
"invoiceNo": "28*****251",
"invoiceDate": "2019-01-01",
"buyerName": "孟**",
"buyerTaxNo": "",
"buyerContact": "",
"buyerBank": "",
"salerName": "上海***有限公司",
"salerTaxNo": "913*****40M",
"salerContact": "上海市普陀区...",
"salerBank": "",
"invoiceAmt": "2891.98",
"totalTaxAmt": "28.92",
"totalAmt": "2920.90",
"notes": "",
"status": 1,
"itemList": [
{
"name": "*信息技术服务*数据服务费",
"spec": "API-AL",
"unit": "项",
"amount": "1.0",
"taxRate": "0.01",
"taxAmt": "28.92",
"priceAmt": "2891.98",
"priceUnit": "2891.980198019801980"
}
]
}
}

5. 错误码与排查
接口存在两类错误:网关层 HTTP 状态码与业务层 ret_code / remark。
常见 HTTP 状态码:
| 状态码 | 含义 | 可能原因 | 排查办法 |
|---|---|---|---|
| 400 | 请求参数错误 | 缺少 imgBase64/imgUrl,或图片格式不支持 | 检查请求体是否包含有效图片字段 |
| 401 | 身份校验失败 | APPCODE 缺失、错误或未启用该 API | 在控制台确认 APPCODE 与授权状态 |
| 403 | 请求被禁止 | 剩余次数已用完或接口未购买 | 查看控制台余量与有效期 |
| 404 | 服务地址不存在 | 路径拼写错误 | 核对路径 /ocrCheckVAT |
| 500 | 服务端内部错误 | 服务商端异常 | 稍后重试或联系平台 |
| 503 | 服务暂不可用 | 限流或维护 | 降低调用频率,稍后重试 |
业务层失败返回示例:
{
"showapi_res_code": 0,
"showapi_res_error": "",
"showapi_res_body": {
"ret_code": -1,
"remark": "查验不一致!"
}
}
常见业务提示:
- "查验不成功,请重试。":服务商端核验超时或网络波动,可稍后重试。
- "查验不一致!":票面信息与税局底账不一致。
- "查无此票!":税局系统中未找到该发票记录。
- "超过该张发票当日查验次数":单张发票当日查验次数已达上限,次日再试。
6. 频控与合规
- 调用频次:具体 QPS 上限以阿里云控制台实时配置为准。
- 次数扣减:当 HTTP 响应状态码为 200 时扣减调用次数;非 200 状态码不扣减。
- 数据合规:发票图片属于敏感经营数据,传输请使用 HTTPS,并避免在日志中完整留存图片 Base64。核验结果仅用于业务校验,不作为唯一法律依据。
- 缓存策略:发票状态在短时间内通常不变,对同一张发票的核验结果可做短时缓存(如 5~15 分钟),以减少重复调用。

7. 多语言接入示例
curl
curl -X POST '<调用地址>/ocrCheckVAT' \
-H 'Authorization: APPCODE <YOUR_APPCODE>' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'imgBase64=<BASE64_IMAGE_STRING>'
Python
import base64
import requests
def verify_invoice(image_path: str, appcode: str, endpoint: str):
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
url = f"{endpoint}/ocrCheckVAT"
headers = {
"Authorization": f"APPCODE {appcode}"}
resp = requests.post(
url, data={
"imgBase64": b64}, headers=headers, timeout=30
)
return resp.json()
# 使用示例
result = verify_invoice("invoice.jpg", "<YOUR_APPCODE>", "<调用地址>")
print(result["showapi_res_body"]["remark"])
Node.js
const fs = require('fs');
const axios = require('axios');
async function verifyInvoice(imagePath, appcode, endpoint) {
const b64 = fs.readFileSync(imagePath, {
encoding: 'base64' });
const url = `${
endpoint}/ocrCheckVAT`;
const headers = {
Authorization: `APPCODE ${
appcode}` };
const data = new URLSearchParams({
imgBase64: b64 });
const resp = await axios.post(url, data, {
headers, timeout: 30000 });
return resp.data;
}
Java
// 省略依赖引入,示例使用 JDK HttpClient + Jackson
public Map<String, Object> verifyInvoice(
String imagePath, String appcode, String endpoint) throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get(imagePath));
String b64 = Base64.getEncoder().encodeToString(bytes);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint + "/ocrCheckVAT"))
.header("Authorization", "APPCODE " + appcode)
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(
"imgBase64=" + URLEncoder.encode(b64, StandardCharsets.UTF_8)))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
return new ObjectMapper().readValue(
response.body(), new TypeReference<Map<String, Object>>() {
});
}
PHP
<?php
$appcode = '<YOUR_APPCODE>';
$endpoint = '<调用地址>';
$imagePath = 'invoice.jpg';
$b64 = base64_encode(file_get_contents($imagePath));
$ch = curl_init("{$endpoint}/ocrCheckVAT");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(['imgBase64' => $b64]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: APPCODE {$appcode}"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo $data['showapi_res_body']['remark'];
?>

8. 工程实践建议
- 前置校验:调用前检查图片大小与格式,建议压缩至 2MB 以内,避免 Base64 过大导致请求超时。
- 参数二选一:代码中显式校验 imgBase64 与 imgUrl 有且仅有一个,否则直接返回参数错误。
- 超时与重试:建议设置 30 秒超时;当返回 503 或 "查验不成功,请重试。" 时,采用指数退避重试 2~3 次。
- 结果缓存:对同一张发票的核验结果做幂等缓存,键可设为
发票代码_发票号码,TTL 5~15 分钟。 - 密钥管理:APPCODE 不要硬编码在前端或公开仓库,通过环境变量或密钥管理服务注入。
- 敏感数据:日志中不要打印完整 Base64 字符串;返回结果中购方/销方信息按需脱敏展示。
- 限流保护:客户端实现令牌桶或固定窗口限流,避免突发流量触发网关限流。

9. 技术 FAQ
Q1:可以同时传 imgBase64 和 imgUrl 吗?
不建议。两个参数二选一,服务端以其中一个为准;同时传入可能造成请求体过大。
Q2:图片格式有要求吗?
通常支持 JPG、PNG、BMP 等常见图片格式。建议上传清晰、完整的电子发票 PDF 转图或拍照件。
Q3:为什么返回 "查无此票"?
可能原因:发票尚未同步至税局系统;输入图片模糊导致 OCR 识别错误;发票状态异常。建议核对图片清晰度与票面信息。
Q4:HTTP 200 但 ret_code 不为 0 是否扣次数?
根据该接口的次数扣减规则,HTTP 状态码为 200 时即视为一次成功调用并扣减剩余次数。因此 ret_code 不为 0 的业务失败(如查无此票)仍会扣减次数。
Q5:如何降低调用成本?
对高频重复核验场景使用短时缓存;批量处理前先做参数校验与去重;设置合理的限流策略。
10. 小结
本文围绕增值税发票在线查验接口,介绍了其适用场景、请求参数、返回结构、常见错误码以及多语言接入示例。接入时需注意:
- 仅传 imgBase64 或 imgUrl 之一;
- 使用
Authorization: APPCODE <appcode>鉴权; - 完整调用地址、QPS 与收费规则以阿里云控制台为准;
- 对敏感发票图片与核验结果做好传输加密、日志脱敏与缓存控制。
通过合理的重试、缓存与限流设计,可将该能力稳定集成到财务、报销或发票归档系统中。