ERRORS
Error codes
Errors use standard HTTP status codes plus a stable code you can switch on in your app. Messages are written for humans and name the problem parameter.
Error format
Every error response has the same four fields:
400 Bad Request
{
"status": "error",
"code": "parameterInvalid",
"message": "Parameter 'from' must be an ISO-8601 date (YYYY-MM-DD) or datetime (e.g. 2026-01-31T13:45:00Z); URL-encode '+' in offsets as %2B (got 'yesterday').",
"requestId": "01JBDX4Q8Z6M3N2P1R0S9T8V7W"
}code- Stable, machine-readable identifier. Branch on this, not on the message text.
message- Plain-English explanation. May change wording over time.
requestId- Unique ID for the request, also sent in the
X-Request-Idheader on every response.
Validation errors are reported one at a time, so fix the reported parameter and try again.
Error code reference
| Status | Code | Retry? | How to fix |
|---|---|---|---|
| 400 | parameterInvalid | No | Fix the parameter named in message (unknown name, bad value or repeated parameter). |
| 401 | apiKeyMissing | No | Send your key. See authentication. |
| 401 | apiKeyInvalid | No | Check the key for typos or missing characters. |
| 404 | notFound | No | The article or resource does not exist (or is no longer available). |
| 404 | routeNotFound | No | Check the path, including the /v1 prefix. |
| 405 | methodNotAllowed | No | Use GET (or HEAD). |
| 429 | rateLimited | Yes | Wait for the Retry-After seconds, then retry. |
| 402 | insufficientCredits | No | Sharedstack could not reserve the maximum charge for this article request (`insufficientCredits`). `X-Credits-Remaining` is `unknown` if Stack does not return a balance. |
| 500 | internalError | Yes | Retry with backoff. If it persists, contact support with the requestId. |
| 503 | serviceUnavailable | Yes | Retry with backoff and check API status. |
Retrying safely
Retry only 429 rateLimited and 5xx errors. Wait for Retry-After when it's present, otherwise back off exponentially with some random jitter. Don't retry quotaExceeded: it won't succeed until your quota resets. The SDKs do all of this for you.
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
export async function getWithRetry(url, { attempts = 4 } = {}) {
for (let attempt = 0; ; attempt++) {
const response = await fetch(url, { headers: { "X-Api-Key": process.env.NEWSSEARCHAPI_API_KEY } });
const body = await response.json();
if (response.ok) return body;
const giveUp = !RETRYABLE.has(response.status) || body.code === "quotaExceeded" || attempt + 1 >= attempts;
if (giveUp) throw new Error(`${body.code}: ${body.message} (requestId ${body.requestId})`);
const retryAfter = Number(response.headers.get("Retry-After"));
const delay = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}import os, random, time
import requests
RETRYABLE = {429, 500, 502, 503, 504}
def get_with_retry(url, attempts=4):
for attempt in range(attempts):
response = requests.get(url, headers={"X-Api-Key": os.environ["NEWSSEARCHAPI_API_KEY"]}, timeout=10)
body = response.json()
if response.ok:
return body
give_up = response.status_code not in RETRYABLE or body["code"] == "quotaExceeded" or attempt + 1 == attempts
if give_up:
raise RuntimeError(f"{body['code']}: {body['message']} (requestId {body['requestId']})")
retry_after = float(response.headers.get("Retry-After", 0))
time.sleep(retry_after or 2 ** attempt * 0.5 + random.random() * 0.25)Getting help
If an error doesn't make sense, email developer support with the requestId. Your console's request logs also show every call, its status and its request ID.
Stuck or found a mistake?Email developer support