Developers · MCP
Build on the registry your team already uses.
External agents operate Limena over the Model Context Protocol: the same 103 capabilities Limena’s own assistant uses, with scoped API keys, rate zones, and every mutation written to the audit log. Point an MCP client at the endpoint below and your agent can read and write your CRM, within the scopes you grant it.
- url
- https://api.limena.io/mcp/sseHTTP + Server-Sent Events. A local checkout can also run it over STDIO.
- auth
- Authorization: Bearer lmn_…
- requires
- an Ultra workspace + a key with mcp:accessKeys are created on Pro and up; the connection itself is an Ultra capability. The catalogue below is the same on every plan.
Connect
Limena speaks MCP over two transports. Most clients use the hosted SSE endpoint; a local checkout of the server can run over STDIO. This is the same configuration the app shows under Settings → Connect an AI client.
Connecting needs an Ultra workspace and an API key with the mcp:access scope.
Create the key under Settings → API keys (available on Pro and up). Browsing this reference needs no account.
{
"mcpServers": {
"limena": {
"url": "https://api.limena.io/mcp/sse",
"headers": {
"Authorization": "Bearer <YOUR_API_KEY>"
}
}
}
}Self-hosting / local development (STDIO transport)
STDIO runs the MCP server as a subprocess on your machine; you only need it with a local checkout of the Limena repo. Replace <REPO> with the checkout path and <YOUR_API_KEY> with your lmn_ key.
{
"mcpServers": {
"limena": {
"command": "<REPO>/.venv/bin/python",
"args": ["-m", "app.ai.mcp.server"],
"cwd": "<REPO>",
"env": {
"LIMENA_API_KEY": "<YOUR_API_KEY>"
}
}
}
}Authentication & keys
- format
- lmn_ + base58, shown once at creationThe secret is hashed at rest and never displayed again.
- header
- Authorization: Bearer lmn_…
- create
- Settings → API keysAvailable on Pro and up. Admin-scoped keys are restricted to the owner.
- revoke
- any key, any time; the next call with it is refused
Two-tier scopes
mcp:accessis the connection scope. A key without it can open no MCP session at all.- Granular
<family>:<read|write>scopes are checked per call.create_taskneedstasks:write;search_contactsneedscontacts:read. Holdingmcp:accessalone grants nothing beyond the handshake. - Grant the minimum set. A key carries exactly the scopes you select when you create it, so an agent can do only what its key allows.
Scopes
The scopes the published catalogue uses, grouped by resource family. Each is a read or write grant on one family, plus the mcp:access connection scope.
- mcp:access
- open an MCP connectionRequired to connect. Grants no data access on its own.
- Contacts
- contacts:read · contacts:write
- Companies
- companies:read · companies:write
- Deals
- deals:read · deals:write
- Tasks
- tasks:read · tasks:write
- Notes
- notes:read · notes:write
- Meetings
- meetings:read · meetings:write
- Calls
- calls:read
- Outreach
- outreach:read · outreach:write
- Campaigns
- campaigns:read · campaigns:write
- Attachments
- attachments:read
- Search
- search:read
- Analytics
- analytics:read
- Users
- users:read
- bulk
- bulk:write
- imports
- imports:read
- lists
- lists:read · lists:write
- segments
- segments:read · segments:write
- tags
- tags:read
- templates
- templates:read · templates:write
Capability reference
Every capability an MCP client can call, grouped by resource family. Mutating calls are flagged and need a write scope.
103 capabilities · 52 mutating
Contacts
contacts:read · contacts:writearchive_contactmutatesArchive (soft-delete) a contact. It moves to Trash and can be restored later, not permanently erased. In the assistant this returns a confirmation card and the contact is archived only when the user confirms; over the API it is archived immediately.
bulk_create_contactsmutatesCreate up to 100 contacts atomically. Each entry may carry optional `tags` and `custom_fields` for per-contact enrichment (e.g. prospect_intel shapes), and an optional `owner_id` naming the teammate who will own that contact (omit to own it yourself). Any failure mid-batch rolls back the whole transaction: zero contacts are written. Returns {contacts: [<detail>, ...], created_count: N}.
bulk_find_emailsmutates10/hourFind work emails for up to 10 people in one call and add each hit as a new reachable contact, with per-person outcomes and the same provenance as find_email (observed vs inferred origin, source URLs, confidence, verification status). People are resolved one by one and the batch is not atomic: contacts already created stay created if a later lookup stops the batch. Per-person status is one of created, not_found, error, or skipped; when the monthly email finder quota runs out or the provider fails mid-batch, the batch stops and stopped_early names the reason with every remaining person reported as skipped, never dropped. Needs email finding enabled for the workspace.
correct_contact_emailmutatesCorrect a contact's email address, with provenance. update_contact deliberately cannot change email (an identity-stable patch); this is the audited path for fixing a wrong address. Requires provenance_url naming where the corrected address was observed; it is recorded in the audit trail beside the previous value. Returns a typed conflict when another active contact already has the address. Resets the bounce flag set against the old address and re-derives suppression for the new one. Stamps the email provenance custom fields (email_source correction, email_status unverified). Returns {contact, previous_email}.
create_contactmutatesCreate a new contact in the tenant's CRM.
describe_crm_schemaThe real schema of the CRM records you can read and write: per-entity columns with their type, nullability, and foreign keys, plus this tenant's custom fields (on contact/company/deal). Call it to answer what fields/columns an entity has. Report what it returns, never infer the schema from a tool's arguments. Pass an entity to scope it, or omit for all.
find_emailmutates10/hourFind a person's work email from their name + company (domain or name) and add them as a new reachable contact, with the email's provenance recorded: whether the address was observed on real web pages (with source URLs) or inferred from a pattern, plus confidence and verification status. Searches the open web first at no cost; a paid search runs only on a web miss and counts toward the workspace's monthly email finder quota. Confirms before the lookup + create. If no email is found, nothing is created; QUOTA_EXHAUSTED means the monthly allowance is used up and FINDER_UNAVAILABLE means the provider failed, so not_found always means a real miss. Needs email finding enabled for the workspace. Returns the resolved email + the created contact, or that none was found.
find_or_create_contactmutatesEmail-keyed find-or-create. Looks up the tenant's contacts by lowercased email; returns the existing record if matched, otherwise creates with the supplied fields. `first_name` and `last_name` are required only on the create path. `tags` and `custom_fields` apply only on the create path; the find branch returns the existing record unchanged, and any provided field that differs from the matched record is named in the response's warnings so the drop is never silent. Returns {contact, created: bool}.
find_similar_contactsFind contacts semantically similar to a seed contact (by contact_id) or to a free-text description (by query). Exactly one must be given. Returns contacts ranked nearest-first as {contact, distance} (cosine distance, 0 = identical). An unindexed seed returns an empty list.
get_contactGet full details for a specific contact by id.
restore_contactmutatesRestore a soft-deleted contact from Trash back to active (the inverse of archive_contact). In the assistant this returns a confirmation card and the contact is restored only when the user confirms; over the API it is restored immediately. Use this to undo an archive. Refuses if the contact isn't in the trash.
search_contactsSearch contacts by name, email, or company, or omit query to list them. Results are ordered by sort_by, newest first, so no query with the default sort returns the most recently added contacts. Can filter to the current user's own contacts (mine), one teammate's (owner_id), unassigned contacts with no owner (unowned), a tag, a status, one company (company_id), whether the contact has a dialable phone number (has_phone), or untouched leads. Returns the matched rows plus the total matched count; when total exceeds the page, fetch the rest with page=2, 3, …
update_contactmutatesPartially update a contact's editable fields.
Companies
companies:read · companies:writearchive_companymutatesArchive (soft-delete) a company. It moves to Trash and can be restored later, not permanently erased. Its contacts are unlinked (not deleted). In the assistant this returns a confirmation card and the company is archived only when the user confirms; over the API it is archived immediately.
create_companymutatesCreate a new company in the tenant's CRM. Mirrors the REST POST /companies endpoint.
find_or_create_companymutatesFind-or-create on the canonical match key: normalised domain first, then case-insensitive name among active companies. Returns the existing record if either key matches (a name-match backfills a missing domain), otherwise creates with the supplied fields. tags, custom_fields, source and the other extras apply only on the create path: the find branch returns the existing record as-is without mutating it (updating a match is a separate update_company call), and any provided field that differs from the matched record is named in the response's warnings so the drop is never silent. `name` is required only on the create path. Returns {company, created: bool}.
find_similar_companiesFind companies semantically similar to a seed company (by company_id) or to a free-text description (by query). Exactly one must be given. Returns companies ranked nearest-first as {company, distance} (cosine distance, 0 = identical). An unindexed seed returns an empty list.
get_companyGet full details for a company by id, including contacts.
list_company_contactsList contacts that belong to a specific company.
restore_companymutatesRestore a soft-deleted company from Trash back to active (the inverse of archive_company). In the assistant this returns a confirmation card and the company is restored only when the user confirms; over the API it is restored immediately. Contacts that were unlinked at delete time are NOT re-linked. Refuses if the company isn't in the trash.
search_companiesSearch companies by name or domain. Returns {companies, total} with contact counts, owner and location; when total exceeds the page, fetch the rest with page=2, 3, … Can filter to the current user's own companies (mine), one teammate's (owner_id), unassigned companies with no owner (unowned), companies carrying a tag (tag), a city, a region or nation, a country, a research source, a prospect-intel fit_rating or a prospect_stage. Filter here rather than paging and sorting on your side: geography and fit are the two things that used to require reading every company one at a time. For Scotland or England use region, not country: both nations store country=United Kingdom.
update_companymutatesUpdate a company's fields. Pass only the fields to change. Domain input is canonicalised (scheme/path stripped).
Deals
deals:read · deals:writearchive_dealmutatesArchive (soft-delete) a deal. It moves to Trash and can be restored later, not permanently erased. In the assistant this returns a confirmation card and the deal is archived only when the user confirms; over the API it is archived immediately.
create_dealmutatesCreate a new deal in the pipeline. Requires a contact; company is optional. Value is in cents. Stage defaults to the tenant's first open pipeline stage if omitted.
find_similar_dealsFind deals semantically similar to a seed deal (by deal_id) or to a free-text description (by query). Exactly one must be given. Returns deals ranked nearest-first as {deal, distance} (cosine distance, 0 = identical). An unindexed seed returns an empty list. Use for 'find deals like our best closed ones' or 'deals about platform migrations'; for structured filtering by stage/owner use search_deals.
get_dealGet full details for a specific deal by id.
get_pipeline_summaryCurrent deal pipeline grouped by stage with totals.
restore_dealmutatesRestore a soft-deleted deal from Trash back to active (the inverse of archive_deal). In the assistant this returns a confirmation card and the deal is restored only when the user confirms; over the API it is restored immediately. Use this to undo an archive. Refuses if the deal isn't in the trash.
search_dealsSearch and filter deals. Supports filtering by stage, owner, contact, or company; use mine for the current user's own deals. Optional text query matches deal title. Returns {deals, total}; when total exceeds the page, fetch the rest with page=2, 3, …
update_dealmutatesUpdate a deal's fields. Pass only the fields to change. Stage changes are audited with direction (forward/backward).
Tasks
tasks:read · tasks:writearchive_taskmutatesArchive (soft-delete) a task. It moves to Trash and can be restored later with restore_task, not permanently erased. In the assistant this returns a confirmation card and the task is archived only when the user confirms; over the API it is archived immediately.
complete_taskmutatesMark a task as completed. Idempotent if already completed.
create_taskmutatesCreate a follow-up task. Optionally attach to a contact, deal, or company. Assignee defaults to the current user.
get_my_tasksGet open tasks assigned to the current user, sorted by due date (overdue first, then upcoming, then no-date). Use this whenever the user asks about their own tasks.
list_tasksList tasks, optionally filtered by entity or assignee. Returns open tasks by default; set include_completed=true for all.
restore_taskmutatesRestore a soft-deleted task from Trash back to active (the inverse of archive_task). Find the task_id with search_trash, which lists archived records. In the assistant this returns a confirmation card and the task is restored only when the user confirms; over the API it is restored immediately. Refuses if the task isn't in the trash.
update_taskmutatesEdit an existing task: reschedule (due_at; null clears the due date), set status (todo/in_progress/waiting/done), priority, size (s/m/l), reassign, rename, edit the description, or re-link it to a contact, deal, or company. Only the fields provided change; setting status to 'done' completes the task and leaving 'done' reopens it.
Notes
notes:read · notes:writecreate_notemutatesAdd a note to a contact, deal, or company, or a comment to a task. To @-mention a teammate so they get notified, write @[Their Name](user:<user_id>) in the body. User ids come from list_users.
list_entity_notesList notes attached to a contact, deal, company, or task (a task's notes are its comments). Returns newest first.
recent_notesList notes created across ALL records in the last N days, newest first, optionally filtered to notes whose body contains a given text. Use for 'any notes about pricing this week?' or 'what did I note recently?' when you don't have a specific record in hand. For notes on ONE known record, use list_entity_notes instead.
search_notesSemantic search across ALL note bodies: matches by meaning, not substring, so 'pricing pushback' finds a note that says 'thought the quote was steep'. Returns notes ranked nearest-first with their parent record reference and a cosine distance (0 = identical). Use when the user half-remembers a note; for literal text or a time window use recent_notes, and for one known record use list_entity_notes.
update_notemutatesEdit an existing note or task-comment: replace its body with new text (pass the whole body, not a diff). The note keeps its author and parent record. Get the note_id from list_entity_notes, recent_notes, or search_notes. Only the note's author or an admin may edit it.
Meetings
meetings:read · meetings:writelist_meetingsList meetings logged against a contact, company, or deal. Returns newest first.
log_meetingmutatesLog a meeting against a contact, company, or deal: date, duration, attendees, location, and notes. Records a past or scheduled meeting; surfaces on the entity's timeline.
recent_meetingsList meetings logged across ALL records in the last N days, newest first, optionally narrowed to one entity kind. Use for 'what meetings happened this week?' when you don't have a specific record in hand. For meetings on ONE known record, use list_meetings instead. Distinct from list_upcoming_events, which reads the user's real connected calendar.
update_meetingmutatesEdit a logged meeting's details: title, when it happened, duration, location, or notes. Pass only the fields to change. Get the meeting_id from list_meetings or recent_meetings. This edits a logged meeting record; for a calendar invite use update_calendar_event instead.
Calls
calls:readlist_call_recordingsList captured calls (uploaded recordings with an AI transcript and summary), newest first. Calls are logged against a contact, company, or deal, or are the team's internal calls (standups, deal reviews) via entity_type 'internal' with no entity_id. Returns each call's title, date, transcript/summary status, AI summary, sentiment, action items, and suggested next step. Use to recall what was discussed on recent calls with a record or in team meetings.
Outreach
outreach:read · outreach:writedraft_emailGenerate a {subject, body} email draft from a brief, using the tenant's configured LLM. Read-only: no DB write. The caller passes the returned draft to send_one_off_email (or discards it). Optional reply_to_outreach_id enriches the prompt with the inbound thread for reply drafting.
get_campaign_summaryAggregate outreach stats for a date range or campaign by name. Returns total_sent, total_replied, reply_rate, open_rate, bounce_rate, by_channel.
get_outreach_historyGet the most recent outreach records for a contact.
get_recent_outreachMost recent outreach records, optionally filtered to one contact. Useful for context before drafting a follow-up.
log_outreachmutatesRecord an outreach event against a contact.
send_one_off_emailmutatesStage a single-recipient email send through the human-approval queue (M33d). Creates a pending SendApprovalRequest backed by an implicit one-off campaign; a human must approve in the Limena UI before the email leaves. Idempotent within 5 minutes per tenant+contact. When reply_to_outreach_id is supplied (M33f), the send is threaded as a reply to that inbound message: In-Reply-To and References headers are attached to the outgoing email and, for Gmail, the message lands in the same conversation.
upload_outreach_feedbackmutatesBulk-record outreach status updates from an external mailmerge tool. Unmatched emails create new contacts; the response lists them.
Campaigns
campaigns:read · campaigns:writeadd_contacts_to_campaignmutatesAdd individual contacts to a campaign as manual-source recipients. Up to 500 contacts per call. Idempotent: contacts already in the campaign via this source are silently skipped. Returns suppression breakdown (unsubscribed, email_invalid).
add_list_to_campaignmutatesAdd all contacts from a prospect list to a campaign. Auto-promotes eligible list items to contacts. Returns suppression breakdown and promotion count.
add_segment_to_campaignmutatesAdd contacts matching a saved segment to a campaign. Snapshot at add-time. Re-add the segment to pick up new matches. Returns suppression breakdown.
cancel_campaign_sendmutatesCancel a campaign send that is sending, paused, or scheduled. Already-sent messages are not retracted, so cancelling a send that has started cannot be undone. Cancelling a scheduled send that has not fired yet only removes the schedule: an AI campaign returns to its review queue with its drafts intact and can be scheduled again.
check_send_approvalCheck the status of a send approval request. Returns pending, approved, or rejected with decision details, plus recipients_blocked_unverified (addresses that were pattern-derived and never checked, already excluded from the send) and recipients_unconfirmed (addresses that WILL be sent to but carry no record of where they came from). Read both before approving a send.
create_campaignmutatesCreate a new campaign. Idempotent: returns the existing campaign if one with the same name already exists.
get_campaignGet campaign detail including member count, outreach count, send status, and last sent date.
list_campaign_membersList contacts that are members of a campaign.
pause_campaign_sendmutatesPause a campaign that is currently sending. Already-queued messages will self-skip. Resume must be done by a human via the UI.
remove_contact_from_campaignmutatesRemove a contact from a campaign across all sources. Idempotent: returns removed=false if the contact was not a member.
request_campaign_sendmutatesRequest approval to send a campaign. Creates a pending approval that a human must approve or reject in the Limena UI before any emails are sent. Returns the approval ID for polling via check_send_approval. Idempotent: a second call while one is pending returns the existing approval.
update_campaignmutatesUpdate campaign metadata. Only send the fields you want to change. Supports: name, description, compose_mode (template, ai_generated, or ai_templated), tracking_enabled, system_prompt_override.
Attachments
attachments:readlist_entity_attachmentsList file attachments on a contact, deal, company, task, or note. Returns newest first. Read-only: uploads + deletes go through the REST API.
Search
search:readsearch_crmHybrid keyword + semantic search across the CRM. Exact name / email / title matches rank first; contacts and companies also surface semantically related records a keyword miss would skip. Returns up to per_type results in each bucket: contacts, companies, campaigns, deals, outreach.
search_everythingONE semantic search across the whole workspace (contacts, companies, deals, notes, calls, email threads, meetings, tasks, form submissions and document attachments), ranked nearest-first by meaning with a relevance cutoff. Use FIRST for open 'what do we know about X' questions instead of fanning out across per-type search tools; each hit is a compact {entity_type, id, label, snippet, date, distance} line; follow up with the type's detail read for anything relevant. For structured filtering (stage, status, owner lists) use the per-type search tools; for exact-name lookup use search_crm.
search_trashSearch the Trash (soft-deleted, archived contacts, companies, deals and tasks) by name, title or subject. The regular search tools never return archived records; this one returns ONLY them, each as a {entity_type, id, label, archived_at} line. Use it to find a record to bring back that was archived in another conversation, then call restore_contact / restore_company / restore_deal / restore_task with the id. Omit the query to list everything currently in the trash.
Analytics
analytics:readcampaign_comparisonCompare reply rates or open rates across named campaigns.
count_recordsTotal record count for a tenant-scoped entity, with the same typed filters as the search tools so 'how many companies are unassigned' is one call.
list_campaignsList all campaigns for the tenant with member and outreach counts. Use to discover campaign names before filtering by them.
outreach_by_channelBreakdown of outreach volume and reply rates by channel.
outreach_summaryAggregate outreach stats for a date range / campaign.
query_analytics10/hourNatural language analytics question. Delegates to the AnalyticsAgent; consumes LLM tokens; rate-limited 10/hour.
recent_activityMost recent outreach and deal stage changes.
top_contactsList contacts ranked by engagement metric. Each result carries its contact_id, usable directly in follow-up calls (add to campaign, update, get) without re-searching by name.
Users
users:readlist_usersList the tenant's active team members. Useful for discovering user IDs when assigning tasks or deal ownership. Deactivated accounts are never listed, because they cannot be assigned work.
bulk
bulk:writebulk_reassign_companiesmutatesReassign a set of companies (by id, 1 to 100) to a single owner in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. Omit owner_id to assign them to yourself; pass a teammate's owner_id (resolve it with list_users) to assign to them; set unassign=true to clear the owner. Resolve the set first with search_companies (e.g. unowned=true) so you act on the exact companies. Returns {matched, affected, skipped}.
bulk_reassign_contactsmutatesReassign a set of contacts (by id, 1 to 100) to a single owner in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. Omit owner_id to assign them to yourself; pass a teammate's owner_id (resolve it with list_users) to assign to them; set unassign=true to clear the owner. Resolve the set first with search_contacts (e.g. unowned=true) so you act on the exact contacts. Returns {matched, affected, skipped}.
bulk_reassign_dealsmutatesReassign a set of deals (by id, 1 to 100) to a single owner in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. Omit owner_id to assign them to yourself; pass a teammate's owner_id (resolve it with list_users) to assign to them. A deal always has an owner, so it cannot be unassigned. Resolve the set first with search_deals so you act on the exact deals. Returns {matched, affected, skipped}.
bulk_set_status_contactsmutatesSet the lifecycle status on a set of contacts (by id, 1 to 100) in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. The status must be one of the tenant's configured statuses (setting 'unsubscribed' also suppresses future outreach). Resolve the set first with search_contacts so you act on the exact contacts. Returns {matched, affected, skipped}.
bulk_tag_companiesmutatesAdd or remove tags on a set of companies (by id, 1 to 100) in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. mode='add' adds every tag to every company (idempotent); mode='remove' removes them. Resolve the set first with search_companies so you act on the exact companies. Returns {matched, affected, skipped}.
bulk_tag_contactsmutatesAdd or remove tags on a set of contacts (by id, 1 to 100) in one step. In the assistant the whole set is shown as one confirmation card before anything changes; over the API it applies immediately, so pass dry_run=true first to see the exact set. mode='add' adds every tag to every contact (idempotent); mode='remove' removes them. Resolve the set first with search_contacts so you act on the exact contacts. Returns {matched, affected, skipped}.
imports
imports:readget_import_jobGet one CSV import job by id with its status, imported/duplicate/error counts, failure reason, and whether an error report is available. Use to explain how an import went.
get_migration_jobGet one CRM migration job by id with its overall status, per-file breakdown (each file's entity type, status, row + error counts), the unresolved-foreign-key count, and any failure reason. Use to explain how a migration went, file by file.
list_import_jobsList the tenant's single-file CSV import jobs, newest first, each with its status, row counts, and any failure reason. Use to answer 'did my import finish' or to find a job to inspect.
list_migration_jobsList the tenant's multi-file CRM migration jobs (e.g. a HubSpot export), newest first, each with its vendor, status, file count, and error count. Use to answer 'did my migration finish'.
lists
lists:read · lists:writecreate_prospect_listmutatesStage a set of researched prospects as a new prospect list, so the existing list pipeline (dedupe, cross-reference against the user's contacts, disposition) takes over. Use this to save the results of a web research request. Columns are kept as given. In the assistant the user confirms before the list is created, so say it was proposed and is awaiting confirmation; over the API it is created immediately.
get_prospect_listGet one prospect list by id with its per-disposition cleaning breakdown (safe-to-email, duplicate, previously-contacted, invalid email) and the known-contact tally. Use to report how a list cleaned, or to check it before promoting its safe rows to contacts.
list_prospect_listsList the tenant's prospect lists (uploaded or AI-researched), newest first, with each list's cleaning progress and safe-to-email count. Use to answer 'what lists do I have' or to find a list before reading or promoting it.
promote_prospect_listmutatesPromote a prospect list's safe-to-email rows to CRM contacts (creating companies as needed). Already-known people are linked, not duplicated. Reports how many were created, already existed, or were skipped. In the assistant the user confirms before any contact is created; over the API it runs immediately, so check the list with get_prospect_list first.
segments
segments:read · segments:writecreate_segmentmutatesSave a named, reusable contact segment from a structured SegmentQuery filter. Deterministic: the query is compile-checked against the field registry and stored as given, with nothing generated. Fails cleanly if the name is already taken. Use list_segments to see what exists first.
delete_segmentmutatesPermanently delete a saved segment (the named filter only. No contacts are touched, and campaigns already built from it keep their recipients). There is no trash for segments, so this cannot be undone. Get the segment_id from list_segments.
list_segmentsList the tenant's saved segments (named, reusable filters over contacts, e.g. 'UK prospects, no email in 30 days'), newest first. Use to find a segment to report on or to target with a campaign.
update_segmentmutatesRename a saved segment and/or replace its filter query (the query replaces wholesale, not a merge: send the complete new SegmentQuery). At least one of name or query is required. Get the segment_id from list_segments.
tags
tags:readlist_tagsList the workspace's whole tag vocabulary, alphabetically, with the number of records carrying each tag and the slug it is matched on. Read this BEFORE writing tags on a contact or company: a tag name is matched by its slug (lowercased, every run of non-alphanumeric characters becomes a hyphen), so 'M&A' and 'M and A' are different tags, and writing a name that slugs differently from an existing tag silently creates a near-duplicate rather than reusing it.
search_tagsFind existing tags matching a piece of text, ranked exact-slug then prefix then substring, most-used first. Use it to check whether a tag you are about to write already exists under a different spelling before you create a second one.
templates
templates:read · templates:writecreate_email_templatemutatesSave a reusable email/campaign template from an explicit subject and body. Deterministic: the text is stored as given and nothing is generated. Fails cleanly if the name is already taken (use list_email_templates to check). Merge variables are {{double_braced}} and the contact ones are bare, not namespaced: {{first_name}}, {{last_name}}, {{email}}, {{phone}}, {{job_title}}, {{linkedin_url}}, plus {{company.name}} (the only dotted one) and the tenant's own {{custom:<key>}} fields. Anything else, such as {{contact.first_name}} or {{company_name}} or {{company.domain}}, is not a variable and sends as an empty string, so the result lists any it does not recognise under `warnings`. Do not add a {{signature}} or a written sign-off: the sender's signature is appended automatically at send time.
get_email_templateGet one email template by id with its full subject, body, and merge variables. Use to read or reuse a template's content when drafting an email.
list_email_templatesList the tenant's saved email/campaign templates, most-recently-updated first, optionally filtered by folder. Returns each template's name, subject, merge variables, and folder. Use to find a template to reuse or reference.
update_email_templatemutatesEdit a saved email template: rename it, replace its subject or body (each replaces wholesale, not a diff), or refile it. At least one change is required. Get the template_id from list_email_templates. Merge variables are {{double_braced}} and the contact ones are bare, not namespaced: {{first_name}}, {{last_name}}, {{email}}, {{phone}}, {{job_title}}, {{linkedin_url}}, plus {{company.name}} (the only dotted one) and the tenant's own {{custom:<key>}} fields. Anything else, such as {{contact.first_name}} or {{company_name}} or {{company.domain}}, is not a variable and sends as an empty string, so the result lists any it does not recognise under `warnings`. Do not add a {{signature}} or a written sign-off: the sender's signature is appended automatically at send time.
Governance
Governance is a property of the surface, not the agent’s goodwill. Every MCP call passes the same checks, in code.
- authentication
- scoped Bearer API keysmcp:access to connect; a granular scope checked on every call.
- rate_limits
- 120 requests / minute per key
query_analyticsis limited to 10 / hour, since it runs an LLM. - audit
- every mutation is written to the audit logRecorded with the acting key. Mutations write two rows (the service event and an mcp_client provenance row); reads are not logged.
- isolation
- PostgreSQL row-level security, per tenantThe app role has no BYPASSRLS; a query without a tenant context returns zero rows.
MCP calls execute immediately. There is no confirm step on this surface.
A mutating call runs the moment your agent invokes it, governed by the key’s scopes, the rate zone, and the audit log. The proposal-and-confirm gate, where a person approves every write before it runs, belongs to Limena’s in-app assistant, a separate surface. Over MCP, a key acts on its own authority, so grant scopes deliberately.
REST API
Beyond MCP, the core resources are a REST API documented with OpenAPI. Same lmn_ Bearer keys and the same granular scopes.
- Swagger UI is the interactive reference. ReDoc and the raw OpenAPI document are published too.
- The MCP endpoints are a streaming mount and are not part of the OpenAPI document; use the capability reference above for those.
Build something on your CRM.
Want it wired into your stack? We’ll walk the surface with you on your own data.
{
"method": "tools/call",
"params": {
"name": "create_task",
"arguments": {
"subject": "Follow up with Helix Logistics",
"due_at": "2026-06-19",
"entity_type": "deal"
}
}
}