Developer docs
Programs and assistants can do for a network what its members do in the app: import the roster, plan and run launches, answer the agent’s questions, edit drafts, and triage pitches. Approving stays with the members, in the app.
Four ways in
| Way in | What it is | Reached with |
|---|---|---|
| REST API | Every resource of a network under /api/v1, for scripts in any language, described by an OpenAPI 3.1 document. | An API key |
| TypeScript SDK | A typed method for every route, and helpers that wait for long work, in Node, Bun, Deno, and Workers. | An API key and the network’s ID |
| MCP server | 23 tools for assistants such as Claude Code, over Streamable HTTP at /mcp. | An API key |
| Skills and plugin | Two Agent Skills that plan launch promos and triage guest pitches, and a Claude Code plugin that installs them with the MCP server. | An API key in CROSSFEED_API_KEY |
What a program can and can’t do
An API key acts for its network. It reads the network and today’s usage, renames the network and sets its CPM, imports the roster and settles its shows, plans launches and starts their runs, answers the agent’s questions, edits draft insertion orders, exports approved ones as CSV, reads the evidence of every Qloo call, loads the inbox, starts pitch runs, and edits or discards drafted replies.
Only a member of the network, signed in to the app, approves an insertion order or a reply. No route or tool approves either (ADR 0002, approval stays in the app): an API key is held by a program that may run unattended, and a plan or an email never ships without a person. An order exports only once a member has approved it; until then its export answers 409 conflict. Members, invites, API keys, the network’s own Qloo key, deleting a launch, and deleting the network are the app’s too.
When a run needs a decision, such as the slot budget, it waits on a question for a person. A program relays the question and answers with that person’s words.
API keys
An owner makes a key in the network’s settings, under API keys, and sees its secret once. The settings show the network’s ID under the keys. Send the key with every request:
Authorization: Bearer cf_live_…A key reaches its own network alone: to it, every other network is the same 404 not_found. Keys don’t expire. Revoking one stops it at once, and its next request answers 401 unauthorized. A member’s session reaches the API too, from Crossfeed’s own pages; programs use a key.
Long work
An import, a comp search, measuring the roster’s overlap, profiling its listeners, an inbox load, and a run answer 202 at once with the resource as it stands; read the resource until the work settles. A run is running, or waiting on its question; then completed, or stopped or failed with the reason in error. With one of the network’s API keys in CROSSFEED_API_KEY and its ID in CROSSFEED_NETWORK_ID, both from its settings, import a made-up roster:
curl -X POST "https://crossfeed.m1kuorg.workers.dev/api/v1/networks/$CROSSFEED_NETWORK_ID/roster" \
-H "Authorization: Bearer $CROSSFEED_API_KEY" \
-H 'Content-Type: text/csv' \
--data-binary $'name,downloads\nDeep Cuts,12000\nField Notes,8000\n'It answers 202, with the new shows pending. Read the roster until its status is settled:
curl "https://crossfeed.m1kuorg.workers.dev/api/v1/networks/$CROSSFEED_NETWORK_ID/roster" -H "Authorization: Bearer $CROSSFEED_API_KEY"Limits
| Limit | How much |
|---|---|
| Requests | 120 requests a minute per API key, the MCP server’s included; past it, 429 rate_limited |
| Model runsa launch run, an inbox load, or a pitch run | 10 a day per network; past it, 429 limit |
| Qloo calls | 2,000 a day per network, counted on the network’s own Qloo key apart from everyone else’s once an owner adds one |
| A run | At most 12 model turns and 60 Qloo calls |
| Launches | At most 100 a network keeps at once |
| API keys | At most 10 a network keeps in use at once |
| Roster | At most 50 shows |
| Inbox | At most 50 pitches of up to 10,000 characters each |
| Uploads | 5 MB a body |
A day is a UTC day, and counts reset at 00:00 UTC. A network counts as one visitor of its own, so its members’ pages and its keys share its daily counts, beside the limits for everyone in the beta terms. Settings shows the network’s usage, and so does GET /api/v1/networks/{networkId}/usage.
Errors
Every refusal is JSON, with a stable code to branch on and a message for a person that says what to do:
{
"error": {
"code": "conflict",
"message": "The roster already has shows. Import with replace=true to import over it: shows already on it keep their matches."
}
}| Code | Status | When |
|---|---|---|
invalid | 400 | The request, or a value in it, can’t be used as it is. The message names the field. |
unauthorized | 401 | No API key, or one that is unknown or revoked. |
forbidden | 403 | The caller can’t do this, such as an API key deleting a launch. |
not_found | 404 | No such resource, or one the key can’t reach, such as another network. |
conflict | 409 | The resource’s state refuses it, such as an import over a roster without replace=true. |
too_large | 413 | An upload over 5 MB. |
unreadable | 422 | An upload that can’t be read, such as a CSV with no name column. |
limit | 429 | A daily cap, the network’s or all of Crossfeed’s. Counts reset at 00:00 UTC. |
rate_limited | 429 | Too many requests a minute with one key. Wait a minute: it answers Retry-After: 60. |
internal | 500 | Something went wrong in Crossfeed. Try again in a moment. |