<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Solomon Amos - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Solomon Amos - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 11 Sep 2026 23:36:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/samos/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Connect to HMRC's Making Tax Digital API: A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ If you write software for anyone who pays UK tax, sooner or later you'll need to talk to HMRC. Making Tax Digital (MTD) for Income Tax went live on 6 April 2026, and it's already mandatory for self-em ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-connect-to-hmrc-making-tax-digital-api/</link>
                <guid isPermaLink="false">6aa47c59141bdd8e9f05b331</guid>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ oauth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MTD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tax ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tax return ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HMRC Tax Filing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HMRC ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HMRC Tax Returns ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Solomon Amos ]]>
                </dc:creator>
                <pubDate>Fri, 11 Sep 2026 22:10:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3dd244f7-30e5-484e-8552-3938d1555bf3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you write software for anyone who pays UK tax, sooner or later you'll need to talk to <a href="https://www.gov.uk/government/organisations/hm-revenue-customs">HMRC</a>.</p>
<p>Making Tax Digital (MTD) for Income Tax went live on 6 April 2026, and it's already mandatory for self-employed people and landlords whose qualifying income is over £50,000. That threshold drops to £30,000 in April 2027 and to £20,000 in April 2028, so the population it covers roughly triples over the next two years.</p>
<p>In practice, that means a lot of small businesses now need software that can send their figures to HMRC, and someone has to build that software.</p>
<p>The first time you open HMRC's developer documentation, it's a wall of acronyms: MTD, ITSA, OAuth scopes, fraud prevention headers, and obligations. It looks intimidating, and a lot of it is genuinely fiddly. But the path from zero to your first authenticated API call is more approachable than it looks once someone walks you through it in order.</p>
<p>That's what this guide does. I built the HMRC integration for a Making Tax Digital app, and I'm going to take you through the same steps I took, in the order I took them: what MTD is, how to get a sandbox app, how the OAuth login flow works, what those fraud prevention headers are, and how to make a real call. The code is Node and TypeScript, but the ideas carry over to any language.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5ff319e8638f6a0ef52a6236/ce6e430b-0129-4b55-bb9c-7216036219e4.png" alt="HMRC campaign graphic reading Making Tax Digital is here" style="display: block;" width="600" height="400" loading="lazy">

<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-making-tax-digital-actually-is">What Making Tax Digital Actually is</a></p>
</li>
<li><p><a href="#heading-step-1-create-a-sandbox-application">Step 1: Create a Sandbox Application</a></p>
</li>
<li><p><a href="#heading-step-2-understand-the-oauth-scopes">Step 2: Understand the OAuth Scopes</a></p>
</li>
<li><p><a href="#heading-step-3-the-oauth-20-authorization-code-flow">Step 3: The OAuth 2.0 Authorization Code Flow</a></p>
</li>
<li><p><a href="#heading-step-4-fraud-prevention-headers-the-part-nobody-warns-you-about">Step 4: Fraud Prevention Headers (the Part Nobody Warns You About)</a></p>
</li>
<li><p><a href="#heading-step-5-make-your-first-authenticated-call">Step 5: Make Your First Authenticated Call</a></p>
</li>
<li><p><a href="#heading-common-gotchas-that-will-cost-you-an-afternoon">Common Gotchas that Will Cost You an Afternoon</a></p>
</li>
<li><p><a href="#heading-where-to-go-next">Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-what-making-tax-digital-actually-is">What Making Tax Digital Actually is</h2>
<p>Making Tax Digital is HMRC's programme to move tax record-keeping and filing into software.</p>
<p>Instead of one big annual Self Assessment return typed into a website, MTD for Income Tax asks people to keep digital records and send HMRC a cumulative update of their income and expenses every quarter, then a final declaration at the end of the year.</p>
<p>(For the official scope and timeline, see <a href="https://www.gov.uk/guidance/use-making-tax-digital-for-income-tax">GOV.UK's MTD for Income Tax guidance</a>.)</p>
<img src="https://cdn.hashnode.com/uploads/covers/5ff319e8638f6a0ef52a6236/485240c3-b58e-408c-913a-c9559e48c1ea.png" alt="HMRC campaign graphic reading Making Tax Digital for Income Tax, one year to go" style="display: block;" width="600" height="400" loading="lazy">

<p>For developers, the important part is that HMRC doesn't give you a single "file my taxes" button. It gives you a set of REST APIs on its Developer Hub: one to list a person's businesses, one to read their filing obligations (what's due and when), one to submit the quarterly figures, one to trigger a tax calculation, and so on.</p>
<p>You compose these into a journey. Every one of them sits behind the same two gates: an OAuth 2.0 access token, and a set of fraud prevention headers. Get those two things right and the rest is ordinary JSON over HTTPS.</p>
<h2 id="heading-step-1-create-a-sandbox-application">Step 1: Create a Sandbox Application</h2>
<p>You never start against live taxpayer data. HMRC runs a full sandbox at <code>test-api.service.hmrc.gov.uk</code> that mirrors the production host (<code>api.service.hmrc.gov.uk</code>) and lets you create fake taxpayers to test with.</p>
<p>Here is the practical sequence:</p>
<ol>
<li><p>Register for a free account on the <a href="https://developer.service.hmrc.gov.uk/">HMRC Developer Hub</a>.</p>
</li>
<li><p>Create an application. You get a <strong>client ID</strong> and a <strong>client secret</strong>. Treat the secret like a password: keep it in an environment variable, never in your source.</p>
</li>
<li><p>Set a <strong>redirect URI</strong>. This is the URL HMRC sends the user back to after they log in. For local work something like <code>http://localhost:3000/auth/hmrc/callback</code> is fine. It has to match exactly later, character for character.</p>
</li>
<li><p><strong>Subscribe</strong> your application to the APIs you need. This is the step beginners miss. Listing it isn't enough. You click into each API and subscribe. If you forget, calls come back <code>403 Forbidden</code> with no obvious reason.</p>
</li>
</ol>
<p>In the code, the only thing that differs between sandbox and production is the base URL, so it's worth keeping those in config rather than hard-coding them:</p>
<pre><code class="language-ts">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',
  },
};
</code></pre>
<p>One thing I learned the hard way: pick the environment from an explicit setting, not from <code>NODE_ENV</code>. You'll eventually want a production deployment that still points at the HMRC sandbox during a beta, and if your URLs are keyed off <code>NODE_ENV</code> alone, that combination forces production HMRC and rejects your sandbox credentials with <code>client_id is invalid</code>. A small <code>HMRC_BASE_URL</code> environment variable that always wins saves you that confusion.</p>
<h2 id="heading-step-2-understand-the-oauth-scopes">Step 2: Understand the OAuth Scopes</h2>
<p>When your app asks a user for access, it asks for specific <strong>scopes</strong>, which are named permissions. For MTD Income Tax the ones you want are <code>read:self-assessment</code> and <code>write:self-assessment</code> (HMRC also exposes <code>read:vat</code> and <code>write:vat</code> for the VAT APIs). HMRC documents these in its <a href="https://developer.service.hmrc.gov.uk/api-documentation/docs/authorisation">OAuth 2.0 authorisation guide</a>. You join them with spaces:</p>
<pre><code class="language-ts">const scopes = ['read:self-assessment', 'write:self-assessment'].join(' ');
</code></pre>
<p>The user sees these spelled out on HMRC's consent screen, so only ask for what you actually use.</p>
<h2 id="heading-step-3-the-oauth-20-authorization-code-flow">Step 3: The OAuth 2.0 Authorization Code Flow</h2>
<p>This is the heart of the integration, and it's the standard three-legged OAuth dance. Think of it as your app, the user, and HMRC passing a baton. There are four moments: send the user to HMRC, HMRC sends them back with a code, you swap the code for tokens, and later you refresh those tokens.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5ff319e8638f6a0ef52a6236/7cd5e83f-2e18-4b09-9c2d-e9029ed33fe6.png" alt="Sequence diagram of the OAuth flow between the user, your application and the HMRC API" style="display: block;" width="600" height="400" loading="lazy">

<h3 id="heading-3a-send-the-user-to-hmrc">3a. Send the User to HMRC</h3>
<p>You build an authorization URL and redirect the browser to it. The query string carries your client ID, the scopes, your redirect URI, <code>response_type=code</code>, and a <code>state</code> value:</p>
<pre><code class="language-ts">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();
}
</code></pre>
<p>That <code>state</code> value matters. It's your defense against cross-site request forgery (CSRF). You generate a random, unguessable string, store it server-side, attach it here, and check it again when the user comes back. If the value that returns doesn't match one you issued, you reject the callback. A common pattern is to generate a UUID and stash it with the user's ID and a timestamp:</p>
<pre><code class="language-ts">const state = uuidv4();
await setStateToken(state, { createdAt: Date.now(), userId });
res.redirect(getAuthorizationUrl(state));
</code></pre>
<p>The user now logs in on HMRC's own pages (with their Government Gateway credentials) and approves the scopes. Your app never sees their password, which is the whole point of OAuth.</p>
<h3 id="heading-3b-handle-the-callback">3b. Handle the Callback</h3>
<p>HMRC redirects back to your redirect URI with two query parameters: <code>code</code> and <code>state</code> (or an <code>error</code> if the user declined). First validate the state, then make it single-use by deleting it immediately so it can never be replayed:</p>
<pre><code class="language-ts">const stored = await getStateToken(state);
if (!state || !stored) {
  return redirectError('Invalid or expired authorization request');
}
await deleteStateToken(state); // single-use: delete the moment it validates

// Optional but sensible: expire stale requests (here, 10 minutes).
if (Date.now() - stored.createdAt &gt; 10 * 60 * 1000) {
  return redirectError('Authorization request has expired. Please try again.');
}
</code></pre>
<h3 id="heading-3c-exchange-the-code-for-tokens">3c. Exchange the Code for Tokens</h3>
<p>The <code>code</code> is short-lived and useless on its own. You exchange it, server to server, for an access token and a refresh token. This is a <code>POST</code> to the token endpoint with <code>grant_type=authorization_code</code>, form-encoded, including your client secret. The secret is why this happens on your backend and never in the browser:</p>
<pre><code class="language-ts">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, // seconds until the access token dies
  };
}
</code></pre>
<p>Store these tokens server-side, tied to the user, and never send them to the client. The access token is what authorises every later API call. Note <code>expires_in</code>: HMRC access tokens are short-lived (currently four hours), so you'll need step 3d.</p>
<h3 id="heading-3d-refresh-the-token">3d. Refresh the Token</h3>
<p>When the access token expires, you don't make the user log in again. You use the refresh token to get a fresh pair, with <code>grant_type=refresh_token</code>:</p>
<pre><code class="language-ts">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,
  };
}
</code></pre>
<p>A good habit is to check the stored token's expiry before each call and refresh proactively if it's close to running out. HMRC's <a href="https://developer.service.hmrc.gov.uk/api-documentation/docs/authorisation">OAuth documentation</a> has the full lifecycle.</p>
<h2 id="heading-step-4-fraud-prevention-headers-the-part-nobody-warns-you-about">Step 4: Fraud Prevention Headers (the Part Nobody Warns You About)</h2>
<p>Here's the surprise. Most APIs are happy with a bearer token. HMRC is not. By law, it requires your software to send a set of <strong>fraud prevention headers</strong> on every MTD call: a couple of dozen <code>Gov-Client-*</code> and <code>Gov-Vendor-*</code> headers describing the device, the network path, and the software making the request. They help HMRC detect credential abuse across thousands of third-party vendors.</p>
<p>This guide is about getting connected, so I'll keep this high level: read <a href="https://developer.service.hmrc.gov.uk/guides/fraud-prevention/">HMRC's Fraud Prevention Headers specification</a> carefully, because the rules are precise and the failure modes are quiet.</p>
<p>The single most important decision is the connection method, sent in <code>Gov-Client-Connection-Method</code>. A server-mediated web app uses <code>WEB_APP_VIA_SERVER</code>; a server-mediated mobile app uses <code>MOBILE_APP_VIA_SERVER</code>. That choice decides which other headers are required and which are forbidden, so don't just emit everything.</p>
<p>The genuinely good news for beginners is that HMRC gives you a <a href="https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/txm-fph-validator-api">Test Fraud Prevention Headers API</a> that inspects your request and tells you exactly which headers are missing or malformed. Build against that validator from your first commit and you turn a guessing game into a checklist.</p>
<p>You don't need the full depth of that spec to get your first sandbox call working, but you will need it before you go anywhere near production data, so budget real time for it rather than leaving it to the end.</p>
<h2 id="heading-step-5-make-your-first-authenticated-call">Step 5: Make Your First Authenticated Call</h2>
<p>Now you have a token and your headers. Time for a real request. Every MTD call sets two things on top of the token: an <code>Accept</code> header that pins the API version, and the fraud headers:</p>
<pre><code class="language-ts">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),
  };
  // Only set a JSON Content-Type when there is actually a body (see gotcha 3).
  if (data !== null &amp;&amp; data !== undefined) {
    headers['Content-Type'] = 'application/json';
  }
  const res = await axios({ baseURL: hmrcConfig.baseUrl, method, url: path, headers, data });
  return res.data;
}
</code></pre>
<p>A great first call is "list this person's businesses", which uses their National Insurance number (NINO) and the Business Details API. It's read-only, so it's a safe way to confirm your token and headers are accepted:</p>
<pre><code class="language-ts">// 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',
);
</code></pre>
<p>Once that returns, the natural next call is obligations: what quarterly updates are due and when. This one is on a newer API version, which is the perfect lead-in to the gotchas:</p>
<pre><code class="language-ts">// 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',
);
</code></pre>
<p>To test these in the sandbox, you create a fake taxpayer using HMRC's "Create Test User" API, which hands you a NINO and Government Gateway credentials to log in with during the OAuth flow.</p>
<h2 id="heading-common-gotchas-that-will-cost-you-an-afternoon">Common Gotchas that Will Cost You an Afternoon</h2>
<p>A few traps catch nearly everyone the first time:</p>
<h3 id="heading-1-pin-the-api-version-or-get-a-406">1. Pin the API Version, or Get a 406.</h3>
<p>Different HMRC APIs live at different versions: Business Details is on <code>2.0</code>, Obligations on <code>3.0</code>, the calculation APIs on later versions again. The version lives in the <code>Accept</code> header (<code>application/vnd.hmrc.3.0+json</code>). Send the wrong version, or forget the header, and HMRC replies <code>406 Not Acceptable</code>. When a call mysteriously fails, check the version first.</p>
<h3 id="heading-2-the-state-token-is-single-use">2. The <code>state</code> Token is Single-Use.</h3>
<p>Validate it, then delete it immediately, before you do anything else. If you leave it lying around, you've weakened the CSRF protection it exists to provide. Expire stale ones too.</p>
<h3 id="heading-3-dont-send-content-type-applicationjson-on-a-bodyless-get">3. Don't send <code>Content-Type: application/json</code> on a bodyless GET.</h3>
<p>This one is genuinely surprising. HMRC's edge (CloudFront) rejects a GET that carries a JSON <code>Content-Type</code> with no body as a <code>403 Bad request</code>, which can break every read endpoint at once. Only set <code>Content-Type</code> when there's an actual body to send, as in the snippet above.</p>
<h3 id="heading-4-subscribe-your-app-to-each-api">4. Subscribe Your App to Each API.</h3>
<p>As mentioned in step 1, an unsubscribed API returns <code>403</code>, which reads like an auth problem but is not.</p>
<h3 id="heading-5-the-redirect-uri-must-match-exactly">5. The Redirect URI Must Match Exactly.</h3>
<p>A trailing slash or <code>http</code> versus <code>https</code> mismatch produces an opaque error on the consent screen. Copy it, don't retype it.</p>
<h2 id="heading-where-to-go-next">Where to Go Next</h2>
<p>That's the whole spine: register a sandbox app and subscribe it, run the OAuth Authorization Code flow to get tokens, attach fraud prevention headers, and make versioned JSON calls.</p>
<p>From here, the rest of MTD is more of the same: submit the cumulative quarterly figures, trigger a tax calculation, read the result, and eventually submit the final declaration. Each is another endpoint with the same two gates you've already cleared.</p>
<p>The kindest thing I can tell a beginner is to lean on HMRC's own tooling: the sandbox, the test users, and especially the fraud-header validator. Drive each of those to a clean result and the integration stops feeling like a regulatory maze and starts behaving like an ordinary, if unusually careful, REST API.</p>
<p>I work on <a href="https://taptax.co.uk?utm_source=freecodecamp&amp;utm_medium=editorial&amp;utm_campaign=ebas_fcc_mtdapi">TapTax</a>, a Making Tax Digital app for UK sole traders, which is where I picked up the details above.</p>
<p><em>About the author: Solomon Amos is the founder of TapTax and built its HMRC Making Tax Digital integration. He has spent the last three years as a lead technical architect on HMRC digital modernisation programmes, and holds a PhD in engineering, with research in machine learning. You can find him on</em> <a href="https://www.linkedin.com/in/solomonudoh/"><em>LinkedIn</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
