Reference
The COB API
Everything on this page is the live production contract. Base URL https://api.cob.farm, one bearer token, JSON in and out. If you can make an HTTP request, you can build on COB, and if you use Python, pip install cob-farm makes it three lines.
Authentication
Every request carries your API key as a bearer token. Keys are created in the console; the secret is shown once at creation and only a hash is stored. Keys draw on your account's prepaid credit balance.
Authorization: Bearer cob_sk_live_...
Quickstart
Uploads are quoted before ingestion and you are charged exactly the quote, never more. If your balance can't cover a batch, the commit returns 402 with the exact shortfall and nothing is charged or ingested.
Endpoints
| Route | What it does |
| GET /health | Liveness. No auth. Returns {"ok": true}. |
| GET /v2/pricing | Current rates: per-query price and the per-document / page / image / table upload rates. Rates can be fractions of a cent; each file total rounds up to whole cents once. Always authoritative, read it instead of hardcoding prices. |
| GET /v2/silos | List your silos. |
| POST /v2/silos | Create a silo. Body {"name": "deal-room"} → {"silo_id": "silo_..."}. Save the id. |
| GET /v2/silos/{silo_id} | Silo detail with live_status (complete / processing / ingesting / error) and progress (0-1). Poll this after committing an upload. |
| POST /v2/documents | Stage a batch: send filenames, receive presigned S3 upload URLs. |
| POST /v2/documents/commit | Quote the staged batch, charge credits, begin ingestion. |
| POST /v2/chat | Ask a silo a question. Returns a page-cited answer. |
| GET /v2/silos/{silo_id}/documents | List the documents in a silo (your originals only). |
| DELETE /v2/silos/{silo_id}/documents/{filename} | Remove one document from a silo. The corpus rebuilds; poll until complete. |
POST /v2/documents → upload files → POST /v2/documents/commit
Streams whitespace keepalive bytes while the quote computes (long documents take a while to scan), then one JSON object. Await the full body, do not line-stream; leading whitespace is legal JSON. Failures arrive as {"error": ...} in a 200 body, exactly like chat. Uploading is a three-beat flow. Stage: tell COB the filenames; it returns a batch_id and one presigned S3 POST per file (valid 1 hour, 1 GB/file, 25 files/batch, PDF·DOCX·PPTX). Upload: POST each file's bytes to its presigned URL, a plain multipart form using the returned fields plus your file as file. Commit: COB inspects the staged bytes, quotes the batch (pages, described images, visual pages, table pages, priced per GET /v2/pricing; a visual page is a page carrying a table, a real image, or math content, and tables counts table pages, including scanned pages that likely carry tables), checks your balance, and on success debits the quote and begins ingestion, returning 202.
| Field | Type | Notes |
| silo_id | string | Target silo. Required on stage and commit. |
| filenames | string[] | Stage only. The names you're about to upload. |
| batch_id | string | Commit only. From the stage response. |
Commit responses: 202 {"quoted_cents", "files": [{filename, quoted_cents, pages, images, visual_pages, tables, scanned_pages}]} · 402 {"quoted_cents", "balance_cents", "shortfall_cents", "files"} (nothing charged) · 409 if the silo is mid-ingestion. Then poll GET /v2/silos/{silo_id} until live_status is complete.
POST /v2/chat
Queries run COB‑2 Deep Search, a real agentic process, so turns routinely take one to several minutes on large corpora. The connection stays open the whole time: the response streams keepalive whitespace while the agent works, then delivers one complete JSON object. Leading whitespace is legal JSON, so every standard client (requests, fetch, the SDK, your agent framework) that waits for the body and parses it behaves identically to a normal JSON API. Don't line-stream this endpoint; just await the body.
| Field | Type | Notes |
| silo_id | string | Silo to ask. Required. |
| query | string | The question. Required. |
| conversation_id | string? | Pass a previous response's id for multi-turn context (kept 24h). Omit to start fresh, one is minted for you. |
Response: {"answer", "conversation_id", "cost_cents"}. Citations arrive inline in the answer text as [document.pdf, Page N]. Set your HTTP client timeout generously, 900 seconds is safe.
GET /v2/silos/{silo_id}/documents
Lists what's in a silo: your original uploads, nothing else. The pipeline's internal working files never appear here. Response: {"silo_id", "count", "documents": [{"filename", "size_bytes"}]}, sorted by filename. Use the returned filename values verbatim with the DELETE route below.
DELETE /v2/silos/{silo_id}/documents/{filename}
Removes one document and everything derived from it: the file itself, its extracted content, and its vectors all leave the silo, so deleted material can never appear in answers again. The silo walks through processing while the corpus rebuilds; the call returns 202 immediately and you poll GET /v2/silos/{silo_id} until live_status is complete. URL‑encode the filename in the path. One deletion at a time per silo: while one is rebuilding, further deletes (and uploads) return 409, wait for complete between them. Upload credits are not refunded on deletion.
Responses: 202 {"accepted", "silo_id", "filename", "status"} · 404 if the silo or document is not found · 409 if the silo is busy.
Errors
| Status | Meaning |
| 400 | Malformed request, missing field, unsupported file type, or too many files. |
| 401 | Missing, malformed, or revoked API key. |
| 402 | Insufficient credits. Body carries quoted_cents / balance_cents / shortfall_cents. Nothing was charged. |
| 404 | Silo not found or not yours; on deletion, also a document that is not in the silo. |
| 409 | Silo busy: an ingestion or deletion is in flight. Wait for complete, then retry. |
| 502 | An upstream stage failed (agent or file manager). Nothing was charged for the failed call; retry. |
| 200 + {"error"} | A failure that occurred mid-stream on /v2/chat arrives inside the JSON body. Check for an error key; the Python SDK raises it as an exception automatically. |
All error bodies are {"error": "human-readable reason", ...}.
Pricing
Prepaid credits, integer cents, no surprises. Queries are a flat rate per call. Uploads are quoted per batch before ingestion from what's actually inside your files, per document, per page, per image, per detected table, and the commit response itemizes it per file. Current rates always come from GET /v2/pricing; treat that endpoint as the source of truth rather than copying numbers from anywhere, including this page. When the balance can't cover an action, the action doesn't happen, you are never billed behind your back.
Python SDK
pip install cob-farm → import cob. The SDK wraps everything above, including the staged upload dance and the streaming chat handling.
| Surface | Does |
| cob.Client(key) | Connect. Or set COB_API_KEY and call cob.Client(). |
| client.pricing() / client.silos() | Rates · your silos. |
| client.create_silo(name) | Make a new silo → Silo. Save silo.id. |
| client.get_silo(id) | Attach to an existing silo → Silo. |
| silo.upload(paths, wait=False) | Stage + upload + commit in one call → Receipt (.total_cents .files .batch_id). |
| silo.wait(timeout=None) | Block until ingestion completes, printing live progress %. quiet=True to silence. |
| silo.status / silo.progress | Live state, same numbers the console pills show. |
| silo.ask(q, conversation_id=None) | → Answer (.text .cost_cents .conversation_id). |
| silo.documents() | List the silo's documents (0.1.3+) → [{"filename", "size_bytes"}]. |
| silo.delete_document(filename) | Remove one document (0.1.2+). Returns the silo, so silo.delete_document("old.pdf").wait() blocks until the rebuild completes. |
| cob.upload(key, silo_id, paths) / cob.ask(key, silo_id, q) | Flat one-call tier, no objects. |
| cob.InsufficientCredits, cob.SiloBusy, ... | Typed exceptions; the credits one carries .shortfall_cents. |