# Search API

Find sources for a question or topic in one call: multi-engine retrieval, full-text fetching, LLM reranking, and an optional cited answer or structured output.

**POST** `/v1/search`

Legacy alias: POST https://searchpipe.tech/search (permanent, same handler and billing).

## Authorize

Authenticate with your sp- API key in the `Authorization: Bearer sp-YOUR_KEY` header; `X-API-Key: sp-YOUR_KEY` is also accepted. Create or rotate keys in [API Keys](https://searchpipe.tech/dashboard/api-keys). Check your remaining balance with `GET /v1/account/balance` — it is free to call and not logged as usage.

## Request

Send `Content-Type: application/json`. Only `query` is required; every other field has a default.

### Search depth and output

**Search depth**

| Depth | Credits / call | Behavior |
| --- | --- | --- |
| `fast` | 0.5 credits | Retrieval, rule fusion, and page fetching. This mode skips all LLM steps, including answers and audits. Keeps fused engine order and raw engine scores. |
| `basic` · default | 1 credit | LLM-ranked results, with an optional cited answer. |
| `advanced` | 2 credits | LLM-ranked results, with statement-level auditing when an answer is generated and audit data is available. |

**Output type**

| Value | What to read |
| --- | --- |
| `search_results` · default | `results`; no answer generation. |
| `answer` | `results` + `answer`, with inline `[n]` citations referencing zero-based result indices. |
| `structured` | `results` + `output`, a JSON object generated using `output_schema`. `answer` is null. |

### Body parameters

**Core search**

| Parameter | Type / default | Description |
| --- | --- | --- |
| `query` | string · required `—` | Search question or keywords; up to 2,000 characters. |
| `max_results` | integer `5` | 1–20. This is a maximum; fewer results may match your request. |
| `search_depth` | string `basic` | fast (0.5 credits), basic (1 credit), or advanced (2 credits). See the mode comparison above. |
| `output_type` | string `search_results` | search_results, answer, or structured. When explicitly set, takes priority over include_answer. |

**Sources and dates**

| Parameter | Type / default | Description |
| --- | --- | --- |
| `include_domains` | string[] \| null `null` | Up to 300 patterns. example.com includes subdomains; *.example.com matches subdomains only; example.com/docs also requires the /docs path prefix. Each pattern is limited to 253 characters. |
| `exclude_domains` | string[] \| null `null` | Up to 150 patterns, using the same matching rules. Exclusions take priority over inclusions. |
| `time_range` | string \| null `null` | day, week, month, or year. A recency hint passed to supporting search engines. |
| `start_date` | YYYY-MM-DD \| null `null` | Inclusive earliest publication date; results without a known publish date are not filtered. |
| `end_date` | YYYY-MM-DD \| null `null` | Inclusive latest publication date; results without a known publish date are not filtered. |

**Topic and market**

| Parameter | Type / default | Description |
| --- | --- | --- |
| `topic` | string `general` | general or news. news adds a news retrieval channel; it does not guarantee every result is a news article. |
| `language` | string \| null `null (auto)` | Market language, such as en; up to 5 characters. Overrides query-language detection and biases retrieval, rather than translating results. |
| `country` | string \| null `null` | Two-letter country bias, such as US. Combined with language: en + de becomes en-DE. This is not a source-country filter. |
| `safe_search` | integer `0` | 0 off, 1 moderate, 2 strict. Passed to upstream engines; coverage depends on the engine. |

**Content and output**

| Parameter | Type / default | Description |
| --- | --- | --- |
| `include_raw_content` | boolean `false` | Include extracted page text in results[].raw_content when available. This is extracted text, not the original HTML. |
| `include_images` | boolean `false` | Run an additional image search. images contains URL/description pairs and may be empty if retrieval fails. |
| `objective` | string \| null `null` | Up to 500 characters describing what matters for ranking. It does not change what the answer covers; fast skips LLM reranking. |
| `output_schema` | object \| null `null` | Required for structured output. A flat object with 1–20 primitive properties; serialized schema up to 4,000 characters. See Structured output for validation limits. |
| `stream` | boolean `false` | REST only. Use with output_type=answer or structured, or include_answer=true. Returns Server-Sent Events. |
| `include_answer` | boolean `false` | Deprecated alias for output_type=answer. Still supported when output_type is omitted; explicit output_type always wins. |

`include_answer=true` remains supported as a deprecated alias for `output_type="answer"`. Explicit `output_type` always wins, including `search_results`.

### Request example

This request returns search results and asks for a cited answer. It uses the default `basic` depth: 1 credit.

**curl**

```
curl -X POST https://searchpipe.tech/v1/search \
  -H "Authorization: Bearer sp-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"FastAPI tutorial","max_results":5,"output_type":"answer"}'
```

**Python**

```
import httpx

resp = httpx.post(
    "https://searchpipe.tech/v1/search",
    headers={"Authorization": "Bearer sp-YOUR_KEY"},
    json={"query": "FastAPI tutorial", "max_results": 5, "output_type": "answer"},
    timeout=60,
)
resp.raise_for_status()
data = resp.json()
print(data["answer"])
for r in data["results"]:
    print(r["score"], r["title"], r["url"])
```

**JavaScript**

```
const resp = await fetch("https://searchpipe.tech/v1/search", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sp-YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query: "FastAPI tutorial", max_results: 5, output_type: "answer" }),
});
if (!resp.ok) throw new Error(`Search failed (${resp.status}): ${await resp.text()}`);
const data = await resp.json();
console.log(data.answer);
for (const r of data.results) console.log(r.score, r.title, r.url);
```

**Go**

```
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"query":          "FastAPI tutorial",
		"max_results":    5,
		"output_type": "answer",
	})
	req, _ := http.NewRequest("POST", "https://searchpipe.tech/v1/search", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer sp-YOUR_KEY")
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{Timeout: 60 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
```

**Java**

```
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class SearchPipeDemo {
    public static void main(String[] args) throws Exception {
        String json = """
            {"query":"FastAPI tutorial","max_results":5,"output_type":"answer"}
            """;
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(10))
            .build();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://searchpipe.tech/v1/search"))
            .timeout(Duration.ofSeconds(60))
            .header("Authorization", "Bearer sp-YOUR_KEY")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();
        HttpResponse<String> resp =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(resp.body());
    }
}
```

**C# (.NET)**

```
using System.Net.Http.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "sp-YOUR_KEY");

var payload = new
{
    query = "FastAPI tutorial",
    max_results = 5,
    output_type = "answer",
};

HttpResponseMessage resp = await client.PostAsJsonAsync("https://searchpipe.tech/v1/search", payload);
resp.EnsureSuccessStatusCode();
Console.WriteLine(await resp.Content.ReadAsStringAsync());
```

**Endpoint versions and aliases**

Versioned endpoints: `POST /v1/search`, `POST /v1/extract`, `POST /v1/verify`, and `POST /v1/verify/batch`. Existing paths `/search`, `/extract`, `/verify`, and `/verify/batch` remain valid aliases permanently, using the same handlers and billing. The account balance check `GET /v1/account/balance` ships under the versioned prefix only.

## Response

The result list is present in every output mode. Optional fields may be null. This illustrative `basic` answer response shows the envelope; values are examples, not a live search.

```
{
  "query": "FastAPI tutorial",
  "results": [{
    "id": "a43f294b8c6d012e",
    "url": "https://fastapi.tiangolo.com/tutorial/",
    "title": "Tutorial - User Guide - FastAPI",
    "content": "A step-by-step guide to using FastAPI.",
    "score": 0.94,
    "raw_content": null,
    "published_date": null,
    "evidence": null
  }],
  "answer": "See the FastAPI tutorial for a step-by-step guide. [0]",
  "ai_generated": true,
  "output": null,
  "answer_audit": null,
  "images": null,
  "request_id": "example-request-id",
  "response_time": 2.4,
  "usage": {"credits_charged": 1.0, "cached": false},
  "warnings": null,
  "notices": null
}
```

**Response fields**

| Field | Meaning |
| --- | --- |
| `query / results` | The query and result list. results may be empty; inspect notices and apply your own result-quality checks. |
| `answer / output` | answer is text for answer mode; output is an object for structured mode. Either can be null when generation is unavailable. Structured mode keeps answer=null. |
| `ai_generated` | Whether a generated answer or structured output was produced. Results-only searches can still use LLM reranking. |
| `request_id / response_time` | REST request identifier (also X-Request-Id) and server processing time in seconds. |
| `usage` | REST billing information: credits_charged and cached. A cache hit still costs credits. |
| `images` | Image URL/description pairs when requested; otherwise null. Image retrieval is best effort. |
| `answer_audit` | Statement-level report for advanced answer mode when available. Null for structured, basic, or fast output. |
| `warnings / notices` | REST string arrays, or null. warnings explain parameters that did not take effect; notices describe cache hits or empty results. Keep warnings for developer diagnostics. |

**Fields on each result**

| Field | Meaning |
| --- | --- |
| `id` | Stable identifier derived from the result URL. |
| `url / title / content` | Source URL, title, and content snippet. content may include supporting excerpts. |
| `score` | LLM relevance score on basic/advanced; raw upstream score on fast. Fast scores are not normalized or comparable across engines. |
| `raw_content` | Extracted page text when requested and available; otherwise null. |
| `published_date` | Source-provided publication date, or null. Date coverage is incomplete. |
| `evidence` | Quote, character offsets, source tier, and fetch metadata when available. May be null. |

On basic/advanced, scores reflect LLM relevance ranking. Fast uses raw engine scores, which can exceed 1 and are not comparable across engines; use the returned order instead of applying a universal score threshold.

## Evidence and audited answers

Search results can carry evidence without a separate verification call. Advanced generated answers can also include a statement-level audit. These are two different layers:

| Layer | What it tells you | Availability |
| --- | --- | --- |
| `results[].evidence` | A verbatim quote, character offsets, source tier, and page-fetch metadata. | Included in search pricing when evidence is available; may be null. |
| `answer_audit` | Checks generated answer statements against their cited sources. | Advanced depth with output_type=answer (or include_answer=true), when audit data is available. No additional audit charge. |

`span_start` is inclusive and `span_end` is exclusive. Offsets refer to extracted page text, or to `content` when evidence falls back to a search snippet. A snippet fallback is not a verified fetch of the original page. Request `include_raw_content=true` when you need the extracted text.

Source tiers are `official`, `press`, `academic`, `preprint`, `community`, or `unknown`. Fetch metadata includes `fetch_status`, `redirected`, `final_url`, and `fetched_at`. A source classification is not a truth verdict.

Audit statements include `text`, `verdict`, `confidence`, and `citations`. Verdicts are `supported`, `partial`, `unsupported`, or `inconclusive`. Statements without a valid citation marker are `uncited` and are not judged.

### Audited answer example

**curl**

```
curl -X POST https://searchpipe.tech/v1/search \
  -H "Authorization: Bearer sp-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What is retrieval-augmented generation?", "output_type": "answer", "search_depth": "advanced"}'

# response.answer_audit (nullable) -> score + per-statement verdicts
# response.results[n].evidence (nullable) -> quote, span, source tier
```

**Python**

```
import httpx

resp = httpx.post(
    "https://searchpipe.tech/v1/search",
    headers={"Authorization": "Bearer sp-YOUR_KEY"},
    json={
        "query": "What is retrieval-augmented generation?",
        "output_type": "answer",
        "search_depth": "advanced",
    },
    timeout=60,
)
resp.raise_for_status()
data = resp.json()
print(data["answer"])
audit = data.get("answer_audit")
print("audit score:", audit["score"] if audit else "unavailable")
for s in (audit or {}).get("statements", []):
    print(s["verdict"], "-", s["text"])
for r in data["results"]:
    evidence = r.get("evidence")
    if evidence:
        print(evidence["quote"], "[", evidence["source_tier"], "]")
```

**JavaScript**

```
const resp = await fetch("https://searchpipe.tech/v1/search", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sp-YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: "What is retrieval-augmented generation?",
    output_type: "answer",
    search_depth: "advanced",
  }),
});
if (!resp.ok) throw new Error(`Search failed (${resp.status}): ${await resp.text()}`);
const data = await resp.json();
console.log(data.answer);
console.log("audit score:", data.answer_audit?.score ?? "unavailable");
for (const s of data.answer_audit?.statements ?? []) console.log(s.verdict, "-", s.text);
for (const r of data.results) {
  if (r.evidence) console.log(r.evidence.quote, "[", r.evidence.source_tier, "]");
}
```

**Go**

```
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"query":          "What is retrieval-augmented generation?",
		"output_type": "answer",
		"search_depth":   "advanced",
	})
	req, _ := http.NewRequest("POST", "https://searchpipe.tech/v1/search", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer sp-YOUR_KEY")
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{Timeout: 60 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body)) // answer_audit.statements + results[n].evidence
}
```

**Java**

```
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class SearchPipeAuditedAnswer {
    public static void main(String[] args) throws Exception {
        String json = """
            {"query":"What is retrieval-augmented generation?","output_type":"answer","search_depth":"advanced"}
            """;
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(10))
            .build();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://searchpipe.tech/v1/search"))
            .timeout(Duration.ofSeconds(60))
            .header("Authorization", "Bearer sp-YOUR_KEY")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();
        HttpResponse<String> resp =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(resp.body()); // answer_audit + evidence fields
    }
}
```

**C# (.NET)**

```
using System.Net.Http.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "sp-YOUR_KEY");

var payload = new
{
    query = "What is retrieval-augmented generation?",
    output_type = "answer",
    search_depth = "advanced",
};

HttpResponseMessage resp = await client.PostAsJsonAsync("https://searchpipe.tech/v1/search", payload);
resp.EnsureSuccessStatusCode();
Console.WriteLine(await resp.Content.ReadAsStringAsync()); // answer_audit + evidence
```

## Structured output

Set `output_type="structured"` and provide `output_schema`. SearchPipe generates a cited answer internally, then asks the model to convert it into a JSON object using your schema. Read the object from `output`; `answer` and `answer_audit` are null.

```
curl -X POST https://searchpipe.tech/v1/search \
  -H "Authorization: Bearer sp-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What is FastAPI and what language is it written in?",
    "output_type": "structured",
    "output_schema": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "language": {"type": "string"}
      },
      "required": ["name", "language"]
    }
  }'
```

Schemas must contain 1–20 flat properties of type `string`, `number`, `integer`, or `boolean`, and serialize to at most 4,000 characters. Nested objects and arrays are not supported. A missing or invalid schema returns `422`.

## Stream an answer

REST requests with `stream=true` return `text/event-stream`. Request an answer or structured output; streaming a results-only request returns `422`. Streaming is available through REST only — the MCP server does not stream.

```
curl -N -X POST https://searchpipe.tech/v1/search \
  -H "Authorization: Bearer sp-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"FastAPI tutorial","output_type":"answer","stream":true}'
```

**SSE events**

| Event | How to use it |
| --- | --- |
| `meta` | Initial request_id, query, and credits_charged. Use the final envelope for final billing. |
| `results` | Search results and optional images, when available. Result IDs may not yet be filled. |
| `delta` | Incremental text; zero or more events. Structured mode may stream the internal answer text, not partial JSON. |
| `done` | Authoritative, complete SearchResponse. Read final answer/output, audit, usage, warnings, and notices here. |
| `error` | Terminal stream failure with error.code, message, and request_id. The call is refunded; do not treat partial text as complete. |

**Example event framing**

```
event: meta
data: {"request_id":"example-id","query":"FastAPI tutorial","credits_charged":1.0}

event: done
data: {"query":"FastAPI tutorial","answer":null,"results":[],"ai_generated":false,"images":null,"answer_audit":null,"output":null,"request_id":"example-id","response_time":2.4,"usage":{"credits_charged":1.0,"cached":false},"warnings":["answer was requested but could not be generated (deadline or upstream failure)"],"notices":["no results matched the request"]}
```

Example with no results: meta goes directly to done. Other requests may include results and delta events before done.

Results or deltas can be absent, including on empty results or generation failure. Cache hits still use SSE, but delta counts can differ. Handle `error` events and a connection closing before `done`, even after HTTP 200. Client disconnection does not automatically refund a request.

## Error handling

Inspect the HTTP status and `error.code`; keep `error.request_id` when reporting a problem. The full status-code table lives in [Errors & retries](https://searchpipe.tech/docs#errors). Search-specific behaviors:

- A null `answer`/`output` or an empty `results` list can arrive with HTTP 200 — check the response before using it.
- `warnings` explain parameters that did not take effect; `notices` describe cache hits or empty results.
- Cache hits (default TTL 300 seconds for identical parameters) are charged at the selected depth.
- Pipeline failures (`502`) and rejected output (`400`) are refunded automatically. A retry is a new request and may incur a new charge.

## Related pages

[Search API product page](https://searchpipe.tech/search-api)[Extract API reference](https://searchpipe.tech/docs/extract)[Verify API reference](https://searchpipe.tech/docs/verify)[MCP Server reference](https://searchpipe.tech/docs/mcp)[Pricing](https://searchpipe.tech/pricing)
