{"version":"1.0.0","message":"You are talking to LDM.delivery — a permission-based B2B outreach platform. Every response you get over MCP carries an `_expert` block with the next step. Treat this like a consultation: tell us your goal, we route you. This endpoint is the map; the `_expert` blocks are turn-by-turn directions.","how_it_works":"Each capability is one MCP tool named ldm_<operationId> (e.g. ldm_CampaignsController_pour). Path params are top-level string args; request bodies go in `body`; tenant in `tenant_id` (or X-Tenant-Id). Set header X-LDM-Guidance: off to suppress the `_expert` layer.","recommended_flow":{"title":"Cold outreach — end-to-end (the pro sequence)","steps":["1. Audience: ldm_contacts_create / ldm_ContactsController_importStart → gather people into a contact list (ldm_ContactListsController_addContacts). Narrow + relevant beats big.","2. Hygiene FIRST: ldm_EmailVerificationController_verifyBatch — verify addresses before you ever send. Unverified lists bounce, and bounces burn your domain reputation. Non-negotiable.","3. Creative: ldm_creatives_create → ldm_CreativesController_preview → ldm_ContentAnalyzerController_check (spam-trigger words/links) → ldm_CreativesController_startInboxPlacementTest (inbox vs spam BEFORE scaling).","4. Senders: ldm_accounts_create → ldm_EmailAccountsController_checkDns (SPF/DKIM/DMARC) → ldm_EmailAccountsController_testConnection. TWO honest paths, your choice: (a) warm up to raise limits — ldm_EmailAccountsController_warmupStart, takes weeks, the platform does NOT warm for you and it is optional; or (b) skip warm-up and go straight to placement-checked low-volume sends (path 5).","5. Send by MEASUREMENT: send(limit=1), then read placement per provider via control emails — ldm_MailingController_getControlEmailStats + ldm_DeliverabilityController_getMatrix. Spam is a signal, not a failure to hide.","6. React to spam: first swap the relay (ldm_EmailAccountsController_relayHealth / ldm_DeliverabilityController_startCreativeThroughRelay) and re-measure. Still spam on another relay? It is the creative or the volume — fix the copy or send less. Do not touch the rest of the pool.","7. Analytics: set an ACTIVE tracking domain (ldm_TrackingDomainController_create → healthCheck → setDefault) for clean open/click data.","8. Multi-touch + replies: enrol non-responders into a follow-up sequence (ldm_SequencesController_enroll); watch ldm_dialogs_list, classify, and promote opportunities with ldm_DialogsController_createLead."],"pitfalls":["Verify the list BEFORE the first send — this single step protects your sender reputation more than anything else.","Landing in spam is INFORMATION, not shame. It means change one variable at a time: relay first, then creative, then volume — the provider is telling you what is wrong.","Warm-up raises limits but it is a manipulation of provider signals, the platform does not do it for you, and it is often unnecessary. Checking inbox placement on every send is the more honest and frequently sufficient path.","A single email is not outreach. Most replies come from touch 2–4 — always plan follow-ups (sequences).","Confirm INBOX placement (not just \"sent\") on the first send before any real volume."]},"domains":[{"slug":"campaigns","title":"Campaigns (the send conveyor)","about":"A campaign is a staged conveyor: recipients move NEW → SCREENED → READY → sent. Each stage is a separate MCP call so you can inspect and gate between them.","typical_flow":["ldm_CampaignsController_create — create the campaign (attach a creative pool + routing)","ldm_CampaignsController_pour — fill recipients FROM A CONTACT LIST: body MUST carry {listId}","ldm_CampaignsController_screen — suppression + stop-list gate (NEW → SCREENED|SKIPPED)","ldm_CampaignsController_prepare — render creative + preflight (SCREENED → READY), call in batches until remaining=0","ldm_CampaignsController_sendReadiness — dry-run ALL send gates, read-only","ldm_CampaignsController_dispatch — assign sender + schedule, then ldm_CampaignsController_send (limit=1 for the first test)","ldm_CampaignsController_metrics — sent/delivered/opened/clicked/replied afterwards"],"pitfalls":["⛔ pour with an EMPTY body pours your ENTIRE database. ALWAYS pass {\"listId\": \"<contact-list-id>\"}.","pour reads STATIC contact-list membership — smart-filter lists are not expanded, and you cannot pour directly from a COMPANY list. Materialise contacts into a contact list first.","Verify the list (ldm_EmailVerificationController_verifyBatch) BEFORE pouring — bounces from a dirty list wreck deliverability for the whole domain.","First send should be limit=1. Confirm INBOX placement (not just \"sent\"), then ramp volume gradually via the send-window.","A single send is rarely enough — plan follow-ups (ldm_SequencesController_enroll) for non-responders.","A campaign that is ACTIVE must be paused before it can be deleted."],"see_also":["contact-lists","creatives","email-accounts","email-verification","suppression","sequences","tracking"]},{"slug":"creatives","title":"Creatives (email templates + AI Autopilot)","about":"A creative is the message — and the single biggest lever on reply rate after targeting. Simple creatives are templates with variables + spintax; Autopilot creatives generate per-contact variants through a bank + QC + sandbox pipeline. Short, specific, one clear ask beats a feature dump.","typical_flow":["ldm_creatives_create — create a creative (or ldm_CreativesController_aiGenerate for an AI draft)","ldm_CreativesController_preview — render for a sample contact BEFORE sending","ldm_ContentAnalyzerController_check — screen the copy for spam-trigger words/links that hurt placement","ldm_CreativesController_startInboxPlacementTest — see where it lands (inbox / spam / promotions) before scaling","ldm_CreativesController_getAvailableVariables — personalise with merge variables (name, company, role)","ldm_CreativesController_createTestSend — send yourself a real test copy"],"pitfalls":["Preview + content-check + a limit=1 inbox-placement test are cheap insurance — always run them before volume.","Spam-trigger words, image-heavy HTML and too many links tank placement. Plain, personal, one CTA wins.","Rules/QC/meta-prompt are JSON, edited via PATCH .../rules — do not expect to change them by editing code."],"see_also":["campaigns","deliverability","email-verification"]},{"slug":"contacts","title":"Contacts (people + channels)","about":"A contact is a person. Email/phone are CHANNELS on the contact, not custom fields. Contacts join campaigns via contact lists.","typical_flow":["ldm_contacts_create — create a person (email goes in as a channel, not a custom field)","ldm_ContactsController_importStart — bulk import (preview first with ldm_ContactsController_importPreview)","ldm_ContactListsController_addContacts — put contacts into a list so a campaign can pour them","ldm_ContactsController_bulk — mass tag / list / field ops (async): pass contactIds[] OR selectAll:true"],"pitfalls":["Email is a channel (addChannel), NOT a custom field.","Custom-field WRITE for a contact: ldm_CustomFieldsController_setValue with entity in UPPER case (CONTACT). Company uses setValues instead.","bulk addToList needs the list target in the listId parameter."],"see_also":["contact-lists","custom-fields","companies","campaigns"]},{"slug":"companies","title":"Companies (organisations)","about":"A company is an organisation, deduplicated by DOMAIN or INN (never by name — different legal entities can share a name).","typical_flow":["ldm_companies_create — create a company","ldm_CompaniesController_importStart / importCombined — bulk import (companies, or companies+contacts together)","ldm_CompaniesController_lookupInn / enrichFromInn — enrich a Russian company from its INN via DaData"],"pitfalls":["Dedup is by domain/INN only. Two companies with the same name are kept separate on purpose.","Custom-field WRITE for a company: ldm_CompanyCustomFieldController_setValues (plural). Contacts/leads use CustomFieldsController_setValue."],"see_also":["contacts","custom-fields","company-lists"]},{"slug":"contact-lists","title":"Contact lists","about":"A static or smart set of contacts. Campaigns pour from STATIC membership — the list is how you hand an audience to a campaign.","typical_flow":["ldm_contact_lists_list — see existing lists","ldm_ContactListsController_create — create a list","ldm_ContactListsController_addContacts — add contacts by id","Then reference {listId} in ldm_CampaignsController_pour"],"pitfalls":["Smart-filter lists are not expanded by campaign pour — pour reads the static membership rows only."],"see_also":["contacts","campaigns"]},{"slug":"company-lists","title":"Company lists","about":"Grouping of companies. You cannot pour a campaign directly from a company list — materialise the companies' contacts into a CONTACT list first.","typical_flow":["ldm_company_lists_list — existing company lists","ldm_CompanyListsController_create → addCompanies","To send: gather the contacts of those companies into a contact list, then pour"],"see_also":["companies","contact-lists","campaigns"]},{"slug":"leads","title":"Leads (deals in a pipeline)","about":"A lead is a deal moving through a pipeline of stages. Replies from outreach become leads; stages can carry automations.","typical_flow":["ldm_leads_create — create a lead (pipeline_id is TEXT, not a uuid cast)","ldm_LeadsController_kanban — board view of a pipeline","ldm_LeadsController_move — move a lead to another stage","ldm_LeadsController_setInterest / getInterest — AI interest classification","ldm_LeadsController_dossier — full company+contact+activity view"],"pitfalls":["pipeline_id is a text field — do not cast it to uuid."],"see_also":["pipelines","dialogs","contacts"]},{"slug":"pipelines","title":"Pipelines & funnel stage automations (CRM)","about":"A pipeline is the CRM funnel: an ordered list of stages a lead moves through. Stage automations are the automation engine of the funnel — they fire when a lead ENTERS a stage (send a follow-up, enrol in a sequence, assign, notify), so the funnel advances itself instead of by hand.","typical_flow":["ldm_pipelines_list → ldm_PipelinesController_findById — see the funnel and its stages","ldm_PipelinesController_createStage → reorderStages — shape the funnel","ldm_PipelinesController_createAutomation — attach an automation that fires on stage entry","ldm_PipelinesController_getAutomationLogs / getAllAutomationLogs — verify automations actually fired","ldm_PipelinesController_getStats — conversion metrics per stage"],"pitfalls":["An automation with no clear enter-condition can fire on every move — check the logs after wiring one."],"see_also":["leads","sequences","dialogs"]},{"slug":"dialogs","title":"Dialogs (the inbox)","about":"Dialogs are the two-way inbox: inbound replies threaded with your sends. This is where outreach turns into conversations and leads.","typical_flow":["ldm_dialogs_list — list threads (or ldm_DialogsController_getThread for one)","ldm_DialogsController_classifyReply — AI classify an inbound reply","ldm_DialogsController_generateReply → ldm_DialogsController_reply — draft then send a response","ldm_DialogsController_createLead — turn a promising dialog into a lead","ldm_dialogs_compose — start a fresh outbound thread"],"pitfalls":["Mail send/receive on the DEV box is degraded (no mail stack) — exercise dialogs on prod for real transport."],"see_also":["leads","email-accounts"]},{"slug":"email-accounts","title":"Email accounts (senders) + relay pool","about":"Sending identities. DNS health (SPF/DKIM/DMARC) is mandatory; warm-up is OPTIONAL. What truly decides inbox vs spam is measured placement per provider (see the deliverability domain) — not a warm-up score.","typical_flow":["ldm_accounts_create — add a sender (pass email only; SMTP/IMAP auto-detected via lookup)","ldm_EmailAccountsController_checkDns — verify SPF/DKIM/DMARC before sending (mandatory)","ldm_EmailAccountsController_testConnection — verify SMTP/IMAP actually connect","Then choose your path: (a) ldm_EmailAccountsController_warmupStart to raise limits over weeks — optional, the platform does NOT warm for you; or (b) skip it and let placement measurement guide low-volume sends.","ldm_SettingsController_setSendWindow + ldm_SettingsController_setMinSendInterval — cap and pace volume; LOWERING volume is a valid, honest response to spam placement (not just a reason to warm more).","ldm_EmailAccountsController_relayHealth — relay-pool eligibility; swap a relay when a provider shows spam."],"pitfalls":["Send from a DNS-unverified mailbox and you land in spam — always checkDns first. (This part is not optional.)","Warm-up is one path (raises limits) but it is manipulative and optional. The honest alternative: start low-volume and let inbox-placement per provider tell you what to fix — relay first, then creative, then volume.","Spread volume across senders and let the relay pool rotate; do not hammer one mailbox.","Dev box has no mail stack — send/receive is only real on prod."],"see_also":["deliverability","tracking","campaigns","email-verification"]},{"slug":"tracking","title":"Tracking (opens/clicks) + tracking domains","about":"Open pixel + click redirects drive analytics. Campaign-sent links (mapped /t/c path) produce analytics; directly-created createLink links (clicked via /r) do NOT log events.","typical_flow":["ldm_TrackingDomainController_create — add a tracking domain (external: DNS CNAME → cnameTarget, then health-check)","ldm_TrackingDomainController_healthCheck → setDefault — make it ACTIVE and default","ldm_TrackingController_getAnalyticsStats — open/click rates (from campaign sends only)"],"pitfalls":["Analytics come only from campaign-sent tracking. A createLink link clicked via /r produces zero analytics.","No ACTIVE default tracking domain → the open pixel falls back to APP_URL/api."],"see_also":["campaigns","email-accounts"]},{"slug":"email-verification","title":"Email verification (list hygiene)","about":"The reputation gate. Verifying addresses before you send is the single highest-leverage deliverability move: invalid addresses bounce, bounces raise your spam signal, and a burned domain is slow and expensive to recover. Do this on every new list.","typical_flow":["ldm_EmailVerificationController_verifyBatch — verify a whole list at once (do this right after import, before any campaign)","ldm_EmailVerificationController_verifyOne — spot-check a single address","Then suppress/skip invalid + risky addresses, and only pour the clean set into a campaign"],"pitfalls":["Never send to an unverified list. A high bounce rate on the first send can damage a domain for weeks.","Treat \"risky\"/catch-all results with caution — send to them last, in small volume, from a warmed mailbox."],"see_also":["contacts","campaigns","email-accounts"]},{"slug":"sequences","title":"Sequences (multi-touch follow-up)","about":"Cold outreach is a multi-touch game — the majority of replies land on the 2nd–4th touch, not the first. A sequence automates spaced follow-ups (with A/B variants) so non-responders are worked without manual chasing. If you send one email and stop, you leave most of the pipeline on the table.","typical_flow":["ldm_SequencesController_create → addStep (space touches a few days apart; +addVariant for A/B)","ldm_SequencesController_enroll — enrol contacts (typically the non-responders from a campaign)","ldm_SequencesController_listEnrollments — monitor progress; pause/cancel enrollments once someone replies"],"pitfalls":["Stop the sequence for a contact the moment they reply — continuing to drip a live conversation reads as spam.","Follow-ups should add value or a new angle, not just \"bumping\" the thread."],"see_also":["campaigns","creatives","dialogs"]},{"slug":"suppression","title":"Suppression (do-not-contact)","about":"The compliance gate. Suppressed emails/domains are auto-skipped by campaign screening. Check BEFORE you send.","typical_flow":["ldm_suppression_check — batch-check emails before sending","ldm_suppression_add / ldm_SuppressionController_addBulk — add addresses","ldm_SuppressionController_addDomain — suppress a whole domain"],"pitfalls":["Screening already applies suppression, but checking first avoids wasting render/send budget."],"see_also":["stoplist","campaigns"]},{"slug":"stoplist","title":"Stop-list","about":"Per-tenant (and global/SUPER) block-list applied during campaign screening alongside suppression.","typical_flow":["ldm_stoplist_check — batch-check emails","ldm_stoplist_add / ldm_StopListController_addBulk — add entries"],"see_also":["suppression","campaigns"]},{"slug":"imports","title":"Imports","about":"Bring external data in. Always PREVIEW first (proposed mapping), then START, then check the LOG/rollback if needed.","typical_flow":["ldm_ContactsController_importPreview / ldm_CompaniesController_importPreview — see the proposed mapping","ldm_ContactsController_importStart / ldm_CompaniesController_importStart — run it","rollback via importRollback with the batchId if the mapping was wrong"],"pitfalls":["Company dedup on import is by domain/INN only — never by name."],"see_also":["contacts","companies","exports"]},{"slug":"exports","title":"Exports","about":"Pull data out as a downloadable file (async job).","typical_flow":["ldm_exports_columns — see exportable columns for an entity","ldm_exports_create — start an export job","ldm_ExportsController_status → ldm_ExportsController_download — poll then download"],"see_also":["contacts","companies","imports"]},{"slug":"deliverability","title":"Deliverability — the inbox-placement loop (the honest path)","about":"The most honest deliverability signal is WHERE your mail actually lands, per provider — not how \"warmed\" an account looks. The platform measures placement on real sends (control emails + a per-provider matrix), so the provider itself tells you what is wrong. Spam is a signal to act on, not a failure to hide. The loop: measure → swap relay → then fix creative → then lower volume.","typical_flow":["Measure: send a little with control emails on, then read placement per provider — ldm_MailingController_getControlEmailStats + ldm_DeliverabilityController_getMatrix. Per-sender snapshot: ldm_EmailAccountDeliverabilityController_getAccountSnapshot.","Spam on a provider? FIRST swap the relay (do not churn the whole pool): ldm_EmailAccountsController_relayHealth to pick another sender, or ldm_DeliverabilityController_startCreativeThroughRelay to probe the creative through a different relay.","Still spam on another relay? Now it is the creative (or the volume): ldm_ContentAnalyzerController_check → ldm_CreativesController_startInboxPlacementTest, or lower volume via the send-window. ldm_DeliverabilityController_startCreativeTune tunes a variant; promote the winner with ldm_DeliverabilityController_promoteTunedVariant.","Per-provider blocks are visible and actionable: ldm_MailingController_blockGuardProviders / ldm_MailingController_blockGuardDashboard; auto-pause on spam: ldm_MailingController_evaluateControlEmailAutoPause."],"pitfalls":["Spam placement is information: change ONE variable at a time — relay first, then creative, then volume.","Do not thrash the relay pool. Swap one relay, re-measure, decide.","Warm-up raises limits but is a manipulation of provider signals and is not always needed — measuring placement on every send is the more honest and often sufficient path."],"see_also":["email-accounts","creatives","campaigns","tracking"]},{"slug":"custom-fields","title":"Custom fields","about":"Per-tenant field definitions on contacts/companies/leads, plus their values.","typical_flow":["ldm_customfields_list — see definitions (filter by entity)","ldm_customfields_create — define a field","Write a value — see the pitfalls, the write API differs by entity"],"pitfalls":["Contact/Lead value write: ldm_CustomFieldsController_setValue, entity in UPPER case (CONTACT / LEAD).","Company value write: ldm_CompanyCustomFieldController_setValues (plural, different controller)."],"see_also":["contacts","companies","leads"]},{"slug":"ai","title":"AI (generation + balance)","about":"Unified AI egress. Generation costs balance — check balance BEFORE a big task so you fail fast, not mid-run.","typical_flow":["ldm_AiController_getBalance — check balance first","ldm_ai_generate — run a completion","ldm_AiController_getUsage — usage stats afterwards"],"pitfalls":["Check AI balance before a large task; a mid-task 402 is worse than an upfront skip."],"see_also":["creatives","dialogs"]},{"slug":"tasks","title":"Tasks — the pipeline & scoring engine","about":"A task runs a list of items through configurable pipeline steps (SITE_AVAILABILITY, AI_CONTENT, KEYWORDS, scoring…) and produces per-item results/scores. Two main uses: lead/ICP scoring, AND scoring relays/senders by inbox placement — run candidate relays, read where they land, keep the ones that reach the inbox. This is also the legacy mailing engine.","typical_flow":["ldm_tasks_list → ldm_TasksController_findById — see tasks and their config","ldm_TasksController_getPipelineSteps → addPipelineStep — configure what the task evaluates","ldm_TasksController_start — run (or resume); fills items from the linked list if empty","ldm_TasksController_getMonitor / getAnalytics — watch it live (open/click/reply); ldm_TasksController_getScoringResults for scores","Relay scoring: score senders, then act on placement — swap to relays that reach the inbox (ldm_EmailAccountsController_relayHealth, ldm_MailingController_getControlEmailStats)"],"pitfalls":["A tripped circuit breaker pauses the task — inspect ldm_TasksController_getCircuitBreaker and reset with ldm_TasksController_resetCircuitBreaker once the cause is fixed.","AI steps cost balance and use the task AI settings (ldm_TasksController_getAiSettings) — check before a big run.","On the dev box scheduled runs are OFF (RUN_CRON_JOBS=false) — trigger manually for tests."],"see_also":["deliverability","email-accounts","leads","campaigns"]},{"slug":"settings","title":"Settings — workspace config (throttle, integrations)","about":"Tenant/user configuration. Deliverability-relevant knobs live here: the send window and per-account min interval (your throttle), mailing auto-approve, mail transports, and the Inbox Check integration. Also profile, security/sessions, AI providers and notifications.","typical_flow":["ldm_SettingsController_getSendWindow / setSendWindow — restrict sending to work hours/days (SUPER)","ldm_SettingsController_getMinSendInterval / setMinSendInterval — pace per-account volume (seconds); lowering volume is an honest response to spam placement","ldm_SettingsController_getTransports / updateTransports — which mail transports are enabled","ldm_SettingsController_testInboxCheckConnection — verify the Inbox Check integration that powers placement measurement"],"pitfalls":["⛔ ldm_SettingsController_deleteAllData wipes ALL tenant data irreversibly — never call it casually.","Tenant-level mailing auto-approve means the WHOLE workspace sends without manual approval — treat as a deliberate, audited choice (SUPER only)."],"see_also":["email-accounts","deliverability","campaigns"]},{"slug":"briefs","title":"Briefs — structured inputs for AI generation","about":"A brief is a schema-validated structured document that feeds AI generation (creatives, content). Sections can be locked so generation keeps them fixed; variables parameterise it; every change is audited.","typical_flow":["ldm_briefs_list → ldm_BriefsController_findOne — see briefs","ldm_BriefsController_getSchemas — read the schema BEFORE editing content","ldm_BriefsController_updateContent / updateVariables — edit the brief body and its variables","ldm_BriefsController_updateLocks — lock sections that generation must not change; ldm_BriefsController_copy to fork a proven brief"],"pitfalls":["Check the schema (getSchemas) before writing content — briefs are validated and a bad shape is rejected."],"see_also":["creatives"]},{"slug":"browser","title":"Browser worker (Phantom) — drive a real browser","about":"A worker-controlled real browser (Phantom desktop client) for scraping and automation the platform APIs cannot reach directly — e.g. LinkedIn. You can hand it a logged-in session (cookies) so it browses sites already authenticated, then drive it: navigate, read HTML, click, type, screenshot.","typical_flow":["ldm_PhantomBrowserController_getClientStatus — is a desktop worker connected?","ldm_PhantomBrowserController_createProfile — create a profile (optionally with cookies + proxy)","ldm_PhantomBrowserController_setCookies — hand it a logged-in session; accepts the Cookie-Editor export format directly (normalised server-side)","ldm_PhantomBrowserController_launchProfile → navigate → getHtml / screenshot / click / typeText — drive the page"],"pitfalls":["Cookies can be passed in the browser-extension (Cookie-Editor) format — the server normalises expirationDate→expires, sameSite no_restriction→None, etc. Example: {\"cookies\":[{\"domain\":\".linkedin.com\",\"name\":\"li_at\",\"value\":\"AQ...\",\"path\":\"/\",\"expirationDate\":1814817974.7,\"secure\":true,\"httpOnly\":true,\"sameSite\":\"no_restriction\"}]}","Commands need a connected desktop worker (getClientStatus) — without one they queue/fail.","This box (RU) is small — keep browser concurrency to 1–2."],"see_also":["contacts","companies"]}],"docs":{"api_reference":"https://developers.live-direct-marketing.online/api-reference","help_ru":"https://live-direct-marketing.online/help/","help_en":"https://live-direct-marketing.online/help/en/","agent_card":"/api/v1/.well-known/agent-card.json"}}