Developers
Agent API & MCP
Funded.so exposes a small, well-documented HTTP API and an MCP server so AI agents (Hermes Agent, Claude, Cursor, custom scripts) can check what the library already holds and submit publicly available pitch decks. Everything an agent submits becomes an unpublished draft that a human editor reviews before it goes live.
Overview
| What | Where |
|---|---|
| REST base URL | https://funded.so/api/v1 |
| OpenAPI 3.0 spec | https://funded.so/api/v1/openapi.json |
| MCP server (Streamable HTTP) | https://funded.so/api/mcp |
| Plain-text version of this page | https://funded.so/llms.txt |
| Support | siya@ciya.so |
Both surfaces share one implementation: the REST endpoints are for scripts and HTTP-capable agents, the MCP server wraps the same operations as tools for agents that speak the Model Context Protocol.
Authentication
Staff create keys at /admin/api-keys. Each key has a name, optional expiry, and scopes. The plaintext key is shown once; only a SHA-256 hash is stored. Send it on every request:
Authorization: Bearer fnd_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| Scope | Grants |
|---|---|
| decks:read | Search the published library, run duplicate checks, and read submission status. |
| decks:write | Submit new decks for ingestion into the review queue. |
Operators can also set FUNDED_API_KEYS (comma-separated plaintext keys, each at least 16 characters, starting with fnd_) in the server environment. Those keys have every scope and are the way to authenticate in fixture/demo mode without a database.
Quickstart (REST)
1. Confirm the key works and discover the other URLs.
curl -s https://funded.so/api/v1/me \
-H "Authorization: Bearer $FUNDED_API_KEY"2. Check whether the deck already exists before doing any work.
curl -s "https://funded.so/api/v1/decks/lookup?sourceUrl=https%3A%2F%2Fexample.com%2Fdecks%2Fseries-a.pdf&company=Example%20Robotics" \
-H "Authorization: Bearer $FUNDED_API_KEY"3. Submit the deck. JSON with a direct PDF link is the preferred path.
curl -s -X POST https://funded.so/api/v1/submissions \
-H "Authorization: Bearer $FUNDED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"companyName": "Example Robotics",
"deckYear": 2024,
"evidence": "Founder published the deck on the company blog when announcing the Series A.",
"fundingStage": "series-a",
"idempotencyKey": "https://example.com/decks/series-a.pdf",
"publisher": "Company blog",
"sourceUrl": "https://example.com/decks/series-a.pdf",
"title": "Example Robotics Series A Pitch Deck (2024)"
}'Alternatively upload a file (PDF, PPTX, PPT, or ordered slide images) with multipart/form-data; the total upload is capped at 4MB, so host larger PDFs at a public URL instead.
curl -s -X POST https://funded.so/api/v1/submissions \
-H "Authorization: Bearer $FUNDED_API_KEY" \
-F "file=@deck.pdf" \
-F "title=Example Robotics Series A Pitch Deck (2024)" \
-F "companyName=Example Robotics" \
-F "evidence=Shared publicly by the founder on LinkedIn"4. Poll for progress. The response tells you when slides exist and when an editor has published the deck.
curl -s https://funded.so/api/v1/submissions/<id> \
-H "Authorization: Bearer $FUNDED_API_KEY"{
"created": true,
"submission": {
"adminUrl": "https://funded.so/admin/decks/8d0f…/editor",
"company": {
"name": "Example Robotics",
"slug": "example-robotics"
},
"createdAt": "2026-09-01T20:00:00.000Z",
"id": "8d0f2b0e-1c58-4a5f-9a5b-6b3c4d5e6f70",
"job": {
"error": null,
"id": "…",
"progress": 0,
"status": "queued",
"type": "deck_ingest"
},
"message": "Accepted and waiting for the background worker.",
"publicUrl": null,
"published": false,
"slideCount": 0,
"slug": "example-robotics-series-a-pitch-deck-2024-k3j9xq",
"sourceUrl": "https://example.com/decks/series-a.pdf",
"status": "queued",
"title": "Example Robotics Series A Pitch Deck (2024)",
"updatedAt": "2026-09-01T20:00:00.000Z"
}
}Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /me | decks:read | Verify the key; returns scopes and discovery links. |
| GET | /decks | decks:read | Search the published library (q, stage, industry, year, limit, cursor). |
| GET | /decks/lookup | decks:read | Duplicate check by sourceUrl, company, and/or title across published decks and drafts. |
| POST | /submissions | decks:write | Submit a deck (JSON sourceUrl or multipart file). 202 on create, 200 on idempotent replay, 409 on duplicate URL. |
| GET | /submissions | decks:read | List submissions made with this key (status filter, limit). |
| GET | /submissions/{id} | decks:read | Processing status for one submission. |
| GET | /openapi.json | public | Machine-readable spec for client generation. |
Errors always use the shape { "error": { "code", "message", "details?" } }. Validation problems return 400 with per-field details; 401 means a missing or bad key, 403 a missing scope, 409 a duplicate source URL (the existing deck is in details.deck), 413 an oversized upload, 429 a rate limit (honour Retry-After).
Submission fields
| Field | Required | Notes |
|---|---|---|
| sourceUrl | yes for JSON | Direct https link to the PDF file, not a landing page. Private hosts and non-PDF responses are rejected. |
| title | yes | Human title, e.g. “Airbnb Seed Pitch Deck (2009)”. |
| companyName | yes | Used to find or create the company record. |
| companyWebsite | no | Set on newly created companies. |
| fundingStage | no | Stage slug or name: pre-seed, seed, series-a, series-b, growth… |
| deckYear / amountRaised / currency | no | Round facts if the source states them. |
| publisher / publishedOn / evidence | no | Provenance. Evidence is what lets an editor approve quickly. |
| description / notes / rightsHolder | no | Context for the reviewer. |
| idempotencyKey | no | Reuse the same key on retries (the source URL works well) to avoid duplicate drafts. |
| agent | no | Your agent name/version, stored for provenance. |
Submission lifecycle
| Status | Meaning |
|---|---|
| queued | Accepted and waiting for the background worker. |
| processing | The pipeline is rendering pages and extracting text right now. |
| needs_review | Slides were extracted. An editor still has to review and publish the draft. |
| manual_review | The source (legacy .ppt) needs manual conversion by an editor before slides exist. |
| published | The deck is live in the public library. |
| failed | Processing failed. Read `job.error`, fix the source, and submit again. |
| blocked | The file matches a content hash that was removed after a rights request. It will not be published. |
| dry_run | Fixture/demo mode processed the source without persisting anything. |
Processing usually completes within a minute for PDFs under 50 pages. Poll every 15–30 seconds; there is no webhook yet. `needs_review` is the normal end state for an agent: publication is a human decision.
MCP server
The MCP server speaks the Streamable HTTP transport at https://funded.so/api/mcp. It is stateless: every JSON-RPC POST carries the bearer key, no session id is required, and plain JSON responses are returned (no SSE stream). Tools:
| Tool | Scope | Description |
|---|---|---|
| search_decks | decks:read | Search the published Funded.so pitch deck library by free text, funding stage, industry, or year. Use it to learn what is already in the library before submitting. |
| lookup_deck | decks:read | Check whether a deck already exists (published or awaiting review) by exact source URL, company name, and/or title. Call this before submit_deck to avoid duplicates. |
| submit_deck | decks:write | Submit a publicly available pitch deck PDF for ingestion. The deck is created as an unpublished draft, its pages are rendered and text-extracted, and an editor reviews it before publication. |
| get_submission | decks:read | Fetch the processing status of a submission made with this API key. Statuses: queued, processing, needs_review, manual_review, published, failed, blocked, dry_run. |
| list_submissions | decks:read | List recent submissions made with this API key, optionally filtered by status. |
mcp_servers:
funded:
url: "https://funded.so/api/mcp"
headers:
Authorization: "Bearer fnd_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
timeout: 300
connect_timeout: 60After saving the config, run `hermes mcp test funded` to confirm the connection and `/reload-mcp` inside an active session to pick up the tools. Hermes reads servers from the top-level mcp_servers key only.
{
"mcpServers": {
"funded": {
"headers": {
"Authorization": "Bearer fnd_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
"type": "http",
"url": "https://funded.so/api/mcp"
}
}
}curl -s -X POST https://funded.so/api/mcp \
-H "Authorization: Bearer $FUNDED_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Agent playbook
- Search or lookup first. If the company or exact source URL is already in the library or the review queue, do not submit again.
- Only submit decks that are genuinely public: published by the company or a founder, filed with a regulator, or reported by reputable press. Never upload leaked, paywalled, or confidential material.
- Send the direct PDF URL, not an article page. If the PDF sits behind a viewer, find the underlying file link or upload the file.
- Fill in evidence and publisher. Editors approve quickly when the provenance is one click away.
- Use idempotencyKey (the source URL is a good choice) so network retries never create duplicate drafts.
- Poll status instead of resubmitting. A failed job includes job.error explaining what to fix.
- Stay within 20 submissions and 120 reads per minute per key. Back off on 429 responses.