Overview
Tootela exposes the full platform via a single JSON-RPC 2.0 endpoint that is fully compatible with the Model Context Protocol (MCP). One endpoint, 40+ tools, 9 modules. This means you can:
- Call any tool directly from Python, JavaScript, curl, or any HTTP client
- Connect any MCP-compatible AI agent (Claude, GPT, Gemini, and more) to your whole Tootela workspace
- Automate across CRM (contacts, deals, pipelines), Projects (tasks, sprints, epics), Helpdesk (tickets, replies), HR (employees, leave), Expenses, Onboarding, Recruitment, and Forms
- Build outreach scripts that discover leads, draft emails, and sync deals to the right pipeline
Authentication
All requests require a Bearer token. Generate one in your workspace at Settings → API Tokens.
Authorization: Bearer tla_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTokens are scoped to your organisation. Use environment variables — never hardcode them.
MCP Server
Add Tootela to any MCP-compatible AI client (Claude Desktop, Cursor, Windsurf, and more) and let it create contacts, log activities, and manage deals directly from your workflow.
MCP client config
Most MCP clients accept a config like this (example: Claude Desktop at ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"tootela": {
"url": "https://api.tootela.net/api/mcp",
"transport": "http",
"headers": {
"Authorization": "Bearer tla_YOUR_TOKEN_HERE"
}
}
}
}Request envelope
Every call uses the same JSON-RPC 2.0 structure:
{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "create_company",
"arguments": {
"name": "Acme Corp",
"domain": "acme.com",
"industry": "SaaS"
}
}
}CRM & Email
Manage contacts, companies, deals, pipelines, activities, and send personalised emails.
name* = required.
list_contactsList CRM contacts. Filter by name or email with an optional search string.
| Parameter | Type | Description |
|---|---|---|
search | string | Partial match on name or email |
limit | number | Max results (default 50, max 200) |
create_contactCreate a new CRM contact, optionally linked to an existing company.
| Parameter | Type | Description |
|---|---|---|
email | string | Email address |
first_name | string | First name |
last_name | string | Last name |
phone | string | Phone number |
job_title | string | Role, e.g. CEO, Head of Growth |
company_id | string | UUID of an existing company |
source | string | e.g. outbound, linkedin, referral |
notes | string | Free-form notes |
list_companiesList CRM companies. Returns id, name, domain, website, industry, size, city, country, custom_fields (JSONB) and created_at.
| Parameter | Type | Description |
|---|---|---|
search | string | Partial match on company name |
limit | number | Max results (default 50, max 200) |
create_companyCreate a new CRM company record.
| Parameter | Type | Description |
|---|---|---|
name* | string | Company name |
domain | string | Website domain, e.g. acme.com |
website | string | Full URL — auto-derived from domain if omitted |
industry | string | e.g. Fintech, SaaS, Marketing Agency |
size | string | e.g. 1-10, 11-50, 51-200 |
city | string | City |
country | string | Country |
create_dealCreate a deal in a sales pipeline. Automatically placed at the first stage of the target pipeline.
| Parameter | Type | Description |
|---|---|---|
name* | string | Deal name, e.g. "Outreach — Acme Corp" |
value | number | Monetary value |
currency | string | ISO code (default: EUR) |
company_id | string | UUID of linked company |
contact_id | string | UUID of linked contact |
pipeline_id | string | UUID of the target pipeline — uses default pipeline if omitted |
expected_close_date | string | ISO date YYYY-MM-DD |
list_pipelinesList all sales pipelines in the organisation. Use this to get a pipeline_id before creating deals.
create_pipelineCreate a sales pipeline with default stages: Lead → Qualified → Proposal → Negotiation → Won. Useful for organising deals by geography, campaign, or quarter.
| Parameter | Type | Description |
|---|---|---|
name* | string | Pipeline name, e.g. "France", "Germany", "Outbound Q2" |
update_contactUpdate fields on an existing contact. Pass only the fields you want to change.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the contact to update |
email | string | New email address |
first_name | string | First name |
last_name | string | Last name |
phone | string | Phone number |
job_title | string | Role |
company_id | string | UUID of linked company |
lead_status | string | new | contacted | qualified | converted | lost |
notes | string | Free-form notes |
update_companyUpdate fields on an existing company. Pass only the fields you want to change.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the company to update |
name | string | Company name |
domain | string | Domain, e.g. acme.com |
website | string | Full website URL |
industry | string | Industry sector |
size | string | e.g. 1-10, 11-50, 51-200 |
city | string | City |
country | string | Country |
delete_companyDelete a company by id. Scoped to the token's organisation. Deals attached to the company keep their company_id but the reference will resolve to null.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the company to delete |
list_dealsList CRM deals with optional filters. Use to check existing deals before creating duplicates.
| Parameter | Type | Description |
|---|---|---|
pipeline_id | string | Filter by pipeline UUID |
contact_id | string | Filter by contact UUID |
company_id | string | Filter by company UUID |
status | string | open | won | lost |
search | string | Partial match on deal name |
limit | number | Max results (default 50, max 200) |
update_dealUpdate fields on an existing deal — move it to a new stage, change value, mark won/lost.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the deal to update |
name | string | Deal name |
value | number | Monetary value |
status | string | open | won | lost |
stage_id | string | UUID of the new pipeline stage |
pipeline_id | string | Move to a different pipeline |
expected_close_date | string | ISO date YYYY-MM-DD |
list_pipeline_stagesList stages of a specific pipeline. Use stage UUIDs with update_deal to move deals forward.
| Parameter | Type | Description |
|---|---|---|
pipeline_id* | string | UUID of the pipeline |
list_activitiesList CRM activities. Filter by contact, deal, or type.
| Parameter | Type | Description |
|---|---|---|
contact_id | string | Filter by contact UUID |
deal_id | string | Filter by deal UUID |
type | string | call | email | meeting | note | task |
limit | number | Max results (default 50, max 200) |
log_activityLog a CRM activity (email, call, meeting, note, task) against a contact or deal.
| Parameter | Type | Description |
|---|---|---|
type* | string | call | email | meeting | note | task |
title* | string | Activity title or email subject |
notes | string | Body / description / email content |
contact_id | string | UUID of linked contact |
deal_id | string | UUID of linked deal |
due_date | string | ISO datetime for tasks / reminders |
update_activityUpdate an existing activity: change notes, due date, or mark it as done.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the activity to update |
title | string | New title |
notes | string | Updated notes / body |
due_date | string | ISO datetime |
done | boolean | Mark as completed |
create_contact_pointLog a direct interaction with a contact (call, email, LinkedIn, WhatsApp, etc.) including the outcome. Useful for tracking outreach history.
| Parameter | Type | Description |
|---|---|---|
type* | string | call | email | meeting | linkedin | whatsapp | sms | note | other |
contact_id | string | UUID of the contact |
company_id | string | UUID of the company (if no specific contact) |
summary | string | What was discussed / message content |
outcome | string | positive | neutral | negative | no_response |
contacted_at | string | ISO datetime (defaults to now) |
send_emailSend a personalised email via Tootela. Use for cold outreach, follow-ups, and automated sequences.
| Parameter | Type | Description |
|---|---|---|
to* | string | Recipient email address |
subject* | string | Subject line |
body* | string | Plain text or HTML |
to_name | string | Recipient display name |
from_name | string | Sender display name |
reply_to | string | Reply-to address |
Custom Fields
Custom fields let you extend companies, contacts, and deals with organisation-specific attributes (e.g. Contact Page URL, Lead Source, Outreach Status).
Data model: custom field values are stored on the entity itself, inside a custom_fields JSONB column keyed by field_key. Use the set_*_custom_field tools below : they resolve the field_key from the definition and merge into the JSONB so the UI picks the value up immediately.
name* = required.
list_custom_field_defsList custom field definitions (name, key, type, options, order). Use this first to discover field_def_id values.
| Parameter | Type | Description |
|---|---|---|
entity_type | string | contact | company | deal |
pipeline_id | string | Filter to deal-level fields scoped to a specific pipeline |
create_custom_field_defCreate a new custom field on a CRM entity. field_key is auto-generated from name if omitted (snake_cased). Use options for select fields.
| Parameter | Type | Description |
|---|---|---|
entity_type* | string | contact | company | deal |
name* | string | Display name, e.g. "Contact Page URL" |
field_key | string | Stable machine key. Auto-generated from name if omitted. |
field_type | string | text | number | date | boolean | select (default: text) |
options | array | Array of string options (only for field_type="select") |
required | boolean | Mark the field as required (default false) |
order | number | Display order, ascending (default 0) |
pipeline_id | string | UUID of a pipeline to scope the field to (deals only) |
delete_custom_field_defDelete a custom field definition. Existing values in entity JSONB blobs are left in place but become orphaned.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the custom field definition |
list_custom_field_valuesDiagnostic: list rows from the legacy crm_custom_field_values key/value table. The UI does not read from this table; use it to audit historical data or migrate into the entity JSONB columns.
| Parameter | Type | Description |
|---|---|---|
entity_id | string | Single entity (company/contact/deal) UUID to filter by |
entity_ids | array | Multiple entity UUIDs to filter by |
field_def_id | string | Filter by a single custom field definition UUID |
field_def_ids | array | Filter by multiple custom field definition UUIDs |
limit | number | Max results (default 500, max 5000) |
set_company_custom_fieldSet a single custom field value on a company. Resolves field_key from field_def_id and merges into crm_companies.custom_fields JSONB (what the UI reads).
| Parameter | Type | Description |
|---|---|---|
company_id* | string | UUID of the company |
field_def_id* | string | UUID of the custom field definition |
value* | string | Value to set (stringified; booleans as "true"/"false") |
set_contact_custom_fieldSet a single custom field value on a contact. Merges into crm_contacts.custom_fields JSONB.
| Parameter | Type | Description |
|---|---|---|
contact_id* | string | UUID of the contact |
field_def_id* | string | UUID of the custom field definition |
value* | string | Value to set |
set_deal_custom_fieldSet a single custom field value on a deal. Merges into crm_deals.custom_fields JSONB.
| Parameter | Type | Description |
|---|---|---|
deal_id* | string | UUID of the deal |
field_def_id* | string | UUID of the custom field definition |
value* | string | Value to set |
Projects
Manage projects, tasks, sprints, and epics. All operations are scoped to the token's organisation.
list_projectsList all projects in the organisation.
create_projectCreate a new project.
| Parameter | Type | Description |
|---|---|---|
name* | string | Project name |
description | string | Optional description |
color | string | Hex color (e.g. #6366f1) |
icon | string | Emoji or icon key |
list_tasksList project tasks with filters.
| Parameter | Type | Description |
|---|---|---|
project_id | string | Filter by project UUID |
sprint_id | string | Filter by sprint UUID |
assignee_id | string | Filter by assignee user UUID |
status | string | todo | in_progress | in_review | done |
priority | string | low | medium | high | urgent |
search | string | Partial match on title |
limit | number | Max results (default 50, max 200) |
create_taskCreate a new task in a project.
| Parameter | Type | Description |
|---|---|---|
project_id* | string | UUID of the project |
title* | string | Task title |
description | string | Task description |
status | string | todo | in_progress | in_review | done |
priority | string | low | medium | high | urgent |
assignee_id | string | UUID of assignee |
sprint_id | string | UUID of sprint |
stage_id | string | UUID of board stage |
due_date | string | ISO date YYYY-MM-DD |
story_points | number | Estimation in points |
labels | array | List of label strings |
item_type | string | task | feature | user_story | epic | subtask |
update_taskUpdate a task. Pass only the fields you want to change.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the task |
title | string | New title |
description | string | New description |
status | string | todo | in_progress | in_review | done |
priority | string | low | medium | high | urgent |
assignee_id | string | UUID of user, or null to unassign |
sprint_id | string | UUID of sprint, or null for backlog |
due_date | string | ISO date YYYY-MM-DD |
list_sprintsList sprints in the organisation.
| Parameter | Type | Description |
|---|---|---|
project_id | string | Filter by project UUID |
status | string | planned | active | completed |
create_sprintCreate a new sprint (auto-numbered within the project).
| Parameter | Type | Description |
|---|---|---|
project_id* | string | UUID of the project |
name* | string | Sprint name |
goal | string | Sprint goal description |
start_date | string | ISO date YYYY-MM-DD |
end_date | string | ISO date YYYY-MM-DD |
list_epicsList epics / user stories.
| Parameter | Type | Description |
|---|---|---|
project_id | string | Filter by project UUID |
status | string | draft | ready | in_progress | completed |
create_epicCreate a new epic / user story.
| Parameter | Type | Description |
|---|---|---|
project_id* | string | UUID of the project |
title* | string | Epic title |
description | string | Description |
acceptance_criteria | string | Acceptance criteria |
priority | string | low | medium | high | urgent |
status | string | draft | ready | in_progress | completed |
story_points | number | Estimated points |
list_membersList organisation members. Useful to resolve names to user UUIDs for task assignment.
Helpdesk
Manage support tickets, replies, and statuses.
list_ticketsList helpdesk tickets.
| Parameter | Type | Description |
|---|---|---|
status | string | open | in_progress | waiting | resolved | closed |
priority | string | low | normal | high | urgent |
assigned_to | string | Filter by assigned agent UUID |
search | string | Partial match on subject |
limit | number | Max results |
create_ticketCreate a new helpdesk ticket on behalf of a customer.
| Parameter | Type | Description |
|---|---|---|
subject* | string | Ticket subject |
priority | string | low | normal | high | urgent |
customer_name | string | Customer display name |
customer_email | string | Customer email |
channel | string | widget | page | email | api |
initial_message | string | First message from the customer |
update_ticketUpdate ticket status, priority, or assignee.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the ticket |
status | string | open | in_progress | waiting | resolved | closed |
priority | string | low | normal | high | urgent |
assigned_to | string | UUID of agent |
reply_ticketAdd an agent reply to a ticket. Marks first_response_at automatically.
| Parameter | Type | Description |
|---|---|---|
ticket_id* | string | UUID of the ticket |
content* | string | Reply message body |
is_internal | boolean | True for internal notes |
HR
Access employees, departments, and leave requests.
list_employeesList HR employees in the organisation.
| Parameter | Type | Description |
|---|---|---|
status | string | active | inactive | on_leave | terminated |
department_id | string | Filter by department UUID |
search | string | Partial match on name or email |
list_departmentsList organisation departments.
list_leave_requestsList time-off / leave requests.
| Parameter | Type | Description |
|---|---|---|
status | string | pending | approved | rejected | cancelled |
employee_id | string | Filter by employee UUID |
limit | number | Max results |
update_leave_requestApprove, reject, or cancel a time-off request.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the leave request |
status* | string | approved | rejected | cancelled |
review_comment | string | Optional comment |
Expenses
List, approve, or reject expense reports. Approvals respect the multi-step approval chain.
list_expensesList expense reports.
| Parameter | Type | Description |
|---|---|---|
status | string | draft | submitted | approved | rejected |
user_id | string | Filter by submitter UUID |
limit | number | Max results |
approve_expenseApprove an expense. Moves to the next approver in the chain, or marks as final approved.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the expense |
notes | string | Optional approval comment |
reject_expenseReject an expense.
| Parameter | Type | Description |
|---|---|---|
id* | string | UUID of the expense |
reason | string | Rejection reason |
Onboarding
Track new hire onboarding journeys and task completion.
list_onboarding_processesList onboarding journeys.
| Parameter | Type | Description |
|---|---|---|
status | string | not_started | in_progress | completed | cancelled |
employee_id | string | Filter by employee UUID |
list_onboarding_tasksList onboarding task instances.
| Parameter | Type | Description |
|---|---|---|
process_id | string | Filter by process UUID |
assigned_to | string | Filter by assignee user UUID |
status | string | pending | in_progress | completed | skipped |
Recruitment
Manage job postings and candidate pipelines.
list_jobsList job postings.
| Parameter | Type | Description |
|---|---|---|
status | string | draft | published | closed | archived |
search | string | Partial match on title |
create_jobCreate a new job posting.
| Parameter | Type | Description |
|---|---|---|
title* | string | Job title |
department_id | string | UUID of department |
employment_type | string | full_time | part_time | contract | internship |
location | string | Job location |
description | string | Job description |
salary_min | number | Minimum salary |
salary_max | number | Maximum salary |
status | string | draft | published |
list_candidatesList job candidates.
| Parameter | Type | Description |
|---|---|---|
job_posting_id | string | Filter by job UUID |
stage | string | applied | screening | interview | offer | hired | rejected |
search | string | Partial match on name or email |
Forms
Read published forms and their submissions.
list_formsList forms in the organisation.
| Parameter | Type | Description |
|---|---|---|
status | string | draft | published |
list_form_submissionsList submissions for a given form.
| Parameter | Type | Description |
|---|---|---|
form_id* | string | UUID of the form |
limit | number | Max results (default 50, max 200) |
Code Examples
Python: country-named pipeline + full outreach flow
import httpx, json, os
MCP_URL = "https://api.tootela.net/api/mcp"
TOKEN = os.environ["TOOTELA_TOKEN"]
def call(tool, args):
r = httpx.post(
MCP_URL,
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={"jsonrpc": "2.0", "id": "1", "method": "tools/call",
"params": {"name": tool, "arguments": args}},
timeout=10,
)
result = r.json().get("result", {})
content = result.get("content", [])
return json.loads(content[0]["text"]) if content else result
# 1. Find or create a "France" pipeline
pipelines = call("list_pipelines", {})["pipelines"]
france = next((p for p in pipelines if p["name"] == "France"), None)
if not france:
france = call("create_pipeline", {"name": "France"})["pipeline"]
# 2. Create company
company = call("create_company", {
"name": "Acme Agency",
"domain": "acme-agency.fr",
"industry": "Marketing Agency",
"city": "Bordeaux",
"country": "France",
})["company"]
# 3. Create deal in the France pipeline
deal = call("create_deal", {
"name": f"Outreach — {company['name']}",
"company_id": company["id"],
"pipeline_id": france["id"],
})["deal"]
# 4. Log the email draft as an activity
call("log_activity", {
"type": "email",
"title": "Cold outreach",
"notes": "Hi team, we'd love to show you Tootela...",
"deal_id": deal["id"],
})curl: list pipelines
curl -X POST https://api.tootela.net/api/mcp \
-H "Authorization: Bearer tla_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": { "name": "list_pipelines", "arguments": {} }
}'curl: send an email
curl -X POST https://api.tootela.net/api/mcp \
-H "Authorization: Bearer tla_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "send_email",
"arguments": {
"to": "founder@startup.io",
"to_name": "Alex",
"subject": "Quick question about your stack",
"body": "Hi Alex,\n\nI noticed you...",
"reply_to": "eliot@tootela.net"
}
}
}'Error Handling
Tool errors return HTTP 200 with a JSON-RPC error object. Auth failures return HTTP 401.
// Auth failure — HTTP 401
{"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"Unauthorized: invalid or missing Bearer token"}}
// Tool error — HTTP 200
{"jsonrpc":"2.0","id":"1","error":{"code":-32603,"message":"duplicate key value violates unique constraint"}}
// Unknown method — HTTP 200
{"jsonrpc":"2.0","id":"1","error":{"code":-32601,"message":"Method not found: tools/unknown"}}Ready to automate?
Questions? support@tootela.net