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

WhatWhere
REST base URLhttps://funded.so/api/v1
OpenAPI 3.0 spechttps://funded.so/api/v1/openapi.json
MCP server (Streamable HTTP)https://funded.so/api/mcp
Plain-text version of this pagehttps://funded.so/llms.txt
Supportsiya@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
ScopeGrants
decks:readSearch the published library, run duplicate checks, and read submission status.
decks:writeSubmit 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"
Example submission response (202 Accepted)
{
  "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

MethodPathScopePurpose
GET/medecks:readVerify the key; returns scopes and discovery links.
GET/decksdecks:readSearch the published library (q, stage, industry, year, limit, cursor).
GET/decks/lookupdecks:readDuplicate check by sourceUrl, company, and/or title across published decks and drafts.
POST/submissionsdecks:writeSubmit a deck (JSON sourceUrl or multipart file). 202 on create, 200 on idempotent replay, 409 on duplicate URL.
GET/submissionsdecks:readList submissions made with this key (status filter, limit).
GET/submissions/{id}decks:readProcessing status for one submission.
GET/openapi.jsonpublicMachine-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

FieldRequiredNotes
sourceUrlyes for JSONDirect https link to the PDF file, not a landing page. Private hosts and non-PDF responses are rejected.
titleyesHuman title, e.g. “Airbnb Seed Pitch Deck (2009)”.
companyNameyesUsed to find or create the company record.
companyWebsitenoSet on newly created companies.
fundingStagenoStage slug or name: pre-seed, seed, series-a, series-b, growth…
deckYear / amountRaised / currencynoRound facts if the source states them.
publisher / publishedOn / evidencenoProvenance. Evidence is what lets an editor approve quickly.
description / notes / rightsHoldernoContext for the reviewer.
idempotencyKeynoReuse the same key on retries (the source URL works well) to avoid duplicate drafts.
agentnoYour agent name/version, stored for provenance.

Submission lifecycle

StatusMeaning
queuedAccepted and waiting for the background worker.
processingThe pipeline is rendering pages and extracting text right now.
needs_reviewSlides were extracted. An editor still has to review and publish the draft.
manual_reviewThe source (legacy .ppt) needs manual conversion by an editor before slides exist.
publishedThe deck is live in the public library.
failedProcessing failed. Read `job.error`, fix the source, and submit again.
blockedThe file matches a content hash that was removed after a rights request. It will not be published.
dry_runFixture/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:

ToolScopeDescription
search_decksdecks:readSearch 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_deckdecks:readCheck 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_deckdecks:writeSubmit 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_submissiondecks:readFetch the processing status of a submission made with this API key. Statuses: queued, processing, needs_review, manual_review, published, failed, blocked, dry_run.
list_submissionsdecks:readList recent submissions made with this API key, optionally filtered by status.
Hermes Agent — ~/.hermes/config.yaml
mcp_servers:
  funded:
    url: "https://funded.so/api/mcp"
    headers:
      Authorization: "Bearer fnd_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    timeout: 300
    connect_timeout: 60

After 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.

Claude Desktop / Cursor / other MCP clients
{
  "mcpServers": {
    "funded": {
      "headers": {
        "Authorization": "Bearer fnd_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
      },
      "type": "http",
      "url": "https://funded.so/api/mcp"
    }
  }
}
Smoke test with curl
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

  1. Search or lookup first. If the company or exact source URL is already in the library or the review queue, do not submit again.
  2. 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.
  3. 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.
  4. Fill in evidence and publisher. Editors approve quickly when the provenance is one click away.
  5. Use idempotencyKey (the source URL is a good choice) so network retries never create duplicate drafts.
  6. Poll status instead of resubmitting. A failed job includes job.error explaining what to fix.
  7. Stay within 20 submissions and 120 reads per minute per key. Back off on 429 responses.