Pocket Search
← Back

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.

EndpointDescription
POST /v1/agentSubmit a research question. Set preset (fast, low, medium, high, xhigh) or model. Always returns status: "queued" with the resp_… id.
GET /v1/agent/:idPoll 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/cancelCancel a running response. Returns {"response_id": "…", "status": "cancelling"}. Poll for the terminal cancelled status.
POST /v1/responsesAlias for /v1/agent (OpenAI SDK compatibility).
GET /v1/responses/:idAlias for /v1/agent/:id.
POST /v1/responses/:id/cancelAlias 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.

ToolDescription
start_researchStart 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_researchCheck 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_researchYour 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_researchStop a queued or running run. This does not refund the credits.
get_credit_balanceRemaining 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

PresetResearch gradeCredits
fastFocused (up to 10 min)1
lowFocused (up to 10 min)1
mediumThorough (up to 30 min)2
highThorough (up to 30 min)2
xhighComprehensive (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"
  }
}
HTTPtypeWhen
400invalid_request_errorValidation (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)
401invalid_request_errorMissing or invalid Bearer token
404invalid_request_errorUnknown response ID or wrong owner
429rate_limit_errorConcurrency 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"
  }
}
codeMeaningWhat to do
insufficient_quotaThe 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_limitToo 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.