missing_credentials
missing_credentials means the API did not find a credential it could use. Either the request carried no API key or signature, or what it carried was not in a form the API accepts. The API rejects the request before it does anything else.
| Field | Value |
|---|---|
| HTTP status | 401 Unauthorized |
error.type |
authentication_error |
error.code |
missing_credentials |
retryable |
false |
| Credit used | No |
It is listed with the other codes in the error code reference.
Example response
{ "error": { "type": "authentication_error", "code": "missing_credentials", "message": "No valid API key or signature provided", "retryable": false, "docs": "https://docs.renderscreenshot.com/errors/missing_credentials" }, "request_id": "req_0a6f3e2d91c7b548" }
How the API looks for a credential
The API tries three places, in this order, and uses the first one that works:
- The header
Authorization: Bearer <key>. The word must beBearerwith a capital B, followed by exactly one space. - The query parameter
api_key=<key>. - A signed URL:
key_id,expiresandsignaturetogether (see signed URLs).
A key is used only if it has the shape rs_live_ (or another prefix) followed by exactly 32 letters and digits. Anything else, such as a key with a space or newline at the end, is skipped as if it were absent.
The API can also use this code when the value has the right shape but is not an active key. So handle missing_credentials, invalidapikey and revoked_key the same way in your code.
Common causes
| Cause | How to recognize it |
|---|---|
| The environment variable is not set in this process | Works in your shell, fails in cron, CI, a container or a background worker |
| Trailing newline | The key comes from a file, for example a Docker or Kubernetes secret written with echo |
| Quotes are part of the value | .env file loaded by a tool that does not remove quotes |
Authorization: bearer ..., Token ... or Bearer: ... |
Header written by hand |
The value already contains Bearer and your code adds it again |
The header becomes Bearer Bearer rs_live_... |
| A proxy or redirect removes the header | Calls through an internal gateway fail, direct calls work. Many HTTP clients drop Authorization when they follow a redirect to another host |
A signed URL without key_id, or with an rs_live_ key as key_id |
Only signed URLs fail |
| Key copied from the masked value in the dashboard | The full key is shown only once, when you create it |
Find the problem without printing the key
Run this check in the same environment as the failing code: the same container, CI job or worker. It reports what is wrong with the value, but never prints the key:
// check-key.mjs (Node 22): find out why a key is not accepted, without printing the key. const name = process.argv[2] ?? 'RS_API_KEY'; const key = process.env[name]; const problems = []; if (key === undefined) problems.push(`${name} is not set in this process`); else { if (key !== key.trim()) problems.push('leading or trailing whitespace or newline'); if (/^["']|["']$/.test(key.trim())) problems.push('quote characters are part of the value'); if (/^bearer\s/i.test(key.trim())) problems.push('the value includes the word "Bearer"'); const bare = key.trim().replace(/^["']|["']$/g, '').replace(/^bearer\s+/i, ''); if (!/^rs_(live|pub|secret)_/.test(bare)) problems.push('prefix is not rs_live_, rs_pub_ or rs_secret_'); else if (!/^rs_[a-z]+_[A-Za-z0-9]{32}$/.test(bare)) problems.push(`unexpected length or characters after the prefix (${bare.length} chars total)`); if (/^rs_(pub|secret)_/.test(bare)) problems.push('rs_pub_ and rs_secret_ keys are for signed URLs; use an rs_live_ key here'); } if (problems.length) { console.error(`${name}: ${problems.join('; ')}`); process.exitCode = 1; } else { console.log(`${name}: format OK (${key.slice(0, 8)}...${key.slice(-4)})`); }
If the format is OK and the request still fails, compare the last four characters with the keys on the API Keys page of the dashboard. The key can be revoked, or it can belong to another account. If you have no key yet, create a free account to get one.
Handle it in code
Treat every authentication code as a configuration error. In this Python version, the worker stops and a person gets the request ID:
# auth_errors.py (Python 3.9+, requests) import os import requests API = os.environ.get("RS_BASE_URL", "https://api.renderscreenshot.com") AUTH_CODES = {"missing_credentials", "invalid_api_key", "revoked_key"} class CredentialsError(RuntimeError): """Configuration problem. Stop the worker and alert a person; never retry.""" def screenshot(url): key = os.environ.get("RS_API_KEY", "").strip() # strip the newline from secret files if not key: raise CredentialsError("RS_API_KEY is empty in this process") res = requests.post( f"{API}/v1/screenshot", headers={"Authorization": f"Bearer {key}"}, # exactly "Bearer", one space json={"url": url}, timeout=(10, 90), ) if res.status_code == 401: body = res.json() if body["error"]["code"] in AUTH_CODES: raise CredentialsError(f"{body['error']['code']}: {body['error']['message']} ({body['request_id']})") res.raise_for_status() return res.content
Tested on 2026-10-15 with Node 22.18, and with Python 3.14 and requests 2.32.3 against a local mock that returns the API's error envelope. The key check found a trailing newline, quotes, a Bearer prefix, a short key and an unset variable. The Python function stripped the newline and succeeded.
Related
- Bearer token authentication
- API keys: key types and where to create them.
- invalid_signature: the signed URL arrived complete, but the signature does not match.