Sales cycles (CRM board)
The board leads move on: sales cycles and their stages, lead stats and funnel, follow-ups due, moving leads in bulk and turning them into deals.
Each workspace has one or more sales cycles (e.g. “New customers”, “Distributors”). A cycle is a list of stages; every contact sits in one cycle (sales_cycle_id) at one stage (pipeline_stage). “New leads” and “Lost” always exist. Each cycle has a deal pipeline with the same stages, kept in step: moving a deal moves its contact, and the other way round.
/crm/cyclesList sales cyclesPUT/crm/cyclesSave the sales cyclesGET/crm/stagesStages of the default cycleGET/crm/lead-statsLead statistics and funnelGET/crm/followupsFollow-ups duePOST/contacts/bulk/moveMove leads to a cycle and stagePOST/crm/convert-to-dealsTurn leads into dealsGET/crm/settingsCRM housekeeping settingsPATCH/crm/settingsChange CRM housekeeping settings/crm/cyclesList sales cycles
Every cycle with its stages (fixed for New leads / Lost, won for the won stage) and how many leads it holds.
curl "https://meetzy.me/api/v1/crm/cycles" \ -H "Authorization: Bearer $MEETZY_API_KEY"
const res = await fetch("https://meetzy.me/api/v1/crm/cycles", {
headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://meetzy.me/api/v1/crm/cycles",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
)
data = res.json(){
"configured": false,
"multi_supported": true,
"cycles": [
{
"id": "default",
"name": "Main pipeline",
"color": "#007AFF",
"is_default": true,
"stages": [
{
"id": "new_leads",
"name": "New leads",
"color": "#8E8E93",
"fixed": true
},
{
"id": "in_discussion",
"name": "In discussion",
"description": null,
"color": "#5856D6",
"won": false
},
{
"id": "negotiation",
"name": "Negotiation",
"description": null,
"color": "#007AFF",
"won": false
}
],
"count": 1
}
]
}/crm/cyclesSave the sales cycles
Replaces every cycle at once (the first one is the default). Stages are listed in order and the last one is the won stage. A renamed stage keeps its leads when you send its previous_id; leads of a removed stage go back to New leads, leads of a removed cycle go to the default one. Don’t include New leads and Lost — they are added.
Body
cyclesobject[]required- Each
{ id?, name, color, stages: [{ name, description?, previous_id? }] }. Omitidfor a new cycle. A stagedescriptionis its playbook (up to 1500 characters): shown on every lead at that stage, and followed by Claude.
Returns the saved cycles (same shape as GET /crm/cycles) plus moved (leads) and deals_moved.
curl -X PUT "https://meetzy.me/api/v1/crm/cycles" \ -H "Authorization: Bearer $MEETZY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cycles": [ { "id": "default", "name": "New customers", "color": "#007AFF", "stages": [ { "name": "In discussion", "previous_id": "in_discussion" }, { "name": "Proposal sent" }, { "name": "Won" } ] }, { "name": "Distributors", "color": "#AF52DE", "stages": [ { "name": "Contacted" }, { "name": "Contract signed" } ] } ] }'
const res = await fetch("https://meetzy.me/api/v1/crm/cycles", { method: "PUT", headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "cycles": [ { "id": "default", "name": "New customers", "color": "#007AFF", "stages": [ { "name": "In discussion", "previous_id": "in_discussion" }, { "name": "Proposal sent" }, { "name": "Won" } ] }, { "name": "Distributors", "color": "#AF52DE", "stages": [ { "name": "Contacted" }, { "name": "Contract signed" } ] } ] }), }); const data = await res.json();
import os, requests
res = requests.put(
"https://meetzy.me/api/v1/crm/cycles",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
json={
"cycles": [
{
"id": "default",
"name": "New customers",
"color": "#007AFF",
"stages": [
{
"name": "In discussion",
"previous_id": "in_discussion"
},
{
"name": "Proposal sent"
},
{
"name": "Won"
}
]
},
{
"name": "Distributors",
"color": "#AF52DE",
"stages": [
{
"name": "Contacted"
},
{
"name": "Contract signed"
}
]
}
]
},
)
data = res.json()See the notes on the left.
/crm/stagesStages of the default cycle
Ids, names and colours of the default cycle’s stages — the values pipeline_stage takes.
curl "https://meetzy.me/api/v1/crm/stages" \ -H "Authorization: Bearer $MEETZY_API_KEY"
const res = await fetch("https://meetzy.me/api/v1/crm/stages", {
headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://meetzy.me/api/v1/crm/stages",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
)
data = res.json(){
"stages": [
{
"id": "new_leads",
"name": "New leads",
"color": "#6B7280"
},
{
"id": "in_discussion",
"name": "In discussion",
"color": "#3B82F6"
}
]
}/crm/lead-statsLead statistics and funnel
For one cycle: leads per stage (and how many are hot), totals (active, hot, warm, won, lost, new this period and change, follow-ups due, stale, unassigned, conversion rate), the funnel, leads by source and by owner.
Query parameters
periodstringweek,month(default),quarter,year…ownerstringmeor a teammate’s email.cyclestring- Cycle id. Default: the default cycle.
curl "https://meetzy.me/api/v1/crm/lead-stats?period=quarter&owner=me" \ -H "Authorization: Bearer $MEETZY_API_KEY"
const res = await fetch("https://meetzy.me/api/v1/crm/lead-stats?period=quarter&owner=me", {
headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://meetzy.me/api/v1/crm/lead-stats?period=quarter&owner=me",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
)
data = res.json(){
"cycle": "default",
"cycles": [
{
"id": "default",
"name": "Main pipeline",
"color": "#007AFF",
"count": 1,
"active": 1,
"won": 0,
"hot": 0
}
],
"stages": [
{
"id": "new_leads",
"name": "New leads",
"color": "#6B7280",
"count": 1,
"hot": 0
},
{
"id": "in_discussion",
"name": "In discussion",
"color": "#3B82F6",
"count": 0,
"hot": 0
}
],
"won_stage": "won",
"first_stage": "new_leads",
"totals": {
"total": 1,
"active": 1,
"hot": 0,
"warm": 0,
"lost": 0,
"won": 0,
"new": 1,
"new_change": 100,
"won_period": 0,
"lost_period": 0,
"followups_due": 0,
"stale": 0,
"with_email": 1,
"unassigned": 1,
"conversion_rate": 0
},
"funnel": [
{
"id": "new_leads",
"name": "New leads",
"color": "#6B7280",
"reached": 1
},
{
"id": "in_discussion",
"name": "In discussion",
"color": "#3B82F6",
"reached": 0
}
],
"by_source": [
{
"source": "manual",
"count": 1
}
],
"by_owner": [
{
"owner": "unassigned",
"count": 1,
"hot": 0,
"won": 0
}
]
}/crm/followupsFollow-ups due
Contacts whose follow-up date (reminder_date) is today, late, or within the next days.
Query parameters
daysinteger- Look ahead this many days. Default 7.
ownerstringmefor your own.
curl "https://meetzy.me/api/v1/crm/followups" \ -H "Authorization: Bearer $MEETZY_API_KEY"
const res = await fetch("https://meetzy.me/api/v1/crm/followups", {
headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://meetzy.me/api/v1/crm/followups",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
)
data = res.json(){
"followups": [
{
"id": "e43414d6-f56f-42be-a64c-a836d6015efc",
"display_name": "Camille Laurent",
"company": "Payfit",
"reminder_date": "2026-10-08",
"reminder_note": "Send the pricing",
"assigned_to": "olivia@acme.com",
"pipeline_stage": "in_discussion",
"lead_score": 55
}
]
}/contacts/bulk/moveMove leads to a cycle and stage
Moves up to 5,000 contacts to a stage of a sales cycle; their open deals follow to that cycle’s pipeline.
Body
contact_idsuuid[]required- Contacts to move.
cycle_idstring- Target cycle. Default: the default cycle.
stage_idstring- Target stage. Default: the first stage.
curl -X POST "https://meetzy.me/api/v1/contacts/bulk/move" \ -H "Authorization: Bearer $MEETZY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact_ids": [ "e43414d6-f56f-42be-a64c-a836d6015efc" ], "cycle_id": "default", "stage_id": "in_discussion" }'
const res = await fetch("https://meetzy.me/api/v1/contacts/bulk/move", { method: "POST", headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "contact_ids": [ "e43414d6-f56f-42be-a64c-a836d6015efc" ], "cycle_id": "default", "stage_id": "in_discussion" }), }); const data = await res.json();
import os, requests
res = requests.post(
"https://meetzy.me/api/v1/contacts/bulk/move",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
json={
"contact_ids": ["e43414d6-f56f-42be-a64c-a836d6015efc"],
"cycle_id": "default",
"stage_id": "in_discussion"
},
)
data = res.json(){
"moved": 1,
"cycle_id": "default",
"stage_id": "in_discussion",
"stage_name": "In discussion"
}/crm/convert-to-dealsTurn leads into deals
Creates a deal for each lead (up to 2,000) — chosen by id or by stage — in their cycle’s pipeline at the same stage. Leads that already have an open deal are skipped.
Body
lead_idsuuid[]- Leads to convert.
stage_idsstring[]- Or every lead in these stages.
amountnumber- Amount of each new deal. Default 0.
curl -X POST "https://meetzy.me/api/v1/crm/convert-to-deals" \ -H "Authorization: Bearer $MEETZY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_ids": [ "negotiation" ], "amount": 5000 }'
const res = await fetch("https://meetzy.me/api/v1/crm/convert-to-deals", { method: "POST", headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "stage_ids": [ "negotiation" ], "amount": 5000 }), }); const data = await res.json();
import os, requests
res = requests.post(
"https://meetzy.me/api/v1/crm/convert-to-deals",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
json={
"stage_ids": ["negotiation"],
"amount": 5000
},
)
data = res.json(){
"created": 12,
"skipped": 3
}/crm/settingsCRM housekeeping settings
inactivity_days_lost: after how many days without activity a lead is moved to Lost automatically (0 = never).
curl "https://meetzy.me/api/v1/crm/settings" \ -H "Authorization: Bearer $MEETZY_API_KEY"
const res = await fetch("https://meetzy.me/api/v1/crm/settings", {
headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://meetzy.me/api/v1/crm/settings",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
)
data = res.json(){
"inactivity_days_lost": 0
}/crm/settingsChange CRM housekeeping settings
Body
inactivity_days_lostintegerrequired- Days without activity before a lead is lost (0 = never, max 3650).
curl -X PATCH "https://meetzy.me/api/v1/crm/settings" \ -H "Authorization: Bearer $MEETZY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inactivity_days_lost": 90 }'
const res = await fetch("https://meetzy.me/api/v1/crm/settings", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.MEETZY_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "inactivity_days_lost": 90 }), }); const data = await res.json();
import os, requests
res = requests.patch(
"https://meetzy.me/api/v1/crm/settings",
headers={"Authorization": f"Bearer {os.environ['MEETZY_API_KEY']}"},
json={
"inactivity_days_lost": 90
},
)
data = res.json(){
"inactivity_days_lost": 90
}