OpenSERP Cloud uses standard HTTP status codes and a consistent JSON error envelope:
{
"error": "short_machine_readable_code",
"code": 400,
"message": "Human-readable explanation",
"reason": "OPTIONAL_DETAIL_TAG"
}
Use error and code in application logic. Treat message as display text — its wording may change.
Status codes
| Status | error |
When it happens | Charged? | Retry? |
|---|---|---|---|---|
200 |
— | Success | Yes, for billable search requests | — |
400 |
bad_request |
Missing or invalid query parameters | No | No |
401 |
unauthorized |
Missing, wrong, or revoked API key | No | No |
402 |
insufficient_credits |
Balance is too low | No | After top-up |
404 |
not_found |
Unknown engine or path | No | No |
408 |
timeout |
Search took too long | No | Yes, with backoff |
422 |
unprocessable |
Parameter values cannot be used together | No | No |
429 |
rate_limited |
Per-key rate limit exceeded | No | Yes, honor Retry-After |
500 |
internal_error |
Server error | No | Yes, with backoff |
502 / 503 |
service_unavailable |
No requested engine could complete the request | No | Yes, with backoff |
Any/Fast endpoints return 502 if every requested or default engine fails. Those failed responses are not charged.
Common validation reasons
reason |
Cause |
|---|---|
EMPTY_QUERY |
No text was provided. |
INVALID_LIMIT |
limit is outside 1..100. |
INVALID_DATE_RANGE |
date is not YYYYMMDD..YYYYMMDD or the end date is before the start. |
UNKNOWN_ENGINE |
engine or engines includes an unsupported engine. |
UNKNOWN_LANG / UNKNOWN_COUNTRY |
The language or region code is unsupported. |
A 400 will not succeed until you change the parameters — don’t retry it.
JavaScript SDK errors
The SDK throws typed errors. Catch RateLimitError separately for backoff, then SERPError for everything else:
import { OpenSERP, RateLimitError, SERPError } from "@openserp/sdk";
const client = new OpenSERP({ apiKey: process.env.OPENSERP_KEY });
try {
const response = await client.search({ engine: "google", text: "openserp" });
console.log(response.results.length);
} catch (err) {
if (err instanceof RateLimitError) {
console.warn("rate limited", { status: err.status, requestId: err.requestId });
} else if (err instanceof SERPError) {
console.error("api error", {
status: err.status,
code: err.code,
reason: err.reason,
requestId: err.requestId,
});
} else {
throw err;
}
}
The SDK also exports CaptchaError, TimeoutError, CloudOnlyError, and OssOnlyError if you need to branch on those.
Python SDK errors
from openserp import OpenSERP, RateLimitError, SERPError
try:
response = client.search(engine="google", text="openserp")
except RateLimitError as err:
print("rate limited", err.status, err.request_id)
except SERPError as err:
print("api error", err.status, err.code, err.reason, err.request_id)
Rate limits
Rate limits are per API key and only hit during bursts or high concurrency. When the API returns 429, it includes Retry-After in seconds:
HTTP/1.1 429 Too Many Requests
Retry-After: 4
Content-Type: application/json
{ "error": "rate_limited", "code": 429, "message": "Rate limit exceeded." }
Always honor Retry-After. If your workload needs sustained high throughput, ping support@openserp.org before launch so we can review limits.
Retry strategy
Retry only 408, 429, 500, 502, and 503. Use exponential backoff with jitter and a small attempt cap. Do not retry 400, 401, 402, 404, or 422 — those need a configuration or input change first.
JavaScript SDK retry hook
The SDK’s retry option runs after every failed request. Return true to retry, false to throw:
import { OpenSERP, SERPError } from "@openserp/sdk";
const RETRYABLE = new Set([408, 429, 500, 502, 503]);
const client = new OpenSERP({
apiKey: process.env.OPENSERP_KEY,
retry: async (err, attempt) => {
if (attempt >= 3 || !(err instanceof SERPError) || !RETRYABLE.has(err.status)) {
return false;
}
const retryAfter = Number(client.lastResponse?.headers.get("retry-after") ?? 0);
const waitMs = retryAfter
? retryAfter * 1000
: Math.min(2 ** attempt * 250, 8000) + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, waitMs));
return true;
},
});
const response = await client.search({ engine: "google", text: "openserp" });
console.log(response.results.length);
fetch retry
const RETRYABLE = new Set([408, 429, 500, 502, 503]);
async function searchWithRetry(url: string, key: string, maxAttempts = 4) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
if (res.ok) return res.json();
if (!RETRYABLE.has(res.status) || attempt === maxAttempts) {
throw new Error(`OpenSERP ${res.status}: ${await res.text()}`);
}
const retryAfter = Number(res.headers.get("Retry-After") ?? 0);
const waitMs = retryAfter
? retryAfter * 1000
: Math.min(2 ** attempt * 250, 8000) + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
Python SDK retry hook
import os, random, time
from openserp import OpenSERP, SERPError
RETRYABLE = {408, 429, 500, 502, 503}
client: OpenSERP
def should_retry(err: Exception, attempt: int) -> bool:
if attempt >= 3 or not isinstance(err, SERPError) or err.status not in RETRYABLE:
return False
headers = client.last_response.headers if client.last_response else {}
retry_after = float(headers.get("retry-after", 0) or 0)
wait = retry_after or min(2 ** attempt * 0.25, 8.0)
time.sleep(wait + random.random() * 0.25)
return True
client = OpenSERP(api_key=os.environ["OPENSERP_KEY"], retry=should_retry)
response = client.search(engine="google", text="openserp")
Partial failures
Balanced megasearch can still return results when one requested engine fails. The response contains successful results, and billing follows X-Credits-Used.
Any/Fast endpoints try to return one successful engine response. If one engine succeeds, the response is 200 and includes meta.engine_used, meta.engines_tried, and X-Engine-Used. If every engine fails, the response is 502 and is not charged:
{
"meta": {
"request_id": "019dc6c1-da45-706e-a57c-d671fa2862ee",
"took_ms": 1180,
"engine_used": "bing",
"engines_tried": ["google", "bing"]
},
"results": []
}
Debugging checklist
- Copy
meta.request_id(orclient.lastResponse?.requestId) and include it when contacting support. - Reproduce the same parameters in the Search Playground.
- Check
X-Credits-Used,X-Credits-Remaining, andX-Engine-Used. - Confirm the API key prefix matches the environment you expected.
Next
- Endpoints reference — every endpoint and parameter.
- Authentication — key rotation and 401 diagnostics.