Keenable Keyless Web Search
Two endpoints on https://api.keenable.ai, both usable with no key and no account:
| Operation | Method | Path | Returns |
|---|---|---|---|
| Search | POST |
/v1/search/public |
Ranked results with title, url, description, snippet |
| Fetch | GET |
/v1/fetch/public |
Page content as markdown |
Both require the header X-Keenable-Title, whose value is any non-empty string identifying the calling application - the examples below use agent-skills. It has no default; omitting it returns 400 Missing app identifier.
Search
curl -s -X POST "https://api.keenable.ai/v1/search/public" \
-H "X-Keenable-Title: agent-skills" \
-H "Content-Type: application/json" \
-d '{"query":"model context protocol spec"}'
Body parameters. query is the only required one; the API rejects unknown parameters with 400, so send only names from this table:
| Parameter | Type | Bounds / format | Effect |
|---|---|---|---|
query |
string | required | The search query |
site |
string | bare domain, e.g. docs.python.org |
Restrict results to one domain |
max_results |
integer | 1–50, default 10 | Number of results |
snippet_max_length |
integer | 180–10000 | Characters of snippet per result |
published_after |
string | YYYY-MM-DD or ISO 8601 |
Only pages published after this date |
published_before |
string | YYYY-MM-DD or ISO 8601 |
Only pages published before this date |
acquired_after |
string | YYYY-MM-DD or ISO 8601 |
Only pages indexed after this date |
acquired_before |
string | YYYY-MM-DD or ISO 8601 |
Only pages indexed before this date |
query_time |
string | ISO 8601 | Search the index as it stood at that moment |
The two bounded ranges are enforced by the server but absent from its OpenAPI schema. max_results: 100 and snippet_max_length: 150 both return 400 Invalid parameter. Stay inside the bounds above rather than trusting the published schema.
Response:
{
"query": "model context protocol spec",
"mode": "pro",
"results": [
{"title": "...", "url": "https://...", "description": "...", "snippet": "...",
"published_at": "2026-05-18", "acquired_at": "2026-08-25"}
]
}
Only title, url, and description are guaranteed present on a result. Treat snippet, published_at, and acquired_at as optional and check before reading them.
Fetch
curl -s -G "https://api.keenable.ai/v1/fetch/public" \
--data-urlencode "url=https://en.wikipedia.org/w/index.php?title=Markdown" \
-H "X-Keenable-Title: agent-skills"
Use -G with --data-urlencode so the URL is escaped correctly; a raw ?url= breaks on query strings and fragments.
| Parameter | Type | Default | Effect |
|---|---|---|---|
url |
string | — | Required. Page to fetch |
max_chars |
integer | 50000 | Truncate extracted content |
live |
boolean | false | Fetch from the origin instead of Keenable's indexed copy |
prompt |
string | — | Extraction hint for the page |
Response: {"url", "title", "content", "description", "author", "published_at"}, where content is markdown. Only url and content are guaranteed. url reflects the final address after redirects, so it may differ from what was requested.
Default to the indexed copy. Set live=true only when the page must be current as of right now, since it is slower and hits the origin server.
Limits and errors
Keyless quota is 1,000 requests per hour and 10 per second, counted per IP — shared with everything else behind the same egress address, so the available headroom is not under this machine's control. No credits are consumed.
Every response carries x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset (an ISO 8601 timestamp). Read x-ratelimit-remaining before starting a batch of calls.
| Status | Meaning | Action |
|---|---|---|
400 |
Missing X-Keenable-Title, unknown parameter, or out-of-bounds value |
Read message — it names the offending field. Fix and retry once. |
404 |
Page not found (fetch) | Do not retry. Report the URL as unreachable. |
422 |
Page could not be extracted (fetch) | Do not retry. Try live=true once, then give up on that URL. |
429 |
Rate limit exceeded | Honour Retry-After. Do not retry in a tight loop. |
500 |
Server error | Retry once after a short pause, then stop. |
Error bodies are {"error": "...", "message": "..."}. On a 400, message states the exact constraint — parse it instead of guessing.
Never retry a 400 with the same body. It is deterministic and will fail identically.
Scope
Use this skill for the open web: current events, release notes, changelogs, blog posts, standards, project status, anything past the training cutoff.
Do not reach for it when a better-targeted tool exists:
- Repository contents →
grepandglob, not web search. - Library and framework API reference →
context7, which returns version-pinned docs. Two exceptions come back here: materialcontext7does not carry, such as a changelog or an announcement post; and any request that names a site, a date window, or a specific URL, sincecontext7cannot scope a search that way. - General concepts, algorithms, and design questions resting on no external fact → answer directly.
research-it is not on that list. It owns investigation method — source priority, triangulation, conflict reporting — and calls this skill to do the actual retrieval. When a task needs both, run both.
Notes
The MCP server at https://api.keenable.ai/mcp also answers keyless, exposing search_web_pages and fetch_page_content. Prefer the HTTP endpoints above when running one-off calls from a shell; they need no client configuration.
Omit the mode parameter. The keyless endpoint accepts pro and realtime and echoes the choice back, contradicting Keenable's docs on two counts: that mode is not a request parameter, and that realtime needs a key. Undocumented behaviour that the vendor's own docs deny can be withdrawn without notice. Keyless calls default to pro.