API Reference
Perplexity Agent API compatible. The response shapes follow the v1 contract.
Authentication
Send your API key as a Bearer token: Authorization: Bearer $POCKET_SEARCH_KEY
Endpoints
Async-only. Every POST /v1/agent returns status: "queued" immediately, then you poll GET /v1/agent/:id until the status is terminal. A run can use the preset's full budget (10m / 30m / 60m) plus a bounded salvage window. A synchronous create() would outlive your HTTP timeout, and a timed-out sync call strands a paid answer whose resp_ id you never received. background: false is rejected with a 400; omit the field or send true.
| Endpoint | Description |
|---|---|
| POST /v1/agent | Submit a research question. Set preset (fast, low, medium, high, xhigh) or model. Always returns status: "queued" with the resp_… id. |
| GET /v1/agent/:id | Poll a response. Completed responses contain the output array with search_results and the Markdown report. Results stay available for 24 hours. |
| POST /v1/agent/:id/cancel | Cancel a running response. Returns {"response_id": "…", "status": "cancelling"}. Poll for the terminal cancelled status. |
| POST /v1/responses | Alias for /v1/agent (OpenAI SDK compatibility). |
| GET /v1/responses/:id | Alias for /v1/agent/:id. |
| POST /v1/responses/:id/cancel | Alias for /v1/agent/:id/cancel. |
MCP
Pocket Search also works as an MCP server at https://pocketsearch.ai/mcp. Use the same Bearer key to authenticate. In Claude, ChatGPT, or any other connector app, add the URL as a custom connector and sign in. Runs use the same credits as the HTTP API.
Async, like everything here. A run takes minutes, so no tool call waits for the research to finish. start_research returns a resp_ id and tells you when to check again (retry_after_seconds, next_check_at). Pick up the report page by page once the run has finished.
| Tool | Description |
|---|---|
| start_research | Start one deep-research run. question, preset (10m / 30m / 60m), and an idempotency_key are required. Credits: 1 / 2 / 4 per preset (trials: 10m and 30m only). |
| get_research | Check on a run (view: "status") or read the finished report (view: "report"). Pages default to about 24,000 characters; follow next_offset until has_more is false. |
| list_research | Your recent runs, newest first, with id, question snippet, and status. Handy for finding an earlier resp_ id again, since a chat in Claude or ChatGPT has no memory of past sessions. |
| cancel_research | Stop a queued or running run. This does not refund the credits. |
| get_credit_balance | Remaining credits, what each preset costs, and which presets this account may use. |
Idempotency is required on starts. Pick a new idempotency_key for each request and reuse the same key when you retry it. The same key with the same request returns the same resp_ id and charges credits once (this works across /v1 and MCP). Reusing a key with different parameters is rejected. Failed runs refund their credits automatically, and results stay available for 24 hours, just like the HTTP API.
Raw JSON-RPC example
# MCP clients (Claude, ChatGPT, Cursor, …) handle the initialize # handshake for you; with raw JSON-RPC, initialize first. curl -X POST https://pocketsearch.ai/mcp \ -H "Authorization: Bearer $POCKET_SEARCH_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"start_research","arguments":{ "question": "State of solid state battery manufacturing, 2026", "preset": "30m", "idempotency_key": "ssb-2026-001"}}}' # 200: the job handle, not the answer. The report arrives via get_research. { "jsonrpc": "2.0", "id": 1, "result": { "structuredContent": { "id": "resp_7f3d…", "status": "in_progress", "preset": "30m", "credits_reserved": 2, "retry_after_seconds": 300, "next_check_at": "2026-09-28T14:05:00Z", "next_step": "The question is NOT answered yet …" } } }
SDK setup
Both SDKs hit /v1/responses on our server, but the baseURL differs because of how each SDK constructs paths.
Perplexity SDK
// No /v1 suffix. The SDK bakes // /v1 into its request paths const client = new Perplexity({ apiKey: POCKET_SEARCH_KEY, baseURL: "https://pocketsearch.ai", });
OpenAI SDK
// Include /v1. The SDK appends // /responses to the baseURL const client = new OpenAI({ apiKey: POCKET_SEARCH_KEY, baseURL: "https://pocketsearch.ai/v1", });
Always poll. Both SDKs' default create() call sends no background field. On our server that means a background run: you get resp_… back immediately and must poll for the result. Research can run the full preset budget, which exceeds both SDKs' default HTTP timeouts, so don't set a short client timeout and wait on create(). Submit, then poll retrieve().
Presets
| Preset | Research grade | Credits |
|---|---|---|
| fast | Focused (up to 10 min) | 1 |
| low | Focused (up to 10 min) | 1 |
| medium | Thorough (up to 30 min) | 2 |
| high | Thorough (up to 30 min) | 2 |
| xhigh | Comprehensive (up to 60 min) | 4 |
Failed runs refund their reserved credits automatically.
Response statuses
- queued
- in_progress
- completed
- failed
- incomplete
- cancelled
Non-terminal: queued, in_progress. Terminal: completed, failed, incomplete, cancelled.
Examples
Submit a research question
curl -X POST https://pocketsearch.ai/v1/agent \ -H "Authorization: Bearer $POCKET_SEARCH_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": "State of solid state battery manufacturing, 2026", "preset": "high" }' # 200. Always immediate, always "queued" (async-only) { "id": "resp_7f3d…", "object": "response", "created_at": 1786767120, "status": "queued", "output": [] }
Poll for result
# The run can take up to the preset's budget (high = 30m). # Poll until terminal. while true; do curl -s https://pocketsearch.ai/v1/agent/resp_7f3d… \ -H "Authorization: Bearer $POCKET_SEARCH_KEY" \ | grep -q '"status": "\(completed\|failed\|incomplete\|cancelled\)"' \ && break sleep 15 done # 200 when status is terminal, then read .output
SDK create + poll (the pattern that works)
// Do NOT await create() to completion. It returns queued immediately, and the run // outlives both SDKs' default HTTP timeouts. Submit, then poll retrieve(). const created = await client.responses.create({ model: "zai/glm-5.1", input: "Solid state battery manufacturing, 2026", preset: "high", }); let resp = created; while (!["completed", "failed", "incomplete", "cancelled"].includes(resp.status)) { await new Promise((r) => setTimeout(r, 15000)); resp = await client.responses.retrieve(created.id); } console.log(resp.output_text); // surfaced on both SDKs (see "Read the answer text")
Completed response
{
"id": "resp_7f3d…",
"object": "response",
"created_at": 1786767120,
"completed_at": 1786768365,
"model": "default",
"status": "completed",
"output_text": "# State of Solid State Battery Manufacturing\n\n…",
"output": [
{
"type": "search_results",
"queries": ["solid state battery manufacturing 2026", "…"],
"results": [
{ "id": 1, "title": "…", "url": "https://…", "source": "web" },
{ "id": 2, "title": "…", "url": "https://…", "source": "web" }
]
},
{
"type": "message",
"id": "msg_a1b2…",
"role": "assistant",
"status": "completed",
"content": [
{
"type": "output_text",
"text": "# State of Solid State Battery Manufacturing\n\nToyota plans…",
"annotations": [
{
"type": "url_citation",
"url": "https://idtechex.com/…",
"title": "IDTechEx",
"start_index": 148,
"end_index": 213
}
]
}
]
}
],
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0,
"pocket_search": {
"mode": "30m",
"credits_charged": 2,
"credits_refunded": 0,
"duration_ms": 1245000,
"steps": 68,
"research_receipt": {
"version": 1,
"queries": [
{"text": "solid state battery manufacturing 2026", "results": [{"url": "https://…", "label": "…", "title": "…"}]}
],
"sourceInvestigations": [{"label": "Toyota production timeline", "sources": [{"url": "https://…", "label": "…"}]}]
}
}
},
"error": null
}Cancel a running response
curl -X POST https://pocketsearch.ai/v1/agent/resp_7f3d…/cancel \ -H "Authorization: Bearer $POCKET_SEARCH_KEY" # 200 { "response_id": "resp_7f3d…", "status": "cancelling" }
Read the answer text
// Both SDKs surface output_text on the poll. OpenAI synthesizes it; the // Perplexity SDK's retrieve() is a passthrough, so we ship it ourselves. const res = await client.responses .retrieve("resp_7f3d…"); console.log(res.output_text); // "# State of Solid State Battery…"
Errors
Errors use the OpenAI envelope:
{
"error": {
"code": null,
"message": "input is required",
"type": "invalid_request_error"
}
}| HTTP | type | When |
|---|---|---|
| 400 | invalid_request_error | Validation (background: false, malformed fields, unknown preset), cancel on terminal, unresolvable previous_response_id, blocked content (the pre-run content screen; the category rides in param) |
| 401 | invalid_request_error | Missing or invalid Bearer token |
| 404 | invalid_request_error | Unknown response ID or wrong owner |
| 429 | rate_limit_error | Concurrency limit. Includes Retry-After header. |
Why a run failed
POST /v1/agent returns queued as soon as the request is valid, so a run that is then refused (no credits left, too many runs in flight) is not an HTTP error. The poll returns status: "failed" and an error object whose code says which, so you do not have to read the message:
{
"status": "failed",
"error": {
"code": "insufficient_quota",
"message": "credit quota exhausted"
}
}| code | Meaning | What to do |
|---|---|---|
| insufficient_quota | The key's credits cannot cover this run. Nothing was charged. (The same code OpenAI uses for running out of credit.) | Add credits, or pick a cheaper preset. |
| concurrency_limit | Too many runs are already in flight for this key (or for the whole API). Nothing was charged. | Wait for a run to finish, then submit again. |
Any other failure has "code": null and a message. Branch on the code when there is one, and treat the message as text for people.