OpenSERP Cloud Quickstart →

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

  1. Copy meta.request_id (or client.lastResponse?.requestId) and include it when contacting support.
  2. Reproduce the same parameters in the Search Playground.
  3. Check X-Credits-Used, X-Credits-Remaining, and X-Engine-Used.
  4. Confirm the API key prefix matches the environment you expected.

Next