Skip to main content

API Reference

Dewey is a document backend for AI applications. Upload your documents; Dewey handles conversion, chunking, embedding, and retrieval. Your application can focus on what to do with the answers, not on building the infrastructure to find them.

What you can build

The API is organized around four core capabilities:

CapabilityDescription
IngestionUpload PDFs, Word docs, PowerPoint decks, HTML, plain text, and more. Dewey converts, chunks, and embeds each document automatically. You get a status lifecycle and real-time upload events via SSE.
RetrievalHybrid keyword + semantic search with cross-encoder reranking. Returns ranked chunks with section and document provenance.
ResearchAn agentic deep-research endpoint that runs a multi-step tool-call loop over your corpus and streams a cited, markdown-formatted answer over SSE.
ClaimsExtract discrete, importance-scored facts from every document in a collection. Claims are linked to their source section and browsable, filterable, and searchable in the dashboard.
ContradictionsCompare extracted claims across the entire corpus, cluster conflicting statements by severity, and generate suggested resolution instructions. Apply a resolution in one click to keep future research consistent.
DuplicatesIdentify near-duplicate documents by measuring how much content they share. Promote one member of each cluster to canonical and exclude the rest from retrieval and contradiction detection — useful when the same document arrives in multiple versions or from multiple sources.
MCP ServerA Model Context Protocol server exposing your collections as tools. Plug Dewey directly into Claude, Cursor, or any MCP-compatible agent.

Design philosophy

Provenance over summaries. Every answer comes back with the exact document, section, and chunk it was drawn from. Hallucinations are hard to ship when citations are mandatory.

Structure is preserved, not flattened. Dewey understands the heading hierarchy of your documents. Query at chunk, section, or document granularity, whichever your application needs.

Depth is a dial, not a binary. Retrieval, research at four depth levels, and a full agentic loop all share the same API shape. You choose how much compute to spend; the interface stays consistent.

Bring your own model. Research endpoints support OpenAI, Anthropic, and Google Gemini models via BYOK. Dewey manages the tool loop and context assembly; you decide what intelligence runs it.

Base URL

https://api.meetdewey.com/v1

Client libraries

Official SDKs wrap the REST API and handle authentication, retries, and SSE streaming for you.

TypeScript

npm install @meetdewey/typescript-sdkmeetdewey/typescript-sdk

Python

pip install meetdeweymeetdewey/python-sdk

MCP Server

npx @meetdewey/mcpmeetdewey/mcp

Data model

Every document produces three queryable layers that are first-class API primitives:

LayerDescription
MarkdownClean normalized text of the document, available once processing completes.
SectionsThe heading hierarchy: title, level, position, extractive summary, and character offsets into Markdown.
ChunksFixed-size overlapping text segments within each section, each embedded as a vector.

Quickstart#

Create a collection, upload a document, and run your first query. Pick your preferred language below.

# 1. Create a collection
curl -X POST https://api.meetdewey.com/v1/collections \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "My docs", "projectId": "YOUR_PROJECT_ID"}'

# 2. Upload a document
curl -X POST https://api.meetdewey.com/v1/collections/YOUR_COLLECTION_ID/documents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@report.pdf"

# 3. Query
curl -X POST https://api.meetdewey.com/v1/collections/YOUR_COLLECTION_ID/query \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "What are the key findings?"}'
Replace YOUR_API_KEY with a key from your dashboard.

Step-by-step walkthrough

The same five steps in detail, with full error handling:

1. Create a collection

A collection is an isolated corpus. All documents and retrieval are scoped to it.

import { DeweyClient } from '@meetdewey/typescript-sdk'

const client = new DeweyClient({ apiKey: process.env.DEWEY_API_KEY! })

const collection = await client.collections.create({
  name: 'My Docs',
  projectId: process.env.DEWEY_PROJECT_ID!,
})
console.log(collection.id) // 3f7a1b2c-...

2. Upload a document

const doc = await client.documents.upload(
  collection.id,
  new Blob([require('fs').readFileSync('report.pdf')], { type: 'application/pdf' }),
)
console.log(doc.status) // "uploading"

3. Wait for ready

Processing is async. Poll the document or subscribe to SSE events (see Real-time Events).

let ready = false
while (!ready) {
  await new Promise(r => setTimeout(r, 2000))
  const d = await client.documents.get(collection.id, doc.id)
  if (d.status === 'ready') ready = true
  if (d.status === 'error') throw new Error(d.errorMessage ?? 'Processing failed')
}

4. Query

const results = await client.retrieval.query(
  collection.id,
  'What are the key findings?',
  { limit: 5 },
)
for (const r of results) {
  console.log(r.chunk.content, '-', r.document.filename)
}

5. Research (optional)

For grounded, cited answers from an agentic loop:

for await (const event of client.research.stream(
  collection.id,
  'Summarize the key findings and their implications',
  { depth: 'balanced' },
)) {
  if (event.type === 'chunk') process.stdout.write(event.content)
  if (event.type === 'done') console.log('\nSources:', event.sources)
}

Authentication#

Dewey has two auth planes. Most operations use a project API key. A small number of management operations (creating webhooks, managing org membership) require a JWT issued at sign-in.

API keys: data plane

Two keys are provisioned per project: a live key and a test key. Use them for all collection, document, retrieval, research, and provider-key endpoints. Pass the key in the Authorization header:

Authorization: Bearer dwy_live_...   # production
Authorization: Bearer dwy_test_...   # sandbox - same pipeline, isolated data
import { DeweyClient } from '@meetdewey/typescript-sdk'

const client = new DeweyClient({ apiKey: 'dwy_live_...' })

JWTs: management plane

Webhook endpoints and org/project management routes require a short-lived JWT. Obtain one by calling the login endpoint:

// POST /auth/login
{ "email": "you@example.com", "password": "..." }

// Response
{ "token": "eyJ...", "user": { "id": "...", "email": "..." } }

Then pass the token as a Bearer header alongside requests:

Authorization: Bearer eyJ...
JWTs are also available in the Dewey dashboard under Settings → API. For automated scripts that manage webhooks, call POST /auth/login programmatically and cache the token.

Org and project IDs

Management-plane routes include :orgId and :projectId in their paths. These are UUIDs visible in the dashboard under Settings → General, or returned by GET /orgs and GET /orgs/:orgId/projects. One account can belong to multiple orgs; each org can have multiple projects; each project has its own API keys and collections.

Get your API keys from the .

Security

All data is encrypted at rest. PostgreSQL, object storage, and Redis are encrypted at the infrastructure level. Sensitive credentials — project API keys, provider API keys, and webhook signing secrets — are additionally encrypted at the application layer using AES-256-GCM before being written to the database.

Public collections

Collections with visibility: "public" allow unauthenticated reads on /query, /v1/collections/:id/sections/scan, and document reads.

Collections#

A collection is an isolated corpus of documents with its own chunking and embedding settings. All retrieval operations are scoped to a collection.

The Collection object

FieldTypeDescription
idstring (UUID)Unique collection identifier.
namestringDisplay name of the collection.
visibility"private" | "public"Access control. private requires an API key; public allows unauthenticated reads on /v1/collections/:id/query, /v1/collections/:id/sections/scan, and document reads.
descriptionstring | nulloptionalHuman-readable description.
chunkSizenumberToken size for each chunk when splitting documents. Range: 64–4096.
chunkOverlapnumberOverlapping token count between adjacent chunks. Range: 0–512.
embeddingModelstringEmbedding model used to vectorize chunks.
enableSummarizationbooleanWhen true (default), Dewey generates AI summaries for sections with generic titles to improve search quality. Disable during bulk ingestion to skip summarization and speed up processing.
enableCaptioningbooleanWhen true (default), Dewey generates AI captions for images and tables after ingestion. Requires a BYOK provider key; documents are ingested normally without one.
enableRerankingbooleanWhen true (default), search results are re-scored with a cross-encoder for higher relevance. Disable for lower query latency at the cost of some result quality.
enableDeduplicationbooleanWhen true, Dewey detects near-duplicate documents and excludes non-canonical members from retrieval. Off by default. See the Duplicates section.
llmModelstring | nulloptionalDefault LLM model for this collection's post-processing (section summarization, image captioning, table captioning). Overrides the system default. Accepts any model ID from the supported models list. Set to null to use the system default.
instructionsstring | nulloptionalNatural-language guidance injected into the system prompt for every research session on this collection — for example, noting units, preferred sources, or how to handle missing information. Has no effect on basic search queries. Max 4000 characters.
createdAtstring (ISO 8601)Timestamp when the collection was created.
POST/v1/collections

Create a collection.

// Request
{
  "projectId": "proj_...",              // required - from Dashboard → Project Settings
  "name": "Engineering Docs",
  "visibility": "private",              // optional - "private" | "public", default "private"
  "description": "...",                 // optional
  "chunkSize": 512,                     // optional - 64-4096, default 512
  "chunkOverlap": 64,                   // optional - 0-512, default 64
  "embeddingModel": "text-embedding-3-small"  // optional, fixed for the lifetime of the collection
}
const collection = await client.collections.create({
  projectId: 'proj_...',
  name: 'Engineering Docs',
  visibility: 'private',
  chunkSize: 512,
  chunkOverlap: 64,
})
// => { id: '3f7a1b2c-...', name: 'Engineering Docs', ... }
GET/v1/collections

List all collections for the project.

const collections = await client.collections.list()
// => [{ id: '3f7a1b2c-...', name: 'Engineering Docs', ... }, ...]
GET/v1/collections/:id

Fetch a single collection.

const collection = await client.collections.get('3f7a1b2c-...')
// => { id: '3f7a1b2c-...', name: 'Engineering Docs', ... }
GET/v1/collections/:id/stats

Storage and document counts.

The CollectionStats object

FieldTypeDescription
docCountnumberTotal number of documents in the collection.
totalFileSizeBytesnumberCombined size of all uploaded files in bytes.
storageBytesnumberObject storage consumed by raw files and rendered Markdown.
dbBytesnumberDatabase storage consumed by chunks and embedding vectors.
totalSectionsnumberTotal section count across all documents in the collection.
totalChunksnumberTotal chunk count across all documents in the collection.
statusCountsRecord<string, number>Document count per processing status, e.g. { "ready": 42, "error": 1 }. Only statuses with at least one document are included.
summarizedCountnumberNumber of ready documents that have at least one AI-generated section summary.
captionedCountnumberNumber of ready documents that have at least one AI-generated caption chunk (image or table).
claimsExtractedCountnumberNumber of ready documents from which factual claims have been extracted.
totalClaimsCountnumberTotal number of extracted claims in the collection.
PATCH/v1/collections/:id

Update collection metadata.

// Request - all fields optional
{
  "name": "Renamed Docs",
  "visibility": "public",
  "description": "...",
  "chunkSize": 1024,              // 64–4096, affects new documents only
  "chunkOverlap": 128,            // 0–512, affects new documents only
  "enableSummarization": false,   // set to false during bulk ingestion to skip summarization
  "enableCaptioning": false,      // set to false to disable AI image and table captioning
  "enableReranking": false,       // set to false to skip cross-encoder reranking on queries (lower latency)
  "enableDeduplication": true,    // set to true to detect near-duplicate documents (off by default)
  "llmModel": "gpt-4o",          // optional — override default model for summarization and captioning
  "instructions": "All figures are in USD unless stated otherwise."  // optional — injected into research system prompt; set to null to clear
}
const updated = await client.collections.update('3f7a1b2c-...', {
  name: 'Renamed Docs',
  visibility: 'public',
})
// => { id: '3f7a1b2c-...', name: 'Renamed Docs', visibility: 'public', ... }
DELETE/v1/collections/:id

Soft-delete a collection. Returns 204 No Content.

await client.collections.delete('3f7a1b2c-...')
// => 204 No Content

Recompute

When you change a collection's llmModel, existing summaries and captions were generated with the previous model. Use these endpoints to regenerate them with the new model.

POST/v1/collections/:id/recompute/summaries

Reset all AI-generated section summaries and re-enqueue summarization jobs for every ready document. Returns { enqueued: number }.

POST/v1/collections/:id/recompute/captions

Delete all caption chunks (image and table), reset image captions, and re-enqueue captioning jobs for every ready document. Returns { enqueued: number }.

POST/v1/collections/:id/recompute/claims

Delete all extracted claims and re-enqueue claim extraction jobs for every ready document. Also clears the cached claim map. Returns 204 No Content.

// Response  200 OK  (summaries / captions)
{ "enqueued": 42 }

// Response  204 No Content  (claims)
await client.collections.recomputeSummaries('3f7a1b2c-...')
await client.collections.recomputeCaptions('3f7a1b2c-...')
await client.collections.recomputeClaims('3f7a1b2c-...')

Documents#

Documents are ingested through a pipeline: convert → section → chunk → embed → ready. Status updates are emitted in real time (see Events).

Status lifecycle

pending → uploading → processing → sectioned → embedded → ready
                                                         ↓
                                                       error  (retryable)
StatusDescription
pendingDocument has been created but the file has not yet been received.
uploadingFile has been received and is being saved to storage.
processingFile is saved. Dewey is converting it to Markdown and extracting the heading structure.
sectionedSection manifest is ready and queryable. Chunking and embedding are in progress.
embeddedAll chunks have been embedded. Final indexing is in progress.
readyFully indexed. All retrieval and research endpoints can use this document.
errorProcessing failed. Check errorMessage for details. Retryable via the retry endpoint.

The Document object

FieldTypeDescription
idstring (UUID)Unique document identifier.
collectionIdstring (UUID)ID of the collection this document belongs to.
filenamestringOriginal filename as uploaded.
statusstringCurrent processing status. One of: pending, uploading, processing, sectioned, embedded, ready, error.
fileSizeBytesnumber | nullFile size in bytes.
sectionCountnumber | nullNumber of sections detected. Populated after the document reaches "sectioned".
chunkCountnumber | nullNumber of chunks created. Populated after the document reaches "embedded".
contentHashstring | nullSHA-256 hash of the uploaded file. Used for deduplication: if you supply the same hash on upload-url, Dewey skips re-storing the file.
markdownStorageKeystring | nullInternal storage key for the rendered Markdown. Use GET /v1/documents/:id/markdown to fetch the content.
errorMessagestring | nullError details when status is "error". null otherwise.
tagsstring[]User-supplied tags for filtering. Always lowercase and deduplicated. Empty array by default.
metadataobjectArbitrary key-value metadata supplied at upload time or via PATCH. Empty object by default. Supports nested values.
createdAtstring (ISO 8601)Timestamp when the document was created.

Upload flow (recommended)

For large files, get a presigned URL and upload directly from the client, then confirm.

POST/v1/collections/:id/documents/upload-url

Request a presigned upload URL.

// Request
{
  "filename": "q4-report.pdf",
  "contentType": "application/pdf",
  "fileSizeBytes": 2097152,
  "contentHash": "sha256:abc123...",  // optional - enables deduplication
  "tags": ["annual", "finance"],      // optional
  "metadata": { "region": "us" }      // optional
}
const { documentId, uploadUrl } = await client.documents.requestUploadUrl(
  '3f7a1b2c-...',
  { filename: 'q4-report.pdf', contentType: 'application/pdf' }
)

// PUT the file to uploadUrl, then confirm:
await fetch(uploadUrl, { method: 'PUT', body: fileBlob })
await client.documents.confirmUpload('3f7a1b2c-...', documentId)

The UploadUrlResponse object

FieldTypeDescription
documentIdstring (UUID)Document ID to pass to the /confirm endpoint after the upload.
uploadUrlstring | nullPresigned URL for a direct PUT upload, valid for 15 minutes. null when the content hash matched an existing file (deduplication hit).
documentDocument | undefinedoptionalPresent only on a deduplication hit (uploadUrl is null). The existing Document object; no upload or confirm step is needed.
When contentHash matches an already-stored file, the response status is 200 (not 201) and document is populated. Skip the PUT and confirm steps: the document is already ready.
POST/v1/collections/:id/documents/:docId/confirm

Confirm upload complete and start ingestion. Call after PUT to the presigned URL. Optionally set or override tags and metadata at confirm time.

// Request body (all fields optional)
{
  "tags": ["annual", "finance"],
  "metadata": { "region": "us" }
}

Returns the document object (200 OK). Status transitions to "uploading".

Direct upload

POST/v1/collections/:id/documents

Multipart upload. Streams the file to storage and starts ingestion immediately.

Maximum file size is 25 MB. Files larger than that return 413 Payload Too Large. For large files use the presigned URL flow above instead.
curl https://api.meetdewey.com/v1/collections/:id/documents \
  -H "Authorization: Bearer dwy_live_..." \
  -F "file=@report.pdf" \
  -F 'tags=["annual","finance"]' \   # optional JSON-encoded array
  -F 'metadata={"region":"us"}'       # optional JSON-encoded object

// Response  202 Accepted
{ ...document }
import { readFileSync } from 'node:fs'

const file = new Blob([readFileSync('report.pdf')], { type: 'application/pdf' })
const doc = await client.documents.upload('3f7a1b2c-...', file, {
  tags: ['annual', 'finance'],
  metadata: { region: 'us' },
})
// => { id: '9a2c4e6f-...', status: 'uploading', tags: ['annual', 'finance'], ... }
POST/v1/collections/:id/documents/batch

Upload multiple files in one multipart request.

GET/v1/collections/:id/documents

List documents in a collection. Supports pagination via ?limit (default 100, max 500), ?offset, and optional ?status filter. Returns { documents, total }.

const { documents, total } = await client.documents.list('3f7a1b2c-...')
// paginate: client.documents.list('3f7a1b2c-...', { limit: 50, offset: 50 })
// filter:   client.documents.list('3f7a1b2c-...', { status: 'error' })
GET/v1/documents/:id

Fetch document metadata.

const doc = await client.documents.get('3f7a1b2c-...', '9a2c4e6f-...')
// => { id: '9a2c4e6f-...', filename: 'report.pdf', status: 'ready', ... }
GET/v1/documents/:id/wait

Long-poll until the document reaches a terminal state (ready or error), then return the document object. Times out after 5 minutes with 408. Useful for connector platforms (Power Automate, n8n) that need a single blocking action instead of a polling loop.

// Response  200 OK — document finished processing
{ "id": "9a2c4e6f-...", "status": "ready", "sectionCount": 12, ... }

// Response  200 OK — document failed
{ "id": "9a2c4e6f-...", "status": "error", "errorMessage": "...", ... }

// Response  408 Request Timeout — did not finish within 5 minutes
{ "error": "Document did not finish processing within 5 minutes" }
GET/v1/documents/:id/markdown

Fetch the full rendered Markdown. Returns 404 until processing completes.

const markdown = await client.documents.getMarkdown('3f7a1b2c-...', '9a2c4e6f-...')
// => "# Q4 Report

## Executive Summary

..."
GET/v1/documents/:id/sections

Fetch the section manifest: an ordered list of sections with titles, levels, and positions.

const sections = await client.sections.list('3f7a1b2c-...', '9a2c4e6f-...')
// => [{ id: 'b1c2d3e4-...', title: 'Executive Summary', level: 2, ... }]

The Section object

FieldTypeDescription
idstring (UUID)Unique section identifier.
documentIdstring (UUID)ID of the parent document.
titlestringSection heading text.
levelnumberHeading level (1 = h1, 2 = h2, etc.).
positionnumberZero-based index of this section within the document.
summarystring | nullSection summary used by the /sections/scan full-text index. Extractive by default; sections with generic or short titles (e.g. "Introduction", "Chapter 3") receive an AI-generated summary automatically after the document reaches ready.
summaryType"extractive" | "generated" | nullHow the summary was produced. "extractive" means it was pulled verbatim from the section text; "generated" means an AI model wrote it. null until the summary is available.
chunkCountnumberNumber of chunks created from this section.
markdownOffsetStartnumberCharacter offset into the document Markdown where this section begins.
markdownOffsetEndnumberCharacter offset where this section ends.
DELETE/v1/documents/:id

Delete a document and its stored files. Returns 204 No Content.

await client.documents.delete('3f7a1b2c-...', '9a2c4e6f-...')
// => 204 No Content
POST/v1/documents/:id/retry

Retry a document stuck in error status.

const doc = await client.documents.retry('3f7a1b2c-...', '9a2c4e6f-...')
// => { id: '9a2c4e6f-...', status: 'processing', ... }
POST/v1/collections/:id/documents/retry-failed

Retry all documents in a collection that are currently in error status. Returns an array of the documents that were re-queued.

// Response  200 OK - array of re-queued documents (may be empty)
[{ "id": "9a2c4e6f-...", "status": "uploading", ... }, ...]
POST/v1/collections/:id/documents/batch-confirm

Confirm upload complete for multiple documents at once and start ingestion. Used after batch presigned-URL uploads.

// Request
{ "documentIds": ["9a2c4e6f-...", "b3d4e5f6-..."] }   // 1–500 IDs

// Response  200 OK - array of updated document objects
[{ "id": "9a2c4e6f-...", "status": "uploading", ... }, ...]
DELETE/v1/collections/:id/documents/batch

Delete multiple documents and their stored files in one request. Returns 204 No Content.

// Request
{ "ids": ["9a2c4e6f-...", "b3d4e5f6-..."] }   // at least 1 ID

Manage tab (dashboard UI)

Every collection page in the dashboard has a Manage tab — a document table that ties the endpoints below into a single auditing-and-editing surface. Use it to triage, tag, and edit large corpora without writing code; the same operations are available programmatically if you'd rather script them. The Manage and Overview tabs share the same search bar and filters, so switching between them preserves your context.

Capabilities

  • Omnisearch — one bar for filename, tags, and metadata. The tag dropdown injects tag:<name> tokens into the query. Filename matching is fuzzy via POST /documents/search.
  • Bulk operations — add or remove tags and edit metadata across every selected document at once. Up to 500 documents per request via PATCH /documents/bulk.
  • Sticky selection across pagination — paginate without losing your selection. Navigate to any page, refine the filter, multi-select, then act on the union.
  • Single-doc edit panel — click any row to open a side panel for editing that document's tags and metadata. Backed by PATCH /documents/:docId.
  • Tag chips — every row shows its tags as colored chips so you can scan a list visually before drilling in.
Bulk writes affecting more than one document show a confirmation modal listing the destination tags/metadata and the document count. There is no undo, so the modal is the safety net.

Search bar DSL

The search bar accepts a small DSL that compiles into the same query parameters you'd send to POST /documents/search. Tokens AND together; the free-text portion fuzzy-matches filenames. Wrap values that contain spaces in double quotes.

TokenEffect
tag:<name>Restrict to documents with this tag. Multiple tag: tokens AND together.
meta.<key>:<value>Restrict by metadata key/value (e.g., meta.region:us). Multiple meta. tokens AND together.
(free text)Fuzzy match against filenames using trigram similarity. Joined and sent as the q parameter.
tag:annual meta.region:us 2024-q1

Keyboard shortcuts

KeyAction
⌘A / Ctrl-ASelect every document on the current page.
EscClear the current selection.
Click rowOpen the single-doc edit panel.

Update tags and metadata

PATCH/v1/collections/:id/documents/:docId

Update a document's tags and/or metadata. Metadata is shallow-merged with existing values by default.

// Request
{
  "tags": ["q1", "finance"],          // replaces existing tags entirely
  "metadata": { "region": "emea" },   // merged into existing metadata by default
  "replaceMetadata": true             // optional — replace instead of merge
}

// Response  200 OK
{ ...document }
const doc = await client.documents.update(
  '3f7a1b2c-...',
  '9a2c4e6f-...',
  { tags: ['q1', 'finance'], metadata: { region: 'emea' } },
)

Bulk update tags & metadata

PATCH/v1/collections/:id/documents/bulk

Update tags and/or metadata on up to 500 documents in a single request. Metadata is shallow-merged by default.

// Request
{
  "documents": [
    {
      "id": "9a2c4e6f-...",
      "tags": ["q1", "finance"],
      "metadata": { "region": "emea" }
    },
    {
      "id": "b3d4e5f6-...",
      "tags": ["hr"],
      "replaceMetadata": true,    // optional — replace instead of merge
      "metadata": { "year": 2024 }
    }
  ]
}

// Response  200 OK
[ ...documents ]
const docs = await client.documents.bulkUpdate(
  '3f7a1b2c-...',
  [
    { id: '9a2c4e6f-...', tags: ['q1', 'finance'], metadata: { region: 'emea' } },
    { id: 'b3d4e5f6-...', tags: ['hr'], replaceMetadata: true, metadata: { year: 2024 } },
  ],
)

List tags

GET/v1/collections/:id/tags

Return all distinct tags used across documents in a collection, with document counts. Useful for building tag-filter UIs.

// Response  200 OK
{
  "tags": [
    { "name": "annual",  "count": 12 },
    { "name": "finance", "count":  8 }
  ]
}
const { tags } = await client.documents.listTags('3f7a1b2c-...')
// tags[0].name, tags[0].count
POST/v1/collections/:id/documents/search

Fuzzy filename search using trigram similarity. Returns documents whose filenames match the query, ordered by similarity score.

// Request
{
  "q": "annual-report",                  // required — search string
  "limit": 20,                           // optional, 1–100, default 20
  "tags": ["finance"],                   // optional — docs must have ALL these tags
  "anyTags": ["internal", "external"],   // optional — docs must have ANY of these tags
  "metadata": { "region": "us" }         // optional — docs must contain all these key-value pairs
}
// Response  200 OK
[
  {
    "score": 0.82,
    "document": {
      "id": "9a2c4e6f-...",
      "filename": "annual-report-2024.pdf",
      "status": "ready",
      ...
    }
  }
]
const results = await client.documents.searchDocuments(
  '3f7a1b2c-...',
  'annual-report',
  { limit: 10, tags: ['finance'] },
)
// results[0].score, results[0].document.filename

Images#

Images and figures extracted from documents are stored with stable, auth-gated URLs and embedded in the document Markdown as standard ![alt](url) references. Access requires the same collection-level auth as all other document routes.

AI captions (used to make images findable via /query and /research) require a BYOK provider key. Images without captions are still stored and rendered in the document viewer.

The DocumentImage object

FieldTypeDescription
idstring (UUID)Unique image identifier.
sectionIdstring (UUID) | nullID of the section the image appears in.
placeholderstringInternal placeholder used during ingestion (e.g. dewey://img-0).
mimeTypestringMIME type of the image (e.g. image/png).
captionstring | nullAI-generated caption. null until the caption job runs (BYOK required).
positionnumberSequential order of the image within the document.
GET/v1/documents/:id/images

List all images extracted from a document, ordered by position.

// Response  200 OK
[
  {
    "id": "5a9f1892-...",
    "sectionId": "e3d7c1a0-...",
    "placeholder": "dewey://img-0",
    "mimeType": "image/png",
    "caption": "A bar chart showing accuracy vs. model size.",
    "position": 0
  }
]
GET/v1/documents/:id/images/:imageId

Stream raw image bytes with the correct Content-Type. Responses are permanently cached (Cache-Control: public, max-age=31536000, immutable). Pass your API key as a Bearer token for private collections.

Table Captions#

After a document is ingested, Dewey runs an async job that generates a one-to-two sentence AI caption for each table chunk, describing its subject, key columns, and notable values. Captions are stored alongside the chunk and rendered as italicised figcaptions beneath tables in the document viewer.

Table captioning requires a BYOK provider key. Documents without one are ingested normally; captions are simply not generated.

GET /v1/documents/:id/chunks/captions

Returns all chunks that have a caption, ordered by section position then chunk position within the document. Use this to build a content-keyed lookup for rendering captions in your own viewer.

GET/v1/documents/:id/chunks/captions

List all table chunks that have an AI-generated caption, ordered by document position.

// Response  200 OK
[
  {
    "content": "| Quarter | Revenue |\n|---------|---------|\n| Q1 | $2.4M |",
    "caption": "Quarterly revenue table showing Q1 results of $2.4M."
  }
]

Retrieval#

Two retrieval primitives: hybrid chunk search for direct content retrieval, and section scan for lightweight exploration of large corpora without loading chunk content.

POST/v1/collections/:id/query

Hybrid semantic + BM25 search over chunk content. Candidates are ranked by Reciprocal Rank Fusion, then re-scored by a cross-encoder reranker. Returns chunks with full citation lineage.

// Request
{
  "q": "what are the side effects of the treatment?",
  "limit": 10,                          // optional, 1–50, default 10
  "tags": ["annual", "finance"],        // optional — docs must have ALL these tags
  "anyTags": ["internal", "external"],  // optional — docs must have ANY of these tags
  "metadata": { "region": "us" }        // optional — docs must contain all these key-value pairs
}
// Response  200 OK
[
  {
    "score": 0.97,
    "chunk": {
      "id": "c1d2e3f4-...",
      "content": "The most common side effects reported were...",
      "position": 3,
      "tokenCount": 128
    },
    "section": {
      "id": "b1c2d3e4-...",
      "title": "Adverse Events",
      "level": 2
    },
    "document": {
      "id": "9a2c4e6f-...",
      "filename": "clinical-trial-2024.pdf"
    }
  }
]
const results = await client.retrieval.query(
  '3f7a1b2c-...',
  'what are the side effects of the treatment?',
  { limit: 10, tags: ['annual'], metadata: { region: 'us' } },
)
// results[0].score, results[0].chunk.content, results[0].document.filename
The query pipeline runs in three stages: (1) vector search and BM25 full-text search are run in parallel, (2) scores are fused with Reciprocal Rank Fusion over a wider candidate pool, (3) a cross-encoder reranker (bge-reranker-base) re-scores all candidates and returns the top limit results. Reranking runs in-process with no external API call and is on by default.
POST/v1/collections/:id/sections/scan

Full-text search over section titles and summaries. Returns ranked sections without chunk content. Use this to identify which sections to fetch before loading chunks.

// Request
{
  "query": "adverse events and safety profile",
  "top_k": 20,                           // optional, 1–100, default 20
  "tags": ["annual", "finance"],         // optional — docs must have ALL these tags
  "anyTags": ["internal", "external"],   // optional — docs must have ANY of these tags
  "metadata": { "region": "us" }         // optional — docs must contain all these key-value pairs
}
// Response  200 OK
{
  "results": [
    {
      "score": 0.91,
      "section": {
        "id": "b1c2d3e4-...",
        "title": "Adverse Events",
        "level": 2,
        "summary": "This section describes reported adverse events..."
      },
      "document": {
        "id": "9a2c4e6f-...",
        "filename": "clinical-trial-2024.pdf"
      }
    }
  ]
}
const { results } = await client.sections.scan(
  '3f7a1b2c-...',
  'adverse events and safety profile',
  { topK: 20, tags: ['annual'], metadata: { region: 'us' } },
)
// results[0].score, results[0].section.title, results[0].document.filename

Section content and chunks

GET/v1/sections/:id

Fetch a section with its full Markdown content sliced from the document.

// Response  200 OK
{
  ...section,
  "content": "## Adverse Events

The most common side effects..."
}
GET/v1/sections/:id/chunks

Fetch all chunks for a section (embedding vectors excluded).

const chunks = await client.sections.getChunks('b1c2d3e4-...')
// => [{ id: 'c1d2e3f4-...', content: '...', position: 0, tokenCount: 128 }, ...]

Research#

The research endpoint runs a multi-step agentic loop, searching, scanning, and reading sections, then produces a grounded, cited answer. Two variants are available: a streaming endpoint that returns Server-Sent Events, and a buffered /sync endpoint that returns a single JSON response when the answer is complete.

POST/v1/collections/:id/research

Run an agentic research query. Returns a Server-Sent Events stream.

// Request
{
  "q": "What role does silent adult-to-child transmission play in major polio outbreaks?",
  "depth": "exhaustive",              // optional - "quick" | "balanced" | "deep" | "exhaustive", default "balanced"
  "model": "gpt-5.4",                 // optional - defaults to "gpt-5.4" for deep/exhaustive, "gpt-4o-mini" otherwise
  "tags": ["annual", "finance"],      // optional — restrict search to docs with ALL these tags
  "anyTags": ["internal", "public"],  // optional — restrict search to docs with ANY of these tags
  "metadata": { "region": "us" }      // optional — restrict search to docs matching this metadata
}

// Headers required for SSE
Accept: text/event-stream
for await (const event of client.research.stream(
  '3f7a1b2c-...',
  'What role does silent adult-to-child transmission play in major polio outbreaks?',
  { depth: 'balanced', tags: ['annual'], metadata: { region: 'us' } },
)) {
  if (event.type === 'chunk') process.stdout.write(event.content)
  if (event.type === 'done') console.log(event.sources)
}
POST/v1/collections/:id/research/sync

Run an agentic research query and return the complete answer as a single JSON response. Useful for environments that cannot consume Server-Sent Events, such as Power Automate or serverless functions with response-buffering requirements.

// Request — same body as the streaming endpoint
{
  "q": "What role does silent adult-to-child transmission play in major polio outbreaks?",
  "depth": "balanced",             // optional
  "tags": ["annual"],              // optional
  "anyTags": ["internal"],         // optional
  "metadata": { "region": "us" }   // optional
}

// Response
{
  "answer": "...",       // full markdown answer with inline citations
  "sessionId": "uuid",
  "sources": [ /* ResearchSource objects */ ]
}

Depth levels

DepthMax iterationsCreditsDefault modelToolsBest for
quick50.5gpt-4o-miniHybrid searchFast factual lookups
balanced101gpt-4o-miniHybrid searchGeneral questions (default)
deep203gpt-5.4Hybrid search · section scan · section readMulti-document synthesis
exhaustive508gpt-5.4Hybrid search · section scan · section readComprehensive research across large corpora
BYOK required for deep and exhaustive. Configure an OpenAI, Anthropic, or Google Gemini key in your project settings. Quick and balanced use the Dewey-managed key.

Supported models

OpenAI, Anthropic, and Google Gemini models are supported. The model field is routed to the appropriate provider based on the model ID prefix (gpt- / o → OpenAI, claude- → Anthropic, gemini- → Google Gemini). Common choices:

ModelProviderNotes
gpt-5.4OpenAIDefault for deep and exhaustive. Highest quality.
gpt-4o-miniOpenAIDefault for quick and balanced. Fast and cost-effective.
o3OpenAIReasoning model. Good for complex multi-hop questions.
claude-opus-4-7AnthropicMost capable Claude model.
claude-sonnet-4-6AnthropicFast and capable. Good balance of quality and speed.
gemini-2.5-proGoogle GeminiMost capable Gemini model.
gemini-2.5-flashGoogle GeminiFast and cost-effective.
gemini-2.5-flash-liteGoogle GeminiLightest and fastest Gemini model.

SSE events

Each event is a data: {...}\n\n line. Parse with JSON.parse(event.data).

typeFieldsDescription
tool_callquery, tool?Model is calling a tool. tool is undefined for search, "scan_sections", or "get_section_chunks".
chunkcontentStreamed answer token from the final generation pass.
donesessionId, sources[]Research complete. Sources include document and section for each citation.
errormessageAn error occurred. The stream will close after this event.
// Example SSE stream
data: {"type":"tool_call","query":"silent transmission polio"}

data: {"type":"tool_call","query":"Blake2014-PNAS.pdf › Results","tool":"get_section_chunks"}

data: {"type":"chunk","content":"In Congo 2010"}
data: {"type":"chunk","content":", adults had an estimated R of 1.85"}

data: {"type":"done","sessionId":"e4f5...","sources":[
  {"chunkId":"...","filename":"Blake2014-PNAS.pdf","sectionTitle":"Results"},
  {"chunkId":"...","filename":"PatriarcaPA1997.pdf","sectionTitle":"Outbreak Analysis"}
]}

The DoneEvent object

FieldTypeDescription
sessionIdstring (UUID)Identifier for this research session. Use to retrieve or delete the session via the history endpoints.
sourcesarrayCitations used in the answer. Each item identifies the chunk, document, and section the model drew from.

The ResearchSource object

FieldTypeDescription
chunkIdstring (UUID)ID of the cited chunk. Use GET /v1/sections/:sectionId/chunks to fetch all chunks for the parent section.
contentstringText of the cited chunk.
documentIdstring (UUID)ID of the source document.
filenamestringFilename of the source document.
sectionIdstring (UUID)ID of the section the chunk belongs to. Use GET /v1/sections/:id to fetch the full section content.
sectionTitlestringTitle of the section.
sectionLevelnumberHeading level of the section (1 = h1, 2 = h2, etc.).

Research history

GET/v1/collections/:id/research

List the last 50 research sessions for a collection.

The ResearchSession object

FieldTypeDescription
idstring (UUID)Unique session identifier.
querystringOriginal research question.
responsestringFinal answer in Markdown.
depthstringDepth level used for this session.
modelstringModel used for the agentic loop.
sourcesResearchSource[]Citations. Each item is a ResearchSource object with chunkId, content, documentId, filename, sectionId, sectionTitle, and sectionLevel.
createdAtstring (ISO 8601)Timestamp when the session was created.
Point-in-time snapshot. The sources field in a stored ResearchSession is a frozen snapshot captured when the session ran. Documents added, updated, or removed from the collection after that moment do not alter the session record — the same session ID always returns the same answer and sources.
GET/v1/collections/:id/research/:sessionId

Retrieve a single research session by ID. Returns the ResearchSession object.

DELETE/v1/collections/:id/research/:sessionId

Delete a research session. Returns 204 No Content.

Hosted Agents#

Hosted agents are saved configurations that wrap the same agentic loop as /research, but with a fixed identity, prompt, model, and allow-list of collections. You define the agent once via the dashboard or the CRUD API, then invoke it by slug. Each invocation runs the executor against the agent's configured tools and collections, streams events, persists a run record, and enforces credit and concurrency limits.

Agents vs. /research

Both endpoints run the same executor — multi-step tool use, hybrid search, section scanning, and grounded answers with citations. The difference is how the run's configuration gets specified.

/researchHosted Agents
ConfigurationPer-call: query + depth + (optional) tags/metadataSaved per agent: prompt, model, toolset, allow-list, timeout
CallerApplication code that constructs the query inlineApplication code that references an agent by slug
ScopeOne collection per callA pre-approved set of collections per agent
System promptBuilt-in research personaAuthor-supplied; the agent is whatever you make it
PersistenceResearchSession — answer, sources, depthAgentRun + AgentRunStep — full execution trace, replayable
Best forAd-hoc questions where the caller decides everythingRepeatable workflows where ops controls the prompt and tools
If you find yourself building the same query template into every call to /research, you probably want a hosted agent. If your app is short-lived and just needs an answer once, stick with /research.

Agent

FieldTypeDescription
idstring (UUID)Stable UUID. Use this in API calls when you have it; the slug is also acceptable.
slugstringURL-safe identifier matching ^[a-z0-9](?:[a-z0-9-]{0,98}[a-z0-9])?$. Locked after first save — PATCH requires confirmSlugChange: true.
namestringHuman-readable name for the dashboard.
descriptionstring | nulloptionalOptional one-line description.
systemPromptstringAuthoring prompt. Dewey appends a footer at invocation time with allow-list collection IDs, available tool names, and an explicit "untrusted content" notice for tool results — so you do not need to add those yourself.
modelAgentModelSee "Supported models" below.
timeoutMsnumberWall-clock cap per invocation. Capped at 600_000 (10 min) in v1. Default 300_000.
toolsetAgentToolsetBoolean flags for the five tools: search_collection, scan_sections, get_section_chunks, list_documents, get_document. Disable any tool by setting it to false.
allowedCollectionIdsstring[] | nullRestrict the agent to specific collections, or set null to allow all collections in the project. The allow-list is re-resolved against the live agents table on every tool call — updates take effect mid-run.
createdBystring (UUID)User ID of the author.
createdAtstring (ISO 8601)
updatedAtstring (ISO 8601)

Create an agent

POST/v1/orgs/:orgId/projects/:projectId/agents

Create a new agent. Authentication: JWT (admin role on the project). Returns 201 with the Agent object.

// Request
{
  "slug": "compliance-helper",
  "name": "Compliance Helper",
  "description": "Answers questions about our compliance docs.",
  "systemPrompt": "You are a compliance specialist. Cite specific clauses.",
  "model": "claude-sonnet-4-6",
  "timeoutMs": 300000,                // optional
  "toolset": {                        // optional - all true by default
    "search_collection": true,
    "scan_sections": true,
    "get_section_chunks": true,
    "list_documents": false,
    "get_document": false
  },
  "allowedCollectionIds": ["3f7a1b2c-..."]   // null = all collections
}

// 409 if slug collides; 400 if allowedCollectionIds is an empty array.

List, read, update, delete

GET/v1/orgs/:orgId/projects/:projectId/agents

List agents. Returns { agents, hasMore }. Supports limit and offset query params.

GET/v1/orgs/:orgId/projects/:projectId/agents/:agentId

Read a single agent.

PATCH/v1/orgs/:orgId/projects/:projectId/agents/:agentId

Update agent fields. Uses JSON Merge Patch semantics: omitted keys are unchanged, null clears nullable fields, allowedCollectionIds: null widens to all collections. Slug changes require confirmSlugChange: true.

DELETE/v1/orgs/:orgId/projects/:projectId/agents/:agentId

Hard-delete the agent. Cascades to AgentRun and AgentRunStep records via FK. Returns 204.

Invoke (streaming)

POST/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/invoke

Run an agent against a query. Returns a Server-Sent Events stream. Authentication: JWT (project member) or API key (org-scoped).

// Request
{
  "query": "What does our policy say about data residency?"
}

// Headers required for SSE
Accept: text/event-stream
for await (const event of client.agents.stream(
  orgId,
  projectId,
  'compliance-helper',
  { query: 'What does our policy say about data residency?' },
)) {
  if (event.type === 'chunk') process.stdout.write(event.content)
  if (event.type === 'done') console.log(event.runId)
}
Status codes returned before the SSE stream opens: 402 when BYOK is required and missing, 408 when wall-clock timeout fired before any chunk, 422 when the project's monthly credit quota is exhausted, 429 when the org's tier concurrency cap is reached. Once the stream is open, the same conditions arrive as error events.

Invoke (buffered)

POST/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/invoke/sync

Run an agent and return the complete answer as a single JSON response. Useful for environments that cannot consume SSE — Power Automate, Zapier, response-buffering serverless functions.

// Request — same body as /invoke
{
  "query": "What does our policy say about data residency?"
}

// Response
{
  "runId": "uuid",
  "response": "...",       // full markdown with inline citations
  "status": "succeeded",   // or "failed" | "cancelled" | "timeout"
  "sources": [ /* AgentRunSource objects */ ],
  "warnings": []           // populated for timeout / context-budget cases
}
const result = await client.agents.invokeSync(
  orgId,
  projectId,
  'compliance-helper',
  { query: 'What does our policy say about data residency?' },
)
console.log(result.response)
for (const s of result.sources) {
  console.log(`- ${s.filename} § ${s.sectionTitle}`)
}

Preview (no persistence)

POST/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/preview

Run a draft agent definition without saving. Same SSE shape as /invoke but accepts an unsaved draft in the request body and skips persistence — no AgentRun row is written. Counts against credits and concurrency.

// Request
{
  "query": "Draft a release-notes blurb",
  "draft": {
    "systemPrompt": "You write concise release notes.",
    "model": "claude-sonnet-4-6",
    "timeoutMs": 60000,
    "toolset": { "search_collection": true, "scan_sections": false, ... },
    "allowedCollectionIds": ["3f7a1b2c-..."]
  }
}
Used by the dashboard's Try-it rail to let authors prototype an agent before saving. The draft allow-list is enforced just like the live one — there is no path that bypasses authorization.

SSE events

Both /invoke and /preview emit the same event shapes. /invoke/sync waits for done internally and returns its payload as the response body.

FieldTypeDescription
run_started{ runId: string }First event. runId is the AgentRun.id you can later look up via the runs endpoints. /preview emits a synthetic preview-* runId for shape compatibility.
tool_call{ tool, collectionId, args, stepIndex }The model is calling a tool. Fires before each tool execution.
tool_result{ tool, summary, stepIndex }The tool returned. summary is a short human-readable description for UIs; the full content is fed back to the model and not echoed in events.
chunk{ content: string }Streaming text fragment of the assistant response. Concatenate to reconstruct the answer.
warning{ message: string }Non-fatal — the run continues but partial output may be returned. Emitted on context-budget overflow and on wall-clock timeout (in which case the run terminates after the warning with status=timeout).
error{ message: string, code?: string }Terminal — the run failed. Emitted before done with status=failed.
done{ runId, status, response, iterationsUsed }Terminal — always the last event. status is succeeded | failed | cancelled | timeout.

Run history

GET/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/runs

List the agent's runs in reverse chronological order. Returns { runs, hasMore }. Supports limit, offset, and status filter. The agentSnapshot and steps fields are excluded from the list response for performance — fetch a single run to read them.

GET/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/runs/:runId

Read a single AgentRun including the agentSnapshot taken at invocation time. Useful for replay and debugging.

GET/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/runs/:runId/steps

Paginated trace of the run as AgentRunStep records (one row per tool_call, tool_result, or message). Ordered by stepIndex. Useful for rendering an execution timeline.

DELETE/v1/orgs/:orgId/projects/:projectId/agents/:agentSlug/runs/:runId

Delete a run. Cascades to its steps via FK. Returns 204.

Run-step rows older than 90 days are deleted nightly by a maintenance worker. The AgentRun row itself (response, status, token totals) is preserved indefinitely so historical runs remain summarizable even after the per-step trace is pruned.

Supported models

Same provider matrix as /research — OpenAI, Anthropic, and Google Gemini, routed by model-ID prefix. Hosted agents always require a project provider key (BYOK) regardless of model — the Dewey-managed key is never used for agent invocations because each agent's model and prompt belong to the project that owns it.

Claims Beta#

When claim extraction is enabled on a collection, Dewey automatically extracts factual claims from each document as it is processed. Claims are scored by importance (1 = low, 5 = critical). The claim map endpoint uses UMAP to project all claims into a 2-D space for visualization.

The Claim object

FieldTypeDescription
idstringUnique claim ID.
textstringThe extracted factual claim.
sourceTextstring | nullThe verbatim source passage the claim was extracted from.
sectionIdstringID of the section this claim belongs to.
sectionTitlestringTitle of the source section.
documentIdstringID of the source document.
importancenumberImportance score from 1 (low) to 5 (critical).
positionnumberOrdinal position within the document.
GET/v1/documents/:id/claims

List claims extracted from a specific document. Fast JSON response.

Query parameters: minImportance (1–5, default 1).

// Response  200 OK
{
  "documentId": "9a2c4e6f-...",
  "claims": [
    {
      "id": "c1b2a3d4-...",
      "text": "Revenue grew 42% year-over-year in Q3.",
      "sourceText": "Revenue grew 42% year-over-year in Q3, driven by...",
      "sectionTitle": "Financial Results",
      "sectionId": "s1a2b3c4-...",
      "documentId": "9a2c4e6f-...",
      "importance": 4,
      "position": 7
    }
  ]
}
const { claims } = await client.claims.listByDocument('9a2c4e6f-...', {
  minImportance: 3,
})
GET/v1/collections/:id/claims/map

Stream all claims in a collection with their UMAP 2-D coordinates. Responds with text/event-stream.

This endpoint streams SSE events. Set the Accept: text/event-stream header. Progress events are emitted as claims are embedded and projected; the final done event contains the full claim list with x/y coordinates.

SSE events

typePayload fieldsDescription
progresspct: numberProcessing progress 0–100.
donetotal: number, claims: ClaimMapItem[]All claims with UMAP coordinates. Stream ends after this event.
errormessage: stringAn error occurred.

The ClaimMapItem object

FieldTypeDescription
idstringClaim ID.
textstringThe factual claim.
sourceTextstring | nullVerbatim source passage.
documentIdstringSource document ID.
documentNamestringSource document filename.
sectionIdstringSource section ID.
sectionTitlestringSource section title.
importancenumberImportance score 1–5.
xnumberUMAP x coordinate.
ynumberUMAP y coordinate.
for await (const event of client.claims.mapStream('3f7a1b2c-...')) {
  if (event.type === 'progress') console.log(`${event.pct}%`)
  if (event.type === 'done') console.log(`${event.total} claims`, event.claims)
}

Contradictions Beta#

Contradiction detection analyzes all extracted claims in a collection for conflicts, groups them into clusters, and generates a resolution suggestion for each cluster. Detection runs are asynchronous — trigger a run and poll its status, then fetch results once complete.

The Contradiction object

FieldTypeDescription
idstringUnique contradiction ID.
severity"low" | "medium" | "high"Assessed severity of the conflict.
status"active" | "dismissed" | "applied"Resolution status. active = unresolved.
explanationstringNatural-language explanation of the conflict.
clusterTopicSummarystring | nullShort topic label for the cluster of conflicting claims.
suggestedInstructionstring | nullSuggested resolution instruction to append to collection instructions.
claimsContradictionClaimRef[]The conflicting claims with document provenance.
createdAtstringISO 8601 timestamp.
GET/v1/collections/:id/contradictions

List contradictions in a collection. Returns { total, items }.

Query parameters: status (active | dismissed | applied, default active), severity (low | medium | high), limit (1–100, default 20).

const { total, items } = await client.contradictions.list('3f7a1b2c-...', {
  status: 'active',
  severity: 'high',
})
POST/v1/collections/:id/contradictions/detect

Trigger an async contradiction detection run. Returns the run object.

// Response  200 OK
{
  "runId": "r1a2b3c4-...",
  "status": "enqueued",
  "enqueuedAt": "2024-09-01T12:00:00Z"
}
const run = await client.contradictions.detect('3f7a1b2c-...')
console.log(run.runId, run.status)

The ContradictionRun object

FieldTypeDescription
idstringRun ID.
statusstringenqueued | running | completed | failed.
claimsProcessednumber | nullClaims analysed so far.
clustersAnalyzednumber | nullClusters examined.
contradictionsFoundnumber | nullContradictions detected.
modelstring | nullLLM model used for detection.
startedAtstring | nullISO 8601 start timestamp.
completedAtstring | nullISO 8601 completion timestamp.
errorstring | nullError message if status is failed.
createdAtstringISO 8601 creation timestamp.
GET/v1/collections/:id/contradictions/runs/latest

Get the status and stats of the most recent contradiction detection run.

const run = await client.contradictions.getLatestRun('3f7a1b2c-...')
console.log(run.status, run.contradictionsFound)
PATCH/v1/collections/:id/contradictions/:contradictionId

Update a contradiction. Currently used to dismiss it by setting status to 'dismissed'. Returns the updated Contradiction.

// Request
{ "status": "dismissed" }
const updated = await client.contradictions.dismiss('3f7a1b2c-...', 'c1b2a3d4-...')
POST/v1/collections/:id/contradictions/:contradictionId/apply-instruction

Apply a resolution instruction to a contradiction. The instruction is appended to the collection's research instructions. Returns 204 No Content.

Pass a custom instruction in the request body to override the suggested instruction. If omitted, the contradiction's suggestedInstruction is used.

// Request (optional)
{ "instruction": "Use the 2024 annual report as the authoritative source." }

// Response  204 No Content
// Use suggested instruction
await client.contradictions.applyInstruction('3f7a1b2c-...', 'c1b2a3d4-...')

// Override with custom instruction
await client.contradictions.applyInstruction(
  '3f7a1b2c-...', 'c1b2a3d4-...',
  'Use the 2024 annual report as the authoritative source.',
)

Duplicates Beta#

Fuzzy deduplication identifies near-duplicate documents within a collection by measuring how much content they share. Each cluster has one canonical member; the rest are marked near_duplicate and excluded from retrieval and contradiction detection. Detection is off by default — enable it per-collection with enableDeduplication: true.

The DuplicateGroup object

FieldTypeDescription
idstringGroup ID.
canonicalDocumentIdstringID of the document representing this group in retrieval.
detectedAtstringISO 8601 timestamp of detection.
membersDuplicateGroupMember[]Members with filename, relationship, and coverage percentages.

Each member has relationship (canonical | near_duplicate), coverageToCanonical (fraction 0–1), and coverageFromCanonical.

POST/v1/collections/:id/duplicates/detect

Trigger an async deduplication run across every ready document. Returns 409 if a run is already in flight.

// Response  202 Accepted
{
  "runId": "r1a2b3c4-...",
  "status": "pending",
  "jobsEnqueued": 142,
  "enqueuedAt": "2026-04-16T12:00:00Z"
}
const run = await client.duplicates.detect('3f7a1b2c-...')
console.log(run.runId, run.jobsEnqueued)

The DuplicateRun object

FieldTypeDescription
idstringRun ID.
statusstringpending | running | completed | failed.
jobsEnqueuednumber | nullTotal per-document jobs queued for the run.
jobsProcessednumber | nullJobs completed so far.
duplicatesDetectednumber | nullNear-duplicate documents found.
duplicateGroupsCreatednumber | nullDistinct clusters created.
startedAtstring | nullISO 8601 start timestamp.
completedAtstring | nullISO 8601 completion timestamp.
errorstring | nullError message if status is failed.
createdAtstringISO 8601 creation timestamp.
GET/v1/collections/:id/duplicates/runs/latest

Get the status and stats of the most recent deduplication run.

const run = await client.duplicates.getLatestRun('3f7a1b2c-...')
console.log(run.status, run.duplicateGroupsCreated)
GET/v1/collections/:id/duplicates

List duplicate groups with their members. Returns { total, items }.

Query parameters: limit (1–100, default 50), offset (default 0).

const { total, items } = await client.duplicates.list('3f7a1b2c-...', { limit: 20 })
for (const group of items) {
  for (const m of group.members) {
    if (m.relationship === 'near_duplicate') {
      console.log(m.filename, Math.round((m.coverageToCanonical ?? 0) * 100) + '%')
    }
  }
}
PATCH/v1/collections/:id/duplicates/:groupId

Promote a different member to canonical. The previous canonical becomes a near_duplicate. Coverage fields are cleared since they describe the old pairing.

// Request
{ "canonicalDocumentId": "d2e4f6a8-..." }

// Response
{ "success": true, "changed": true }
await client.duplicates.promoteCanonical('3f7a1b2c-...', 'g1h2i3j4-...', 'd2e4f6a8-...')
DELETE/v1/collections/:id/duplicates/:groupId

Disband a duplicate group. All former members rejoin retrieval as distinct documents.

await client.duplicates.disband('3f7a1b2c-...', 'g1h2i3j4-...')

Provider Keys#

Store your own OpenAI, Anthropic, or Google Gemini key per project. When configured, Dewey uses your key for research, bypassing the shared managed key. Keys are encrypted at rest with AES-256-GCM.

Provider key routes authenticate with your project API key, not a JWT. This lets server-side automation manage keys without a browser session.
POST/v1/projects/:id/provider-keys

Store a provider key.

// Request
{
  "provider": "openai",  // "openai" | "anthropic" | "gemini"
  "key": "sk-...",
  "name": "Production key"
}
const key = await client.providerKeys.create('proj_abc123', {
  provider: 'openai',
  key: 'sk-...',
  name: 'Production key',
})
// => { id: 'k1a2b3c4-...', provider: 'openai', keyPreview: 'sk-t...cdef', ... }

The ProviderKey object

FieldTypeDescription
idstring (UUID)Unique key identifier.
provider"openai" | "anthropic" | "gemini"Key provider.
namestringDisplay label.
keyPreviewstringFirst 4 and last 4 characters of the key (e.g. "sk-t...cdef"). Plaintext is never stored or returned.
createdAtstring (ISO 8601)Timestamp when the key was stored.
GET/v1/projects/:id/provider-keys

List stored provider keys. Plaintext is never returned.

const keys = await client.providerKeys.list('proj_abc123')
// => [{ id: 'k1a2b3c4-...', provider: 'openai', keyPreview: 'sk-t...cdef', ... }]
DELETE/v1/projects/:id/provider-keys/:keyId

Remove a provider key. Returns 204 No Content.

await client.providerKeys.delete('proj_abc123', 'k1a2b3c4-...')
// => 204 No Content

Real-time Events#

Subscribe to document status changes as they happen using Server-Sent Events.

GET/v1/collections/:id/documents/events

SSE stream of document status events for a collection. Stays open until the client disconnects.

EventSource cannot send custom headers, so the API key is passed as a query parameter: ?key=dwy_live_...
const es = new EventSource(
  `https://api.meetdewey.com/v1/collections/${collectionId}/documents/events` +
  `?key=${apiKey}`
)

es.onmessage = (e) => {
  const event = JSON.parse(e.data)
  // event.documentId, event.collectionId, event.status, event.sectionCount?
  if (event.status === 'ready') {
    console.log(`${event.documentId} is queryable`)
  }
}

// Ping sent every 20 seconds to keep the connection alive.

The DocumentEvent object

FieldTypeDescription
documentIdstring (UUID)ID of the document that changed.
collectionIdstring (UUID)ID of the containing collection.
statusstringNew document status. Any value from the processing lifecycle.
sectionCountnumberoptionalNumber of sections detected. Present only when status is "sectioned".

Webhooks#

Webhooks let your server receive push notifications when documents finish processing, no polling required. Configure one or more endpoints per project; Dewey will POST a signed JSON payload to each endpoint whenever a subscribed event fires.

Webhook endpoints are managed via the management plane (JWT auth), not an API key. Creating, updating, and deleting endpoints requires admin role. Listing endpoints and deliveries requires member role.
GET/v1/orgs/:orgId/projects/:projectId/webhooks

List all webhook endpoints for a project. Secrets are never returned after creation.

POST/v1/orgs/:orgId/projects/:projectId/webhooks

Create a webhook endpoint. Returns the signing secret exactly once. Store it securely.

const res = await fetch(
  `https://api.meetdewey.com/v1/orgs/${orgId}/projects/${projectId}/webhooks`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${jwtToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: 'https://yourapp.com/hooks/dewey',
      events: ['document.ready', 'document.error'], // omit for all events
      description: 'Production handler',
    }),
  }
)
const { id, secret } = await res.json()
// Save secret - it will not be returned again
PATCH/v1/orgs/:orgId/projects/:projectId/webhooks/:endpointId

Update a webhook endpoint: change the URL, subscribed events, enabled state, or description.

DELETE/v1/orgs/:orgId/projects/:projectId/webhooks/:endpointId

Delete a webhook endpoint and all its delivery history.

GET/v1/orgs/:orgId/projects/:projectId/webhooks/:endpointId/deliveries

List recent deliveries for an endpoint (newest first). Supports ?limit up to 100, default 50.

The WebhookEndpoint object

FieldTypeDescription
idstring (UUID)Unique identifier for this endpoint.
urlstringHTTPS URL that receives POST requests.
eventsstring[]Event types that trigger this endpoint. Empty array means all events.
enabledbooleanWhether this endpoint is active. Disabled endpoints are skipped at dispatch time.
descriptionstringoptionalOptional human-readable label.
secretstringoptionalSigning secret (64-char hex). Returned only in the POST /webhooks response. Store it immediately.
createdAtstring (ISO 8601)When the endpoint was created.
updatedAtstring (ISO 8601)When the endpoint was last updated.

Event types

EventFires when
document.readyA document has finished processing and is fully queryable.
document.errorA document failed at some stage of the ingestion pipeline.

Payload shape

Every webhook POST has Content-Type: application/json and the following top-level shape:

{
  "id": "wh_3f7a1b2c4d5e6f...",   // unique delivery ID
  "type": "document.ready",        // event type
  "created": 1712345678,           // Unix timestamp (seconds)
  "data": {
    "documentId":   "9a2c4e6f-...",
    "collectionId": "3f7a1b2c-...",
    "projectId":    "1a2b3c4d-...",
    // document.error only:
    "error": "Conversion failed: unsupported file format"
  }
}

Signature verification

Every delivery includes an X-Dewey-Signature header containing an HMAC-SHA256 of the raw request body, computed with the endpoint's signing secret:

X-Dewey-Signature: sha256=<hex digest>
X-Dewey-Event-Id:   <delivery UUID>
X-Dewey-Event-Type: document.ready

Verify the signature before processing the event:

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

function verifyWebhook(
  rawBody: string,
  signature: string,
  secret: string,
): boolean {
  const expected = 'sha256=' +
    createHmac('sha256', secret).update(rawBody).digest('hex')
  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}

// Express / Next.js example
app.post('/hooks/dewey', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-dewey-signature'] as string
  if (!verifyWebhook(req.body.toString(), sig, process.env.DEWEY_WEBHOOK_SECRET!)) {
    return res.sendStatus(401)
  }
  const event = JSON.parse(req.body.toString())
  // handle event.type === 'document.ready' etc.
  res.sendStatus(200)
})
Always use a constant-time comparison (timingSafeEqual / hmac.compare_digest) to prevent timing attacks. Dewey retries failed deliveries up to 10 times with exponential backoff; return 2xx as quickly as possible and process the event asynchronously.

The WebhookDelivery object

FieldTypeDescription
idstring (UUID)Unique delivery ID (also sent as X-Dewey-Event-Id).
endpointIdstring (UUID)The endpoint this delivery was sent to.
eventTypestringThe event type that triggered this delivery.
payloadobjectThe full JSON payload that was POSTed.
status"pending" | "success" | "failed"Current delivery status. "pending" while retries are in flight.
attemptsnumberNumber of delivery attempts made so far.
lastAttemptAtstring (ISO 8601)optionalWhen the most recent attempt was made.
responseStatusnumberoptionalHTTP status code returned by your server on the last attempt.
responseBodystringoptionalFirst 1,024 bytes of your server's response body.
createdAtstring (ISO 8601)When this delivery record was created.

MCP Server#

Dewey ships a native Model Context Protocol server. Any MCP-compatible client (Claude, Cursor, custom agents) can search, research, manage collections and documents, extract claims, and detect contradictions directly, with no API integration required. Two ways to connect: a hosted endpoint with OAuth (recommended), or a local install with an API key.

Hosted MCP

Add Dewey as a custom MCP server in your client with this URL:

https://mcp.meetdewey.com/mcp

On first connect, your client redirects you through an OAuth consent screen on app.meetdewey.com. Sign in to your Dewey account, pick the org the client should access, and approve. The client receives a scoped access token — no API key in your client config, no shared secrets.

Scopes

ScopeGrants
dewey:readSearch and read documents, collections, claims, and contradictions.
dewey:fullEverything in dewey:read plus create, update, delete, and run agentic operations (research, contradiction detection, deduplication).

Manage and revoke connected MCP clients from Connected apps in your dashboard.

Local install

Prefer to run the MCP server locally? Add Dewey to your claude_desktop_config.json:

{
  "mcpServers": {
    "dewey": {
      "command": "npx",
      "args": ["-y", "@meetdewey/mcp"],
      "env": {
        "DEWEY_API_KEY": "dwy_live_...",
        "DEWEY_COLLECTION_ID": "3f7a1b2c-..."  // optional - scope to one collection
      }
    }
  }
}
On macOS with Node.js installed via Homebrew or nvm, you may need to add "NODE_EXTRA_CA_CERTS": "/etc/ssl/cert.pem" to the env block above to allow Node to trust system certificates.

Available tools

Search

ToolDescription
dewey_searchHybrid semantic + keyword search over chunk content in a collection. Supports tags, anyTags, and metadata filters.
dewey_scan_sectionsLightweight search over section titles and summaries. Fast corpus exploration without loading chunk content. Supports tags, anyTags, and metadata filters.
dewey_researchRun a full agentic research query with configurable depth. Returns a grounded, cited answer. Supports tags, anyTags, and metadata filters.
dewey_get_sectionFetch the full Markdown content of a section by its ID.
dewey_get_section_chunksFetch all text chunks for a section. Use when you need finer-grained content than full section Markdown.
dewey_get_document_sectionsList all sections in a document — the table of contents with heading levels and section IDs.
dewey_get_document_markdownFetch the full Markdown content of a document. Use for document-level analysis when individual sections are not enough.

Claims

ToolDescription
dewey_list_claimsList factual claims extracted from documents in a collection or a specific document. Claims are scored by importance (1–5). Filter with min_importance to focus on the most significant findings.

Quality

ToolDescription
dewey_detect_contradictionsTrigger an async contradiction detection run. Analyzes all extracted claims for conflicts and generates resolution suggestions.
dewey_get_contradiction_runGet the status of the latest contradiction detection run. Use to poll progress after calling dewey_detect_contradictions.
dewey_list_contradictionsList contradictions detected in a collection — clusters of conflicting claims with severity ratings and suggested resolution instructions.
dewey_resolve_contradictionApply or dismiss a detected contradiction. Applying appends the resolution instruction to collection settings so future research respects it.

Management

ToolDescription
dewey_list_collectionsList all collections in the project.
dewey_create_collectionCreate a new collection in a project with optional chunk size, overlap, and embedding model settings.
dewey_get_collection_statsGet statistics for a collection: document count, storage, section and chunk counts, total extracted claims, and processing status breakdown.
dewey_update_collectionUpdate collection settings: name, description, custom research instructions, visibility, and feature flags.
dewey_describe_collectionAuto-generate a description for a collection using an LLM that reads document filenames and section headings.
dewey_delete_collectionPermanently delete a collection and all its data. Cannot be undone.
dewey_list_documentsList documents in a collection with their processing status.
dewey_get_documentFetch metadata for a single document by ID, including status, tags, and metadata.
dewey_wait_for_documentLong-poll until a document finishes processing (ready or error). Blocks up to 5 minutes. Useful for "upload → wait → query" agentic loops.
dewey_list_document_tagsList all tags used in a collection with document counts. Useful for discovering filter values before searching.
dewey_update_documentUpdate the tags and/or metadata on a document. Metadata is shallow-merged by default.
dewey_delete_documentPermanently delete a document and all its sections, chunks, and extracted claims.
dewey_batch_delete_documentsDelete multiple documents at once. Cannot be undone.
dewey_retry_documentRetry processing a document that failed ingestion.
dewey_retry_failed_documentsRetry all documents in a collection that are currently in error status.
dewey_recompute_summariesRe-run AI section summarization across all documents. Useful after changing the collection LLM model.
dewey_recompute_captionsRe-run AI captioning for all images and tables. Useful after changing the collection LLM model.
dewey_recompute_claimsRe-extract claims from all ready documents. Deletes existing claims and re-runs extraction with the current LLM model.
The MCP server uses the same project API key as the REST API. All tools respect collection visibility. Public collections are accessible without authentication.
Looking for framework integrations? See the Integrations page for LangChain, LlamaIndex, Haystack, Vercel AI SDK, and Streamlit.

CLI#

dewey is a single binary that gives developers terminal-native access to every Dewey capability — upload, retrieval, research, and operations — without writing any code. It is the fastest way to go from an API key to a grounded answer over your own documents.

Install

Download the latest release for your platform from GitHub Releases and move the binary to a directory in your PATH:

curl -fsSL https://raw.githubusercontent.com/meetdewey/dewey-cli/main/install.sh | sh

The script detects your OS and architecture, downloads the correct binary, verifies the SHA-256 checksum, and installs to ~/.local/bin (no sudo required). Override with INSTALL_DIR=/usr/local/bin sh install.sh.

Or download a specific release directly — GoReleaser names archives as dewey_<version>_<os>_<arch>.tar.gz:

# Example: v0.1.0 on macOS arm64
curl -Lo dewey.tar.gz \
  https://github.com/meetdewey/dewey-cli/releases/download/v0.1.0/dewey_0.1.0_darwin_arm64.tar.gz
tar -xzf dewey.tar.gz && mv dewey /usr/local/bin/

Windows users can download the .zip from the Releases page.

Authentication

Set your project API key in the environment. Every data-plane command reads it from there — you never need to pass it on the command line.

export DEWEY_API_KEY=dwy_live_…

The --api-key flag is also accepted (prefer the env var). Commands that require auth exit with code 77 and a hint if the key is missing.

Commands

CommandDescription
dewey uploadUpload one or more files to a collection. Accepts file paths, glob patterns, or stdin (-). Concurrency defaults to 4.
dewey queryHybrid BM25 + vector retrieval. Returns ranked chunks with section and document provenance.
dewey scanLightweight semantic scan over section titles and summaries — cheaper than a full query.
dewey researchAgentic research loop. Streams a cited markdown answer to stdout by default when stdout is a TTY.
dewey watchTail the SSE document-status stream for a collection. Reconnects automatically.
dewey doctorValidate the local environment: API key, DNS, TLS, and a live /collections check.
dewey collectionsSubcommands: list, get, create, update, delete, stats.
dewey docsSubcommands: list, get, markdown, sections, chunks, images, delete, wait.
dewey duplicatesSubcommands: detect, list, resolve, dismiss.
dewey contradictionsSubcommands: detect, list, apply, dismiss.
dewey claimsSubcommands: list, get.
dewey provider-keysSubcommands: list, set, delete.
dewey configSubcommands: get, set, reset, path.

Every command accepts a -c / --collection flag. The last-used collection is cached in ~/.dewey/state.toml so you can omit -c on subsequent commands.

dewey upload

The most-used command. Accepts file paths, glob patterns, or - to read from stdin. Pass --watch to stream live processing events until every document reaches a terminal state, or --wait to block silently (useful in CI).

dewey upload ./papers/*.pdf -c research-papers --watch

Uploading 3 files to research-papers
  [ok] attention.pdf   uploaded  doc_a1
  [ok] bert.pdf        uploaded  doc_a2
  [ok] gpt4.pdf        uploaded  doc_a3
Watching processing events (Ctrl-C to detach):
  [ok] doc_a1  attention.pdf   ready
  [ok] doc_a2  bert.pdf        ready
  [ok] doc_a3  gpt4.pdf        ready
[ok] 3/3 ready in 31s.
FlagDescription
--watchStream live status events and exit when all docs are terminal.
--waitBlock silently until all docs are ready or errored. Useful in CI.
--concurrency NParallel uploads (default 4).
--tag nameAssign tags (repeatable).
--metadata k=vAttach structured metadata (repeatable, JSON values parsed automatically).

dewey research

Streams the agentic answer token-by-token when stdout is a TTY, then prints a citation block. Pass --no-stream to return a single response, or pipe the output to get NDJSON automatically.

dewey research research-papers "how does multi-head attention work?" --depth deep

→ search: multi-head attention mechanism
→ search: scaled dot-product attention
Multi-head attention runs H parallel attention functions...
[continues streaming]

— Citations ——————————————————————————————————
[1] attention.pdf § 3. Multi-Head Attention
[2] attention.pdf § 3.2 Scaled Dot-Product Attention
FlagDescription
--depthquick · balanced · deep · exhaustive (default: balanced). deep and exhaustive require Pro+ and a BYOK provider key.
--modelOverride the model (e.g. gpt-4o-mini).
--stream / --no-streamForce streaming on or off regardless of TTY detection.

dewey doctor

Validates the full environment in one command: API key presence, base URL, DNS resolution, TLS certificate, a live GET /collections check, and telemetry status.

dewey doctor

Checking environment...
  [ok] DEWEY_API_KEY is set (dwy_live_…vJ3K)
  [ok] Base URL → https://api.meetdewey.com/v1 (default)
  [ok] DNS resolves
  [ok] TLS handshake (issued by R12, valid 73 days)
  [ok] GET /collections returned 200 in 142ms (3 collections)
  [ok] Anonymous telemetry: on (DEWEY_TELEMETRY=0 to opt out)
All systems go.

Exits 0 if all checks pass. Exits 1 on any failure with a per-check remediation hint. Point DEWEY_BASE_URL at http://localhost:3000/v1 to run doctor against a local server.

Configuration

The CLI reads ~/.dewey/config.toml on startup. Edit it directly or use dewey config set:

# ~/.dewey/config.toml
default_collection = "research-papers"
output = "human"   # "human" | "json"
color  = "auto"    # "auto" | "always" | "never"
# base_url = "http://localhost:3000/v1"  # uncomment for local dev
Env varDescription
DEWEY_API_KEYProject API key. Required for all data-plane commands.
DEWEY_BASE_URLOverride the API endpoint. Defaults to https://api.meetdewey.com/v1.
DEWEY_TELEMETRYSet to 0 to opt out of anonymous usage telemetry.
NO_COLORDisable ANSI colour output (honoured automatically).

--json mode

Every command supports --json for machine-readable output. The contract is stable across minor CLI versions; breaking changes bump the major version. Streamed commands (research --stream --json) emit NDJSON — one object per event — so they pipe cleanly to jq.

# Stream a research answer and capture only the final done event
dewey research my-collection "question" --stream --json | jq -s 'last'

# List collections as JSON and filter by name
dewey --json collections list | jq '.[] | select(.name == "research-papers")'

# Wait for a document and assert it's ready
dewey --json docs wait doc_abc123 | jq -e '.status == "ready"'

Diagnostic output (progress, status lines) always goes to stderr, so redirecting stdout captures only the data you asked for.

Source code and full flag reference: github.com/meetdewey/dewey-cli.

Usage & Quotas#

Fetch current usage and plan limits for a project. Useful for building quota warnings into your own UI or automation.

GET/v1/usage

Return current usage across all meters for the project. Authenticated with the project API key.

// Response  200 OK
{
  "meters": {
    "documents": { "used": 23,   "limit": 500   },
    "queries":   { "used": 1840, "limit": 2000  },
    "credits":   { "used": 4.5,  "limit": 25    },
    "storage":   { "usedMb": 312, "limitMb": 500 }
  }
}
limit is null when the meter is unlimited on the current plan.

Errors#

All errors return a JSON body with an error field:

{
  "error": "Collection not found"
}
StatusMeaning
400Bad request: invalid or missing parameters.
401Missing or invalid API key or JWT.
402Payment required. Returned when deep/exhaustive research requires a configured provider key (BYOK), or when a plan document/collection quota has been reached.
403Access denied: the key does not have permission for this resource.
404Resource not found.
409Conflict, e.g. duplicate slug.
413Payload too large. File exceeds the 25 MB limit.
422Unprocessable: validation failed.
429Rate or quota exceeded. Query or research credit limits have been reached for the current billing period. Check the Retry-After response header for when the limit resets, or upgrade your plan.
500Internal server error.
503Service degraded: dependency check failed.