HMRC Making Tax Digital API 新手入门指南
只要你给英国任何纳税人群体写软件,迟早都得和HMRC(英国税务海关总署)打交道。
个人所得税领域的 Making Tax Digital(数字税务)服务已于 2026 年 4 月 6 日正式上线。目前,年收入超过 5 万英镑的自由职业者和房东已强制要求接入。这一门槛将在 2027 年 4 月降至 3 万英镑,并在 2028 年 4 月进一步降至 2 万英镑,因此未来两年内受覆盖的人口规模大致会翻三倍。
实际影响是,现在有大量小企业需要使用能向 HMRC 报送财务数据的软件,而总得有人去开发这套软件。
第一次翻开 HMRC 的开发者文档时,映入眼帘的全是缩写:MTD、ITSA、OAuth 权限范围(scopes)、防欺诈请求头、义务(obligations)……看起来挺吓人,其中不少细节也确实琐碎。但只要有人按顺序带你走一遍,从零到发出第一个经认证的 API 请求,整个过程比表面看起来要容易得多。
本文正是这样一份指南。我之前为一款 Making Tax Digital 应用构建过 HMRC 集成,接下来我会按自己当时的实操顺序,带你走一遍相同步骤:解释 MTD 是什么、如何创建沙箱应用、OAuth 登录流程的运作机制、那些防欺诈请求头是什么,以及如何发起一次真实的调用。代码示例使用 Node 和 TypeScript,但其中的核心思路适用于任何语言。
目录
Making Tax Digital 到底是什么
Making Tax Digital(MTD)是 HMRC 推行的计划,目的是把税务记录和申报迁移到软件中完成。
以往是在网站上填写一年一度的 Self Assessment 大申报表,而 MTD for Income Tax 要求纳税人保存数字化记录,每季度向 HMRC 提交累计的收入和支出数据,年末再做一次最终申报。
(官方的适用范围和时间表,可以参考 GOV.UK 的 MTD for Income Tax 指南。)
对开发者来说,关键在于 HMRC 没有提供一个“一键报税”的按钮,而是在 Developer Hub 上提供了一组 REST API:查询某人的业务列表、读取申报义务(什么时候该交什么)、提交季度数据、触发税务计算,等等。
你需要把这些 API 串联成一条完整流程。它们背后都有同样的两道门槛:OAuth 2.0 access token 和一组防欺诈请求头。搞定这两点,剩下的就只是普通的 HTTPS 上的 JSON 请求了。
第 1 步:创建 Sandbox 应用
开发时千万不要直接连真实纳税人数据。HMRC 提供了完整的 sandbox 环境 test-api.service.hmrc.gov.uk,它与生产环境(api.service.hmrc.gov.uk)对应,还允许你创建虚拟纳税人来测试。
具体操作流程如下:
在 HMRC Developer Hub 上注册一个免费账号。
创建一个应用,你会拿到 client ID 和 client secret。secret 要像密码一样对待:放进环境变量,千万别写进源代码。
设置 redirect URI,这是用户登录后 HMRC 回跳的地址。本地开发用
http://localhost:3000/auth/hmrc/callback之类的地址就行。之后必须与配置完全一致,一个字符都不能差。
翻译如下:
订阅你的应用所需 API。这是新手最容易漏掉的一步。仅仅列出 API 是不够的,你需要逐个点击每个 API 并单独订阅。如果忘了这一步,调用时会直接返回
403 Forbidden,且没有明显的错误提示。
在代码中,沙箱环境和生产环境唯一的区别就是基础 URL,所以建议把这些 URL 放在配置文件中,而不是硬编码:
const config = {
sandbox: {
baseUrl: 'https://test-api.service.hmrc.gov.uk',
authUrl: 'https://test-api.service.hmrc.gov.uk/oauth/authorize',
tokenUrl: 'https://test-api.service.hmrc.gov.uk/oauth/token',
},
production: {
baseUrl: 'https://api.service.hmrc.gov.uk',
authUrl: 'https://api.service.hmrc.gov.uk/oauth/authorize',
tokenUrl: 'https://api.service.hmrc.gov.uk/oauth/token',
},
};
有一个坑我是吃过亏才学到的:环境变量不要只根据 NODE_ENV 来选择,而是用一个显式的设置。因为你可能需要一个生产部署,但在测试期间仍指向 HMRC 沙箱。如果 URL 只取决于 NODE_ENV,这种组合会强制指向 HMRC 生产环境,并因 client_id is invalid 拒绝你的沙箱凭证。使用一个永远优先的 HMRC_BASE_URL 环境变量可以帮你避免这种混乱。
步骤 2:理解 OAuth Scopes
当你的应用向用户请求访问权限时,你需要指定具体的 scopes(即命名的权限)。对于 MTD 所得税,你需要的是 read:self-assessment 和 write:self-assessment(HMRC 还针对 VAT API 开放了 read:vat 和 write:vat)。HMRC 在其 OAuth 2.0 授权指南中记录了这些权限。你需要用空格将它们连接起来:
const scopes = ['read:self-assessment', 'write:self-assessment'].join(' ');
用户会在 HMRC 的同意页面上看到这些权限列表,所以只申请你实际用到的权限。
步骤 3:OAuth 2.0 授权码流程
这是集成的核心,也是标准的三步 OAuth 交互。你可以把它想象成你的应用、用户和 HMRC 之间传递接力棒的过程。全程有四个关键时刻:将用户发送给 HMRC,HMRC 带着 code 将用户送回,你用 code 交换 tokens,之后再刷新这些 tokens。
3a. 引导用户跳转至 HMRC
构建授权 URL 并将浏览器重定向到该地址。查询参数包含你的客户端 ID(client ID)、所需权限范围(scopes)、重定向 URI(redirect URI)、response_type=code 以及一个 state 值:
function getAuthorizationUrl(state: string): string {
const params = new URLSearchParams({
response_type: 'code',
client_id: hmrcConfig.clientId,
scope: hmrcConfig.scopes,
state: state,
redirect_uri: hmrcConfig.redirectUri,
});
return hmrcConfig.authUrl + '?' + params.toString();
}
这个 state 值至关重要。它是防御跨站请求伪造(CSRF)攻击的关键。生成一个随机且不可猜测的字符串,将其存储在服务器端,附加在请求中,并在用户返回时再次校验。如果返回的 state 值与你之前颁发的不一致,立即拒绝该回调。一种常见做法是生成一个 UUID,并将其与用户 ID 和时间戳一起保存:
const state = uuidv4();
await setStateToken(state, { createdAt: Date.now(), userId });
res.redirect(getAuthorizationUrl(state));
接下来,用户在 HMRC 自身的页面上登录(使用其 Government Gateway 凭据)并授权上述权限范围。你的应用无法接触用户的密码,这正是 OAuth 的核心优势所在。
3b. 处理回调
HMRC 会携带两个查询参数 code 和 state 重定向回你的 redirect URI(若用户拒绝授权,则返回 error)。首先校验 state,然后立即将其删除,使其变为单次有效,防止重放攻击:
const stored = await getStateToken(state);
if (!state || !stored) {
return redirectError('Invalid or expired authorization request');
}
await deleteStateToken(state); // 单次有效:校验后立即删除
// 可选但明智的做法:让过期请求失效(此处设为 10 分钟)。
if (Date.now() - stored.createdAt > 10 * 60 * 1000) {
return redirectError('Authorization request has expired. Please try again.');
}
3c. 用 Code 换取 Token
这个 code 有效期很短,单独没什么用。你需要用它通过服务器对服务器的请求,换取 access token 和 refresh token。具体做法是向 token 端点发送一个 POST 请求,参数为 grant_type=authorization_code,使用表单编码,并附上你的 client secret。正因为有 secret,这一步必须放在后端完成,绝不能在浏览器里进行:
async function exchangeCodeForTokens(code: string) {
const response = await axios.post(
hmrcConfig.tokenUrl,
new URLSearchParams({
grant_type: 'authorization_code',
code,
client_id: hmrcConfig.clientId,
client_secret: hmrcConfig.clientSecret,
redirect_uri: hmrcConfig.redirectUri,
}).toString(),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
);
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in, // access token 剩余有效秒数
};
}
把这些 token 存在服务端并与用户关联,永远不要发给客户端。之后所有 API 调用都要靠 access token 来授权。注意 expires_in:HMRC 的 access token 有效期很短(目前是四小时),所以你需要第 3d 步。
3d. 刷新 Token
access token 过期后,不需要让用户重新登录。你可以用 refresh token 换取一对新 token,参数为 grant_type=refresh_token:
async function refreshAccessToken(refreshToken: string) {
const response = await axios.post(
hmrcConfig.tokenUrl,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: hmrcConfig.clientId,
client_secret: hmrcConfig.clientSecret,
}).toString(),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
);
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
};
}
一个好习惯是:每次调用前先检查已存 token 的过期时间,快过期就主动刷新。HMRC 的 OAuth 文档有完整的生命周期说明。
第 4 步:防欺诈请求头(没人提醒过你的坑)
这里有个反直觉的点:大多数 API 对 Bearer Token 就能满足要求,HMRC 则不然。法律规定,所有 MTD 请求必须附带一组防欺诈请求头。具体来说,就是几十条描述设备、网络路径以及发起请求的软件信息的 Gov-Client-* 和 Gov-Vendor-* 请求头。这能帮助 HMRC 在成千上万的第三方服务商中识别凭证滥用行为。
本指南的目标是建立连接,所以我只做高层介绍:请仔细阅读HMRC 的防欺诈请求头规范,因为规则非常精确,且失败时的表现往往悄无声息。
最关键的决策是连接方式,它由 Gov-Client-Connection-Method 指定。服务器中转的 Web 应用使用 WEB_APP_VIA_SERVER;服务器中转的移动应用使用 MOBILE_APP_VIA_SERVER。这个选择决定了其他请求头哪些是必需的、哪些是禁止的,所以千万别一股脑全发出去。
对初学者来说,真正的好消息是 HMRC 提供了一款测试防欺诈请求头 API。它会检查你的请求,并准确告诉你哪些请求头缺失或格式错误。从第一个提交开始就基于这个校验器开发,能把猜测游戏变成一份检查清单。
想让第一个沙箱调用跑起来,不需要吃透那份规范的所有细节,但还没碰生产数据之前你肯定得把它搞懂。因此,请为此预留充足时间,别拖到最后一刻。
步骤 5:发起第一个已认证请求
现在你有了令牌和请求头,是时候发起一个真实的请求了。除了令牌,每个 MTD 请求还会设置两样东西:一个用于锁定 API 版本的 Accept 请求头,以及前述的防欺诈请求头:
async function request(method, path, accessToken, req, data = null, apiVersion = '2.0') {
const headers = {
Authorization: `Bearer ${accessToken}`,
Accept: `application/vnd.hmrc.${apiVersion}+json`,
...hmrcConfig.getFraudHeaders(req),
};
// 仅在真正存在请求体时才设置 JSON 内容类型(参见陷阱 3)。
if (data !== null && data !== undefined) {
headers['Content-Type'] = 'application/json';
}
const res = await axios({ baseURL: hmrcConfig.baseUrl, method, url: path, headers, data });
return res.data;
}
一个很好的起步请求是“列出此人的业务”,它使用国民保险号(NINO)和 Business Details API。由于该接口为只读操作,用它来验证令牌和请求头是否被接受非常安全:
// GET /individuals/business/details/{nino}/list (Business Details API v2.0)
const businesses = await request(
'GET',
`/individuals/business/details/${nino}/list`,
accessToken,
req,
null,
'2.0',
);
拿到响应后,下一步自然就是查询义务:哪些季度更新需提交以及何时到期。该接口属于较新的 API 版本,正好可以作为引入下面这些常见陷阱的引子:
// GET /obligations/details/{nino}/income-and-expenditure (Obligations API v3.0)
const obligations = await request(
'GET',
`/obligations/details/${nino}/income-and-expenditure`,
accessToken,
req,
null,
'3.0',
);
要在测试沙箱中验证这些接口,你可以使用 HMRC 的“创建测试用户”API 来生成一个虚拟纳税人,它会返回一个 NINO 和 Government Gateway 凭据,供你在 OAuth 流程中登录使用。
常见陷阱:可能让你耗费一个下午
初次使用几乎所有人都会踩到以下几个坑:
1. 务必锁定 API 版本,否则会收到 406 错误。
不同的 HMRC API 运行在不同的版本上:Business Details 使用 2.0,Obligations 使用 3.0,而计算类 API 则使用更晚的版本。版本信息存放在 Accept 请求头中(例如 application/vnd.hmrc.3.0+json)。如果发送了错误版本,或者干脆漏掉了这个请求头,HMRC 将返回 406 Not Acceptable。一旦请求莫名其妙地失败,应首先检查版本号是否正确。
2. state 令牌是一次性的。
先验证它,然后立刻删掉,别做其他任何事。如果让它一直留着,就等于削弱了它本应提供的 CSRF 保护。过期的 token 也要及时作废。
3. 无 body 的 GET 请求不要发送 Content-Type: application/json。
这一点真的让人意外。HMRC 的边缘节点(CloudFront)会拒绝带 JSON Content-Type 却没有 body 的 GET 请求,返回 403 Bad request,这可能一次性搞挂所有读取类接口。只有在确实有请求体要发送时才设置 Content-Type,就像上面的代码片段那样。
4. 把应用订阅到每个 API。
正如第一步提到的,未订阅的 API 会返回 403,看起来像认证问题,其实不是。
5. 重定向 URI 必须完全一致。
多一个尾部斜杠,或者 http 和 https 不匹配,都会在授权页面上报一个莫名其妙的错误。直接复制粘贴,别手动输入。
接下来该做什么
以上就是整个流程的主干:注册 sandbox 应用并订阅,跑一遍 OAuth Authorization Code 流程拿到 token,附上防欺诈请求头,然后发起带版本号的 JSON 调用。
从这里开始,MTD 的其余部分都是类似的操作:提交累计季度数据、触发税务计算、读取结果,最后提交最终申报。每一步都只是另一个接口,需要跨过的仍是那两道你已经越过的关卡。
给新手最中肯的建议是:多用 HMRC 自带的工具——sandbox、测试用户,尤其是防欺诈请求头验证器。把这些都跑出干净的结果,整个集成过程就不再是令人头大的监管迷宫,而更像一个普普通通(只是格外谨慎)的 REST API。
我在做 TapTax,一款面向英国个体经营者的 Making Tax Digital 应用,上面的这些细节正是我在做这个项目时摸索出来的。
关于作者:Solomon Amos 是 TapTax 的创始人,负责搭建其 HMRC Making Tax Digital 集成。过去三年里,他一直担任 HMRC 数字化改造项目的技术架构负责人,拥有工程博士学位,研究方向为机器学习。你可以在 LinkedIn上找到他。