LLOQU
ProductPricingBlogDocsLogin

On this page

What does Twilio error 31201 mean?Are you looking at the authorization 31201 or the microphone one?How do I decode the token and check every claim?Why a well-formed token still throws 31201Which 312xx code should you actually be debugging?A token generator that passes every checkFAQMake the wrong-credentials mistakes impossible
All articles
TwilioVoice SDKErrors

Twilio Error 31201: Fix the Unknown Authorization Error

Twilio error 31201 is an unknown authorization error: Twilio rejected your Access Token without saying why. How to decode it and fix the seven causes.

Alloqui TeamAug 13, 20269 min read
On this page
What does Twilio error 31201 mean?Are you looking at the authorization 31201 or the microphone one?How do I decode the token and check every claim?Why a well-formed token still throws 31201Which 312xx code should you actually be debugging?A token generator that passes every checkFAQMake the wrong-credentials mistakes impossible
Back to all articles
LLOQUAlloqui

Product

  • Features
  • Pricing
  • Blog

Developers

  • Documentation
  • @alloqui/dialer on npm

Company

  • Contact

Legal

  • Privacy
  • Terms

© 2026 Alloqui

Twilio error 31201 ("Unknown authorization error") means Twilio's signaling gateway rejected your Access Token and could not describe the rejection with a more specific code. The 312xx range has a dedicated code for nearly every token failure — bad signature, expired, malformed, dead account — so landing on 31201 usually means the rejection happened before those checks ran, because Twilio could not resolve the credentials your token points at. This guide shows you how to decode the token, verify it claim by claim, and fix the seven configurations that produce a well-formed token Twilio still refuses.

What does Twilio error 31201 mean?

31201 opens Twilio's 312xx authorization range for the Voice SDK, where the error codes reference defines it as "Unknown authorization error." Every other code in that range names its failure: 31202 is a signature that failed validation, 31203 an invalid or inactive account, 31204 a malformed JWT, 31205 an expired one, 31207 a TTL longer than Twilio permits. Each of those fires because Twilio got far enough to run that specific check and the check failed.

31201 is the residual bucket. The token was rejected, and none of the named checks is what rejected it — which points at the credentials the token references rather than the token's own structure. An API Key SID Twilio cannot find, a key in the wrong account, the wrong region, or the wrong key class all land here, because Twilio never gets a secret to validate the signature against and therefore never reaches 31202.

That vagueness is why the error has stayed mysterious for a decade. The Stack Overflow thread that still ranks for this code, "Receiving Twilio 31201 error, despite having well-formed token", was posted in June 2016 and has zero answers.

Are you looking at the authorization 31201 or the microphone one?

Two unrelated Twilio errors carry the number 31201, and mixing them up sends you debugging the wrong layer for an afternoon.

  • The Voice SDK error codes page lists 31201 as Unknown authorization error — the gateway rejecting your token during Device registration or at connect time. That is the one this post covers.
  • The Error and Warning Dictionary entry for 31201 documents a media failure: Error occurred while accessing microphone. It fires in legacy Twilio Client (twilio.js 1.x) when the browser has already granted permission but getUserMedia cannot acquire an input stream.

Version tells them apart. Twilio renumbered the microphone failure to 31402 in Voice JS SDK 2.x, so on @twilio/voice-sdk v2 a 31201 is always authorization. On the older 1.x Twilio.Device, check the timing: an error at device.setup() or registration is the auth code, while one thrown at connect() or accept() is the microphone code — and its fix is a media fix (a stale deviceId passed to setInputDevice, over-tight audio constraints, or a headset another app is holding).

How do I decode the token and check every claim?

Take the exact string your server handed the browser, from the network response rather than from your source file. Half the 31201 reports we have seen come from a server that is generating a correct token and a client that is sending a different one — a cached copy, a stale env var, an old build.

Paste it into jwt.io and read the decoded payload, or dump it locally without sending your token to a website:

node -e "const p=process.argv[1].split('.');for(const s of p.slice(0,2))console.log(JSON.stringify(JSON.parse(Buffer.from(s,'base64url')),null,2))" "$TOKEN"

That prints the header and the payload. Check them against this list:

FieldMust beFrequent wrong value
alg (header)HS256HS512, or none from a hand-rolled signer
cty (header)twilio-fpa;v=1absent, when the JWT was built by a generic library
issYour API Key SID, starting SKAn AC Account SID
subThe Account SID that owns the key, starting ACThe parent account when the key is on a subaccount
expA Unix timestamp in the futureMilliseconds instead of seconds
grants.identityNon-empty, alphanumeric and underscores onlyAn email address
grants.voicePresent, with outgoing.application_sidMissing entirely

An iss that starts with AC is the single highest-yield thing to look for. It means the token was signed as though your Account SID were an API Key, and Twilio has nothing to look up.

If exp is in the past, stop here — that is 31205, and the fix is a refresh strategy rather than a credentials audit. We cover it in the guide to Twilio JWT token expiry.

Why a well-formed token still throws 31201

Every cause below produces a JWT that decodes cleanly and looks right in jwt.io. That is what makes 31201 frustrating: the token passes inspection and Twilio still refuses it.

You signed with the Auth Token instead of an API Key secret

The AccessToken constructor takes (accountSid, apiKeySid, apiKeySecret, options). The third argument is the 32-character secret Twilio shows exactly once, when the key is created — never your account's Auth Token.

Substituting the Auth Token while keeping a real SK SID in iss normally produces 31202, since Twilio finds the key and the signature fails. The 31201 version is the sloppier variant: teams pass the Account SID as the second argument too, so iss becomes an AC SID. Now there is no key to resolve, no secret to check, and no specific failure to report.

The API Key SID is wrong, deleted, or rotated

Deleting an API Key in the Console invalidates every token signed with it immediately. Rotating a secret without redeploying the server that holds it has the same effect. The usual shape of this bug is an environment variable that was set once, during a spike, pointing at a key somebody cleaned up months later.

Take the SK SID from your decoded iss and search for it in Console → Account → API keys & tokens. If it is not listed there, you have found the problem, and no amount of token-generation debugging will help.

The key is a Restricted API Key

Twilio has three key classes: Main (full access to all API resources), Standard (everything except the Accounts and Keys resources), and Restricted (fine-grained, per-resource permissions).

Main and Standard both mint Access Tokens. Restricted keys do not — Twilio's documentation for error 31204 states it directly: "Use Main or Standard API Keys to create Access Tokens, as Restricted API Keys aren't supported for Access Token generation" (as of August 2026). Security reviews walk into this one. A team replaces a working Standard key with a Restricted key scoped tightly to Voice, deploys, and every browser client stops authorizing. No Voice permission you can add to a Restricted key changes the outcome.

The key and the sub account belong to different accounts

API Keys live inside one account. If you run subaccounts — a common pattern for per-tenant isolation — the key you sign with and the Account SID in sub have to be the same account. A parent-account key with a subaccount sub (or the reverse) is an unresolvable pairing, and Twilio reports the generic authorization failure rather than naming the mismatch.

The identity breaks Twilio's character rule

Twilio's Access Tokens documentation is narrow here: "Voice tokens may only contain alpha-numeric and underscore characters." Email addresses are the standard trap, since [email protected] contains three forbidden characters and is the most natural user identifier to reach for. Hyphenated UUIDs fail too.

An empty identity fails for a different reason: Twilio's 31301 registration error docs list "The Access Token does not include a non-empty identity" as a cause. Hash or transliterate — alice_example_com, or an opaque internal user ID — and keep the mapping on your server.

The VoiceGrant is missing, or points at the wrong TwiML App

Building the grant and forgetting token.addGrant(voiceGrant) is a one-line omission that produces a perfectly valid JWT with no voice permissions in it. Twilio names this cause explicitly in the 31301 docs: "The Access Token does not include a VoiceGrant, or the API key and secret used to generate the token are incorrect."

An outgoingApplicationSid pointing at a TwiML App that was deleted, or that lives in a different account than sub, has the same effect — the grant exists but references nothing resolvable. Confirm the AP SID in Console → Voice → TwiML Apps under the same account as your key.

The token targets one Twilio Region and your resources live in another

Access Tokens carry a target region, defaulting to us1 when you do not set one. Twilio's guidance for non-US regions is unambiguous: "The Twilio resources referred to by the Access Token (the API Key, TwiML Application, and Push Credential) must exist in the Twilio Region specified in the Access Token."

Credentials are themselves region-scoped — an API Key created while the Console's region selector was set to IE1 does not exist in US1 at all. An Irish key inside a default-region token therefore hands Twilio an SK SID it genuinely cannot find, which is 31201 by the same mechanism as a deleted key. Pass region: 'ie1' in the AccessToken options (the region parameter is currently Voice-only) and create the API Key and TwiML App in that region. Setting edge on the client Device changes only the network entry point and will not fix a region mismatch in the token.

Which 312xx code should you actually be debugging?

If the SDK reported something other than 31201, fix that code instead — the specific codes are far cheaper to diagnose.

CodeWhat it meansWhere the fix lives
31201Unknown authorization errorCredentials the token references — this page
31202JWT signature validation failedWrong API Key secret, or alg other than HS256
31203Invalid or inactive accountAccount suspended, closed, or unfunded — check the Console
31204Invalid JWT tokenMalformed JWT, or missing identity/VoiceGrant
31205JWT token has expiredRefresh on the SDK's tokenWillExpire event
31207Expiration interval is too longttl above Twilio's 24-hour maximum

Two neighbours worth knowing: a bare error 31000 can hide an authorization failure underneath, so check twilioError.originalError before assuming it is unrelated. And once the token authorizes but the device still will not come up, you have left token territory for registration — that is covered in Twilio device offline.

A token generator that passes every check

Here is the server-side shape that satisfies all seven causes above, in Node:

const AccessToken = require('twilio').jwt.AccessToken;
const VoiceGrant = AccessToken.VoiceGrant;

const voiceGrant = new VoiceGrant({
  outgoingApplicationSid: process.env.TWILIO_TWIML_APP_SID, // AP..., same account as sub
  incomingAllow: true,
});

const token = new AccessToken(
  process.env.TWILIO_ACCOUNT_SID,   // AC... → sub
  process.env.TWILIO_API_KEY_SID,   // SK... → iss (Main or Standard key, never Restricted)
  process.env.TWILIO_API_KEY_SECRET,// the 32-char secret, not the Auth Token
  {
    identity: userId.replace(/[^a-zA-Z0-9_]/g, '_'), // alphanumeric + underscore only
    ttl: 3600,                                        // 24h max; short is safer
  }
);

token.addGrant(voiceGrant); // the line people forget
res.json({ token: token.toJwt() });

Log the decoded payload of a freshly minted token once in staging and eyeball it against the claim table above. It takes ten seconds and rules out five of the seven causes.

FAQ

Where do I find my Twilio API Key SID and Secret? In the Console under Account → API keys & tokens, where you can create a Standard or Main key. The secret is displayed only at creation time and cannot be retrieved afterwards — if you have lost it, create a new key and update your server, since rotating invalidates existing tokens instantly.

Does error 31201 happen on iOS and Android too? Yes. The token path is identical across the JavaScript, iOS, and Android Voice SDKs, and the long-running twilio/voice-quickstart-android issue #166 tracks exactly this. On mobile it typically surfaces during push registration rather than at connect time, which makes the missing-VoiceGrant and empty-identity causes the ones to check first.

Is 31201 caused by a Twilio outage or a suspended account? Neither, usually. A suspended, closed, or unfunded account produces 31203 specifically, not 31201, and platform incidents show up at status.twilio.com, which rules that out in seconds. A fresh 31201 on a deploy that changed no Twilio configuration is almost always a changed environment variable on your own server.

How long should a Twilio Voice access token last? Twilio caps token age at 24 hours and the helper libraries default to one hour. Ask for the shortest TTL your session length tolerates, then refresh in the browser on the tokenWillExpire event rather than lengthening it. A 24-hour token that leaks is a 24-hour problem.

What is the difference between 31201 and 31002? Sequence. 31201 means the token was never accepted, so no call was attempted. 31002 Connection Declined means authorization succeeded and Twilio then refused the call itself — trial-account restrictions, geo permissions, or a TwiML App problem. Different layers, different fixes.

Make the wrong-credentials mistakes impossible

Every cause above is a decision your token endpoint has to get right and then keep right forever: which key class, which account, which region, which grant, which identity characters. Alloqui takes those decisions off your desk. You connect your own Twilio account, Alloqui creates the API Key and constructs the VoiceGrant server-side from your credentials, and the <Dialer /> component receives a token that is correct by construction. Wrong key type and malformed grant stop being failure modes you can reach.