---
name: search-sh
version: 1.1.0
description: Answer engine for AI agents and people — structured JSON with a grounded, cited answer, semantically reranked sources, verified citations, confidence, images, and places. Fast search, deep research, page extraction, and SSE streaming.
homepage: https://search.sh
---

# Search.sh

An answer engine built for AI agents (and people). Query the web and get back **structured JSON**: an AI-synthesized answer grounded in real sources, semantically reranked results, citations that are **verified against the source text**, a calibrated confidence score, related queries, and optional images and places.

Unlike a raw search API, Search.sh does the retrieval *and* the reasoning: it ranks for relevance (not SEO), reads the pages, selects the passages that actually answer the query, and only cites what the sources support.

## Skill files

| File | URL |
|------|-----|
| **SKILL.md** (this file) | `https://search.sh/skill.md` |
| **llms.txt** | `https://search.sh/llms.txt` |

**Base URL:** `https://search.sh/api`

---

## How it works (why the answers are reliable)

Every search runs through a grounded retrieval pipeline:

1. **Multi-engine retrieval** — Google (and DuckDuckGo in deep mode) via SerpAPI, deduplicated by URL.
2. **Hybrid reranking** — results are reranked by **semantic similarity (embeddings) blended with lexical BM25**, so the pages that are *actually relevant* get read and cited — not just whatever ranked highest for SEO.
3. **Readability extraction** — the main content of the top pages is extracted (boilerplate/nav stripped), and the **passages most relevant to your query are selected** rather than blindly truncating the page.
4. **Grounded synthesis** — the model answers **only from the retrieved content**, not its training data, and is instructed never to invent facts.
5. **Citation verification** — every citation is checked against its source's text. Claims the source doesn't support are **dropped**; the rest are flagged `verified: true`. (See `verified` below.)
6. **Freshness** — queries about recent/latest events are automatically date-restricted so results are current.
7. **Shared result cache** — identical searches (same query, mode, region, language; no conversation history) may be served from a shared cache for up to 24 hours (1 hour for recent/news intents). Cache hits return instantly with `metadata.cached: true` and an `X-Cache: HIT` header, and are **free** — no credits are charged and no `X-Credits-Remaining` header is returned. Follow-ups (requests with `history`) are never cached.

The practical upshot for an agent: prefer `verified` citations, and treat `confidence` as a real signal — it reflects source agreement, authority, and how much of the answer was grounded.

---

## Authentication

Pass an API key via the `Authorization` header:

```
Authorization: Bearer sk-sh_your_api_key_here
```

Get a key at [https://search.sh/dashboard](https://search.sh/dashboard). Prepaid credits — $5 minimum (500 credits). Bonus: $50+ gets +2%, $100+ gets +5%.

- **`/api/search`** works **without** a key on a free tier (5 searches/day per IP). Provide a key for unlimited, metered access. An *invalid* key returns 401; *no* key falls through to the free tier.
- **`/api/extract`** requires a key.
- **`/api/images`** and **`/api/maps`** are currently open (no key), rate-limited per IP.

**Credit costs:**

| Endpoint | Credits | Cost |
|----------|---------|------|
| Fast search | 1 | $0.01 |
| Deep search | 5 | $0.05 |
| Extract | 1 / URL | $0.01 / URL |

You are **not charged** when a search returns zero results, the AI step fails, or the request errors — credits are refunded automatically. Remaining balance is returned in the `X-Credits-Remaining` response header and via `GET /api/credits`.

---

## Quick start

```bash
curl -X POST https://search.sh/api/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-sh_your_api_key" \
  -d '{"query": "best vector databases for production"}'
```

**Response:**
```json
{
  "query": "best vector databases for production",
  "answer": "The top vector databases for production use are **Pinecone**, **Weaviate**, and **Qdrant**...",
  "sources": [
    {
      "title": "Vector Database Comparison 2026",
      "url": "https://example.com/vector-dbs",
      "domain": "example.com",
      "snippet": "We benchmarked 12 vector databases...",
      "position": 1,
      "engine": "google"
    }
  ],
  "citations": [
    {
      "claim": "Pinecone handles 1B+ vectors with sub-10ms latency",
      "source_url": "https://example.com/vector-dbs",
      "source_title": "Vector Database Comparison 2026",
      "verified": true
    }
  ],
  "confidence": 0.86,
  "related_queries": [
    "pinecone vs weaviate performance",
    "self-hosted vector database options",
    "vector database scaling strategies"
  ],
  "metadata": {
    "search_engine": "google",
    "region": "us",
    "language": "en",
    "duration_ms": 4320,
    "mode": "fast",
    "source_count": 9,
    "sources_found": 14,
    "cited_count": 4
  }
}
```

> **On the source counts:** `sources_found` is the total retrieved after dedup; `source_count` is how many are returned in `sources` (capped at 20); `cited_count` is how many distinct claims the answer cited. These measure different things, so they differ by design.

---

## Endpoints

### POST /api/search

The primary endpoint. Search the web and get a grounded, cited answer.

```bash
curl -X POST https://search.sh/api/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-sh_your_api_key" \
  -d '{
    "query": "how does RAG work",
    "mode": "deep",
    "region": "us",
    "language": "en",
    "max_results": 10
  }'
```

**Request body:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `query` | string | *required* | The search query. Max 500 characters. |
| `mode` | string | `"fast"` | `"fast"` — single search + grounded answer. `"deep"` — generates sub-queries, searches multiple engines in parallel, extracts more pages, cross-references. |
| `stream` | boolean | `false` | Stream results via SSE. See Streaming below. |
| `region` | string | `"us"` | Region for results. `"global"` for no bias. Supported: `us, global, gb, de, fr, es, it, nl, pt, pl, jp, cn, kr, in, br, au, ca, ru, se, ch, at, cz, il, ae, sg, hk, tw, mx, ar, za`. |
| `language` | string | `"en"` | ISO language code: `en, es, fr, de, ja`, … |
| `max_results` | number | `10` | Results to fetch per query (clamped 1–20). |
| `include_citations` | boolean | `true` | Set `false` to omit citations, confidence, and related queries. |
| `include_sources` | boolean | `true` | Set `false` to omit the sources array. |
| `extract_content` | boolean | — | Accepted for compatibility. Page extraction is **always performed** (top 3 in fast, top 8 in deep) — it's what makes answers good — so this flag is effectively a no-op. |
| `history` | array | — | Prior conversation turns `[{ "query": "...", "answer": "..." }]` for **context-aware follow-ups**. The new `query` is resolved against this history (e.g. "what about the cheaper ones?" → "budget espresso machines 2026") before searching, and the answer continues the conversation. Send the last few turns; `metadata.search_query` shows the rewritten query when it differs. |

**Response fields:**

| Field | Type | Description |
|-------|------|-------------|
| `query` | string | The original query. |
| `answer` | string | Grounded, markdown-formatted answer (supports headings, **bold**, lists, tables). |
| `sources` | array | Reranked results: `title, url, domain, snippet, position, engine`. |
| `citations` | array | Cited claims with attribution: `claim, source_url, source_title, verified`. |
| `confidence` | number | 0.0–1.0 — source agreement, authority, and grounding. |
| `related_queries` | array | Suggested follow-ups. |
| `metadata` | object | `search_engine, region, language, duration_ms, mode, source_count, sources_found, cited_count`. |
| `sub_queries` | array | *(deep only)* The sub-queries generated and searched. |
| `extracted_content` | array | *(deep only)* Extracted pages: `url, title, content, relevance, published_date`. |

**The `verified` flag on citations:** `true` means the claim is lexically supported by the cited source's extracted text. `false` means it couldn't be confirmed (e.g. the source was a snippet-only result, not one of the fully-extracted pages) — it is **not** an assertion that the claim is wrong. Citations the source actively contradicts are dropped before you see them. For high-stakes use, prefer `verified: true` citations.

---

### GET /api/search

Convenience GET for simple queries.

```bash
curl "https://search.sh/api/search?q=kubernetes+vs+docker&mode=fast&region=us"
```

**Query params:** `q` *(required)*, `mode` (`fast`/`deep`), `region`, `language`, `max_results`, `citations` (`false` to omit), `sources` (`false` to omit).

---

### POST /api/search (Streaming)

Stream results in real time via Server-Sent Events. Set `"stream": true`.

```bash
curl -N -X POST https://search.sh/api/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-sh_your_api_key" \
  -d '{"query": "latest AI news", "stream": true}'
```

**Stream events (in order):**

| Event type | Data | When |
|------------|------|------|
| `progress` | `{ "step": "searching", "percent": 10 }` | Search started |
| `progress` | `{ "step": "searching_subqueries", "percent": 20, "sub_queries": [...] }` | *(deep)* Sub-queries generated |
| `sources` | `[{ "title": "...", "url": "...", ... }]` | Reranked sources |
| `progress` | `{ "step": "extracting", "percent": 40 }` | Extracting pages |
| `progress` | `{ "step": "generating_answer", "percent": 60 }` | Synthesis started |
| `answer_delta` | `"The "` | Answer token (streamed incrementally) |
| `citations` | `[{ "claim": "...", "verified": true, ... }]` | Verified citations |
| `related` | `["query 1", "query 2"]` | Related queries |
| `credits` | `{ "remaining": 487 }` | *(if applicable)* Balance update after a refund |
| `done` | `{ "confidence": 0.86, "metadata": {...} }` | Complete |
| `error` | `"error message"` | If something went wrong |

Each event is a JSON object prefixed with `data: ` and terminated by `\n\n`. The answer text in `answer_delta` may contain inline `[n]` citation markers; strip them if you only want prose (the structured `citations` array is authoritative).

**Parsing (JavaScript):**

```javascript
const res = await fetch("https://search.sh/api/search", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer sk-sh_your_api_key" },
  body: JSON.stringify({ query: "your query", stream: true }),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split("\n\n");
  buffer = lines.pop();
  for (const line of lines) {
    const cleaned = line.replace(/^data: /, "").trim();
    if (!cleaned) continue;
    const chunk = JSON.parse(cleaned);
    switch (chunk.type) {
      case "sources": console.log("Sources:", chunk.data); break;
      case "answer_delta": process.stdout.write(chunk.data); break;
      case "citations": console.log("\nCitations:", chunk.data); break;
      case "done": console.log("\nConfidence:", chunk.data.confidence); break;
    }
  }
}
```

**Parsing (Python):**

```python
import requests, json

res = requests.post(
    "https://search.sh/api/search",
    headers={"Authorization": "Bearer sk-sh_your_api_key"},
    json={"query": "your query", "stream": True},
    stream=True,
)

for line in res.iter_lines():
    if not line:
        continue
    decoded = line.decode("utf-8").removeprefix("data: ").strip()
    if not decoded:
        continue
    chunk = json.loads(decoded)
    if chunk["type"] == "answer_delta":
        print(chunk["data"], end="", flush=True)
    elif chunk["type"] == "done":
        print(f"\nConfidence: {chunk['data']['confidence']}")
```

---

### POST /api/extract

Extract clean main-text from URLs (readability-style: scripts/nav/footer removed). Useful for processing specific pages. **Requires an API key.**

```bash
curl -X POST https://search.sh/api/extract \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-sh_your_api_key" \
  -d '{"urls": ["https://en.wikipedia.org/wiki/Vector_database"]}'
```

**Request:** `{ "urls": [string] }` — max 10 URLs. **Cost:** 1 credit per URL; URLs that yield no content are refunded.

**Response:**
```json
{
  "pages": [
    {
      "url": "https://en.wikipedia.org/wiki/Vector_database",
      "title": "Vector database - Wikipedia",
      "content": "A vector database stores data as high-dimensional vectors...",
      "relevance": 0,
      "published_date": "2026-02-11"
    }
  ]
}
```

URLs are SSRF-guarded (internal/metadata addresses are blocked) and responses are size-capped. Content is up to ~12,000 characters per page.

---

### GET /api/images

Image results for a query (Google Images via SerpAPI). Open, IP rate-limited.

```bash
curl "https://search.sh/api/images?q=northern+lights&region=us"
```

**Response:** `{ "images": [{ "thumbnail", "original", "title", "source", "link", "width", "height" }] }`

---

### GET /api/maps

Local places for a query (Google Maps via SerpAPI). Open, IP rate-limited.

```bash
curl "https://search.sh/api/maps?q=spa+near+berlin"
```

**Response:** `{ "places": [{ "title", "address", "rating", "reviews", "type", "thumbnail", "link", "lat", "lng" }] }`

---

### GET /api/credits

```bash
curl https://search.sh/api/credits -H "Authorization: Bearer sk-sh_your_api_key"
```

**Response:** `{ "credits_balance": 487, "costs": { "search:fast": 1, "search:deep": 5, "extract": 1 } }`

### GET /api/health

```bash
curl https://search.sh/api/health
```

**Response:** `{ "status": "ok", "service": "search.sh" }`

---

## Search modes

### Fast (default)
Quick lookups, factual queries, simple questions.
1. Searches Google. 2. Reranks results (semantic + lexical). 3. Extracts the top 3 pages and selects relevant passages. 4. Synthesizes a grounded, cited answer.
**Typical latency:** 4–10s (use streaming for instant first tokens).

### Deep
Research, comparisons, thorough analysis.
1. Generates up to 3 focused sub-queries. 2. Searches Google + DuckDuckGo in parallel with per-provider deadlines and partial-result fallback. 3. Reranks the merged pool. 4. Extracts the top 8 pages and selects passages. 5. Cross-references them into a comprehensive report.
**Typical latency:** 15–35s.

Use deep when you need depth and breadth; fast when speed matters.

---

## Using results well (agent guidance)

- **Trust, with calibration:** treat `confidence` as a real signal. Below ~0.4, double-check or re-query (try `deep`).
- **Prefer verified claims:** when an answer feeds a downstream decision, filter `citations` to `verified: true`.
- **Chain via `related_queries`** to go deeper without crafting new queries yourself.
- **Zero results** return an empty `sources` array, `confidence: 0`, and no charge — handle that branch.
- **Streaming** gives you the answer token-by-token; the final `done` event carries `confidence` and `metadata`.

### Examples

```bash
# Just the answer
curl -s -X POST https://search.sh/api/search -H "Authorization: Bearer sk-sh_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"current price of bitcoin"}' | jq -r '.answer'

# Research, with verified citations only
curl -s -X POST https://search.sh/api/search -H "Authorization: Bearer sk-sh_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"kafka vs rabbitmq vs sqs","mode":"deep"}' \
  | jq '{answer, confidence, verified: [.citations[] | select(.verified) | .claim]}'

# Answer only, no sources/citations
curl -s -X POST https://search.sh/api/search -H "Authorization: Bearer sk-sh_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"what is kubernetes","include_sources":false,"include_citations":false}' | jq -r '.answer'

# Extract a page
curl -s -X POST https://search.sh/api/extract -H "Authorization: Bearer sk-sh_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://docs.example.com/api"]}' | jq -r '.pages[0].content'
```

---

## Response guarantees

- `sources`, `citations`, `related_queries` are always arrays (possibly empty).
- `confidence` is always a number in `[0.0, 1.0]`.
- `metadata.duration_ms`, `metadata.source_count`, and `metadata.sources_found` are always present.
- Citations you receive are never source-contradicted; each carries a `verified` boolean.
- Streaming always terminates with a `done` or `error` event.

---

## Error responses

All errors return JSON with an `error` field, e.g. `{ "error": "query is required" }`.

| Status | Error | When |
|--------|-------|------|
| 400 | `query is required` / `query too long (max 500 chars)` / `invalid JSON body` | Bad request |
| 400 | `urls array is required` / `max 10 URLs per request` | `/extract` input |
| 401 | `Invalid API key format` / `Invalid or revoked API key` | Bad/expired key |
| 402 | `Insufficient credits` (+ `credits_balance`, `credits_required`) | Out of credits |
| 429 | `Free tier limit reached (5 searches/day)…` | Free tier exhausted — add a key |
| 502 | `Search failed. Please try again.` | Upstream search/AI failure (credits refunded) |

---

## Limits

- Query: 500 characters max.
- Results: 1–20 per request (clamped).
- Extract: 10 URLs/request; ~12,000 chars/page.
- Model context: at least 256,000 tokens with the configured deep-research model.
- Answer tokens: up to 6,000 (fast) / 16,000 (deep).
- Request timeout: up to 300 seconds; provider-level retrieval calls have much shorter deadlines.
