RPC error HTTP 401: Missing or unknown API key
Last updated:
At a glance
| Property | Value |
|---|---|
| Applies to | Every network, HTTP and WebSocket |
| HTTP status | 401 |
| error values | unauthorized, trial_expired, subscription_expired |
| Response headers | X-Triport-Error, X-Triport-Error-Class: tier |
| Reaches a node | No: decided at the gateway |
| WebSocket equivalent | Close 4001, or 4030 for an ended trial |
| Retryable | No, not until the key or the plan changes |
Cause
Authentication runs before anything else, so a 401 says nothing about your method or the chain. The most common version we see is a request that carried no key at all: a deployment missing its environment variable, a client that dropped the header on a redirect, or a WebSocket library that does not send custom headers. A key that is present but not recognised gets the same unauthorized body, so the response alone cannot tell you which of the two you hit.
Solution
- Read the
errorfield first.unauthorizedis about the credential;trial_expiredandsubscription_expiredare about the account, and no change to your request will fix them. - Print what actually leaves the process: the header name, and the first few characters of the value. An empty environment variable sends a blank key, which fails like no key at all.
- Send the key once, in one place: the
x-tokenheader,Authorization: Bearer <key>, the?api-key=query parameter, or the path formhttps://triport.io/r/<API_KEY>/sol. - Copy the key again from the console at /app/keys. A revoked key stays
unauthorizedhowever it is sent. - On
trial_expiredorsubscription_expired, choose or renew a plan. Keys are kept, and requests work again once the plan is active.
Which 401 is this?
All three share the status, so branch on the error string. The message text may be reworded; the code is part of the contract (error envelope).
| error | Meaning | Extra fields | What fixes it |
|---|---|---|---|
| unauthorized | No key, or the key is unknown or revoked | keys_url | Send a valid key |
| trial_expired | The 7-day trial ended | expired_at, upgrade_url | Choose a plan |
| subscription_expired | A paid plan lapsed | expired_at, renew_url | Renew |
401 or 403?
A 401 is about who you are; a 403 is about what your plan may call. If you upgraded and still see 401, the plan is not the problem: the request is not carrying that account's key. If you see 403 tier_insufficient, authentication already worked, and looking at the key will not help.
Handle it in code
Treat a 401 as fatal for the worker and raise an alert. Retry loops are the worst response: they turn a configuration mistake into a steady stream of refused requests, and hide it behind your backoff.
const res = await fetch("https://triport.io/sol", { method: "POST", headers, body });
if (res.status === 401) {
const { error } = await res.json();
// unauthorized | trial_expired | subscription_expired: no retry helps
throw new Error(`Triport auth refused: ${error}`);
}Example
{
"error": "unauthorized",
"message": "Missing or unknown API key. Pass your key as ?api-key=<key> or the header Authorization: Bearer <key>. Create or copy a key at https://triport.io/app/keys",
"keys_url": "https://triport.io/app/keys"
}FAQ
- Does a 401 count against my rate limit?
- Without a valid key there is no plan to count it against, so backing off does not help. Fix the key: retrying the same request cannot succeed.
- Are my keys deleted when a trial ends?
- No. The trial_expired and subscription_expired messages both say your API keys are kept, and requests work again once a plan is active.
- Why do I get 401 on WebSocket but not on HTTP?
- Browsers cannot set custom headers on a WebSocket upgrade. Use the ?api-key= query parameter or the /r/<API_KEY>/ path form for that connection.