MCP tools reference
Every tool the Kata MCP server exposes: 74 tools, with their parameters. This page is built from the server's own tool list, so it always matches what tools/list returns.
tools/call: {"name": "<tool>", "arguments": {...}}. See Connect your agent for the transport and auth. Tools that change bulk data take dry_run: run it first and act on the count.Overview and reports
status
Overview: sends today, capacity, replies, interested leads, domains, mailboxes, company memory size, campaigns (each with state: ok, error with the reason it cannot send, completed when every lead went through the sequence, off when not active), and wrong_person: recent replies saying they are not the right person (with any address they gave in referral) so you can ask for the right contact.
No parameters.
health
Health score (0-100) of every sending domain and mailbox over the last 30 days: domain 50%, recipient behavior 30%, address 10%, IP/provider 10%. When Instantly warmup data is known (10 or more warmup emails landed in 7 days), inbox placement counts 15% (domain 45%, recipients 25%, address 10%, provider 5%): inbox_rate and placement {inbox, spam} per mailbox. Kata pauses a mailbox by itself when more than 10% of 20 or more warmup emails land in spam over 7 days. Use it to decide which mailboxes to pause or slow down.
No parameters.
campaign_report
Performance of one campaign (or all): state (ok, error with the reason, completed, off), totals (sent, sent today, replies, interested, conversions, bounces = hard bounces, soft_bounces, contacts in sequence), the last bounce reasons, and per-variant stats (step, angle, subject, status, sent, successes, rate, p_best = probability of being the best variant).
| Parameter | Type | Description |
|---|---|---|
campaign_id | number | one campaign; omit for all |
leads_to_call
Interested contacts not handled yet, with their reply.
No parameters.
query
Run ONE read-only SQL SELECT (or WITH … SELECT) on the Kata SQLite database, max 200 rows. For anything the other tools do not cover. Dates are Unix ms. Tables: contacts(id, email, first_name, company, data JSON text: use json_extract(data,'$.country'), status new|enrolled|replied|bounced|unsubscribed|converted, value, soft_bounces, created_at, handled_at); campaigns(id, name, status draft|active|paused, target_url, brief, created_at); steps(campaign_id, step, delay_days); variants(id, campaign_id, step, angle, subject, body, status active|retired, parent_id, origin human|ai); enrollments(id, campaign_id, contact_id, step = next step, next_at, status pending|scheduled|done|stopped, mailbox); messages(id, token, enrollment_id, campaign_id, contact_id, mailbox, variant_id, step, subject, body, status scheduled|sending|sent|failed, send_at, sent_at, error, clicked, replied, interested, converted, bounced = hard bounce, bounce_type hard|soft, bounce_code, bounce_reason, value); events(id, type sent|click|open|reply|reply_sent|bounce|visit|unsubscribe|conversion|data|pause|evolve|error|domain, message_id, contact_id, detail, at; reply detail starts with the kind interested|not_interested|unsubscribe|ooo|wrong_person|other, bounce detail with hard|soft and the SMTP code); suppression(email, reason, at); mailboxes(email, domain, display_name, daily_cap, status, provider, note); domains(domain, daily_cap, status, provider, verify_status, note); context_files(id, scope, campaign_id, name, kind, content).
| Parameter | Type | Description |
|---|---|---|
sql required | string | one SELECT statement |
Company and integrations
get_integrations
Which integrations are configured (AgentMail key, Cloudflare token), with the last 4 characters and what the check found (plan, limits, domains, zones). Never returns the keys.
No parameters.
set_integration
Save an integration key after checking it with the service: agentmail_api_key (AgentMail API key of the account that holds the sending domains) or cf_api_token (Cloudflare API token with Zone:Read and DNS:Edit on the sending domains). The key is stored encrypted. Warn the user that a key pasted in a chat stays in the conversation history; the Integrations page in Kata is the safer place.
| Parameter | Type | Description |
|---|---|---|
name required | "agentmail_api_key" | "cf_api_token" | "instantly_api_key" | - |
value required | string | the key or token |
set_company_brief
Write the company memory (company.md), read by the AI everywhere: what the company does, for whom, proof (numbers, clients), tone, what to avoid. brief replaces the whole company.md. Only the fields you pass change.
| Parameter | Type | Description |
|---|---|---|
name | string | company name |
website | string | website |
brief | string | full company.md content |
get_company_brief
Read the company memory (company.md), name and website.
No parameters.
Campaigns
create_campaign
Create a campaign (status draft) and its brief.md. The brief gives the target, offer, proof and expected action; the AI reads it together with company.md. Then run check_campaign_brief.
| Parameter | Type | Description |
|---|---|---|
name required | string | short name |
brief required | string | offer, target, tone, proof, expected action |
target_url | string | page the {{link}} links point to |
followup_delays_days | number[] | follow-up delays in days, e.g. [3, 5] |
update_campaign
Rename a campaign, replace its brief.md (brief) or its link (target_url), or set its sending hours: each email goes out in its lead's local time (hours, default 8-17, weekdays only by default); the time zone comes from the lead's country column, else its email domain (.ie, .fr…), else the campaign's country; a lead with none of them can get it at any hour. One source of truth for the link: the "Link:" line of brief.md. target_url is a shortcut that rewrites that line (it wins over a Link: line inside brief when both are given); campaigns.target_url is only a copy of it. The link may hold contact variables, e.g. https://site.com/start?email={{email}}&fn={{first_name}}: {{link}} fills them per lead, URL-encoded (sean+ie@x.com becomes sean%2Bie%40x.com), and adds kata=<token> for the postback. Returns link: the URL, its variables and an example. Only the fields you pass change.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
name | string | name |
brief | string | full brief.md content |
target_url | string | destination of {{link}}; may contain {{email}}, {{first_name}}, {{company}} or any data key |
country | string | default country of its leads (ISO code like IE, or name): sets their time zone when a lead has no country column and its email domain says nothing; empty = none |
hours | string | sending hours in each lead's local time, start-end, e.g. 8-17 (default) |
weekdays | boolean | Monday to Friday only, in the lead's local time (default true) |
autopilot | boolean | on (default): the agent retires losing angles, writes new ones and gives the best angles more sends. Off: the angles in test stay and share the sends equally. Ask the user before turning it off. |
read_campaign_brief
Read a campaign's brief.md: target, offer, proof, expected action, link, then notes and updates.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
update_campaign_brief
Replace a campaign's brief.md with the full new content. The campaign link is the line "Link: https://…" and only that line (other URLs in the brief are plain text); it is the destination of {{link}} and may hold contact variables ({{email}}, {{first_name}}, any data key), filled and URL-encoded per lead. Removing the Link: line removes the campaign link. Keep it current without asking: target, offer, proof, action, link, then a "Notes and updates" part. Plain text, no markdown headings: the user reads and edits it (angles tried, decisions, results, next steps). Read it first and keep what is still true.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
content required | string | full markdown content |
check_campaign_brief
Check that a campaign's brief.md has what the AI needs to write and send. Returns ready, missing (to ask the user), tips, and checked: every point looked at, with ok and a note. ready covers the five essentials (target, offer, why, action, link = the Link: line). checked also lists informational sending checks that do not change ready: link_variables (the {{vars}} of the link exist on enrolled contacts), angles (each step has a live angle), tracked_links (live angles with hand-written URLs, which are not tracked). The result shows on the campaign page.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
set_campaign_status
Activate or pause a campaign. A campaign is the top switch: draft or paused = nothing planned for it, whatever the mailboxes. active = its emails are planned on the next scheduler run (every minute), only from its mailboxes that are themselves active. Run scheduler_dry_run first to see what would go out. Activating sends the campaign's first email at once when a planned lead is inside its local sending hours (the answer says to whom, or when it goes out), so the user sees it works; the others are spread over the day. Never activate without the user's approval.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
status required | "active" | "paused" | "draft" | - |
set_steps
Set a campaign's sequence: follow-up delays in days after the previous email, e.g. [3, 5] = first email, follow-up 1 three days later, follow-up 2 five days after that. [] = no follow-up. Each follow-up step needs its variants (generate_angles with step).
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
followup_delays_days required | number[] | - |
delete_campaign
Delete a campaign that never sent (variants, steps, enrollments, campaign files; contacts stay). A campaign that sent is refused: pause it instead. Ask the user first; dry_run shows what goes.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
dry_run | boolean | - |
get_campaign_mailboxes
Which mailboxes a campaign sends from. mode all = every active mailbox; mode selected = only the chosen ones. Lists every mailbox with chosen/used, status, daily cap and sent today.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | campaign |
set_campaign_mailboxes
Choose the mailboxes a campaign sends from. all=true: every active mailbox (default). emails: exactly these. add / remove: change the current list. Only new first emails follow the choice; follow-ups stay in the mailbox of their thread. A chosen mailbox still has to be active to send.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | campaign |
all | boolean | send from every active mailbox |
emails | string[] | exactly these mailboxes |
add | string[] | - |
remove | string[] | - |
Angles and emails
generate_angles
The AI writes variants (different angles) for one step. Step 0 = first email.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
step | number | 0 = first email, 1 = follow-up 1... |
count | number | 1 to 6, default 3 |
instructions | string | extra instructions |
add_variant
Add a variant written by hand. Variables: {{first_name}}, {{company}}, {{email}}, any data key, {{sender_first}}; {{link}} = the campaign link (tracked when the mailbox has a link domain, always with kata=<token>). A variable inside a URL is URL-encoded; {{x|url}} encodes anywhere, {{x|raw}} never. A URL written by hand in the body is sent as is: not tracked, no token.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
step required | number | step |
angle | string | angle name |
subject | string | subject (empty for a follow-up) |
body required | string | body, with {{first_name}} {{company}} {{link}} |
list_variants
Variants (angles) of a campaign with full subject and body, status and stats (sent, clicks, replies, interested, conversions, rate, p_best), plus the sequence steps.
| Parameter | Type | Description |
|---|---|---|
campaign_id required | number | id |
set_variant
Retire a variant or put it back in the test (status), or edit its angle, subject or body. Only the fields you pass change. Editing a variant that already sent mixes its stats: prefer add_variant and retire the old one.
| Parameter | Type | Description |
|---|---|---|
id required | number | variant id |
status | "active" | "retired" | - |
angle | string | angle |
subject | string | subject |
body | string | body |
evolve
Retire losing variants and, if there is room, have a new one written from the best.
No parameters.
preview_email
Show an email exactly as a lead would get it (variables filled, opt-out line and signature added), without sending. Same inputs as send_test_email, to is optional. Returns link_in_email (what the email shows for {{link}}), link_destination (the final URL: the Link: line of brief.md with the lead's variables URL-encoded, plus kata=<token>; PREVIEW stands for the token), link_tracked (true when the mailbox's domain has a link domain: the click is counted, then redirected), and untracked_links: URLs written by hand in the body. Those go out as written (variables URL-encoded) but their clicks are not counted and they get no kata token: use {{link}} for anything you want tracked.
| Parameter | Type | Description |
|---|---|---|
mailbox | string | sending address |
contact | string | email of a lead in Kata whose fields and data fill the variables |
campaign_id | number | campaign |
variant_id | number | variant |
step | number | step |
subject | string | own subject |
body | string | own body |
vars | object | variable values |
to | string | optional |
send_test_email
Send one test email now to any address (usually the user's own), exactly as a lead would get it, from any Kata mailbox, paused ones included. Outside the scheduler: no campaign, mailbox status, enrollment or lead changes. Content: variant_id, or campaign_id (+ step) to use its best angle, or your own subject and body. Variables come from contact (a lead already in Kata) and vars; {{link}} is the campaign link with them filled in (URL-encoded), without token and not tracked. No approval needed beyond the user asking for a test.
| Parameter | Type | Description |
|---|---|---|
to required | string | test address |
contact | string | email of a lead in Kata whose fields and data fill the variables |
mailbox | string | sending address in Kata (default: an active one, else any) |
campaign_id | number | campaign |
variant_id | number | variant |
step | number | 0 = first email, 1 = follow-up 1… |
subject | string | own subject instead of a variant |
body | string | own body instead of a variant |
vars | object | values for the variables, e.g. {"first_name": "Yoann"} |
Leads and contacts
import_leads
Import contacts from a CSV (email column required; first_name, company and any other column become variables; a priority column orders the sends: 1 first, then 2, 3…, empty last). Each new address gets a free check (syntax, disposable, domain without mail server): invalid ones get status invalid and never enter a sequence; the result says how many (email_check). The full mailbox check happens on its own before each contact's first email. Pass either the CSV text (csv) or an uploaded file key (file, e.g. uploads/....csv). With campaign_id, contacts enter the sequence. With list_id, they join that lead list (and the campaigns it feeds).
| Parameter | Type | Description |
|---|---|---|
csv | string | full CSV content |
file | string | key of a CSV already uploaded to Kata |
campaign_id | number | campaign to enroll them in |
list_id | number | lead list to add them to (it also enrolls them in the campaigns it feeds) |
add_leads
Add leads to a lead list, new or already in Kata: the simplest way for one lead or a few (for a CSV file, use import_leads). Each lead: email (required), first_name, company, priority (1 is sent first, then 2, 3…; none goes last), and any other field, which becomes a variable (country, phone…). New addresses get the free email check; invalid ones are kept out. The leads then enter the campaigns the list feeds, split between them when it feeds several; a person already in a live sequence is not enrolled twice. Max 1000 per call. Tell the user when the list feeds an active campaign: those people will get emails.
| Parameter | Type | Description |
|---|---|---|
list_id required | number | lead list |
leads required | object[] | the leads Each item: email*, first_name, company, priority, and any other field |
list_contacts
List contacts (leads) with their data (CSV columns), status, value and sequences. Filters combine with AND. Returns count (all matches) and one page.
| Parameter | Type | Description |
|---|---|---|
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id | number | contacts in this lead list |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
limit | number | page size, default 50, max 200 |
offset | number | skip this many |
get_contact
One contact by email or id: fields, data, suppression, enrollments, every message (with body) and events (replies, clicks, bounces).
| Parameter | Type | Description |
|---|---|---|
email | string | |
id | number | contact id |
update_contacts
Edit contacts: set first_name/company and merge keys into data (a value sets the key, null removes it). One contact (email/id) or bulk by filters. Bulk needs the user's approval: run with dry_run first and tell the user the count. Returns matched and changed.
| Parameter | Type | Description |
|---|---|---|
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id | number | contacts in this lead list |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
all | boolean | every contact (no filter) |
first_name | string | new first name |
company | string | new company |
data | object | keys to merge into data, e.g. {"country":"IE"}; null removes a key |
dry_run | boolean | count only, write nothing |
delete_contacts
Delete contacts that were never emailed, with their enrollments and queued emails. Contacts already emailed are kept and listed (use suppress for them). Run dry_run first and get the user's approval.
| Parameter | Type | Description |
|---|---|---|
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id | number | contacts in this lead list |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
dry_run | boolean | count only |
enroll_contacts
Put existing contacts into a campaign's sequence (they start at step 0). Selected by the same filters as list_contacts. Skips suppressed, bounced, unsubscribed, those already in that campaign, and replied/converted unless include_replied. Run dry_run first and get the user's approval.
| Parameter | Type | Description |
|---|---|---|
target_campaign_id required | number | campaign to enroll them in |
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id | number | contacts in this lead list |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
include_replied | boolean | - |
dry_run | boolean | - |
stop_sequence
Stop the sequence for these contacts (all campaigns, or one campaign) and cancel their queued emails. They are not suppressed.
| Parameter | Type | Description |
|---|---|---|
emails required | string[] | - |
campaign_id | number | only this campaign |
suppress
Unsubscribe addresses for good: add them to the suppression list, stop every sequence, cancel queued emails, mark the contacts unsubscribed. undo=true removes them from the suppression list only.
| Parameter | Type | Description |
|---|---|---|
emails required | string[] | - |
reason | string | e.g. asked by phone |
undo | boolean | - |
mark_handled
The Done button on leads to call: mark an interested contact as handled (called, meeting booked). undo=true puts it back in leads_to_call.
| Parameter | Type | Description |
|---|---|---|
email | string | |
id | number | contact id |
undo | boolean | - |
check_emails
Check email addresses. mode free (default, no cost, up to 2000): syntax, typos (gmial.com, with the fix), disposable domains, generic addresses (info@…), and whether the domain has a mail server. mode deep (includes free, max 200, about 2 to 10 seconds per 10 addresses): then asks whether each mailbox really exists. Kata's SMTP verifier asks the recipient's server (free); when that server will not answer, MillionVerifier through Bourse (about $0.002 per address, paid); catch-all addresses go to a decision model (about $0.00003). Addresses the free pass rejects never reach the paid steps. The result says what it cost (cost.usd). Kata already runs free on every import and deep on its own before each contact's first email: use deep only when the user wants a list checked now, tell them it can cost up to $0.002 per address, and ask before more than 100. Give emails, or campaign_id for its contacts not checked yet. Results go to the email_checks table (status, reason, source, decision send/skip). suppress_invalid=true takes rejected addresses out of their sequences: only with the user's approval.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | - |
mode | "free" | "deep" | free = no cost (default); deep = the mailbox exists, may cost |
campaign_id | number | check the contacts of this campaign |
only_unchecked | boolean | default true |
limit | number | max contacts, default 500 (free) or 200 (deep) |
save | boolean | save in email_checks, default true |
suppress_invalid | boolean | stop sequences of rejected addresses |
enrich_leads
Enrich leads from their work email through Bourse (Tomba person + company lookup): job_title, seniority, role, company, company_domain, location, country, linkedin, twitter, company_industry, company_size, company_linkedin, company_description. Only empty fields of the lead's data are filled; enriched_at is set, and leads already enriched are skipped (only_missing=false to redo them). Personal inboxes (gmail, outlook…) are skipped. Costs about $0.021 per address found, nothing on a miss. Without confirm it spends nothing: it returns the count and the cost; tell the user, then call again with confirm=true and the same filters. Give emails, list_id or campaign_id. max: default 50, up to 200 per call.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | - |
list_id | number | the people of this lead list |
campaign_id | number | the leads of this campaign |
max | number | at most this many leads, default 50, max 200 |
confirm | boolean | true = spend; without it only the estimate |
only_missing | boolean | default true: skip leads already enriched |
Lead lists
list_lists
Lead lists: id, name, description, members, unreachable (bounced or unsubscribed) and the campaigns each one feeds. A list is a source: connected to a campaign, its people enter that campaign's sequence, and people added later join too.
No parameters.
create_list
Create a lead list. Optionally fill it right away with the same filters as list_contacts (e.g. file from import_leads, data_match, search). Contacts are not duplicated: a list only groups them. from_unlisted: true makes the list from the leads that entered campaigns without a list (the 'Leads without a list' block of the Canvas) and connects it to those campaigns, with no new enrollment.
| Parameter | Type | Description |
|---|---|---|
name required | string | e.g. Brokers Ireland |
description | string | who is in it, where it comes from |
from_unlisted | boolean | - |
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id | number | contacts in this lead list |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
update_list
Rename a lead list or change its description.
| Parameter | Type | Description |
|---|---|---|
list_id required | number | id |
name | string | new name |
description | string | new description |
add_to_list
Add existing contacts to a lead list, selected by the list_contacts filters. They also enter every campaign the list feeds (an active campaign starts emailing them): run dry_run first and get the user's approval. To add new people, import_leads with list_id.
| Parameter | Type | Description |
|---|---|---|
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id required | number | list to add to |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
dry_run | boolean | - |
remove_from_list
Remove contacts from a lead list (filters as list_contacts; list_id alone is refused, add all: true to empty it). Their sequences in the campaigns this list feeds stop, unless keep_sequences or another connected list still holds them. Contacts stay in Kata. Run dry_run first and get the user's approval.
| Parameter | Type | Description |
|---|---|---|
email | string | one contact |
id | number | one contact id |
emails | string[] | these addresses |
campaign_id | number | contacts enrolled in this campaign |
list_id required | number | list to remove from |
status | "new" | "enrolled" | "replied" | "bounced" | "unsubscribed" | "converted" | - |
search | string | text in email, first name or company |
data_match | object | data fields equal to these values, e.g. {"country":"IE"}; null = key missing |
since | string | added on or after this date (ISO, e.g. 2026-10-01) |
file | string | contacts listed in this imported CSV (file key returned by import_leads) |
all | boolean | - |
keep_sequences | boolean | - |
dry_run | boolean | - |
connect_list
Draw or erase the arrow from a lead list to a campaign. connect (default true) enrolls the list's people into the campaign (same rules as enroll_contacts) and keeps enrolling those added later. connect false stops new enrollments; sequences already started go on. Run dry_run first and get the user's approval when the campaign is active.
| Parameter | Type | Description |
|---|---|---|
list_id required | number | list |
campaign_id required | number | campaign |
connect | boolean | - |
dry_run | boolean | - |
delete_list
Delete a lead list and its arrows. Contacts and their sequences stay. Ask the user first; dry_run shows the counts.
| Parameter | Type | Description |
|---|---|---|
list_id required | number | id |
dry_run | boolean | - |
list_webhook
The webhook URL of a lead list: another tool (a form, Zapier, n8n, a CRM) POSTs leads to it and they join the list on their own, with the free email check, and enter the campaigns the list feeds. Body: one lead {email, first_name, company, priority, other fields become variables} or {leads: [...]} up to 1000. priority 1 is sent first, then 2, 3…; no priority goes after the others. rotate=true makes a new URL (the old one stops working).
| Parameter | Type | Description |
|---|---|---|
list_id required | number | lead list |
rotate | boolean | - |
Mailboxes and domains
list_mailboxes
Sending mailboxes: domain, provider, status (active/paused), daily_cap, note (why paused), sent today, total sent, bounces (hard), domain status, and inbox placement of the Instantly warmup emails over 7 days (warmup_inbox_7d, warmup_spam_7d, inbox_rate when 10 or more landed). A mailbox sends only when it and its domain are active AND an active campaign uses it. Kata never activates a mailbox on its own: only set_mailbox, create_mailbox (a new mailbox) or the user in the UI do, and each change is logged (events type audit). Kata pauses one by itself when hard bounces pass 3%, or when more than 10% of its warmup emails land in spam.
| Parameter | Type | Description |
|---|---|---|
domain | string | only this domain |
create_mailbox
Add one mailbox to Kata, active. provider agentmail (default): creates a new inbox in the AgentMail account; domain is a verified AgentMail domain already in Kata, or empty for @agentmail.to. provider zapbox: connects an existing Zapmail (Google Workspace) mailbox; give its full email. Use a real first name. Ask the user first.
| Parameter | Type | Description |
|---|---|---|
provider | "agentmail" | "zapbox" | - |
username | string | part before @, e.g. marc |
email | string | full address (zapbox) |
domain | string | verified domain, empty = agentmail.to |
display_name | string | e.g. Marc Dubois |
daily_cap | number | emails per day at start (default 10) |
sync_mailboxes
Import the inboxes that already exist in the AgentMail account as Kata mailboxes, paused. Never changes the status of a mailbox already in Kata (it also runs every 15 minutes).
No parameters.
set_mailbox
Activate or pause a mailbox, change its daily_cap, or set its email signature (the line under the sender name). Give email (one address) or domain (all its addresses; activating them also resumes the domain). An active mailbox sends only for active campaigns that use it. Each change is logged (events type audit). Never activate without the user's approval.
| Parameter | Type | Description |
|---|---|---|
email | string | one mailbox |
domain | string | all mailboxes of this domain |
status | "active" | "paused" | - |
daily_cap | number | emails per day per mailbox |
signature | string | line under the sender name in every email from this mailbox, e.g. FightBets · fightbets.com; empty = none |
remove_mailbox
Remove a mailbox from Kata (it stays at the provider; sync will not bring it back). Its queued emails are cancelled. Ask the user first.
| Parameter | Type | Description |
|---|---|---|
email required | string | mailbox |
connect_domain
Connect a domain from the Cloudflare account to AgentMail: writes the DNS records, starts verification, then creates the mailboxes (real first names recommended, 3 per domain). Stops if the domain already receives email elsewhere, unless force=true.
| Parameter | Type | Description |
|---|---|---|
domain required | string | e.g. gofightbets.com |
inboxes required | object[] | Each item: username*, display_name* |
daily_cap | number | emails per day per mailbox at start (default 10) |
force | boolean | replace an existing MX record |
check_domains
Check pending domains and create their mailboxes once verified.
No parameters.
set_domain
Resume a domain Kata paused for bounces, or pause it. Daily limits are set per mailbox (set_mailbox), not per domain.
| Parameter | Type | Description |
|---|---|---|
domain required | string | domain |
status required | "active" | "paused" | - |
remove_domain
Remove a domain and all its mailboxes from Kata (not from the provider or DNS). Ask the user first.
| Parameter | Type | Description |
|---|---|---|
domain required | string | domain |
setup_link_domain
Give a sending domain its own link domain (go.<domain>, else get/try/info) for click and unsubscribe links. Only possible when the domain is in the user's Cloudflare (token in Integrations with DNS Edit): registers the hostname on Kata's zone and writes the CNAME. Active a few minutes later (check_link_domains). Without it, links go straight to the campaign page and clicks are not counted.
| Parameter | Type | Description |
|---|---|---|
domain required | string | sending domain |
check_link_domains
Check the link domains being set up: they become active once Cloudflare has issued their certificate.
No parameters.
warmup_status
Warmup state of the mailboxes in Instantly: in Instantly or not, warmup on/off/paused/error.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | default: every mailbox |
warmup_enable
Turn warmup on in Instantly for mailboxes (emails, or all of a domain). AgentMail mailboxes not yet in Instantly are added automatically by IMAP/SMTP. Other providers must be connected in Instantly first. Ask the user first.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | - |
domain | string | all mailboxes of this domain |
daily_warmup | number | warmup emails per day, default 20 |
warmup_disable
Turn warmup off in Instantly for mailboxes (emails or a domain). remove=true also removes them from Instantly. Ask the user first.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | - |
domain | string | all mailboxes of this domain |
remove | boolean | also delete the accounts from Instantly |
Sending and messages
scheduler_dry_run
What the next scheduler run would plan, without sending or writing anything: for each email the contact, campaign, step, mailbox, variant (angle), subject, planned time (UTC) and final link. Also explains what blocks sending, in this order: (1) campaign status (only active campaigns send; set_sending only flips every campaign at once), (2) mailbox status and its domain status (both must be active), (3) the campaign's chosen mailboxes (none chosen = every active mailbox), (4) daily cap per mailbox, (5) the lead's local sending hours (campaign setting), this shows tomorrow's first run), (6) contacts suppressed, stopped or not due yet. Also lists emails already planned and waiting. as_if_active=true simulates draft or paused campaigns as active (mailboxes stay as they are). No AI personalization in the preview.
| Parameter | Type | Description |
|---|---|---|
campaign_id | number | only this campaign |
as_if_active | boolean | treat draft/paused campaigns as active |
limit | number | emails listed, default 50, max 200 |
run_scheduler
REALLY SENDS. Runs the scheduler now, as the cron does every minute: plans the emails of active campaigns from active mailboxes, then sends every planned email whose time has come. It does nothing the cron would not do a minute later, but it is real sending. To look without sending, use scheduler_dry_run. Ask the user first.
No parameters.
set_sending
Global switch, a shortcut over campaign statuses (there is no separate flag): on=false pauses every active campaign and cancels the emails already planned; on=true sets every paused campaign back to active (drafts stay drafts). Mailbox statuses are not touched. Resuming needs the user's approval.
| Parameter | Type | Description |
|---|---|---|
on required | boolean | - |
list_messages
Emails, newest first: scheduled, sent or failed, with a body preview. Filters: campaign_id, status, flag (clicked/replied/interested/converted/bounced), contact email. Returns count and one page.
| Parameter | Type | Description |
|---|---|---|
campaign_id | number | campaign |
status | "scheduled" | "sending" | "sent" | "failed" | - |
flag | "clicked" | "replied" | "interested" | "converted" | "bounced" | - |
email | string | contact email |
limit | number | default 50, max 200 |
offset | number | skip |
get_message
One email in full: subject, body, mailbox, contact, campaign, angle, status, flags and its events (reply text, click, bounce).
| Parameter | Type | Description |
|---|---|---|
id required | number | message id |
list_inbox
The Inbox: every reply received, one row per lead (its latest reply), newest first. Each row: contact_id, email, first_name, company, status (the reply kind: interested, not_interested, unsubscribe, ooo, wrong_person, other), unread (not opened in Kata yet), answered (you replied after it), replies (how many), message_id (pass it to reply), campaign, mailbox, subject, text (what they wrote), at. Also counts per status, the unread total, and the campaigns and mailboxes that got replies. Filters: status, campaign_id, mailbox, unread=true, q (text in email, name, company, subject or reply). 50 rows a page (limit up to 200), page from 0. Spam complaints and unsubscribe-link clicks are not listed.
| Parameter | Type | Description |
|---|---|---|
status | "all" | "interested" | "not_interested" | "unsubscribe" | "ooo" | "wrong_person" | "other" | - |
campaign_id | number | one campaign |
mailbox | string | the mailbox that received the reply |
unread | boolean | only replies not opened in Kata yet |
q | string | search text |
page | number | page, from 0 |
limit | number | rows per page, default 50, max 200 |
set_reply_status
Correct the AI tag of a lead's latest reply (what the user does from the Inbox). status: interested, not_interested, unsubscribe, ooo, wrong_person or other. Nothing restarts: any status except ooo stops the lead's sequence, ooo leaves it as it is. unsubscribe suppresses the address; moving away from unsubscribe lifts that suppression. interested puts the lead in leads_to_call and counts as a success for the angle. Ask the user before you change a tag.
| Parameter | Type | Description |
|---|---|---|
contact_id required | number | the lead (contact_id from list_inbox) |
status required | "interested" | "not_interested" | "unsubscribe" | "ooo" | "wrong_person" | "other" | - |
reply
Reply to a lead in the same thread, from the mailbox that wrote to them (AgentMail, Gmail, Outlook, Kata's pool; Zapmail with its thread ids, like follow-ups, but Zapmail replies do not come back to Kata yet). Give message_id (one email Kata sent) or email (the lead: the last email sent to them), and text. draft_only=true sends nothing: with text it returns the email as it would go, without text the AI writes a draft from the brief, the company memory and the thread (instructions steers it). Show the draft to the user and send only with their approval. The reply goes out even if the mailbox is paused (a 1:1 answer, not a campaign send) and shows in the lead's timeline (event reply_sent). The sender name and signature are added. Sending marks the lead read in the Inbox (list_inbox gives message_id and email).
| Parameter | Type | Description |
|---|---|---|
message_id | number | an email Kata sent |
email | string | the lead's email: replies in its last thread |
text | string | the reply body, plain text, without signature |
draft_only | boolean | send nothing; without text the AI writes a draft |
instructions | string | for the AI draft, e.g. propose a call on Thursday |
export_csv
Export to CSV and get a download link (valid 24 hours, works for anyone who has it; give it to the user). kind events (default) = the activity log: sent, click, reply, reply_sent, bounce, visit, conversion, pause…; kind messages = the emails with status, flags, bounce type and reason. Filters: campaign_id, type (an event type for events, a status scheduled|sent|failed for messages), mailbox, period 7d|30d|90d|all (default all). 50,000 rows at most.
| Parameter | Type | Description |
|---|---|---|
kind | "events" | "messages" | - |
campaign_id | number | one campaign |
type | string | event type, or message status |
mailbox | string | one sending mailbox |
period | "7d" | "30d" | "90d" | "all" | - |
Context files
list_context
List context files (no content). Company files are always in your context; campaign files must be read with read_context. With campaign_id: company files plus that campaign's files.
| Parameter | Type | Description |
|---|---|---|
campaign_id | number | campaign id |
read_context
Read the full content of one context file.
| Parameter | Type | Description |
|---|---|---|
id required | number | file id from list_context |
save_context
Create or replace a context file (same scope, campaign and name = replaced). scope company: used everywhere (agent and emails). scope campaign: used for that campaign's emails and read on demand. Ask the user before saving.
| Parameter | Type | Description |
|---|---|---|
scope required | "company" | "campaign" | - |
campaign_id | number | required when scope is campaign |
name required | string | file name, e.g. pricing.md |
content required | string | full text content |
delete_context
Delete a context file. Ask the user first.
| Parameter | Type | Description |
|---|---|---|
id required | number | file id |
Conversions
get_postback
The postback URL the user's backend calls to report a conversion (GET or POST, with email or kata=<token>, optional value, event and data: free lead fields saved on the contact, not a conversion when sent alone), and the last conversion received.
No parameters.