Deep Water Docs

Deep Water API

A deterministic research pipeline you call over REST. Submit a query, get back a verified report with every claim cited to its source span. Pay for tokens actually consumed — minimum $0.10 per job.

Base URL

https://api.deepwater.live

Auth

Authorization: Bearer <key>

Status

Live

Authentication

Every request is authenticated with an API key. Create a key from the dashboard at admin.deepwater.live. Keys are scoped per user and can be revoked at any time without affecting other keys.

curl https://api.deepwater.live/v1/research \
  -H "Authorization: Bearer dw_live_…" \
  -H "Content-Type: application/json" \
  -d '{"query":"market share of electric vehicles in the EU in 2025","depth":"standard"}'

Treat keys like passwords. Never embed a key in front-end code — use the dashboard to create per-environment keys and rotate on a schedule.

Quickstart

  1. Create an API key in the dashboard.
  2. POST a query to /v1/research. You’ll get back a job id and an initial queued status.
  3. Choose how you want to be notified of progress: WebSocket (stream) or webhook (HTTP callback). Both can run in parallel.
  4. Read the final report with GET /v1/research/:id.

Create research

POST /v1/research

Submits a new research job. The response returns immediately with 202 Accepted and the job id — the actual research runs asynchronously.

{
  "query": "summarise recent developments in ultrasound neuromodulation",
  "depth": "deep",
  "webhook_url": "https://your-app.example.com/hooks/deepwater",
  "task_model": "gpt-5.4-nano",
  "report_model": "gpt-5.4",
  "search_provider": "serpapi"
}

Request body

FieldTypeNotes
querystringRequired. Plain-language research question.
depthstringOne of light, standard, deep, heavy.
webhook_urlstringOptional HTTPS URL to receive the terminal callback.
task_modelstringOptional override for the model that handles scoping, extraction tasks, and verification.
report_modelstringOptional override for the final synthesis model.
search_providerstringOptional. Defaults to account setting.

Response

{
  "id": "res_01H9XZY5",
  "status": "queued",
  "depth": "deep",
  "created_at": "2026-04-11T02:30:18.912Z",
  "config": {
    "max_urls": 500,
    "max_rounds": 8,
    "max_sub_questions": 8,
    "max_output_tokens": 8000,
    "min_output_tokens": 3000,
    "section_synthesis": true
  }
}

Get research

GET /v1/research/:id

Fetches the full job snapshot including the current status, sub-questions, sources, usage totals, and — once complete — the final markdown report.

{
  "id": "res_01H9XZY5",
  "status": "complete",
  "query": "...",
  "report": "# Ultrasound neuromodulation ...",
  "sources": [ { "url": "https://...", "title": "..." } ],
  "sub_questions": [ { "id": "sq_1", "question": "...", "status": "complete" } ],
  "usage": {
    "cloud_calls": 3,
    "cloud_prompt_tokens": 18240,
    "cloud_completion_tokens": 4210,
    "cloud_cost_usd": 0.41,
    "urls_fetched": 487,
    "gather_rounds": 6
  }
}

Cancel research

POST /v1/research/:id/cancel

Cancels an in-flight job. You’ll only be charged for work already completed. Jobs that have already entered the verifying phase cannot be cancelled — they’re nearly done anyway.

Depth presets

Each preset maps to a budget for sub-questions, gather rounds, and URLs. Heavier presets search more of the web and synthesise longer, more structured reports.

DepthSub-questionsGather roundsURLsTypical price
light32~501 token
standard55~2003 tokens
deep88~5008 tokens
heavy1212~100015 tokens

These are typical minimums. Complex research with long synthesis or heavy paywalls may cost more. 1 token = $1.00.

WebSocket progress

Open a WebSocket to watch a job in real time. As the engine transitions between phases (scope → gather → synthesise → verify) and completes gather rounds, you’ll receive JSON events on the socket. The stream closes on its own when the job reaches a terminal status.

GET wss://api.deepwater.live/v1/research/:id/stream

Connecting

// Browser
const socket = new WebSocket(
  `wss://api.deepwater.live/v1/research/${jobId}/stream`,
);

socket.onmessage = (event) => {
  const payload = JSON.parse(event.data);
  console.log(payload.kind, payload.status, payload.message);
};

socket.onclose = () => {
  console.log('stream closed, fetch final report');
};

Event schema

{
  "jobId": "res_01H9XZY5",
  "kind": "phase_enter" | "phase_exit" | "scope_outline" | "gather_round" | "synthesis_token" | "terminal",
  "phase": "scope" | "gather" | "synthesise" | "verify",
  "status": "queued" | "scoping" | "gathering" | "synthesising" | "verifying" | "complete" | "failed",
  "message": "Searching, fetching and extracting sources",
  "data": { "urls": 487, "rounds": 6 },
  "at": "2026-04-11T02:31:04.221Z"
}

Event kinds

KindWhen it’s emitted
phase_enterEngine has transitioned into a new phase.
scope_outlineSub-questions have been identified. data.outline contains them.
gather_roundGather phase completed. data.urls, data.rounds, data.artifacts.
synthesis_tokenReserved for token-level streaming in a later release.
terminalJob reached complete, failed, or cancelled. The server closes the socket immediately after.

Reconnection

If the socket drops, re-open it against the same job id — you’ll immediately receive a phase_enter event reflecting the current phase, followed by any future events. If the job already finished while you were disconnected, the socket will send a final terminal event and close. Always use GET /v1/research/:id as the source of truth for the final report.

Webhook callbacks

Include webhook_url in the create-research request and we’ll POST to that URL when the job reaches a terminal status. Webhooks are delivered with up to 3 retries and exponential backoff. Use this when you don’t want to hold a WebSocket connection open.

Request format

POST https://your-app.example.com/hooks/deepwater
Content-Type: application/json
X-Deepwater-Signature: sha256=2c1d…

{
  "event": "research.complete",
  "job_id": "res_01H9XZY5",
  "status": "complete",
  "report": "# Ultrasound neuromodulation ..."
}

On failure:

{
  "event": "research.failed",
  "job_id": "res_01H9XZY5",
  "status": "failed",
  "failure_reason": "scope phase timed out after 600s"
}

Signature verification

Every webhook carries an X-Deepwater-Signature header. Verify it with the shared secret assigned when you created the key:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

Retries

If your endpoint returns a non-2xx or the request times out, we retry up to 3 times with exponential backoff (100ms, 400ms, 900ms). Keep the handler idempotent — you may see the same job_id more than once, and should dedupe by job_id + event.

CLI

The Deep Water CLI is a thin wrapper around the REST API. Install globally and point it at your key.

npm install -g @unlikeotherai/deepwater
export DEEPWATER_API_KEY=dw_live_…
deepwater research "recent breakthroughs in room-temp superconductors" --depth deep --stream

The --stream flag opens the WebSocket and prints phase progress to the terminal until the job completes.

MCP server

Deep Water ships an MCP (Model Context Protocol) server so agents and IDE integrations can drive the full API surface — create research, stream progress, manage keys, pull usage — as named tools. Drop-in config for Claude Code, Cursor, Windsurf, and any other MCP-aware client.

{
  "mcpServers": {
    "deepwater": {
      "command": "npx",
      "args": ["-y", "@unlikeotherai/deepwater", "mcp"],
      "env": { "DEEPWATER_API_KEY": "dw_live_..." }
    }
  }
}

Cost model

You pay for the LLM tokens a job actually consumes plus a small fixed infrastructure cost per URL processed, with a 1.5× margin. A hard $0.10 minimum applies so the smallest jobs cover their fixed cost. The live calculator on deepwater.live reflects the exact formula we use.

Errors

Errors follow the RFC 7807 Problem Details format.

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://deepwater.live/problems/invalid-request",
  "title": "Invalid request",
  "status": 400,
  "detail": "depth must be one of light, standard, deep, heavy"
}