Build a full visual editing loop. Generate a flow. Read a screen preview. Edit a block. See the result. Ship the link.
┌─────────────────────────────────┐
│ 1. Generate │ POST /api/create-flow
│ "AI trends in 2026" ─────► │ ──► job_id
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 2. Inspect what the AI made │ GET /api/flows/{id}
│ screens, blocks, preview │ GET /api/flows/{id}/preview/{n}
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 3. Edit specific blocks │ PATCH .../blocks/{bid}
│ change theme, title │ PATCH /api/flows/{id}
│ add screens, blocks │ POST .../screens, .../blocks
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 4. Re-fetch preview │ GET /api/flows/{id}/preview/{n}?v=...
│ (R2-cached, ~50ms) │
└─────────────────────────────────┘
│
└──── loop until satisfied ────┐
▼
┌──────────────────────────────┐
│ 5. Ship the link │
│ https://flow2.co/m/abc │
└──────────────────────────────┘
Every flow ends in a viewable URL at flow2.co/m/{id}. Instant share, native mobile playback, embeddable in iframes. Every viewer becomes a free billboard for the agent who created the flow.
# 1. Sign in at https://flow2.co, then mint a token at /developers
# (Pro plan; tokens can read, write, and edit any flow you own)
# Save it as fvapi_YOUR_KEY for the calls below.
# 2. Create a flow
curl -X POST https://flow2.co/api/create-flow \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"3 wild facts about octopus intelligence"}'
# → {"job_id": "abc"}
# 3. Poll until ready
curl https://flow2.co/api/job-status/abc \
-H "Authorization: Bearer fvapi_YOUR_KEY"
# → {"status": "complete", "flow_id": "x9k2", "url": "/m/x9k2"}
# 4. Read what got made
curl https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
# 5. Look at screen 0 visually
curl https://flow2.co/api/flows/x9k2/preview/0 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
# → {"image_url": "https://cdn.flow2.co/previews/x9k2/screen-0_v...jpg"}
# 6. Edit a block
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/0/blocks/block-001 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"text","text":"Octopuses, ranked by weirdness"}'
# 7. Reskin
curl -X PATCH https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"theme":"neon-pulse"}'
# 8. Ship
echo "https://flow2.co/m/x9k2"
Connecting from Claude Desktop, Claude Code, Cursor, or another MCP-compatible client? Point the client at
mcp.flow2.co. Authentication runs through the client's OAuth flow, no token to manage by hand. Setup, tool reference, and examples: github.com/flowboard/flow2-mcp.
That's the entire loop. Generate, inspect, edit, ship.
All endpoints take a Bearer token in the Authorization header:
Authorization: Bearer <token>
Pick the auth path based on what you're building:
| You're building... | How to authenticate |
|---|---|
| Code that calls our REST API (curl, fetch, your own SDK) | Sign in at flow2.co, mint a token at /developers. Use it as Authorization: Bearer fvapi_... on every call. |
| A connection from an MCP client (Claude Desktop, Claude Code, Cursor) | Point the client at mcp.flow2.co. The client runs OAuth on your behalf; no token to manage by hand. Setup + tool reference: github.com/flowboard/flow2-mcp. |
A token from /developers has full read, write, and edit permissions on every flow you own. There is no "read-only" or "create-only" tier. One token, all endpoints.
For autonomous agents that need to provision their own flow2 account without a human in the loop, POST /api/signup accepts {"email": "..."} and returns an fvapi_* token. The account starts at 0 credits and Free tier; Free tokens can create flows but cannot edit them (editing requires a Pro account so we can verify flow ownership). Upgrade by signing the same email up at /pricing and minting a fresh token from /developers.
Most readers should ignore this section: signing in once at /developers is faster and the token does more.
GET /api/blocks/schema (public, no auth)Returns the canonical list of block kinds, mutation verbs, and example payloads for every supported edit type. This is the first call your agent should make if it doesn't have docs in its system prompt. The response is the docs.
curl https://flow2.co/api/blocks/schema
{
"rest": {
"GET /api/flows/{id}": "Read full flow structure + screen previews",
"PATCH /api/flows/{id}": "Set theme or title",
"GET /api/flows/{id}/preview/{n}": "Preview image URL for screen n",
"GET /api/flows/{id}/screens/{n}": "Read single screen + its preview",
"POST /api/flows/{id}/screens": "Insert a screen",
"DELETE /api/flows/{id}/screens/{n}": "Delete screen n",
"POST /api/flows/{id}/screens/{n}/blocks": "Insert a block",
"PATCH /api/flows/{id}/screens/{n}/blocks/{bid}": "Edit a block (body.type chooses kind)",
"DELETE /api/flows/{id}/screens/{n}/blocks/{bid}": "Delete a block"
},
"block_kinds": { /* full catalogue with patch_example for each */ },
"auth": { /* token rules */ }
}
GET /api/flows/{id} (full flow)Returns the complete flow JSON plus a preview-URL array indexed by screen.
curl https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
{
"success": true,
"string_id": "x9k2",
"url": "/m/x9k2",
"edit_url": "/m/x9k2/edit",
"screen_count": 5,
"preview_urls": [
"https://flow2.co/screenshot/flow-screen/x9k2/0?v=1714234567",
"https://flow2.co/screenshot/flow-screen/x9k2/1?v=1714234567",
/* ... */
],
"content": {
"presentation": {
"title": "Octopus Intelligence",
"themeId": "coral-sunset",
"screens": [
{
"id": "screen-001",
"screenType": "title-slide",
"blocks": [
{ "id": "block-001", "type": "title", "text": "..." },
{ "id": "block-002", "type": "subtitle", "text": "..." }
],
"background": { "imageUrl": "..." }
}
/* ... */
]
}
}
}
The id field on every block is what you'll use to PATCH or DELETE that block. Note them.
GET /api/flows/{id}/screens/{n} (single screen)Returns just one screen plus its preview URL. Use this when you want to inspect/edit one screen without parsing the whole flow.
curl https://flow2.co/api/flows/x9k2/screens/2 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
{
"success": true,
"string_id": "x9k2",
"screen_index": 2,
"screen": {
"id": "screen-003",
"screenType": "content",
"blocks": [ /* ... */ ]
},
"preview_url": "https://flow2.co/screenshot/flow-screen/x9k2/2?v=1714234567"
}
GET /api/flows/{id}/preview/{n} (visual preview)Returns the URL of a rendered preview image (1080×1920 mobile screenshot). The image is lazy-rendered on first request, then cached on R2 indefinitely; subsequent requests for the same flow version are ~50ms. The v= cache-buster auto-rotates whenever the flow is edited.
# Default: JSON with image URL
curl https://flow2.co/api/flows/x9k2/preview/2 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
# → {"image_url": "https://ai-gen-media.flowboard.com/previews/x9k2/screen-2_v....jpg"}
# Or 302-redirect straight to the image (for img tags / browsers)
curl -L https://flow2.co/api/flows/x9k2/preview/2?redirect=1 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
Why this matters: agents are good at text but bad at imagining what HTML will look like. Showing the preview to the agent (vision model or just the URL for a downstream consumer) closes the loop. The agent can SEE that the title is too long, or the chart looks wrong, before asking the user to look at it.
PATCH /api/flows/{id} (flow-level: theme + title)# Reskin the whole flow
curl -X PATCH https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"theme":"neon-pulse"}'
# Rename
curl -X PATCH https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Octopus intel: a tour"}'
# Both at once
curl -X PATCH https://flow2.co/api/flows/x9k2 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"theme":"neon-pulse","title":"Octopus intel: a tour"}'
Available themes: coral-sunset, neon-pulse, aurora-night, cosmic-drift, electric-bloom, nordic-clean, sandstone-warm, citrus-pop, modern-dark, blueprint, mondrian and a few more. Each theme has a -wide variant for desktop/16:9 viewing. Set via the same call.
PATCH /api/flows/{id}/screens/{n}/blocks/{block_id} (edit one block)The type field in the body tells the API which kind of edit to apply. The remaining fields depend on the type. Fetch /api/blocks/schema for the full reference. Common cases:
# Replace text on a title/subtitle/body/quote/callout block
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/0/blocks/block-001 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"text","text":"Octopuses, ranked by weirdness"}'
# Replace bullet list items
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/2/blocks/block-012 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"bullet-list","items":["Eight arms","Three hearts","Blue blood"]}'
# Update a stat block
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/3/blocks/block-021 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"stat","data":{"value":"500M","label":"Years of evolution","trend":"up"}}'
# Swap an image
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/0/blocks/block-003 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"image","imageUrl":"https://ai-gen-media.flowboard.com/generated_images/...png"}'
# Update a CTA button
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/4/blocks/block-029 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"cta-button","text":"Book a demo","url":"https://example.com/demo"}'
# Replace a chart's data
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/3/blocks/block-018 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"chart","data":{"chartType":"bar","title":"Brain neurons by cephalopod","data":[{"label":"Octopus","value":500},{"label":"Squid","value":300}]}}'
# Replace a timeline
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/4/blocks/block-022 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"timeline","data":{"entries":[{"label":"500M BCE","title":"First cephalopods","description":"..."},{"label":"Today","title":"Still weird","description":"..."}]}}'
# Replace screen background image
curl -X PATCH https://flow2.co/api/flows/x9k2/screens/0/blocks/block-001 \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"set-background","imageUrl":"https://ai-gen-media.flowboard.com/...png"}'
All block kinds: text, bullet-list, stat, chart, timeline, cta-button, image, video, team, comparison-table, comparison-slider, set-background, set-video-background, remove-background. The block_id in the URL maps to the id field on the block. Pull it from GET /api/flows/{id} first.
POST /api/flows/{id}/screens/{n}/blocks (add a block)Append a new block to a screen. Send id in blockContent if you want to address it later.
curl -X POST https://flow2.co/api/flows/x9k2/screens/2/blocks \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blockContent": {
"id": "block-octopi-stat",
"type": "stat",
"value": "9",
"label": "Brains per octopus",
"context": "One central + one in each arm"
}
}'
DELETE /api/flows/{id}/screens/{n}/blocks/{block_id} (remove a block)curl -X DELETE https://flow2.co/api/flows/x9k2/screens/2/blocks/block-octopi-stat \
-H "Authorization: Bearer fvapi_YOUR_KEY"
POST /api/flows/{id}/screens (add a screen)Body shape: {position?, screenContent: {screenType, blocks}}. Omit position to append.
curl -X POST https://flow2.co/api/flows/x9k2/screens \
-H "Authorization: Bearer fvapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"position": 5,
"screenContent": {
"screenType": "content",
"blocks": [
{"id": "block-new-title", "type": "title", "text": "One more thing", "level": 1},
{"id": "block-new-body", "type": "body", "text": "Octopus skin can detect light directly. Without eyes."}
]
}
}'
Screen types: title-slide, hero-plus, content, big-stat, stats-grid, chart-focus, timeline, quote-slide, closing.
DELETE /api/flows/{id}/screens/{n} (remove a screen)curl -X DELETE https://flow2.co/api/flows/x9k2/screens/4 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
Every successful save writes a flow_versions row, automatically. Three endpoints to read history and restore. No opt-in — versioning is on by default.
GET /api/flows/{id}/versionsLatest first. Metadata only — content_json is omitted to keep list responses small. Use the per-version GET to pull content.
curl "https://flow2.co/api/flows/x9k2/versions?limit=20" \
-H "Authorization: Bearer fvapi_YOUR_KEY"
{
"success": true,
"string_id": "x9k2",
"count": 20,
"versions": [
{
"id": 198, "string_id": "x9k2", "user_id": 18,
"created_at": "2026-04-29 19:42:11",
"content_hash": "9f3a…", "bytes": 18432,
"source": "api", // 'editor' | 'api' | 'restore'
"client_id": null, // forward-looking
"parent_version_id": 197,
"update_count": 1,
"update_summary": "text x1",
"label": null
},
/* ... */
],
"next_before_id": 179 // pass back as ?before_id= for the next page
}
Pagination via ?limit=N (default 50, max 200) + ?before_id=X (cursor). When next_before_id is null, you've hit the start of history.
GET /api/flows/{id}/versions/{vid}Full content snapshot for one version. content is decoded JSON, not a stringified blob.
curl https://flow2.co/api/flows/x9k2/versions/198 \
-H "Authorization: Bearer fvapi_YOUR_KEY"
{
"success": true,
"version": {
"id": 198, "string_id": "x9k2", "user_id": 18,
"created_at": "2026-04-29 19:42:11",
"source": "api", "update_summary": "text x1",
"content": { "presentation": { "screens": [ /* ... */ ] } }
}
}
POST /api/flows/{id}/versions/{vid}/restoreMake this version the current state. Empty body. Returns the same response shape as a regular save.
curl -X POST https://flow2.co/api/flows/x9k2/versions/198/restore \
-H "Authorization: Bearer fvapi_YOUR_KEY"
Restore is itself a save: it writes a new version row labelled restore from v#198 and applies through the same path as any other edit. Original v198 stays in history. To "undo a restore", just restore to the version right before it.
De-dup: identical content doesn't write a new row, so restoring to the current state is a no-op.
| Code | Meaning |
|---|---|
200 |
Success |
400 |
Malformed request (missing required field, invalid block id) |
401 |
Missing/invalid Bearer token |
402 |
Insufficient credits |
403 |
Token valid, but you don't own this flow |
404 |
Flow doesn't exist, screen index out of range, or flow has no editable content (legacy import) |
405 |
Method not allowed for that resource shape |
500 |
Server error |
Errors always return JSON:
{
"error": "concise machine-friendly summary",
"hint": "what to do about it", // when applicable
"fix": "exact steps", // when applicable
"docs": "/developers" // when applicable
}
| Action | Credits |
|---|---|
POST /api/create-flow |
10 |
POST /ai/generate-image |
2 |
| All editing endpoints | 0 (free) |
Agent edits are free. Mint a flow once (10 credits), then iterate on it as much as you want.
https://mcp.flow2.co (Streamable HTTP). 16 tools: create_flow, check_job_status, check_credits, list_jobs, plus the full editing surface (get_blocks_schema, get_flow, get_screen, get_preview, update_flow_meta, patch_block, add_block, delete_block, add_screen, delete_screen), plus two MCP Apps render tools (render-flow-preview, render-screen-preview) that surface the flow inline in claude.ai via a sandboxed iframe. Per-user OAuth2 with PKCE — point any MCP client (Claude Desktop, Cursor, mcp-inspector) at the URL and it walks dynamic registration → consent → token exchange automatically. See OATH2_IMPLEMENTATION_MCP.md.For everything else we're working on (and what's coming after), see the public roadmap.
Every AI agent today produces text. Most of that text dies in a Slack thread, a markdown blob, an email no one opens. The Flow2 Agent API turns that output into something the recipient actually taps:
flow2.co/m/.... Agents become Flow2's marketing channel automatically.If your agent's output deserves better than markdown. Start here.