Headless browser & search API for AI agents | skill.md | health | source
kamai gives your agent a real Chromium browser, web, image & social search with automatic provider failover, per-domain memory, and PDF brochure generation — over plain HTTPS JSON. No SDK required.
Base URL: https://kamai.minai.work
Identify your app on authenticated routes with either header:
x-api-key: <key> or x-wallet-address: 0x… (Celo).
Sister apps receive an API key from the kamai operators — it bypasses payment entirely.
Without a key, requests are paid from a USDC credit balance (first request each day is free).
curl -X POST https://kamai.minai.work/api/v1/browse \
-H "Content-Type: application/json" \
-H "x-api-key: <your-key>" \
-d '{"url": "https://example.com"}'
Legacy routes need no auth at all — sister backends can call
POST /browse, POST /search/web, POST /search/image
and /browse/memories directly. The /api/v1/… equivalents exist
for credit-paying callers; sister keys make them behave identically.
POST /api/v1/browse · legacy alias /browse
Navigates a real headless Chromium to a URL, optionally performs actions (fill forms, click through flows), then extracts clean text, links, and form fields. Stealth measures are applied to avoid headless detection. JavaScript dialogs (alert/confirm/prompt) are auto-accepted and logged.
{
"url": "https://example.gov.ph/search",
"actions": [
{ "action": "type", "selector": "#q", "text": "business permits" },
{ "action": "click_and_wait", "selector": "button#go" },
{ "action": "wait", "selector": ".results" }
],
"selector": ".results", // optional: narrow extraction to one element
"timeout": 15000 // optional, ms (default 15000, max 30000)
}
// Response
{
"ok": true,
"url": "https://example.gov.ph/results",
"title": "Search Results",
"text": "…page content as plain text…",
"links": [{ "text": "Permit application", "href": "https://…" }],
"forms": [{ "tag": "input", "type": "text", "name": "q", "selector": "#q" }],
"memories": ["Use /Indexes/index for keyword search instead of homepage"],
"actions_performed": ["typed \"business permits\" into #q", "clicked button#go → navigated"],
"sessionId": "…", // your persistent session (see Sessions)
"length": 4500
}
Safety: file:, data:, localhost and private-IP URLs are blocked. Text is capped at 30 000 chars.
Up to 20 actions per request, executed in order before extraction. All click actions auto-scroll the element into view and dismiss cookie/consent overlays first. CSS selectors and Playwright text=… selectors are supported everywhere.
| Action | Params | What it does |
|---|---|---|
type | selector, text | Clear field and type text |
click | selector | Click element (500 ms pause) |
click_and_wait | selector | Click, wait for navigation |
submit | selector? | Programmatic form submit (ASP.NET postbacks) |
select | selector, value | Choose a dropdown option |
wait | selector, timeout? | Wait for element to appear |
wait_ms | ms | Pause (max 5000) |
scroll_to | selector | Scroll element into view (centered) |
js_click | selector | Force-click via JS — bypasses overlays/pointer interception |
set_date | selector, value | Set any date picker, value yyyy-mm-dd |
evaluate | text | Run arbitrary JS on the page |
Sessions are automatic. kamai keeps a persistent browser context per caller identity (API key → wallet → IP), so cookies, auth state, and localStorage carry across requests: log in once, stay logged in. Idle sessions expire after 30 minutes.
For explicit control, create a session via POST /api/v1/session
and pass its sessionId in browse requests;
GET/DELETE /api/v1/session/:id inspect and destroy it.
POST /api/v1/screenshot · legacy alias /screenshot
Capture the relevant part of any URL — including social posts, which are captured by rendering the platform's own embed rather than its login wall.
{ "url": "https://x.com/jack/status/20", "mode": "auto", "format": "jpeg" }
// → { "ok": true, "screenshotId": "…", "imageUrl": "/api/v1/screenshot/…/image",
// "strategy": "embed:x", "width": 550, "height": 225, "sizeBytes": 37888,
// "expiresAt": "…" }
mode auto (default) · relevant · viewport · full · element (needs selector) ·
format jpeg/png · width 320–2000 · maxHeight 200–8000 · scale 1–3 ·
encoding url (default) or base64.
GET /api/v1/screenshot/:id/image is public and needs no auth, so the link can go straight to a vision model.
| Platform | Strategy |
|---|---|
x · instagram · linkedin · facebook · threads · bluesky | embed:* — full post card via the official embed |
tiktok | embed:tiktok — card renders, video poster frame often does not |
reddit | apify:reddit-card — rendered from scraped data, ~110s; not a pixel capture |
| any other URL | page:* — cropped to the main content region |
If an embed returns an error page ("Post not found", a rate-limit notice) the request fails with that reason rather than returning a blank image — a screenshot that proves nothing is worse than no screenshot.
Web, news, image, and social search across multiple premium providers with automatic failover — one API, no search keys to manage on your side. Post-comment crawl for Facebook, X, and Reddit.
POST /api/v1/search/web · legacy alias /search/web
{ "q": "Tim Cook age", "count": 5, "country": "US" }
// → { "ok": true, "source": "web",
// "results": [{ "title", "url", "description", "content"?, "age"? }] }
count 1–20 (default 5) · country 2-letter code or ALL ·
freshness pd|pw|pm|py or durations like 90min, 2h, 3d · maxTokens context budget (default 4096).
source identifies which backend answered — failover is automatic.
When available, results include extracted page content.
POST /api/v1/search/news · legacy alias /search/news
Searches news indexes instead of the general web — use it whenever recency matters.
Unlike /search/web, the freshness window is enforced exactly and every result
carries an ISO 8601 publishedAt you can sort on.
{ "q": "openai funding round", "freshness": "6h", "count": 10 }
// → { "ok": true, "source": "serper_news",
// "results": [{ "title", "url", "description", "source",
// "publishedAt", "age"? }] }
count 1–50 (default 10) · freshness pd|pw|pm|py or durations like
90min, 2h, 3d · sort date (default) or relevance ·
country 2-letter code or ALL · filterSources true (default) ·
cursor/nextCursor pagination for deep coverage.
Ranked newest-first, and non-news sources — app stores, corporate FAQ and support pages, retailers,
job boards, wikis — are dropped; set filterSources: false to keep them.
Tight windows may return fewer than count results — that means there was no fresher coverage.
POST /api/v1/search/image · legacy alias /search/image
{ "q": "Eiffel Tower at night", "count": 10, "safesearch": "strict" }
// → { "ok": true, "results": [{ "title", "url", "imageUrl", "thumbnailUrl",
// "width", "height", "source" }] }
count 1–50 (default 5) · safesearch strict (default) or off.
POST /api/v1/search/social · legacy alias /search/social
Keyword search across social platforms — brand mentions, competitor tracking, community research. One request shape for every platform; results are normalized to one schema.
{ "platform": "reddit", "q": "your brand", "freshness": "3d", "count": 10 }
// → { "ok": true, "source": "social", "platform": "reddit",
// "results": [{ "id", "url", "text", "author", "publishedAt",
// "likes", "comments", "shares", "views", "meta"? }],
// "nextCursor": "…", "hasMore": true }
Ranked newest-first, with publishedAt normalized to ISO 8601 (or null) on every
backend. Pass sort to hand ordering back to the platform instead.
| Platform | Notes |
|---|---|
x (aliases twitter, x.com) | Keyword search with engagement counts · chronological · cursor pagination for multi-day backfill (page with nextCursor; bound with freshness) |
reddit | Full post search · sort: relevance|new|top|comment_count · timeframe: day|week|month|year|all |
linkedin | Public post search · sort: relevance|date · timeframe: day|week|month |
tiktok · youtube · threads · pinterest | Keyword search |
instagram | Keyword search of public reels — runs live, expect 15–60s |
facebook | Public post search — runs live, so expect 20–60s response times; public content only |
facebook-events | Events search |
freshness limits results by age: presets pd|pw|pm|py or exact durations
like 90min, 2h, 3d, 1w — the exact window is always enforced server-side;
tight windows may return fewer than count results.
Pagination: one page per request (count max 100); pass the response's nextCursor back as
cursor and repeat while hasMore is true. Cursors are bound to their platform+q;
bound a backfill with freshness. Available on x (by timestamp), facebook (by day),
reddit, linkedin, tiktok, youtube, threads, pinterest; instagram is single-page.
If a platform's primary backend is unavailable, queries automatically fall back through secondary providers,
then a site-scoped web search (source: "web") where the platform indexes well.
POST /api/v1/search/comments · legacy alias /search/comments
Replies under one public Facebook, X, or Reddit post. Nested replies are flattened. Textless items are dropped.
{ "platform": "facebook", "url": "https://www.facebook.com/…/posts/pfbid…", "count": 300 }
// → { "ok": true, "results": [{ "id", "url"?, "author", "text",
// "publishedAt"?, "likes"? }],
// "hasMore": false, "nextCursor": null }
platform facebook · x (aliases twitter, x.com) · reddit.
count 1–500 (default 100). A deleted, private, or empty post returns ok: true with
an empty list — never HTTP 404 (404 means the route itself is missing).
Expect 20–150s; reddit is the slow end.
Every browse response includes a memories array — learnings saved by agents from
previous visits to that domain. When your agent discovers a better navigation path, save it;
all future callers to that domain benefit.
| Route | Purpose | |
|---|---|---|
| GET | /browse/memories?domain=example.com | Read learnings (omit domain to list all) |
| POST | /browse/memories | Save { "domain", "learning", "strategy"? } |
| DELETE | /browse/memories/:id | Remove a learning |
Also mounted at /api/v1/browse/memories. Free on both paths.
A memory with a strategy field (yt-dlp, github-api) overrides how kamai fetches that domain.
Generate multi-page corporate PDFs from structured JSON. Templates: corporate-overview, product-showcase, event-invitation.
| Route | Purpose | |
|---|---|---|
| GET | /api/v1/brochure/templates | free — list templates + required fields |
| POST | /api/v1/brochure/generate | paid — render a new brochure |
| PATCH | /api/v1/brochure/:id | paid — merge changes & re-render |
| GET | /api/v1/brochure/:id/download | free — the PDF itself, no auth (shareable link) |
Full content schema (sections, products, charts, contact info, images as URLs or base64) is in skill.md.
| Request | Cost |
|---|---|
| Browse (no actions) | $0.009 |
| Browse with actions | $0.013 |
| Search (web or image) | $0.003 |
| Brochure generate / update | $0.050 |
| Brochure templates & downloads, memories, health | free |
Sister keys bypass payment entirely. Credit callers: first request each day is free, then
balance is deducted per request. When balance runs out, the API returns
402 with machine-readable deposit instructions (USDC on Celo, min $0.10).
Deposit via POST /api/v1/deposit with your tx hash;
check balance at GET /api/v1/deposit/balance;
manage your account and API key at GET /api/v1/account.
| Route | Auth | |
|---|---|---|
| POST | /api/v1/browse | key / wallet (legacy /browse: none) |
| POST | /api/v1/search/web · /api/v1/search/news · /api/v1/search/image · /api/v1/search/social · /api/v1/search/comments | key / wallet (legacy /search/*: none) |
| POST | /api/v1/screenshot · GET /:id/image is public | key / wallet |
| GET·POST·DEL | /browse/memories · /api/v1/browse/memories | none |
| POST | /api/v1/brochure/generate | key / wallet |
| PATCH | /api/v1/brochure/:id | key / wallet |
| GET | /api/v1/brochure/templates · /api/v1/brochure/:id/download | none |
| POST·GET·DEL | /api/v1/session | rate-limited |
| GET·POST | /api/v1/account · /api/v1/deposit | wallet |
| GET | /skill.md | none |
| GET | /health | none |
{ "ok": false, "error": "Navigation timeout: 15000ms exceeded" }
401 — missing identity header on an authenticated route402 — insufficient credits; response includes deposit instructions400 — bad request (missing url/q, invalid action params)X-RateLimit-Remaining header on responses