Developer documentation

Build with SearchPipe

Search the web, get cited answers, and inspect the evidence. Connect your agent through MCP or call the REST API from your application.

Make your first search

Create an account, verify your email, then copy your default key from API Keys. Both integration paths use the same search pipeline and credit balance. The free plan provides 1,000 credits per UTC calendar month; sign in with a verified account each month to renew the allowance.

REST API

For applications, scripts, and custom agents. Send JSON with your key in a header.

POST https://searchpipe.tech/v1/search

Authorization: Bearer sp-YOUR_KEY
Also accepts X-API-Key: sp-YOUR_KEY.

MCP Server

For Claude Code, Cursor, Codex, and other MCP clients. Add this remote server URL.

https://searchpipe.tech/mcp/?api_key=sp-YOUR_KEY

Streamable HTTP transport. Connection options →

REST example

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

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"}'
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"])
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);
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))
}
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());
    }
}
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());
API checklist
  1. Get an API key

    Sign up and verify your email — a default sp- prefixed API key is generated automatically on the API Keys page of the dashboard.

  2. Send a POST request

    POST https://searchpipe.tech/v1/search with Content-Type: application/json and authenticate with Authorization: Bearer sp-YOUR_KEY or X-API-Key: sp-YOUR_KEY.

  3. Choose the search and output modes

    Send query (required). max_results defaults to 5 (range 1–20). Choose fast (0.5 credits), basic (1), or advanced (2), and output_type=search_results, answer, or structured. Fast skips answer generation.

  4. Read the structured response

    Read results and the requested answer or output. Optional generation and evidence fields can be null. Inspect warnings, notices, and usage; cache hits still cost credits.

  5. Handle errors and retries

    Fix 422 validation errors before retrying. For 402, add credits; for 429, follow Retry-After. Pipeline failures and rejected output are refunded. A retry is a new request and may incur a new charge.

Choose a search mode and output

search_depth controls processing and cost. output_type controls what you receive alongside the result list.

Search depth
DepthCredits / callBehavior
fast0.5 creditsRetrieval, rule fusion, and page fetching. This mode skips all LLM steps, including answers and audits. Keeps fused engine order and raw engine scores.
basic · default1 creditLLM-ranked results, with an optional cited answer.
advanced2 creditsLLM-ranked results, with statement-level auditing when an answer is generated and audit data is available.
Output type
ValueWhat to read
search_results · defaultresults; no answer generation.
answerresults + answer, with inline [n] citations referencing zero-based result indices.
structuredresults + output, a JSON object generated using output_schema. answer is null.
Fast returns results only. Requesting an answer or structured output with fast still returns results, but answer/output stay null. REST responses explain this in warnings.

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

Connect through MCP

Use https://searchpipe.tech/mcp/?api_key=sp-YOUR_KEY, or use https://searchpipe.tech/mcp/ with an Authorization: Bearer sp-YOUR_KEY header if your client supports it. Treat a URL containing your key as a credential.

claude mcp add --transport http searchpipe "https://searchpipe.tech/mcp/?api_key=sp-YOUR_KEY"

The search tool is searchpipe_search. It accepts the search parameters below, except stream. The searchpipe_verify tool supports separate claim verification. The searchpipe_account_balance tool returns the remaining credit balance and is free to call.

REST API vs MCP Server
AspectREST APIMCP Server
Best forYour application or backendAn MCP-compatible agent or client
TransportHTTPS + JSON; optional SSEStreamable HTTP (MCP)
ParametersFull search request, including streamSearch parameters except stream
ResponseSearch content and request metadataSame search content; REST-only request_id, response_time, usage, warnings, and notices remain null
Billing0.5 / 1 / 2 credits by depthSame search costs and shared credit balance
CachingShared search cache; cache hits are chargedSame cache and billing behavior
Let your agent configure the connection

In API Keys, use “Copy one-line setup” to get a prompt containing your MCP link. Setup instructions are also available at /agent-setup/SKILL.md.

Read the 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
FieldMeaning
query / resultsThe query and result list. results may be empty; inspect notices and apply your own result-quality checks.
answer / outputanswer 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_generatedWhether a generated answer or structured output was produced. Results-only searches can still use LLM reranking.
request_id / response_timeREST request identifier (also X-Request-Id) and server processing time in seconds.
usageREST billing information: credits_charged and cached. A cache hit still costs credits.
imagesImage URL/description pairs when requested; otherwise null. Image retrieval is best effort.
answer_auditStatement-level report for advanced answer mode when available. Null for structured, basic, or fast output.
warnings / noticesREST 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
FieldMeaning
idStable identifier derived from the result URL.
url / title / contentSource URL, title, and content snippet. content may include supporting excerpts.
scoreLLM relevance score on basic/advanced; raw upstream score on fast. Fast scores are not normalized or comparable across engines.
raw_contentExtracted page text when requested and available; otherwise null.
published_dateSource-provided publication date, or null. Date coverage is incomplete.
evidenceQuote, 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:

LayerWhat it tells youAvailability
results[].evidenceA verbatim quote, character offsets, source tier, and page-fetch metadata.Included in search pricing when evidence is available; may be null.
answer_auditChecks 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.

Read score and coverage together. answer_audit.score is the supported fraction of judged statements. Uncited statements are excluded from that denominator; inspect uncited_count and individual statements as well. All-uncited answers score 0. Structured output does not include answer_audit.
Advanced answer example in six languages
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
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"], "]")
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, "]");
}
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
}
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
    }
}
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.

Validate the object in your application. The service checks that model output is a non-empty JSON object; it does not enforce every property type, required field, or factual value. Conversion failure returns output=null with a REST warning. Use basic or advanced; fast skips generation.

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.

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}'
EventHow to use it
metaInitial request_id, query, and credits_charged. Use the final envelope for final billing.
resultsSearch results and optional images, when available. Result IDs may not yet be filled.
deltaIncremental text; zero or more events. Structured mode may stream the internal answer text, not partial JSON.
doneAuthoritative, complete SearchResponse. Read final answer/output, audit, usage, warnings, and notices here.
errorTerminal 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.

Extract pages or verify a claim

Extract known URLs

POST /v1/extract retrieves extracted text for 1–20 public URLs (up to 2,048 characters each). It returns results with url/raw_content, and failed_results with url/error. Individual URL failures do not fail the whole batch. Private-network URLs are blocked.

curl -X POST https://searchpipe.tech/v1/extract \
  -H "Authorization: Bearer sp-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://fastapi.tiangolo.com/tutorial/"]}'

Default cost: 1 credit per URL. Failed URLs are refunded. Extract does not use the search-result cache or SearchResponse envelope.

Verify claims from your own pipeline Early access

For separately supplied claims and citations, use POST /v1/verify, POST /v1/verify/batch, or the MCP tool searchpipe_verify. Search evidence and advanced answer audits work within /search; they do not require this extra call.

Verification returns verdict, confidence, reason_code, and evidences. inconclusive is a valid billed outcome. Pipeline failures return 503 and are refunded.

Verification request and verdict reference
FieldTypeDescription
claimstring · requiredThe claim (statement or citation) to verify — max 2000 characters.
modestring · default autoEvidence route: paper (DOI/arXiv), web, or auto — a DOI or arXiv signal picks the paper route.
evidence_urlsstring[] · optionalUp to 5 caller-supplied citations to verify; URLs are SSRF-checked.
max_evidenceinteger · default 5Maximum number of evidence items to return (1-10).
supported
Fetched evidence whose text explicitly supports the claim.
partial
The evidence supports part of the claim, or the support is weak.
unsupported
Reachable evidence does not support the claim — catches misattributed or hallucinated citations.
inconclusive
No verdict possible: fetch failures, paywalls or insufficient checkable evidence. A valid deliverable, billed like any other verdict.

Reason codes: doi_match (The claim resolved to a DOI that points to a real paper.); title_match (The claim matched a known paper title (fuzzy).); quote_span_found (A verbatim span supporting the claim was located in a fetched source.); no_supporting_evidence (Sources were reachable but none of them supports the claim.); fetch_failed (Candidate sources could not be fetched.); paywall (Candidate sources sit behind a paywall.); low_authority (Only low-authority sources were available, so confidence stays low.); conflicting_evidence (The evidence points in more than one direction.).

Full verification contract and examples →

Official SDKs

The Python SDK is published on PyPI: pip install searchpipe. The TypeScript SDK is not yet published on npm; its source is available in the official integration repository. You can always use the HTTP examples for the current API contract.

SDK client examples

These examples show the existing SDK interface. Typed support can lag behind the live API; check supported fields before adopting a source package.

from searchpipe import SearchPipe

client = SearchPipe(api_key="sp-YOUR_KEY")
response = client.search("FastAPI tutorial", include_answer=True)
print(response.answer)
import { SearchPipe } from "searchpipe";

const client = new SearchPipe({ apiKey: "sp-YOUR_KEY" });
const response = await client.search({ query: "FastAPI tutorial", includeAnswer: true });
console.log(response.answer);

Errors and retries

For REST API errors, inspect the HTTP status and error.code. Keep error.request_id when reporting a problem. The existing detail field is retained; for 422, it contains validation details.

{
  "detail": "Insufficient credits",
  "type": "error",
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits",
    "request_id": "example-request-id"
  }
}
StatusMeaningAction
400Input or output content rejectedRevise the content. Do not retry the same rejected input. Rejected output is refunded.
401Missing or invalid credentialsCheck the key and authentication header.
402Insufficient creditsCheck your remaining credits with GET /v1/account/balance (free to call), then add credits before retrying.
403Email not verified or access deniedVerify your email or check account permissions.
422Invalid request parametersFix fields in detail, such as a missing output_schema or invalid streaming mode.
429Rate limit exceededRespect Retry-After and reduce concurrency.
502Search pipeline failureAutomatically refunded. Retry with backoff.
503Service busy or verification pipeline failedRetry with backoff. A failed pipeline call is refunded.

A null answer/output or an empty result list can arrive with HTTP 200. Check the response before using it. Retrying is a new request and may incur a new charge.

Rate limits and billing

  • Search credits: fast 0.5, basic 1, advanced 2 per call. Requesting an answer, images, raw content, or an available advanced audit does not add a separate search charge.
  • Balance checks are free: GET /v1/account/balance (or the MCP tool searchpipe_account_balance) returns your remaining credits — not billed and not logged as usage.
  • Cache hits are charged. The shared search cache avoids rerunning the pipeline, but does not waive the per-call credit cost. Read usage.credits_charged for the REST request.
  • Refunds: pipeline failures and rejected output are automatically refunded. A successful empty result or unavailable optional answer is not automatically a failed billable request.
  • Limits: the default is 100 requests per minute per API key, with a burst allowance of 20. REST and MCP share the same limit. On HTTP 429, follow Retry-After.
  • Cache lifetime: default 300 seconds for matching search parameters. A cache hit still requires authentication, sufficient credits, and rate-limit checks.

Current pricing and credit plans → · View your usage →

More guides