<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vege Su</title>
    <description>The latest articles on DEV Community by Vege Su (@vegesu).</description>
    <link>https://dev.to/vegesu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4232967%2Ffe460480-042f-4ba6-b808-57fe4e71cd9b.png</url>
      <title>DEV Community: Vege Su</title>
      <link>https://dev.to/vegesu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vegesu"/>
    <language>en</language>
    <item>
      <title>I got a 400 from the official MyInvois Postman collection — here is the fix</title>
      <dc:creator>Vege Su</dc:creator>
      <pubDate>Sun, 08 Nov 2026 01:28:15 +0000</pubDate>
      <link>https://dev.to/vegesu/i-got-a-400-from-the-official-myinvois-postman-collection-here-is-the-fix-ahd</link>
      <guid>https://dev.to/vegesu/i-got-a-400-from-the-official-myinvois-postman-collection-here-is-the-fix-ahd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;LHDN SANDBOX PLAYBOOK — VERIFIED OCT 2026&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're building against Malaysia's LHDN MyInvois API, you probably did exactly what the docs said: imported the official Postman collection, registered a sandbox app, and hit &lt;code&gt;POST /connect/token&lt;/code&gt; with Basic Auth as shipped.&lt;/p&gt;

&lt;p&gt;You got this back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt; &lt;span class="ne"&gt;Bad Request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No body. No hint. The auth method itself is the problem: &lt;strong&gt;the token endpoint rejects Basic Auth, no matter which credentials you send.&lt;/strong&gt; The fix is embarrassingly simple — credentials go in the POST body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://preprod-api.myinvois.hasil.gov.my/connect/token'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'grant_type=client_credentials'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'client_id=&amp;lt;YOUR_CLIENT_ID&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'client_secret=&amp;lt;YOUR_CLIENT_SECRET&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'scope=InvoicingAPI'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns &lt;strong&gt;200&lt;/strong&gt; with an RS256 JWT (&lt;code&gt;expires_in: 3600&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The environment matrix nobody draws
&lt;/h2&gt;

&lt;p&gt;"MyInvois sandbox" (preprod) is not one host. Credentials are not portable between these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you're doing&lt;/th&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Portal login / app registration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preprod-mytax.hasil.gov.my&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taxpayer profile registration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preprod-profile.hasil.gov.my&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API calls&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preprod-api.myinvois.hasil.gov.my&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token (intermediary flow)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preprod-identity.hasil.gov.my&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The trap: the register button on the preprod landing page silently sends you to the &lt;strong&gt;production&lt;/strong&gt; profile page. Register there, call the preprod API, and you'll collect &lt;code&gt;403 invalid_client&lt;/code&gt; all day. The credentials are correct. The environment isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second 400 that isn't your fault
&lt;/h2&gt;

&lt;p&gt;The docs tell you to call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1.0/taxpayer/validate/{TIN}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;400 — IdType/IdValue required&lt;/code&gt;. Those required query params are documented &lt;strong&gt;only in a FAQ entry&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1.0/taxpayer/validate/{TIN}?idType=NRIC&amp;amp;idValue=&amp;lt;IC_NUMBER&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verified behavior: correct TIN + IC pair → &lt;strong&gt;200 with an empty body&lt;/strong&gt; (empty = valid, don't panic). Mismatched pair → 404. Missing params → 400.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three more evening-eaters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PIN email never arrives&lt;/strong&gt; (Gmail, web signup). Use the MyTax &lt;strong&gt;app&lt;/strong&gt; e-KYC instead — passport + selfie, PIN issued instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Individual taxpayer form forces SST / Tourism Tax fields&lt;/strong&gt; even with no SST obligation. No clean fix found — I got through with placeholder values; verify this one yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;preprod occasionally stalls ~20s&lt;/strong&gt; or dies with a TLS broken pipe (&lt;code&gt;curl (35)&lt;/code&gt;). Build retries into your client (&lt;code&gt;--retry 2 --retry-all-errors&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's still genuinely undocumented: the intermediary flow
&lt;/h2&gt;

&lt;p&gt;The portal lets you register an app as an &lt;strong&gt;Intermediary&lt;/strong&gt; — one app submitting invoices on behalf of other taxpayers (&lt;code&gt;onbehalfof&lt;/code&gt; on token issuance). The actual mechanics (claim decoding, the taxpayer consent flow, what the portal allows vs. what the API enforces) are scattered across three doc pages and one FAQ answer. If you're building multi-tenant — a SaaS, a service bureau, an accounting integration — that's where your week goes.&lt;/p&gt;

&lt;p&gt;I'm packaging the verified request/response templates for the whole flow, an error-code field guide, and the intermediary deep-dive into a developer toolkit. I'll announce it here when it ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you'd rather not spend an evening rediscovering these 400s, get notified at launch:&lt;/strong&gt; &lt;a href="https://myinvois-toolkit.carrd.co" rel="noopener noreferrer"&gt;https://myinvois-toolkit.carrd.co&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Everything above was verified against LHDN preprod in October 2026. Found a cleaner fix? Comment below — I genuinely want to know.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>myinvois</category>
      <category>api</category>
      <category>backend</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
