# Creators Toolbox API

A read API over the creator-economy catalog and its typed events — funding rounds, product releases, acquisitions,
hiring, editorial coverage and live launches.

It is built for people making analytics products, developer tools and media
about this market: screen the catalog like an equity screener, then pull a
feed of what those companies actually did.

Base URL: `https://creatorstoolbox.com/api/v1`
Machine-readable spec: `https://creatorstoolbox.com/openapi.json`
Public access and agent guide: `https://creatorstoolbox.com/developers.html`
No-key public page index: `https://creatorstoolbox.com/data/agent-index.json`
CLI: [`@creators-toolbox/cli`](https://github.com/steven-van-opus/Creators-Toolbox/tree/main/cli) — the same data from your
terminal (`creators-toolbox releases figma --since 2026-08-01`).

## Getting a key

```bash
curl -X POST https://creatorstoolbox.com/api/v1/keys \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com"}'
```

The key is shown once. Free tier is **1,000 requests per UTC day**, three
active keys per email address. Send it on every call:

```bash
curl https://creatorstoolbox.com/api/v1/events \
  -H 'Authorization: Bearer ct_live_…'
```

`X-Api-Key: ct_live_…` works too. `GET /api/v1`, `GET /api/v1/meta` and
`POST /api/v1/keys` need no key.

Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Past
the quota you get `429` with `error.code = quota_exceeded`; the counter resets
at 00:00 UTC.

## The feed

```bash
# Everything, newest first
GET /api/v1/events

# Just funding and acquisitions since August
GET /api/v1/events?type=funding,acquisition&since=2026-08-01

# One company's changelog
GET /api/v1/companies/lovable/events
```

An event looks like this:

```json
{
  "id": "evt_9c1f0a2b3d4e5f60",
  "type": "funding",
  "source": "company_news",
  "company_id": "wispr-flow",
  "company": { "id": "wispr-flow", "name": "Wispr Flow", "domain": "wisprflow.ai", "category": "Tool", "url": "https://creatorstoolbox.com/tools/wispr-flow", "logo": "…" },
  "also_about": [],
  "headline": "Wispr Flow Raises a $280M Series B at a $2B Valuation",
  "body": "Wispr Flow raised $280 million in Series B funding…",
  "published_at": "2026-08-17",
  "url": "https://creatorstoolbox.com/blog/wispr-flow-wispr-flow-raises-a-280m-series-b…",
  "source_url": "https://wisprflow.ai/post/series-b",
  "discussion_url": "https://x.com/i/status/2089374511007424595",
  "funding": {
    "amount": 280000000,
    "currency": "USD",
    "round": "Series B",
    "valuation": 2000000000,
    "investors": [{ "name": "Menlo Ventures", "role": "lead" }]
  }
}
```

### Event types

| type | what it is |
| --- | --- |
| `funding` | A raise. Carries the `funding` block. |
| `acquisition` | An acquisition or merger. Carries `deal` when a price was disclosed. |
| `financials` | Revenue, ARR, quarterly results, IPO filings. |
| `partnership` | Integrations and partnerships between companies. |
| `pricing` | Price and plan changes. |
| `product` | Releases, features, changelog entries. The default for a company's own post. |
| `hiring` | Open roles, from the job board. Carries a `job` block. |
| `editorial` | Newsletter and roundup coverage. |
| `launch` | Live launch submissions, merged in at request time. |

### Sources

`company_news` (a company's own announcements), `blog` (editorial),
`jobs`, `launches`.

## The screener

```bash
# Free AI tools, most popular first
GET /api/v1/companies?category=tool&tag=ai&pricing=free

# Companies that shipped something in the last month, busiest first
GET /api/v1/companies?active_since=2026-07-23&sort=events

# Everything from Canada, newest first
GET /api/v1/companies?country=CA&sort=newest
```

Filters: `q`, `category`, `subcategory`, `tag`, `persona`, `pricing`,
`country`, `status`, `min_popularity`, `launched_after`, `launched_before`,
`has_events`, `active_since`. All accept comma-separated values and combine
with AND; multiple values within one filter are OR.

Sorts: `popularity` (default), `name`, `newest`, `events`, `last_event`.

`GET /api/v1/meta` returns every facet value with a count — use it to build
filter UIs without hardcoding the taxonomy.

## Funding

```bash
GET /api/v1/funding?since=2026-01-01&sort=amount
```

Returns funding events plus a summary of the **whole filtered set**, not just
the page:

```json
"summary": {
  "rounds": 6,
  "rounds_with_amount": 5,
  "total_disclosed": 1114000000,
  "currency": "USD",
  "by_round": [{ "round": "Series B", "count": 2 }]
}
```

## MCP server

The same data is exposed as an MCP server so Claude, Cursor and other MCP
clients can query it directly.

- **Endpoint:** `https://creatorstoolbox.com/api/mcp`
- **Transport:** Streamable HTTP (JSON-RPC 2.0 over POST; responses are plain
  JSON rather than SSE, which the spec allows for a request/response server)
- **Auth:** the same bearer key as the REST API

Claude Code:

```bash
claude mcp add --transport http creators-toolbox https://creatorstoolbox.com/api/mcp \
  --header "Authorization: Bearer ct_live_…"
```

Anything that reads `mcp.json`:

```json
{
  "mcpServers": {
    "creators-toolbox": {
      "type": "http",
      "url": "https://creatorstoolbox.com/api/mcp",
      "headers": { "Authorization": "Bearer ct_live_…" }
    }
  }
}
```

### Tools

| tool | what it answers |
| --- | --- |
| `search_companies` | "free AI video tools", "companies from Canada launched this year", "who shipped something last month" |
| `get_company` | One company in full plus its recent events. Takes an id, a domain, or a name — ambiguous names come back as a candidate list. |
| `list_events` | "what happened recently", "what did X ship", "who is hiring" |
| `list_funding` | Rounds with amount, round label, valuation and investors, plus totals for the filtered set |
| `catalog_facets` | The exact category, tag, persona and pricing values the filters accept |

Tool calls are translated into the REST routes above rather than reimplemented,
so filters, sorting, pagination and validation behave identically on both
surfaces. Results come back as `structuredContent` alongside the JSON text
block, and a bad argument (unknown sort, unknown company) returns
`isError: true` with an explanation rather than a protocol error, so the model
can correct itself and retry.

The server's `instructions` tell the client that funding figures are parsed
from announcements and best-effort — worth repeating wherever you surface them.

Check the endpoint with a plain `GET`, which returns the server info and tool
list instead of a protocol error:

```bash
curl https://creatorstoolbox.com/api/mcp
```

## Pagination

Every list response carries a `meta` block:

```json
"meta": { "count": 50, "total": 1281, "limit": 50, "offset": 0, "has_more": true, "next_cursor": "b2ZmXzUw", "generated_at": "2026-08-23T…" }
```

Pass `next_cursor` back as `?cursor=`. `limit` defaults to 50, maximum 200.

## How the data is produced

Companies and events come from a snapshot built at deploy time
(`scripts/build-api-catalog.mjs` → `api/_data/catalog.json`), so reads never
touch a database. `generated_at` on every response is **when the snapshot's
data last changed**, not when the build ran — a deploy that changes nothing
leaves it alone, so the value is a real freshness signal rather than a
redeploy timestamp. Launches are read live from Supabase and cached for 60
seconds.

Event types and funding fields are parsed from announcement text by
`scripts/lib/events.mjs`. It is deliberately conservative — a story only gets
a `funding` block when the text states a raise — but it is still heuristic:
treat `amount`, `valuation`, `round` and `investors` as **best-effort reads of
a press announcement**, not as audited figures. Anything it gets wrong can be
corrected at the source by adding an `event` object to the news item in
`data.ts`:

```ts
news: [
  {
    title: 'Series D',
    body: 'The round closed this week.',
    date: '2026-08-01',
    event: { type: 'funding', amount: 120_000_000, round: 'Series D', investors: ['Index Ventures'] },
  },
]
```

The override always wins over the parser.

## Errors

```json
{ "error": { "code": "quota_exceeded", "message": "Daily quota of 1000 requests reached. It resets at 00:00 UTC." } }
```

| status | code | meaning |
| --- | --- | --- |
| 400 | `bad_request` | Unknown sort key, malformed email. |
| 401 | `missing_api_key` / `invalid_api_key` / `revoked_api_key` | Key problem. |
| 404 | `not_found` | Unknown id or endpoint. |
| 405 | `method_not_allowed` | Wrong verb. |
| 429 | `quota_exceeded` / `too_many_keys` | Slow down. |
| 500 | `internal_error` | Ours. Not charged against quota. |

## Running it locally

```bash
npm run api:catalog   # rebuild api/_data/catalog.json from data.ts
npm run test:api      # routing, filters, pagination, event parsing
npm run test:mcp      # MCP handshake, tool list, every tool call
```

Key issuance and quota need `SUPABASE_SERVICE_ROLE_KEY` and the
`076_api_keys.sql` migration applied. If the quota backend is unreachable the
API keeps serving reads rather than failing closed — quota is not a security
boundary here, the data is public.
