Errors
Error codes and retry guidance
Stealth mode will no longer be available from 1 November 2026. Search and Extract are unchanged.
Errors are never charged. Credits are deducted for searches that return results, and for searches whose query genuinely has no results (confirmed across multiple search sources). An empty response we could not confirm as genuinely empty is never charged; see When are empty results charged?. Your organization's own repeats of the same request within 5 minutes are free.
HTTP Status Codes
| Status | Meaning | Retryable |
|---|---|---|
| 400 | Bad request | No |
| 401 | Invalid or missing API key | No |
| 402 | Insufficient credits | No |
| 403 | Stealth mode is not included in your plan | No |
| 410 | Stealth mode has ended (from 1 November 2026) | No |
| 429 | Rate limit exceeded | Yes |
| 500 | Internal server error | Yes |
| 502 | Extraction service error (not charged) | Yes |
| 503 | Temporarily unavailable | Yes |
| 504 | Extract timed out (not charged) | Yes |
400 Bad Request
Returned when the request is malformed or missing required parameters.
Common causes:
- Missing
qparameter on the Search endpoint - Query exceeds 500 characters
include_contentis nottrueorfalse, orcontent_resultsis not5or10- Missing
urls, more than 10 URLs, or a URL that cannot be parsed on the Extract endpoint dayson the Usage endpoint is not a whole number of at least 1
{
"error": "Query parameter (q or query) is required",
"example": "/api/search?q=javascript"
}An Extract request with URLs that cannot be parsed lists them:
{
"error": "Invalid URLs provided",
"invalid_urls": ["not a url"]
}401 Unauthorized
Returned when the API key is missing, invalid, or inactive.
{
"error": "Invalid or inactive API key"
}When no key is sent at all, the message is "API key required".
Check that you are sending Authorization: Bearer sk_... and that the key is active in your dashboard.
402 Insufficient Credits
Returned when your account does not have enough credits to cover the request cost.
{
"error": "Insufficient credits. Please purchase more credits to continue.",
"remainingCredits": 0
}On the Extract endpoint the message is "Insufficient credits". Extract checks your balance against the maximum cost of the request (every URL charged) before it runs, and then charges only the successful URLs.
The remainingCredits field shows your current balance. Top up credits from the billing page.
403 Forbidden
Returned by the Extract endpoint when a request sets "stealth": true and your plan does not include stealth mode. Nothing is charged and nothing is fetched.
{
"error": "Stealth mode is not included in your plan. Contact support@serpex.dev to add it."
}410 Gone
Returned by the Extract endpoint when a request sets "stealth": true on or after 1 November 2026 (00:00 UTC). Nothing is charged and nothing is fetched. Send the request without stealth.
{
"error": "Stealth mode is no longer available (ended 1 November 2026). Send the request without stealth.",
"retryable": false
}429 Rate Limited
Returned when you exceed your plan's request-per-second limit.
{
"error": "Rate limit exceeded",
"limit": 50,
"retryAfterMs": 800
}Response headers include:
X-RateLimit-Limit— your plan's per-second ceilingX-RateLimit-Remaining— requests remaining in the current windowRetry-After— seconds to wait before retrying
Retry guidance: Wait for retryAfterMs milliseconds, then retry the request. Use exponential backoff if you are consistently hitting the limit.
| Plan tier | Rate limit |
|---|---|
| Starter (largest purchase under $50) | 5 req/sec |
| Standard ($50–$499) | 25 req/sec |
| Scale ($500+) | 50 req/sec |
| Enterprise (contact sales) | Custom |
500 Internal Server Error
Returned by Search and Extract when something unexpected fails on our side. The message is always the same and never includes internal details. The request is not charged.
{
"error": "Internal server error",
"request_id": "3f6c1a52-8d0e-4b7a-9c41-0e2d5a7b9f13"
}Quote the request_id when you contact support@serpex.dev so we can find the exact request.
502 Bad Gateway
Returned by the Extract endpoint when the extraction service fails. Nothing is charged.
{
"error": "Extraction service error. Not charged. Please retry.",
"retryable": true,
"request_id": "3f6c1a52-8d0e-4b7a-9c41-0e2d5a7b9f13"
}Retry guidance: Retry the request. If it keeps failing, contact support with the request_id.
Request ID
Error responses from Search and Extract that point to a problem on our side (500, 502, and the Extract 400 below) include a request_id field. Quote it to support.
If an Extract request is rejected because urls is not a list of 1 to 10 valid absolute URLs, the body is:
{
"error": "Invalid request: send 1 to 10 valid absolute URLs in `urls`.",
"retryable": false,
"request_id": "3f6c1a52-8d0e-4b7a-9c41-0e2d5a7b9f13"
}503 Service Unavailable
Returned when the request cannot be completed right now. It is never charged and is safe to retry.
Search returns this when the search could not be completed after automatic retries:
{
"status": "error",
"error": "Search temporarily unavailable, please retry.",
"retryable": true,
"metadata": {
"status": "error",
"response_time": 30412,
"timestamp": "2025-06-21T10:30:00.000Z"
}
}Extract with "stealth": true returns this when stealth capacity is busy:
{
"error": "Stealth capacity is busy, please retry shortly.",
"retryable": true
}Retry guidance: Wait at least 1 second, then retry. Use exponential backoff with a maximum of 3 retries. If the issue persists beyond a few minutes, check the status page.
504 Gateway Timeout
Returned by the Extract endpoint when extraction does not finish in time. Nothing is charged.
{
"error": "Extraction timed out upstream and was not charged. Retry, or split the request into fewer URLs.",
"retryable": true
}Retry guidance: Retry the request, or send fewer URLs per request.
Error Handling Example
import { SerpexClient, SerpApiException } from "serpex";
const client = new SerpexClient("sk_your_api_key");
try {
const results = await client.search({ q: "test query" });
} catch (error) {
if (error instanceof SerpApiException) {
switch (error.statusCode) {
case 429:
console.log(`Rate limited. Retry after ${error.details?.retryAfterMs}ms`);
break;
case 402:
console.log(`Out of credits. Balance: ${error.details?.remainingCredits}`);
break;
default:
console.log(`API error ${error.statusCode}: ${error.message}`);
}
}
}from serpex import SerpexClient, SerpApiException
client = SerpexClient("sk_your_api_key")
try:
results = client.search({"q": "test query"})
except SerpApiException as e:
if e.status_code == 429:
print(f"Rate limited. Retry after {e.details.get('retryAfterMs')}ms")
elif e.status_code == 402:
print(f"Out of credits. Balance: {e.details.get('remainingCredits')}")
else:
print(f"API error {e.status_code}: {e}")