{"name":"IndieBob","description":"Growth engine for indie software developers: content, cold outreach written per prospect, campaigns, revenue attribution, and a ranked \"what to do next\".","homepage":"https://www.indiebob.com","documentation":"https://www.indiebob.com/agents.md","auth":{"type":"bearer","header":"Authorization","format":"Bearer ib_sk_<64 hex>","obtain":"https://www.indiebob.com/docs/agents#keys","scopes":["read:projects","read:metrics","read:revenue","read:audience","read:content","write:content","read:admin","read:outreach","write:outreach","read:actions"],"errors":{"401":"No, malformed, expired or revoked key.","403":"Valid key missing a scope. The message names the scope, e.g. \"Service key is missing the 'read:outreach' scope\".","429":"A plan limit refused the whole call rather than partially doing it."}},"mcp":{"transport":"stdio","command":"npx","args":["-y","@indiebob/mcp"],"env":{"INDIEBOB_API_KEY":"ib_sk_…","INDIEBOB_BASE_URL":"https://www.indiebob.com"}},"capabilities":[{"id":"whoami","title":"Check the key","summary":"Confirm the key is live and list the scopes it holds. Returns no portfolio data, so it is safe to call first.","method":"GET","url":"https://www.indiebob.com/api/v1","scope":"read:projects","params":[],"returns":"{ ok, version, keyId, scopes[], endpoints{} }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" https://www.indiebob.com/api/v1","response":"{\"ok\":true,\"version\":\"v1\",\"keyId\":\"…\",\"scopes\":[\"read:projects\",\"read:outreach\"]}"}},{"id":"list_projects","title":"List projects","summary":"Every project the key owner has, with health status, stage and settings. Start here: most other capabilities take a project id or slug.","method":"GET","url":"https://www.indiebob.com/api/v1/projects","scope":"read:projects","params":[{"name":"updated_since","in":"query","type":"string","description":"ISO timestamp; only projects changed at or after it."},{"name":"include_archived","in":"query","type":"boolean","description":"true to include archived projects (default: exclude)."},{"name":"limit","in":"query","type":"number","description":"1–200, default 100."}],"returns":"{ data[], count, nextUpdatedSince }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" https://www.indiebob.com/api/v1/projects","response":"{\"data\":[{\"id\":\"…\",\"name\":\"maimu\",\"slug\":\"maimu\",\"healthStatus\":\"watch\"}],\"count\":1}"}},{"id":"read_metrics","title":"Read metrics","summary":"Daily snapshots per project: MRR, visitors, signups, revenue, health. Date-windowed, not cursor-based, because snapshots are restated when late Stripe events land — re-pull a trailing window and upsert.","method":"GET","url":"https://www.indiebob.com/api/v1/metrics","scope":"read:metrics","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing."},{"name":"from","in":"query","type":"string","description":"YYYY-MM-DD, inclusive. Default: 90 days ago."},{"name":"to","in":"query","type":"string","description":"YYYY-MM-DD, inclusive. Default: today."},{"name":"limit","in":"query","type":"number","description":"1–200, default 100."}],"returns":"{ data[], count } — one row per project per day","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" \"https://www.indiebob.com/api/v1/metrics?project=maimu&from=2026-09-01\"","response":"{\"data\":[{\"projectId\":\"…\",\"date\":\"2026-09-01\",\"mrrCents\":0,\"uniqueVisitors\":34,\"signups\":2}],\"count\":1}"}},{"id":"next_actions","title":"What should I do next","summary":"The ranked Do Next feed across the whole portfolio — the same rules the Command Center runs, highest priority first, each with why it fired and where it leads. Deterministic: no model runs, so the answer is the same for the same data.","method":"GET","url":"https://www.indiebob.com/api/v1/actions","scope":"read:actions","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing."},{"name":"limit","in":"query","type":"number","description":"1–50, default 10."}],"returns":"{ actions[{ id, projectId, projectName, kind, priority, title, detail, cta{label,url}, metric? }], count }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" https://www.indiebob.com/api/v1/actions","response":"{\"actions\":[{\"id\":\"…:outreach_awaiting_review\",\"kind\":\"risk\",\"priority\":92,\"title\":\"3 first touches wait for your yes\"}],\"count\":1}"}},{"id":"list_posts","title":"List blog posts","summary":"Blog posts in the key owner's projects, newest-updated first, with status and SEO score. Bodies are not included — use get_post.","method":"GET","url":"https://www.indiebob.com/api/v1/content/posts","scope":"read:content","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing."},{"name":"status","in":"query","type":"string","description":"draft | published | archived. Default: everything except archived."},{"name":"limit","in":"query","type":"number","description":"1–200, default 100."}],"returns":"{ posts[], count }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" \"https://www.indiebob.com/api/v1/content/posts?status=draft\"","response":"{\"posts\":[{\"id\":\"…\",\"title\":\"How I run outreach on one GPU\",\"status\":\"draft\",\"seoScore\":71}],\"count\":1}"}},{"id":"get_post","title":"Read one blog post","summary":"One post with its markdown body and SEO fields. A post outside the key owner's portfolio 404s exactly like a nonexistent one.","method":"GET","url":"https://www.indiebob.com/api/v1/content/posts/{postId}","scope":"read:content","params":[{"name":"postId","in":"path","type":"string","required":true,"description":"Post id."}],"returns":"{ post } — list fields plus bodyMarkdown, metaTitle, metaDescription, ogImageUrl","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" https://www.indiebob.com/api/v1/content/posts/POST_ID","response":"{\"post\":{\"id\":\"…\",\"title\":\"…\",\"bodyMarkdown\":\"# …\",\"status\":\"draft\"}}"}},{"id":"create_post","title":"Draft a blog post","summary":"Create a draft. Markdown is the source of truth — HTML cannot be submitted, it is rendered server-side. The slug is derived from the title.","method":"POST","url":"https://www.indiebob.com/api/v1/content/posts","scope":"write:content","params":[{"name":"project","in":"body","type":"string","required":true,"description":"Project id or slug, inside the key owner's portfolio."},{"name":"title","in":"body","type":"string","required":true,"description":"Post title."},{"name":"bodyMarkdown","in":"body","type":"string","description":"Markdown body."},{"name":"excerpt","in":"body","type":"string","description":"Short summary."},{"name":"metaTitle","in":"body","type":"string","description":"SEO title."},{"name":"metaDescription","in":"body","type":"string","description":"SEO description."}],"returns":"{ post }","example":{"request":"curl -X POST -H \"Authorization: Bearer $INDIEBOB_API_KEY\" -H \"content-type: application/json\" -d '{\"project\":\"maimu\",\"title\":\"Launch week\",\"bodyMarkdown\":\"# Launch week\"}' https://www.indiebob.com/api/v1/content/posts","response":"{\"post\":{\"id\":\"…\",\"slug\":\"launch-week\",\"status\":\"draft\"}}"}},{"id":"publish_post","title":"Publish a blog post","summary":"Publish or re-publish. THE propagation trigger: it snapshots the body, assigns the OG image and fires the content webhook that pushes the post to mirrored sites. Nothing else takes a post live.","method":"POST","url":"https://www.indiebob.com/api/v1/content/posts/{postId}/publish","scope":"write:content","params":[{"name":"postId","in":"path","type":"string","required":true,"description":"Post id."}],"returns":"{ post } with status=published and publishedAt set","example":{"request":"curl -X POST -H \"Authorization: Bearer $INDIEBOB_API_KEY\" https://www.indiebob.com/api/v1/content/posts/POST_ID/publish","response":"{\"post\":{\"id\":\"…\",\"status\":\"published\",\"publishedAt\":\"2026-09-07T20:00:00.000Z\"}}"}},{"id":"list_prospects","title":"List prospects","summary":"Cold-outreach prospects for one project, newest first, with status, source and the facts enrichment read off their own website.","method":"GET","url":"https://www.indiebob.com/api/v1/outreach/prospects","scope":"read:outreach","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing.","required":true},{"name":"status","in":"query","type":"string","description":"new | contacted | replied | bounced | rejected. Default: everything but archived."},{"name":"source","in":"query","type":"string","description":"Where the prospect came from, e.g. launch_venues, import, manual."},{"name":"search","in":"query","type":"string","description":"Substring match on email, company or name."},{"name":"limit","in":"query","type":"number","description":"1–200, default 50."}],"returns":"{ prospects[{ id, email, firstName, company, website, status, source, tags }], count, counts{byStatus,bySource} }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" \"https://www.indiebob.com/api/v1/outreach/prospects?project=maimu&status=new\"","response":"{\"prospects\":[{\"id\":\"…\",\"email\":\"dev@example.com\",\"company\":\"Example\",\"status\":\"new\",\"source\":\"launch_venues\"}],\"count\":1}"}},{"id":"list_sequences","title":"List outreach sequences","summary":"The project's sequences with their status, step count, whether each is personalised (Bob writes every email per prospect) and how many prospects are enrolled.","method":"GET","url":"https://www.indiebob.com/api/v1/outreach/sequences","scope":"read:outreach","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing.","required":true}],"returns":"{ sequences[{ id, name, status, personalised, stepCount, enrolledCount }], count }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" \"https://www.indiebob.com/api/v1/outreach/sequences?project=maimu\"","response":"{\"sequences\":[{\"id\":\"…\",\"name\":\"German restaurants\",\"status\":\"active\",\"personalised\":true,\"stepCount\":3,\"enrolledCount\":42}],\"count\":1}"}},{"id":"enroll_prospects","title":"Enrol prospects in a sequence","summary":"Enrol up to 200 prospects. Unsubscribed, rejected and already-enrolled prospects are skipped and counted back, never silently dropped. On a personalised sequence this opens each first touch for Bob to write; if the plan has fewer emails left than prospects, the whole call is refused with 429 rather than parking them behind a limit.","method":"POST","url":"https://www.indiebob.com/api/v1/outreach/sequences/{sequenceId}/enroll","scope":"write:outreach","params":[{"name":"sequenceId","in":"path","type":"string","required":true,"description":"Sequence id."},{"name":"project","in":"body","type":"string","required":true,"description":"Project id or slug owning the sequence."},{"name":"prospectIds","in":"body","type":"string[]","required":true,"description":"1–200 prospect ids from list_prospects."}],"returns":"{ enrolled, skippedUnsubscribed, skippedRejected, skippedDuplicate, personalised }","example":{"request":"curl -X POST -H \"Authorization: Bearer $INDIEBOB_API_KEY\" -H \"content-type: application/json\" -d '{\"project\":\"maimu\",\"prospectIds\":[\"…\"]}' https://www.indiebob.com/api/v1/outreach/sequences/SEQ_ID/enroll","response":"{\"enrolled\":1,\"skippedUnsubscribed\":0,\"skippedRejected\":0,\"skippedDuplicate\":0,\"personalised\":true}"}},{"id":"list_review_queue","title":"Read the review queue","summary":"The personalised first touches waiting for a human yes — each with the subject and opening Bob wrote for that prospect, and the prospect it is addressed to. This is what a human approves in the dashboard; an agent can read the same rows.","method":"GET","url":"https://www.indiebob.com/api/v1/outreach/messages","scope":"read:outreach","params":[{"name":"project","in":"query","type":"string","description":"Project id or slug. Resolved inside the key owner's portfolio; an unowned value matches nothing rather than 404ing.","required":true},{"name":"sequenceId","in":"query","type":"string","description":"Narrow to one sequence."},{"name":"state","in":"query","type":"string","description":"Message state; default awaiting_review — the review queue itself."},{"name":"limit","in":"query","type":"number","description":"1–200, default 50."}],"returns":"{ messages[{ id, state, subject, opening, dueAt, prospect{ id, email, company }, sequence{ id, name } }], counts{}, count }","example":{"request":"curl -H \"Authorization: Bearer $INDIEBOB_API_KEY\" \"https://www.indiebob.com/api/v1/outreach/messages?project=maimu\"","response":"{\"messages\":[{\"id\":\"…\",\"state\":\"awaiting_review\",\"subject\":\"Your menu, in three languages\",\"prospect\":{\"email\":\"dev@example.com\"}}],\"count\":1}"}},{"id":"approve_message","title":"Approve a first touch","summary":"The yes on one personalised first touch. Sending window, daily caps and the sequence's sending account still apply afterwards — approving does not send. A message that is not awaiting review answers 409 saying what state it is in.","method":"POST","url":"https://www.indiebob.com/api/v1/outreach/messages/{messageId}/approve","scope":"write:outreach","params":[{"name":"messageId","in":"path","type":"string","required":true,"description":"Message id from list_review_queue."},{"name":"project","in":"body","type":"string","required":true,"description":"Project id or slug owning the message."}],"returns":"{ ok, id, state: \"approved\" }","example":{"request":"curl -X POST -H \"Authorization: Bearer $INDIEBOB_API_KEY\" -H \"content-type: application/json\" -d '{\"project\":\"maimu\"}' https://www.indiebob.com/api/v1/outreach/messages/MSG_ID/approve","response":"{\"ok\":true,\"id\":\"…\",\"state\":\"approved\"}"}}]}