Blopus.ai Docs
API reference

Errors

Every error is a clean, predictable JSON envelope — no HTML error pages, no stack traces.

Set a User-Agent, or you will get 403s

If you are not using one of our SDKs, you must send a real User-Agent header. Requests arriving with a default library agent — python-requests/2.x, python-httpx/0.x, node-fetch, Go-http-client/1.1, Java/17 or axios/1.x — are stopped by our edge bot protection and come back 403 Forbidden before they ever reach the API. A valid API key does not help: the request never gets that far.

This catches people out because it looks like an authentication problem. It is not. If you get a 403 whose body is HTML rather than our JSON error shape, this is why.

# WRONG — default library agent, blocked at the edge
curl -s https://api.blopus.ai/v1/search -H "Authorization: Bearer $KEY" ...    # 403

# RIGHT — identify your client
curl -s https://api.blopus.ai/v1/search \
  -H "Authorization: Bearer $BLOPUS_API_KEY" \
  -H "User-Agent: my-agent/1.0" \
  -H "Content-Type: application/json" \
  -d '{"query": "hello"}'
# Python, raw httpx
httpx.Client(headers={"User-Agent": "my-agent/1.0 (+https://example.com)"})

# Node, raw fetch
fetch(url, { headers: { "User-Agent": "my-agent/1.0" } })

Any sensible value works. There is no allow-list and we do not police the string — it simply must not be a bare library default. Including a contact URL is good practice and lets us reach you before anything gets rate-limited.

Agent frameworks: if you drive Blopus from LangChain, LlamaIndex, an MCP client or your own agent loop, check what your HTTP layer actually sends — several frameworks pass the underlying library default straight through. Our official SDKs set this for you, which is the simplest way to avoid the problem entirely.

Troubleshooting

Symptoms first, in the order people actually hit them.

Every request returns 403, and the body is HTML

You are being stopped by edge bot protection before the API sees the request, so the key is irrelevant. Send a real User-Agent. Default library agents (python-requests, python-httpx, node-fetch, Go-http-client, axios) are blocked. See User-Agent. Our SDKs set this for you.

401 with a key that looks correct

Check the header is Authorization: Bearer blp_live_… — not x-api-key, not the bare key. If you just rotated or revoked a key, allow up to 30 seconds for the change to take effect.

A search returns zero results and you expected some

In order of likelihood:

  • A topic filter with an invented value. Topics are exact-match, so a wrong value returns nothing at all. Check the vocabulary. The response note will say so explicitly.
  • A tight freshness window on a subject nobody wrote about today. Widen it, or rely on recency ranking instead of filtering.
  • A language filter. It is strict. A query written in a language already returns results in that language without it — adding the filter mostly costs you documents whose language was never detected.
  • Several topics in one query. No single document is about all of them.

I asked for 10 results and got 8

Near-identical copies are collapsed into one result. Check duplicate_count — a syndicated wire story can absorb twenty copies. If quota_clamped is true instead, the key ran short of credits.

Results are relevant but shallow

You are reading snippets, which are roughly one sentence each. Set include_content=true to get full text in the same call for the same credit, or fetch the URLs worth reading. Add min_words=120 to drop tag listings and stubs.

Results are about a country, but not from it

An English query returns English-language coverage. Writing the query in the local language returns that country’s own press — a measurably different set of publications. No parameter needed; only the query changes.

Results suddenly feel less relevant

Check degraded in the response. If it is true, semantic matching was unavailable and you received lexical-only results. It resolves on its own; retrying a moment later is reasonable.

Requests are slower than usual

Large count values with include_content return a lot of text; that is usually the cause. Deep offset paging also costs more than a narrower query. Filters generally make searches faster, not slower, because they shrink the candidate set.

Still stuck

Email support with the full request body, the response note if present, and the timestamp. Those three make almost every issue reproducible on our side.

Error shape#

Branch on the stable code string; show message to humans.

Status codes#

StatuscodeMeaning
400bad_requestMalformed or invalid parameters (e.g. bad freshness, too many domains).
401unauthorizedMissing, malformed, or revoked API key.
402quota_exceededCredits exhausted. On a paid plan, upgrade or wait for the reset. On the free trial the 400 are one-time and never reset, so the message says so and the remedy is to choose a plan.
402subscription_requiredThe account has no plan at all — deliberately distinct from quota_exceeded, because the remedy differs: choose a plan rather than buy more credits. Branch on error.code, not on the status alone.
429rate_limitedPer-key rate limit hit. Honor the Retry-After header.

Handling errors#