What it is
In one line: Ignaru’s MCP server is https://ignaru.com/api/mcp/mcp; connect it with your personal key as a Bearer header and your agent can build canvases, add and enrich leads and draft outreach, while every send waits for a person’s Approve. The quickest setup is the Ignaru skill: npx skills add https://ignaru.com.
Ignaru has three ways in besides the app. A command line, ignaru. An MCP server, so Claude Code, Claude Desktop, Cursor and Codex can work on your canvases. And a small REST API under /api/v1 that both of them use. They all act as you, with your own personal key.
Writing an agent? Read Ignaru for agents (also markdown at /docs/for-agents.md): the first call to make, the rules, how to save calls, and every common job as one MCP tool or CLI command.
Get a key
The quickest way is ignaru login: it shows a code, opens Ignaru, and once you approve, that computer has its own key. For an agent, a script or CI, make one yourself:
- Open Ignaru and go to Settings, You, API keys.
- Name it after where it will live, like “Laptop” or “Cursor”, pick all your teams or one, and an expiry.
- Copy it. It starts with
ig_pk_and is shown once.
A key acts as you. In each team it has the role you have there today, never more, and it stops reaching a team you leave. A key for all your teams picks one with the X-Ignaru-Team header (the CLI sends it after ignaru teams switch). Revoke a key from the same page and it stops working straight away. Keep it out of repositories: put it in the CLI’s config, an environment variable, or your client’s secret store. Older ig_ team tokens keep working in their one team.
Install
The CLI needs Node 18 or newer and has no dependencies. It is not on npm yet. From a checkout of the repository, build it and link it:
npx tsc -p cli
npm i -g ./cli
ignaru loginOnce it is published you will be able to run npx ignaru with nothing installed.
ignaru login prints a code, opens ignaru.com/cli/authorize, and waits while you check the code and press Approve. The code lasts 10 minutes and works once. The new key is saved to ~/.config/ignaru/config.json with permissions for you only. To skip the file, set IGNARU_TOKEN or pass --token. To point at another server, use --api or IGNARU_API.
Commands
| Command | What it does |
|---|---|
ignaru context | Start here: team, role, what the key may do, credits, limits, and where work is waiting. |
ignaru login | Sign in through your browser. Check the code, press Approve, and this computer gets its own personal key (ig_pk_…). |
ignaru whoami | Which team and user the key acts as, your role, and your credits. |
ignaru teams | Your teams and your role in each. teams create, teams switch, teams invite (which only emails with --yes) and teams link for invite links. |
ignaru loops | Your loops with status, steps, leads and drafts waiting. |
ignaru loop "<loop>" | Describe a loop: the board, leads by stage, drafts waiting, spend, and why it can't go live. |
ignaru new-loop "Name" --steps signals,enrich,coldemail,approve | Create a draft loop, with its whole chain of steps if you pass them. |
ignaru add-step "<loop>" linkedin --after research --into approve | Add a step after another and, optionally, into a later one. Steps by id, title or kind. |
ignaru connect | disconnect | remove-steps | rename | Join or unjoin two steps, remove steps, rename a loop. |
ignaru set-status "<loop>" live|paused|draft | Start or stop a loop (pause and resume do the same). |
ignaru leads acme --stage warm | Search leads: ids, the total, and counts per stage. |
ignaru leads search "UK fintech founders" --limit 10 | Search the lead database for free: masked rows with ids, no emails. Add --companies for companies. |
ignaru leads reveal <ids...> --max-credits 3 | Reveal and verify those people (paid; refused with the price if it is above the cap). Only valid emails are kept. |
ignaru loops run <loop> | Run every step of a loop once now. |
ignaru add-leads --file leads.csv --loop "<loop>" | Add up to 200 leads, merged by email. --dry-run previews. |
ignaru lead "<email or name>" --stage call | Change a lead. |
ignaru replies --handled false --cursor <c> | What leads wrote back, newest first. --handled false shows only the ones nobody has dealt with; --lead narrows to one lead; --cursor pages on. |
ignaru draft "<lead>" · ignaru drafts | Write a draft into Approve; drafts waiting (read-only). |
ignaru credits --week | Balance, and where credits went. |
ignaru ask "…" --loop "<loop>" | Ask Iggy and print the answer. |
ignaru tools · ignaru call <tool> '<json>' | Every MCP tool in one line each, and a way to call any of them. |
ignaru mcp | Print the MCP setup for each client. |
A <loop> can be the full id, the first few characters of it, or the canvas’s name in quotes. Add --json to any read command for machine-readable output, and --no-color or NO_COLOR=1 for plain text.
A canvas built from the terminal:
ignaru new-loop "Seed-stage founders"
ignaru add-step "Seed-stage founders" signals
ignaru add-step "Seed-stage founders" enrich --after n_ab12cd34
ignaru add-step "Seed-stage founders" coldemail --after n_ef56gh78
ignaru add-step "Seed-stage founders" approve --after n_ij90kl12
ignaru loop "Seed-stage founders"Each add-step prints the new step’s id, which is what the next one passes to --after.
MCP
The server speaks Streamable HTTP at https://ignaru.com/api/mcp/mcp and takes the same token as a Bearer header. Run ignaru mcp to print these with the address filled in. Without a valid key it answers 401 with WWW-Authenticate: Bearer realm="ignaru", error="invalid_token": create an ig_pk_ key in Settings, API keys. Sign-in with a browser (OAuth) for Claude or Cursor is not offered yet.
Claude Code
claude mcp add --transport http ignaru https://ignaru.com/api/mcp/mcp \
--header "Authorization: Bearer ig_YOUR_TOKEN"Claude Desktop
Add this to claude_desktop_config.json (Settings, Developer, Edit Config). It uses the mcp-remote bridge, which needs Node.
{
"mcpServers": {
"ignaru": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://ignaru.com/api/mcp/mcp", "--header", "Authorization:${IGNARU_AUTH}"],
"env": { "IGNARU_AUTH": "Bearer ig_YOUR_TOKEN" }
}
}
}Cursor
Add this to ~/.cursor/mcp.json, or .cursor/mcp.json inside a project.
{
"mcpServers": {
"ignaru": {
"url": "https://ignaru.com/api/mcp/mcp",
"headers": { "Authorization": "Bearer ig_YOUR_TOKEN" }
}
}
}Codex
Add this to ~/.codex/config.toml, with IGNARU_TOKEN exported in your shell.
[mcp_servers.ignaru]
url = "https://ignaru.com/api/mcp/mcp"
bearer_token_env_var = "IGNARU_TOKEN"Tools
| Tool | What it does |
|---|---|
context | Start here: team, your role, what the key may do, credits and this week's spend, limits, where work is waiting, every loop. |
whoami | Who am I |
list_step_kinds | Step kinds and prices |
list_teams | List my teams |
create_team | Create a team |
invite_teammates | Email invites to a team. Admin only, and only with confirm: true after the user has seen the list. |
create_team_link | A link anyone can open to join a team as Operator or Viewer (never Admin), for up to 30 days. Admin only. |
list_team_links | A team's live invite links, without the links themselves. Admin only. |
revoke_team_link | Stop an invite link working. Admin only. |
list_loops | List loops |
get_loop | Describe a loop in one call: steps with their settings, pipes as from -> to, leads by stage, drafts waiting, spend, why it can't go live. |
create_loop | A new draft loop, optionally with its whole chain of steps in one call. |
add_step | Add a step after another (by id, title or kind), optionally also into a later one, with its settings in data. |
update_step | Change a step's title, settings (watch, angle, tone, length, cta, prompt, link, maxPerTick, maxCreditsPerDay) or pause it. |
connect_steps | Join two steps |
disconnect_steps | Unjoin two steps |
remove_steps | Remove steps |
update_loop | Rename a loop or change its status. |
set_loop_status | Set a loop live, paused or draft. Going live is refused (not_ready) while go_live_check finds anything missing. |
go_live_check | What a loop is missing before it can go live, each with its fix and link, what a Live day costs and the saving with your own data apps. |
set_loop_budget | Set a loop's daily and total credit budget. Paid work stops at either. Admins only. |
search_leads | Find leads by text, stage or loop, with the total, counts per stage and the next cursor. |
search_lead_database | Search the lead database (Glasa) for people or companies by plain words or typed filters. Free, masked rows, never an email. 409 no_lead_database when none is connected. |
reveal_leads | Reveal and verify contacts for up to 25 people from a search (about 1.1 Glasa credits each). Needs maxGlasaCredits, maxIgnaruCredits and an idempotencyKey; refused with the quote when the price is higher. Only people Glasa marks valid are revealed. Only valid emails come back, and are saved as leads. |
run_loop | Run every step of a loop once now (id or name). 409 busy, 422 loop_paused. |
add_leads | Add up to 200 leads, merged by email; counts added and merged. dryRun previews. |
update_lead | Change a lead's stage, warmth or details, by id, email or name. |
enrich_leads | Find and check leads' details with sources. dryRun gives the credit estimate; a real run needs maxCredits. |
explain_field | Explain an enriched fact |
list_replies | What leads wrote back, with their intent. |
draft_message | Write a draft for a lead into Approve (Iggy writes it if you give no body). Sends nothing. |
list_drafts_waiting | Drafts waiting for an Admin's Approve, by loop. Read-only. |
edit_draft | Reword a draft waiting at Approve. It keeps waiting; an approved one goes back to waiting. Never sends. |
reject_draft | Turn down one draft waiting at Approve. A person can send it back from the app. Never sends. |
reject_drafts | Turn down waiting drafts by filter (olderThan, noLoop, alreadyEmailed), 500 a call. dryRun shows the count first. |
list_connections | The team's connected apps, the steps each drives, what Iggy may do in it and anything to fix. |
run_app_tool | Run one action in a connected app. Reads run now; a change waits for an Admin's yes in the app; sends and posts are refused. |
search_brain | Search what Ignaru knows about the team's company, by words or topic. Read-only. |
get_credits | Balance, and where credits went by reason and by loop. |
digest | What every loop did |
stuck_steps | Stuck steps |
today | Today |
list_notifications | List notifications |
mark_notifications_read | Mark notifications read |
list_calls | List calls |
list_bookings | List Cal.com bookings |
booking_link | Get a booking link |
set_booking_link | Save the team's Cal.com booking page, which Booking steps without a link of their own use. Operator and up. |
ask_iggy | Ask Iggy to answer or change a loop. Spends credits. |
give_feedback | Give feedback on Ignaru |
Also: social_status, create_social_workspace, list_demo_boards, add_video, list_projects, get_project, make_client, add_card, update_card, post_update, list_feedback, triage_feedback, list_node_templates, list_custom_nodes, get_custom_node, validate_custom_node, dry_run_custom_node, save_custom_node, archive_custom_node, add_custom_step, upgrade_custom_step, list_custom_actions, list_pages, get_page, get_page_data, create_page, update_page, reorder_pages, pin_page, delete_page, get_prices, generate_video, list_calendar, find_free_slot, create_event, move_event, connect_app, list_loop_templates, create_loop_from_template. ignaru tools lists every tool in one line each.
Tools take canvases, leads and steps by name as well as by id, and every write takes an optional idempotencyKey. Errors end with Fix: (what to call next) and, where it helps, Valid: (the values that would have worked).
Resources: ignaru://context, ignaru://step-kinds, ignaru://guide and ignaru://loops/{id}. Prompts: quickstart and build_workspace. Try a prompt like “Build me a canvas that finds seed-stage fintech founders and drafts a short intro. Stop before anything is sent.”
REST
Everything above is a plain JSON API. Send the token as Authorization: Bearer ig_…. Errors come back as { "ok": false, "code": "...", "message": "...", "hint": "...", "valid": [...] }: hint says what to do next, valid lists the values that would have worked. Writes take an Idempotency-Key header: a retry with the same key returns the first answer. Each token gets 120 requests a minute. Writes are capped at 60 a minute, exports at 5 and questions to Iggy at 10.
| Endpoint | Notes |
|---|---|
GET /api/v1/context | Team, role, what the key may do, credits, limits, where work waits, loops |
GET /api/v1/me | User, team, credits |
GET /api/v1/steps | Every step kind with what it does, its price, and whether it writes to people |
GET /api/v1/loops | Loops |
POST /api/v1/loops | Create a loop. Body: { name, steps?, idempotencyKey? } |
GET /api/v1/loops/:loop | Board. ?view=summary for steps, pipes, leads by stage, drafts waiting and spend. :loop is an id or a name |
PATCH /api/v1/loops/:loop | Body: { name?, status? } |
POST /api/v1/loops/:loop/run | Run every step once now. Answers { report }, or 409 busy, 422 loop_paused, 404 not_found |
POST /api/v1/leads/search | Free search of the lead database. Body: { kind: people|companies, ask? or filters?, limit ≤ 25, offset }. Masked rows and total, never an email. 409 no_lead_database when none is connected |
POST /api/v1/leads/reveal | Paid: reveal and verify up to 25 people. Body: { glasaPersonIds, maxGlasaCredits, maxIgnaruCredits } and an Idempotency-Key header (required). 402 quote_exceeds_max with the quote when the price is above either cap. Emails come back only when valid |
POST /api/v1/loops/:loop/steps | Body: { kind, after?, into?, title?, data? }. after and into take an id, title or kind; data holds the step's settings |
PATCH /api/v1/loops/:loop/steps | Change one step. Body: { step, title?, data?, paused? }. data merges into its saved settings |
PUT /api/v1/bookings/link | Save the team's Cal.com booking page. Body: { url } (null clears it) |
GET /api/v1/leads | Query: q, stage, loopId, limit, cursor. Returns total, byStage and next |
POST /api/v1/leads | Body: { leads: [...], loopId?, source?, dryRun? }. Returns added, merged and each lead |
PATCH /api/v1/leads/:lead | Body: { stage?, warmth?, why?, role?, company? }. :lead is an id, email or name |
POST /api/v1/leads/enrich | Body: { leadIds (ids, emails or names), fields?, dryRun?, maxCredits (required unless dryRun), verbose? }. 402 quote_exceeds_max when maxCredits can't cover one lead |
GET /api/v1/replies | Query: lead, handled (false for replies nobody has handled yet), limit, cursor. Newest first |
GET /api/v1/drafts | Query: status (default queued), loopId, limit, cursor. Returns total and next, with bodies in full |
POST /api/v1/drafts | Write a draft into Approve. Body: { lead, channel, body? or instruction? } |
PATCH /api/v1/drafts/:id | Reword a draft waiting at Approve. Body: { subject?, body? }. An approved one goes back to waiting. Never sends |
POST /api/v1/drafts/:id/reject | Turn down one waiting draft. Approved and sent drafts are left alone. Never sends |
POST /api/v1/drafts/reject | Turn down waiting drafts by filter. Body: { olderThan?, noLoop?, alreadyEmailed?, dryRun?, limit ≤ 500 }, at least one filter. Oldest first |
GET /api/v1/connections | Connected apps: the steps each drives, what Iggy may do in it, and anything to fix. Never a key |
POST /api/v1/connections/run | Run one app action. Body: { tool, args?, summary? }. A read answers { ran, result }; a change answers { queued, actionId } and waits for an Admin's yes; sends and posts get 403 |
GET /api/v1/brain/search | Query: query, topic, includeReview, limit. The team's Brain facts with how sure each is and where it came from |
GET /api/v1/credits | Query: period. Balance, byReason, byLoop |
GET /api/v1/export | Your data as one JSON download, every row. scope=team and format=csv&type=leads need an admin signed in to the app |
POST /api/v1/ask | Body: { prompt, loopId?, threadId?, model? } |
GET /api/v1/teams | Your teams with your role in each |
POST /api/v1/teams | Create a team, you as Admin. Body: { name, website? } |
POST /api/v1/teams/:id/invites | Admin only. Body: { invites: [{ email, role }], confirm: true }. Without confirm nothing is sent |
GET /api/v1/keys | Your personal keys (never the secret) |
DELETE /api/v1/keys/current | Revoke the key making the call |
curl https://ignaru.com/api/v1/loops \
-H "Authorization: Bearer $IGNARU_TOKEN"What it cannot do
Nothing leaves your team without a person pressing Approve in the app. The CLI, the MCP server and the API can read drafts. They cannot approve, edit or send them, and a token that tries to approve gets a 403 that says so.
Cold email and LinkedIn steps write drafts. Send with Glasa and social likes or follows go out only after an Admin approves. Setting a canvas live starts the finding and the writing, not the sending. An agent can build and run a whole canvas up to the Approve step, and then it waits for you.
In a team’s canvases and leads a key does at most what an Operator can, even an Admin’s key: approving sends, billing and team settings stay in the app. The one Admin power a key keeps is setting up teams: an Admin’s key can invite people, and only with an explicit confirm, because each invite emails a real person. Make a separate key for each place you use one, so you can revoke it without touching the others.
Not set up yet?
The web app is the main way in. The agent and terminal setups are on the download page too.