# LDM Developers > Email delivery API for AI agents. CRM + outbound mailing as a Bearer- > authenticated HTTP API. Same endpoint surface for the web UI and for > autonomous agents — one place to call, one set of scopes to grant. ## Portal sections - [LDM Developer API — UI = MCP](https://developers.live-direct-marketing.online/welcome.md): CRM + outbound email mailing as a Bearer-authenticated HTTP API. Same endpoint surface for the web UI and for AI agents — one place to call, one set of scopes to grant. - [Quickstart](https://developers.live-direct-marketing.online/quickstart.md): Check activation, prepare a small company shortlist and one sample, review it, then configure a campaign sender. - [Authentication](https://developers.live-direct-marketing.online/authentication.md): One endpoint surface, two auth schemes — Bearer for agents/MCP, Cookie/JWT for the UI. - [API Registration](https://developers.live-direct-marketing.online/api-registration.md): How to register and obtain an API key for the LDM API. - [Agent Card — LDM discovery](https://developers.live-direct-marketing.online/agent-card.md): Discovery endpoint for autonomous agents. - [CRM — Companies & Contacts](https://developers.live-direct-marketing.online/crm-companies.md): Create, list, search and update companies and contacts via Bearer. - [Leads & Pipelines](https://developers.live-direct-marketing.online/leads-pipeline.md): Manage sales pipelines, stages and leads end-to-end. - [Mailing Campaign — agent self-approve](https://developers.live-direct-marketing.online/mailing-flow.md): Create email account → creative → mailing task → approve → start. Agent with mailing:write can approve its own task. - [Billing — delivery, preparation and your own content](https://developers.live-direct-marketing.online/billing-byoc.md): Prepaid USD balance, operation prices, the delivery billing event and an itemised example. BYOC means your finished content, not a sending account. - [Anti-spam — suppression, stop-list, marking, block-guard](https://developers.live-direct-marketing.online/anti-spam.md): Four layers: three pre-send checks — suppression (single emails), stop-list (whole domains/companies), marking patterns (regex on outbound bodies) — plus BlockGuard, a reactive layer that pauses accounts and barks templates once providers start blocking. - [Deliverability testing — relay reach, creative placement, marker tuning](https://developers.live-direct-marketing.online/deliverability-testing.md): A 3-phase funnel that separates channel reputation from message content: qualify a relay on neutral mail, test a creative through a qualified relay, then strip spam markers one at a time. Probes are async — a start call returns a jobId you poll. - [AI — providers and generation](https://developers.live-direct-marketing.online/ai-keys.md): Bring your own AI key (OpenAI, Anthropic, AItunnel, HydraAI, LDM-AI). Generate copy / replies on demand. - [Dialogs — read inbox, reply, compose, search](https://developers.live-direct-marketing.online/dialogs.md): Full inbox surface for an autonomous agent: list, search, stats, threads, compose, reply, mark-read, share, attachments. - [AI SDR Playbook — run outbound as an agent](https://developers.live-direct-marketing.online/sdr-playbook.md): The SDR (Sales Development Representative) role for AI agents on LDM: the full outbound process — prospect, build lists, write creatives, launch compliant campaigns, work replies, qualify and hand off leads. - [Claude Skills for the AI SDR — 38 skills](https://developers.live-direct-marketing.online/sdr-skills.md): 38 ready-to-install Claude skills that turn your agent into a working SDR on LDM — with a hard emphasis on INBOX PLACEMENT (seed tests, placement matrix, warmup, block recovery, DNS auth) and Guiding skills that steer the SDR’s week. Every tool reference wired to the LDM MCP. - [Tasks — mailing campaign lifecycle](https://developers.live-direct-marketing.online/tasks-campaigns.md): Beyond create — start, pause, stop, restart, plus per-task analytics and live monitor. - [Custom fields — define and use](https://developers.live-direct-marketing.online/custom-fields.md): Per-tenant custom fields for COMPANY / CONTACT / LEAD / EMAIL_ACCOUNT entities. Read picker on each entity, write values, see them in exports. - [Files — uploads, images, attachments](https://developers.live-direct-marketing.online/files.md): Upload a file, get its `fileId`, attach it to a dialog or use it as image asset. - [Webhooks — event subscriptions](https://developers.live-direct-marketing.online/webhooks.md): Subscribe to lead/dialog/task events with HMAC-signed deliveries; inspect the delivery log; retry failed pushes. - [Briefs & Creatives](https://developers.live-direct-marketing.online/briefs-creatives.md): A brief captures the ICP / value-prop; a creative is the email template (subject + bodyHtml) that uses spintax + variables. - [Pipelines & stages — full CRUD](https://developers.live-direct-marketing.online/pipelines-stages.md): Create a pipeline (auto-seeds default stages), add/reorder/rename/hide stages, attach automations. - [Activities — comments and history](https://developers.live-direct-marketing.online/activities.md): Add a NOTE / CALL / MEETING entry to a lead/contact/company. The same feed shows STAGE_CHANGE, LEAD_CREATED, etc. — system events stream into the same table. - [Tags & saved views](https://developers.live-direct-marketing.online/tags-views.md): Lightweight cross-entity labels (tags) and saved filter sets (views) — for fast re-running of the same queries. - [Imports — bulk load CSV](https://developers.live-direct-marketing.online/imports.md): Upload a CSV, preview the auto-mapping, kick off the import job, watch progress. - [Connectors — external sources](https://developers.live-direct-marketing.online/connectors.md): Receive Tilda form submissions as inbound leads, look up Russian companies by name through DaData. - [Best send time](https://developers.live-direct-marketing.online/best-send-time.md): Compute the optimal local send window for a recipient based on TZ + historical reply patterns. - [Limits & Restrictions](https://developers.live-direct-marketing.online/limits.md): All limits enforced by the public API — rate, body size, password policy, quota, validation. - [Errors](https://developers.live-direct-marketing.online/errors.md): Standard error response shape across all endpoints. - [Legal & compliance — documents and the responsibility split](https://developers.live-direct-marketing.online/legal-compliance.md): Where the legal documents live, how to fetch them via API/MCP/A2A, and which risks are the client's vs the platform's. ## API endpoints Live OpenAPI spec: [https://api.live-direct-marketing.online/api/docs-json](https://api.live-direct-marketing.online/api/docs-json) Interactive reference: [https://developers.live-direct-marketing.online/api-reference](https://developers.live-direct-marketing.online/api-reference) 1304 endpoints across 119 tag group(s). ### A2A - `POST /api/a2a` — A2A JSON-RPC 2.0 dispatch (single or batch, supports SSE for streaming methods) ### AI - `GET /api/ai/balance` — Get balance for current or specified AI provider - `GET /api/ai/balances` — Get balances for all configured AI providers - `POST /api/ai/check-balance` — Check AI balance against threshold (cron/monitoring hook) - `POST /api/ai/generate` — Generate AI completion via configured provider - `GET /api/ai/logs` — Get recent AI usage logs - `GET /api/ai/models` — List available AI models per provider (dynamic) - `GET /api/ai/providers` — List available AI providers with status - `GET /api/ai/usage` — Get AI usage statistics for the given window (days) ### API Keys (tenant-scoped) - `GET /api/api-keys` — List tenant API keys (raw key value hidden) - `POST /api/api-keys` — Create a new tenant API key. Pass `actAsCurrentUser: true` to bind the key to the calling user (required for compose/reply/forward and other user-context endpoints; sets `ownerUserId`). Optional `scopes` array gates which surfaces the key can hit; default `["*"]`. - `PATCH /api/api-keys/{id}` — Upgrade an existing API key in-place: set/unset Act-as-me. Only JWT-authenticated calls are allowed (an ApiKey cannot upgrade another ApiKey). Key value is NOT changed — downstream clients keep working without re-configuration. - `DELETE /api/api-keys/{id}` — Permanently delete a tenant API key - `GET /api/api-keys/{id}/reveal` — Reveal the raw value of an existing API key. Only JWT-authenticated (browser) calls are allowed — an ApiKey cannot read another key's secret. Returns { id, name, key }. - `DELETE /api/api-keys/{id}/revoke` — Revoke (deactivate) a tenant API key without deleting it - `GET /api/api-keys/scopes/structured` — Structured scope catalog for the API Keys UI wizard. Returns presets (AI Agent / Read-only) + grouped scope definitions with descriptions and dangerous-flag. Used by Settings → API Keys to render the scope picker. - `GET /api/api-keys/whoami` — Check that the current Bearer credentials (API key or JWT) are valid, and learn where this key is connected before hitting a wall. No scope required — any authenticated caller passes. Used by the MCP gateway to reject bad keys before building an MCP session, instead of after the first tool call. Beyond the original `{ ok, tenantId, scopes, authMode }`, also returns `baseUrl` (scheme+host the request actually arrived on, honoring x-forwarded-proto/x-forwarded-host behind the nginx edge), `apiPrefix` (the mandatory path prefix, taken from the same constant main.ts uses for setGlobalPrefix), and `closedCapabilityClasses` (LEGACY_CAPABILITIES id-prefix groups where NOT ONE operation is reachable with this key's granted scopes). ### Activities - `GET /api/activities` — List activities filtered by lead/contact/company/type - `POST /api/activities` — Create an activity (call/task/note) linked to a lead/contact/company - `PATCH /api/activities/{id}` — Update an activity (subject, description, due date, done flag) - `DELETE /api/activities/{id}` — Delete an activity by id - `GET /api/activities/feed` — Unified tenant event feed (activities + emails) for /crm/events — filters: date range, type (ActivityType|EMAIL_IN|EMAIL_OUT), entityType (contact|lead|company); paginated, sorted by date desc ### Admin - `GET /api/admin/account-transfers/{operationId}` — Get account transfer operation audit status (SUPER) - `POST /api/admin/account-transfers/{operationId}/deactivate-sources` — Deactivate source users of a completed account transfer (SUPER) - `POST /api/admin/account-transfers/apply` — Apply a confirmed, idempotent account transfer plan (SUPER) - `POST /api/admin/account-transfers/plan` — Dry-run an account transfer between tenant databases (SUPER) - `GET /api/admin/agent-guide/coverage` — Guidance coverage report: how many MCP tools have op-level guidance vs domain-fallback vs none. Shows honestly where the expert layer is empty. - `GET /api/admin/agent-guide/effective` — Preview the effective (defaults + overrides) guide map - `GET /api/admin/agent-guide/overrides` — Get the editable agent-guide overrides (raw JSON overlay + meta) - `PUT /api/admin/agent-guide/overrides` — Replace the agent-guide overrides. Body: { domains?, operations?, controllerDomain? } — field-level overlay on the code defaults. Empty/omitted fields fall back to defaults. - `GET /api/admin/ai-model-mapping` — Get AI model mapping configuration - `GET /api/admin/ai-monitor` — Get AI monitor dashboard data - `GET /api/admin/alert-canary` — System alert channel canary status: last success, consecutive failures (#1160) - `POST /api/admin/alert-canary/run` — Manually run one system alert canary tick (#1160) - `GET /api/admin/billing/pricing` — Platform price registry: code defaults, registry overrides and effective prices + signupCreditUsd. - `PATCH /api/admin/billing/pricing` — Change the platform price (field-level merge): { pricing: { : usd | null }, signupCreditUsd?: usd, selfSignupActiveLimit?: int }. null on an operation = revert to the code default. selfSignupActiveLimit — beta capacity (max ACTIVE self-signup accounts; beyond it — a PENDING queue). The change is visible everywhere without a deploy; an AuditEvent is written. - `GET /api/admin/billing/tenants` — Balances of all tenants (wave 4): dbName, name, selfSignup, balance (null = unlimited), sum of charges over 30 days. For the admin "Billing and pricing" tab. - `GET /api/admin/billing/tenants/{dbName}/ledger` — Ledger for a specific tenant (wave 4): CHARGE|CREDIT|ADJUSTMENT|REFUND movements, cursor pagination. - `PATCH /api/admin/campaigns/{dbName}/{id}/audit-unblock` — Unblock an AUDIT_BLOCKED campaign (manual moderation): reset audit iterations, set PAUSED - `GET /api/admin/i18n` — Get i18n translations bundle - `PUT /api/admin/i18n/bulk` — Bulk-update i18n translation keys - `PUT /api/admin/i18n/key` — Update a single i18n translation key - `DELETE /api/admin/i18n/key` — Delete an i18n translation key - `GET /api/admin/ldmai-queue` — Get LDM AI queue snapshot - `GET /api/admin/ldmai-usage` — Get LDM AI usage statistics - `GET /api/admin/memory` — List memory files - `GET /api/admin/memory/{filename}` — Read a memory file by name - `PUT /api/admin/memory/{filename}` — Write a memory file by name - `DELETE /api/admin/memory/{filename}` — Delete a memory file by name - `POST /api/admin/memory/rename` — Rename a memory file - `GET /api/admin/memory/stats` — Get memory storage stats - `GET /api/admin/pipelines` — List all pipelines (cross-tenant admin view) - `GET /api/admin/queues` — Get stats for all BullMQ queues - `POST /api/admin/queues/{name}/clean` — Clean queue jobs (completed/failed) - `GET /api/admin/queues/{name}/jobs` — List jobs in a queue by status - `POST /api/admin/queues/{name}/jobs/{id}/cancel` — Cancel a queue job by id - `POST /api/admin/queues/{name}/jobs/{id}/retry` — Retry a failed queue job - `POST /api/admin/queues/{name}/pause` — Pause a BullMQ queue - `POST /api/admin/queues/{name}/resume` — Resume a BullMQ queue - `GET /api/admin/scoring/health` — Scoring pipeline health: browser worker reachability and queue depths - `GET /api/admin/security/backups` — List available backups - `POST /api/admin/security/baseline` — Create a new file-integrity baseline - `POST /api/admin/security/check` — Run an on-demand security check - `GET /api/admin/security/cross-tenant-denied` — Aggregate security.cross_tenant_denied events by actor x target tenant (#1159) - `GET /api/admin/security/events` — List security events log - `GET /api/admin/security/honeypot` — List recent honeypot events - `GET /api/admin/security/honeypot/stats` — Get aggregate honeypot statistics - `GET /api/admin/security/integrity` — Check file-integrity vs baseline - `GET /api/admin/security/intrusions` — List detected intrusion attempts - `GET /api/admin/security/lockdown` — Get current lockdown state - `POST /api/admin/security/lockdown/activate` — Activate lockdown mode - `POST /api/admin/security/lockdown/deactivate` — Deactivate lockdown mode - `GET /api/admin/security/lockdown/history` — Get lockdown activation history - `GET /api/admin/security/overview` — Get security overview dashboard - `GET /api/admin/security/ports` — List open network ports detected on the host - `GET /api/admin/security/recommendations` — Get security hardening recommendations - `GET /api/admin/security/score` — Get aggregate security score - `GET /api/admin/security/summary` — Get security summary report - `GET /api/admin/storage` — Get storage usage stats - `POST /api/admin/storage/cleanup-pdfs` — Cleanup old PDFs from storage - `GET /api/admin/storage/pdfs` — List stored PDFs with metadata - `GET /api/admin/support/threads` — List all support threads - `GET /api/admin/support/threads/{id}` — Get a support thread with messages (marks as read) - `POST /api/admin/support/threads/{id}/reply` — Reply to a support thread (optional JPG/PNG attachments) - `POST /api/admin/support/threads/{id}/status` — Close or reopen a support thread - `GET /api/admin/support/unread-count` — Total unread user messages across threads - `POST /api/admin/suppression/global/retro-release` — Retroactive release of global_suppression for policy/routing/soft addresses with no hard/inactive anywhere on the platform (#550, SUPER) - `GET /api/admin/tenants` — List all tenants with metadata - `GET /api/admin/tenants/{dbName}/db-quota` — Get DB quota + current usage for a tenant (SUPER) - `PATCH /api/admin/tenants/{dbName}/db-quota` — Set DB quota (MB) for a tenant; null = unlimited (SUPER) - `GET /api/admin/tenants/{dbName}/email-accounts-max` — Get email accounts cap + current usage for a tenant (SUPER) - `PATCH /api/admin/tenants/{dbName}/email-accounts-max` — Set email accounts cap for a tenant; null = platform default (SUPER) - `PATCH /api/admin/tenants/{dbName}/inbox-check` — Update inbox-check config for a tenant - `POST /api/admin/tenants/{dbName}/inbox-check/connect` — Atomically connect paid Inbox Placement for a user workspace - `POST /api/admin/tenants/{dbName}/inbox-check/test` — Run inbox-check test for a tenant - `PATCH /api/admin/tenants/{dbName}/ldm-ai` — Toggle LDM AI for a tenant - `PATCH /api/admin/tenants/{dbName}/ldm-ai/balance` — Set / top up per-tenant LDM AI balance (USD) - `PATCH /api/admin/tenants/{dbName}/max-streams` — Update max parallel streams for a tenant - `GET /api/admin/tenants/{dbName}/settings-menu` — Get hidden settings-menu tabs for a tenant (#275) - `PATCH /api/admin/tenants/{dbName}/settings-menu` — Set hidden settings-menu tabs for a tenant (#275) - `PATCH /api/admin/tenants/{dbName}/transport` — Update mail transport config for a tenant - `POST /api/admin/tenants/{id}/freeze` — Freeze a tenant — stop all sending immediately (SUPER) - `POST /api/admin/tenants/{id}/legal-hold` — Set/clear legal-hold — pause TTL/purge for a tenant (SUPER) - `POST /api/admin/tenants/{id}/unfreeze` — Unfreeze a tenant — resume sending (SUPER) - `GET /api/admin/tenants/inbox-check` — List inbox-check status across tenants ### Admin / Honeypot - `GET /api/honeypot/actuator` — Honeypot fake Spring Boot actuator (logs probe access) - `GET /api/honeypot/admin-login` — Honeypot fake admin login GET (logs attacker scan) - `POST /api/honeypot/admin-login` — Honeypot fake admin login POST (logs attacker credentials attempt) - `GET /api/honeypot/env` — Honeypot fake .env file (logs secrets-scanner access) - `GET /api/honeypot/phpmyadmin` — Honeypot fake phpMyAdmin (logs DB-scanner access) - `GET /api/honeypot/wp-admin` — Honeypot fake WordPress admin (logs WP-scanner access) ### Admin RPA - `GET /api/admin/rpa` — RPA service keys (redacted), tenant opt-ins, queue readiness and legacy drain inventory - `POST /api/admin/rpa/keys` — Create a dedicated RPA service key - `PUT /api/admin/rpa/keys/{id}/global-access` — Explicitly allow an RPA key to claim work for all active tenant users - `PUT /api/admin/rpa/keys/{id}/grants` — Explicit tenant and user opt-in for a service key - `GET /api/admin/rpa/keys/{id}/reveal` — Reveal an encrypted current RPA key for SUPER; legacy hash-only keys cannot be recovered - `POST /api/admin/rpa/keys/{id}/revoke` — Revoke RPA service access immediately; started sends remain for reconciliation - `POST /api/admin/rpa/keys/{id}/rotate` — Rotate a dedicated RPA key, invalidating the previous secret - `POST /api/admin/rpa/keys/{id}/trace` — Enable detailed heartbeat/poll trace for one hour, or disable it - `GET /api/admin/rpa/logs` — Correlated RPA history; allowlisted metadata only, bounded keyset pagination - `GET /api/admin/rpa/protocol` — RPA protocol endpoints and retry/lease rules - `GET /api/admin/rpa/task-history` — Alias for bounded cross-tenant RPA task history ### Agency - `GET /api/agency` — List agency sub-accounts and parent tenant metadata - `POST /api/agency` — Create a new sub-account tenant under the caller - `DELETE /api/agency/{childId}` — Detach (un-parent) a sub-account from the agency - `GET /api/agency/stats` — Aggregate stats per sub-account child ### Agent Card - `GET /api/v1/.well-known/agent-card.json` — Get the LDM-proprietary agent card. Default response is the curated capability subset (#477); pass ?full=1 for the complete OpenAPI-derived catalog. - `GET /api/v1/.well-known/agent.json` — Get the A2A 0.2.5-compliant agent card. Default response is the curated skill subset (#477); pass ?full=1 for the complete OpenAPI-derived catalog. - `GET /api/v1/.well-known/oauth-protected-resource` — RFC 9728 OAuth 2.0 Protected Resource Metadata (#476). LDM has no OAuth authorization server yet — this document is honest about that and points automatable clients at the key-less MCP bootstrap flow (ldm_register) instead of a dead-end auth server. - `GET /api/v1/agent-guide` — Expert guide: capability map + recommended flows for MCP/A2A agents - `GET /api/v1/agent-guide/campaign-sender-capability` — SDR capability preflight (read-only): structured sender-assignment contract + a receipt required before a campaign write that touches senderConfig or sendWindow - `GET /api/v1/agent-guide/context` — Current tenant context core: recent lists, briefs and tasks - `GET /api/v1/agent-guide/resume` — Resume snapshot (read-only): tenant context core, own recent notifications and self-notes, plus an honest next_step - `GET /api/v1/health` — Public API health check ### Allowlist - `GET /api/allowlist` — List allowlist entries with optional type/search filters - `POST /api/allowlist` — Create an allowlist entry (email or domain) - `DELETE /api/allowlist/{id}` — Delete an allowlist entry by id - `POST /api/allowlist/bulk` — Bulk-create allowlist entries (up to 1000) - `GET /api/allowlist/check` — Check whether an email/domain is allowlisted ### Audit - `GET /api/audit-events` — List audit events with filters (action, actor, target, date range) - `POST /api/audit-events` — Manually write an audit event (used by agents to attribute their actions) - `GET /api/audit-events/aggregates` — Get aggregate audit-event counts over a date range ### Authentication - `POST /api/auth/change-password` — Change the password of the authenticated user (current required) - `POST /api/auth/forgot-password` — Request a password reset email (rate-limited per email and per IP) - `POST /api/auth/impersonate/{userId}` — Impersonate another user (SUPER role only) - `POST /api/auth/login` — Authenticate a user and issue access + refresh tokens. 401 = wrong email/password; 403 = credentials are correct but the account is not ACTIVE yet (email not confirmed / awaiting admin approval / blocked) — the error message says which. - `POST /api/auth/logout` — Revoke a refresh token and end the session - `GET /api/auth/me` — Get current user profile (alias of /auth/profile) - `POST /api/auth/otc` — Exchange the one-time sign-in code from the email-verification redirect for a session (same response as /auth/login). Codes are single-use and expire in 5 minutes. - `GET /api/auth/profile` — Get the current authenticated user profile - `POST /api/auth/refresh` — Exchange a refresh token for a new access + refresh pair - `POST /api/auth/register` — Register a new account (self-service). Requires termsAccepted=true (user agreement: GET /api/legal/terms). channel=web needs a password; channel=mcp/a2a/form (bootstrap from an AI agent) makes the password optional and returns the key as flat api_key/scope/quota fields (agent signup is rate-limited per IP). Creates User+Tenant with status=PENDING and emails a confirmation link (valid 48h) to the address. After the email is confirmed the account auto-activates — at most 1 auto-activation per hour platform-wide; beyond that it stays PENDING until admin approval (the admin is notified by email at every step). Self-signup workspaces have a 50MB database quota: over quota, POST/PUT/PATCH return 403 while reads and DELETE keep working. The auto-issued ldm_* key has SAFE_AGENT_SCOPES (read-all + safe drafts; no email:send / mailing:write) — mint a wider key in Settings → API Keys after activation. - `POST /api/auth/reset-password` — Reset password using a reset token - `POST /api/auth/signup-questionnaire` — Issue #322 — submit the signup questionnaire for accounts that did not qualify for automatic activation (free mailbox, or the website/MX checks did not line up). Authenticated by the signup_token from the verify-email redirect, not by an API key — the key issued at /auth/register stays inert until the account is ACTIVE. Calling this endpoint runs the automatic checks immediately (does the company domain resolve, does it answer over HTTP, does it match the contact mailbox) and saves the answers plus those results on the tenant record; it does NOT activate the account itself. The admin is emailed and approves the workspace as a separate, manual step (#1189: no ETA endpoint for that step). Until approval the account stays PENDING (read-only, cannot send email). Re-submitting overwrites the previous answers. - `GET /api/auth/verify-email` — Confirm email by the token from the signup email. Activates the account automatically (1 auto-activation per hour platform-wide; otherwise it stays PENDING for admin approval). Browsers are redirected to the login page with the result; API clients get JSON when ?format=json. ### Best Send Time - `POST /api/best-send-time` — Resolve recommended send time without scheduling; AI fallback uses normal usage logging and billing ### Billing - `GET /api/billing/balance` — Account balance (USD) + operation prices (platform_render/audit/audit_failed/send) + topUpUrl. Top-up is only via the personal account; every paid response contains a _billing { operation, cost, balance_after } block. - `GET /api/billing/ledger` — Balance movement journal (billing_ledger): entryType (CHARGE|CREDIT|ADJUSTMENT|REFUND), operation, cost, balance_after, campaignId/recipientId. Also shows background cron-pipeline charges that are absent from HTTP responses. Filters: campaignId, entryType. ### Branding - `GET /api/branding` — Get resolved white-label branding for the caller tenant (defaults merged) - `PATCH /api/branding` — Partially update tenant white-label branding - `DELETE /api/branding` — Reset tenant white-label branding back to defaults ### Briefs - `GET /api/briefs` — List briefs with pagination, search and active/deleted filters - `POST /api/briefs` — Create a new brief - `GET /api/briefs/{briefId}/memory` — List brief memory entries (decisions, observations, questions, rejected proposals, migrated noise). Default: only ACTIVE. - `POST /api/briefs/{briefId}/memory` — Record a memory entry: a decision, an observation, an open question, a REJECTED proposal (why is required — an unexplained rejection cannot stop the next agent from repeating it), or migrated NOISE. Does NOT touch Brief.content — use PATCH content for that. - `POST /api/briefs/{briefId}/memory/{entryId}/resolve` — Mark a memory entry RESOLVED — leaves the default feed and stops being sent to the expert flow. - `POST /api/briefs/{briefId}/memory/migrate-content-review` — #790: one-time migration of the temporary content.review block into memory entries (NOISE from redundant[], DECISION from keep[]), then removes review from content. Idempotent no-op if review is already gone. - `GET /api/briefs/{id}` — Get a single brief by id - `PATCH /api/briefs/{id}` — Update brief metadata (name, description, status) - `DELETE /api/briefs/{id}` — Soft-delete a brief with optional reason - `GET /api/briefs/{id}/attachments/content` — Read one Brief attachment as bounded source-backed text/structure/image pages; imageRef yields a real MCP image block (#768) - `GET /api/briefs/{id}/audit` — List audit entries for a brief - `PATCH /api/briefs/{id}/content` — Update brief content body (JSON-merge patch). personas[]/examples[] merge by item id when every item has a stable id -- give a NEW item a UUID id and keep reusing it, or the whole array is replaced (legacy behavior, unsafe under concurrent edits, #782); customFields[] has no id yet and always replaces whole - `POST /api/briefs/{id}/copy` — Duplicate an existing brief - `GET /api/briefs/{id}/expert` — Operation-level SDR expert flow: intake provenance → live schema → gaps → recommendations → save/validate - `PATCH /api/briefs/{id}/intake-alias` — Change the stable human-readable system intake alias (#756) - `PATCH /api/briefs/{id}/locks` — Update brief section locks - `GET /api/briefs/{id}/mail` — Brief intake email address and mail feed (#744) - `GET /api/briefs/{id}/mail/attachments/{attachmentId}/download` — Download a brief mail attachment (#780) — same tenant guard as the rest of the brief - `POST /api/briefs/{id}/restore` — Restore a soft-deleted brief - `PATCH /api/briefs/{id}/variables` — Update brief template variables - `GET /api/briefs/intake` — Existing Brief intake address by briefId/briefUrl; omit both only before a Brief exists for workspace unassigned intake (#755) - `GET /api/briefs/intake/items` — List system intake messages for MCP/UI (#750) - `GET /api/briefs/intake/items/{itemId}` — Read one lossless system intake item with provenance, attachments and work log (#750) - `POST /api/briefs/intake/items/{itemId}/link` — Link an intake item/files to a Brief idempotently (#750) - `POST /api/briefs/intake/items/{itemId}/log` — Append an agent work-log event with intake provenance (#750) - `PATCH /api/briefs/intake/items/{itemId}/status` — Transition an intake item to IN_REVIEW, IGNORED or REJECTED with an audited reason (#750) - `GET /api/briefs/schemas` — Get available brief JSON schemas ### Browser Queue - `GET /api/admin/browser-queue` — Get browser queue stats and fair-share view - `POST /api/admin/browser-queue/{id}/cancel` — Cancel a browser queue job by id - `POST /api/admin/browser-queue/{id}/retry` — Retry a failed browser queue job - `GET /api/admin/browser-queue/jobs` — List browser queue jobs by status with pagination - `POST /api/admin/browser-queue/pause` — Pause the browser queue - `POST /api/admin/browser-queue/resume` — Resume the browser queue ### Campaign Groups - `GET /api/campaign-groups` — List campaign groups (flat, with parentId for tree) - `POST /api/campaign-groups` — Create a campaign group - `PATCH /api/campaign-groups/{id}` — Update a campaign group (name/description/color/parent/sort) - `DELETE /api/campaign-groups/{id}` — Delete a campaign group (children + campaigns are detached) - `POST /api/campaign-groups/reorder` — Reorder campaign groups by id/sortOrder array ### Campaigns - `GET /api/campaigns` — List campaigns - `POST /api/campaigns` — Create a campaign. renderMode: PLATFORM (default) — rendered by our AI (paid, platform_render operation); BYOC — you render the emails yourself and submit them via POST :id/recipients/content, the platform only bills the audit and the send. The mode locks once the pipeline starts. Optional sendWindow is campaign-owned: omitted fields use the safe initial default, enabled:false is explicit 24/7, and later workspace-default changes do not alter a saved campaign. - `GET /api/campaigns/{id}` — Get a campaign by id - `PATCH /api/campaigns/{id}` — Update a campaign (creative pool, routing, status and campaign-owned sendWindow). sendWindow is a partial atomic patch: omitted fields are preserved, malformed fields are rejected, and enabled:false is explicit 24/7. - `DELETE /api/campaigns/{id}` — Delete a campaign (cascades recipients; ACTIVE must be paused first) - `GET /api/campaigns/{id}/activity` — Unified chronological event feed for a campaign: the full recipient lifecycle (ingested→screened→rendered→scheduled→handed→sent→delivered/opened/clicked/replied/bounced) + campaign actions (start/pause/resume/stop) with actor{who,reason} attribution — including rule-based auto-pauses (bounce/complaint limit, control-email spam/inbox placement, auditor). Read-only. Pagination {cursor,limit<=200}, filters {type,recipientId,stage,kind,since}. For summary numbers use metrics; for "why is it stalled right now" use send-readiness. - `POST /api/campaigns/{id}/dispatch` — Assign sender account + schedule to READY recipients (no send yet). Optional per-campaign schedule override (#265): body { scheduledAt: ISO-in-future, recipientIds?: string[] } — explicit override beats the recommended contact best_send_time; a past scheduledAt is rejected with 400 (never silently projected). - `GET /api/campaigns/{id}/geo-risk` — Geo channel-policy risk breakdown of recipients (read-only WARN report) - `GET /api/campaigns/{id}/hard-report` — Deterministic read-only quality snapshot for a campaign (#1245, first slice only). `auditor.configured` is the raw campaign.auditorConfig exactly as stored, with no interpretation. `auditor.effective` is the SAME canonical merge(global,campaign) (resolveEffectiveAuditorConfig, #100) that auditBatch actually executes on the hot path — no second formula. No mutation, no AI call. NOT YET included in this slice: Tier-1/Tier-2 coverage/attempts/histogram, per-recipient decision receipts, last10Sent similarity clustering, sdrExpert findings, verification matrix, account/list/creative/task inventory, unread replies, and the full JSON archive with TTL/manifest/SHA-256 — those remain open follow-up work for issue #1245. - `GET /api/campaigns/{id}/metrics` — Campaign metrics. delivered is estimated as durable sent minus a durable known delivery failure — the union of `finalStatus=BOUNCED` (incl. rows currently awaiting soft-bounce retry) and `inboxPlacement=NOT_RECEIVED` ever recorded for the recipient (#491: read from append-only campaign_event, not a snapshot of the CURRENT row — the row can leave stage=SENT or have inboxPlacement cleared by a canvas step advance without the earlier verdict becoming untrue). The union is counted once per row: `delivery.exclusions.bounced` plus the disjoint `delivery.exclusions.notReceivedWithoutBounce`, so a row carrying both signals is never subtracted twice. delivery.confirmed is null when no reliable delivery signal exists. Inbox Placement remains a separate measurement with its own buckets; only its negative verdict feeds the exclusion. `policyRejected` counts SMTP policy/spam rejects (5.7.x «message blocked», banned sender) — a subset of `bounced` with the opposite diagnosis: sender reputation and route, not a dead address. Being a subset, it is already inside `exclusions.bounced` and is never subtracted again. Do NOT confuse it with `complained` (recipient/FBL complaint about an ACCEPTED message) or `inboxPlacement.spam` (accepted and filed into Spam). `bounceNatures` is the full split (hard/inactive = dead addresses, policy/routing = route, unclassified = nature not parsed); `policyRejectedByProvider`/`policyRejectedByRelay` point at the problem direction. `sent` counts UNIQUE recipients whose message was EVER accepted by SMTP (immutable `mail_send_attempts`), so a soft-bounce retry never lowers it; `sendVolume.acceptedAttempts` counts accepted attempts (one recipient may have several), and the CURRENT pipeline state lives in `currentStageCounts` / `sendVolume.currentStageSent` — those do move backwards. `bounced`/`bounceNatures` are durable: they include rows currently waiting for a soft-bounce retry (`final_status` already reset, `bounce_nature`/`bounce_retries` are not — see #396), not just rows CURRENTLY `final_status=BOUNCED`. The pipeline-state-only reading lives in `bounceVolume.currentStageBounced` / `bounceVolume.pendingRetryBounced` (the latter is the delta the old mutable-only reading used to hide for the whole retry wait). The `notReceivedWithoutBounce` exclusion is still bounded by the CURRENT `stage=SENT` (a row back in retry has no stale placement verdict to carry), while `sent` is monotone and `bounced` is durable (#396) — so the estimate can still lag upward only through `notReceivedWithoutBounce`, never below the truth. - `GET /api/campaigns/{id}/operational-status` — Single source of truth for "what is happening with this campaign right now". Returns lifecycleStatus (DRAFT|ACTIVE|PAUSED|DONE|ERROR|AUDIT_BLOCKED) SEPARATELY from operationalState (RUNNING|WAITING|DEGRADED|BLOCKED|COMPLETED): an ACTIVE campaign that sends nothing is the exact defect this endpoint exists for, so never report "active" without the operational qualifier. primaryBlocker is picked by a documented priority table and carries code, since, retryAt, recoverable, suggestedAction and docsKey; `legitimateWait` marks a normal wait (closed send window, future schedule) rather than a stop. `lastProgressAt` is the last REAL hand-off to transport, not the last campaign event. `nextActions[]` lists machine-readable moves — the endpoint never promises to fix anything by itself. UI, notifications and MCP all read this same computation; send-readiness returns the same state plus the full gate dump. - `POST /api/campaigns/{id}/pause` — Pause an ACTIVE campaign (symmetric to /resume) - `POST /api/campaigns/{id}/pour` — Pour recipients into a campaign. Body takes EXACTLY ONE of: listId (CONTACT list — reads STATIC contactListMembership, smart filters NOT re-expanded), companyListId (#1221: COMPANY list — pours live contacts of ALL companies in that list in one call; a company with zero contacts contributes zero recipients and is not an error), or allContacts:true (entire tenant base). Idempotent (dedup by normalized email) — a repeat call with the same listId/companyListId adds nothing new and returns the durable sourceSync lifecycle/reason. Empty STATIC sources remain WAITING and bound for reconciliation. SMART sources are explicitly unsupported. #565: an empty body is rejected (400) — listId/companyListId/allContacts is REQUIRED, exactly one. #716: ONLY ACTIVE email channels are taken; all ACTIVE channels are materialized and one campaign/email row is retained. Response fields: added, noEmail, duplicate, inactiveChannel, notFound, sourceSync. - `POST /api/campaigns/{id}/pour-from-leads` — Pour recipients into a campaign from CRM leads by filter (pipelineId/stageId/interestStatus/limit). #716: ONLY channels with status ACTIVE are taken; contacts whose email channels are all bounced off do not enter the campaign — their count is in the inactiveChannel field of the response. - `POST /api/campaigns/{id}/preparations` — Start or join a durable bounded preparation operation (no sending). Poll statusUrl; accepted means queued, not completed. Preferred over synchronous prepare for AI and personal PDFs. - `GET /api/campaigns/{id}/preparations/{generationId}` — Read a tenant-bound preparation operation. Completed result has the same fields as prepare; failed contains a safe error code/message. This endpoint never sends. - `POST /api/campaigns/{id}/prepare` — Prepare and audit a bounded window of provider-eligible recipients. limit is the requested batch size, not total queue capacity. Inspect preparation.reason and send-readiness before retrying; remaining audience does not mean more content should be generated now. - `POST /api/campaigns/{id}/push` — Push contacts directly into campaign (API-push, e.g. from form webhook) - `GET /api/campaigns/{id}/recipients` — List campaign recipients. `stage` accepts a single stage or a comma-separated list (e.g. FAILED,CREATIVE_FAILED). `failureReason` filters by the normalized stage_error code — the same normalization as `failureReasons` in /metrics, so summary and list always agree (#405). - `GET /api/campaigns/{id}/recipients/{recipientId}/attachment-link` — Refresh a signed link to a recipient personal PDF without regeneration. Returns the exact fingerprint/hash and expiry; this is not permission to send. - `GET /api/campaigns/{id}/recipients/{rid}` — Get a recipient with its event log. #368: `sendAttempts` is the IMMUTABLE transport history (one row per attempt, `result=success` means SMTP accepted it) and `retiredSends` holds snapshots of sends that a retry has already wiped off the row — a soft-bounce retry (#346) or a canvas step advance resets `stage/sentAt/accountId/fromEmail/messageId/dialogId`, so the recipient columns describe only the CURRENT attempt. `acceptedAttempts` tells how many times a message to this recipient was actually accepted. - `PATCH /api/campaigns/{id}/recipients/{rid}/manual` — Manual edit of a job by an operator: subject/body/scheduledAt/stage. ONLY from the interface under a human session — agents (MCP/API key) get 403 human_only, because this bypasses the auditor. Every edit is written to the campaign log and to the audit-log with the previous values. - `POST /api/campaigns/{id}/recipients/content` — Submit self-rendered (BYOC) email content for SCREENED recipients: [{recipientId, subject, bodyHtml, bodyText?}], batch <=100, bodyHtml <=256KB. Submission is free; the content goes through a mandatory paid audit (audit/audit_failed operations), rejection reasons are coarse categories in auditCategories. Limit of 5 audit iterations per recipient; the campaign is blocked (AUDIT_BLOCKED) on systematic rejections. - `POST /api/campaigns/{id}/recipients/move-stage` — Move campaign recipients to a canvas stage (no send) - `POST /api/campaigns/{id}/recipients/repair-unsubscribe` — Backfill missing unsubscribe block and reconcile bodyHash for already-rendered rows (no AI re-render, no billing); stages:["FAILED"] recovers rows burned by body_hash_mismatch (#511); stages:["SKIPPED"] reconciles the hash on stopped rows without unstopping them (#520); CREATIVE_FAILED rows stuck on quality_below_threshold with a now-valid unsubscribe mechanism are requeued to RENDERED for a fresh audit, not force-passed (#526) - `POST /api/campaigns/{id}/recipients/rerender` — Reset unsent recipients back to SCREENED for a fresh re-render; force:true also grants a new paid audit-attempt window (#531) - `POST /api/campaigns/{id}/reconcile-from-dialogs` — One-time: reconcile campaign recipients from existing dialogs (migration) - `POST /api/campaigns/{id}/restore-infra-failed` — Resume a PAUSED campaign (reset circuit breaker) — also retries AI failures - `POST /api/campaigns/{id}/resume` - `POST /api/campaigns/{id}/retry-ai-failures` — Retry unsent AI failures with the current tenant provider/model — reset=0 is an outcome, read reason (retried | already_recovered | nothing_to_retry | same_provider) - `POST /api/campaigns/{id}/screen` — Screening of NEW recipients (suppression + stop-list): NEW→SCREENED|SKIPPED. A pipeline step between pour and prepare. - `POST /api/campaigns/{id}/send` — Hand dispatched READY recipients to the mail_outbox queue (limit=1 for first test send) - `GET /api/campaigns/{id}/send-readiness` — Dry-run all send gates for a campaign: status, breaker, campaign-owned send window, sender pool, routing, inbox-check, due READY rows. Read-only, nothing is sent. `effective.sendWindow` exposes the canonical fields, source, configured, windowOpen and nextOpenAt; malformed stored windows fail closed and never become 24/7. operationalState is RUNNING|WAITING|DEGRADED|BLOCKED|COMPLETED; the separate `pipeline` block reports whether the AI stages (render + tier-2 audit) are halted by a provider outage while sending of READY rows continues (DEGRADED + primaryBlocker AI_STAGES_HALTED). `bounceNatures` splits bounces by nature (hard/inactive = dead addresses; policy/routing = route reputation, address is alive) — durable (#396): includes rows currently waiting for a soft-bounce retry, same predicate as GET /metrics. `relayOverview.pausedDirections` lists relay→provider pairs closed by TTL block rules — the campaign keeps sending via other directions, do NOT restart it (warning DIRECTIONS_PAUSED). `directionWait` (#506) counts rows CURRENTLY deferred because no eligible relay exists for the recipient's provider right now (relay×provider block-rule or empty pool) — these rows are QUEUED with a `nextRetryAt`, NOT terminal FAILED, and are reported separately from `terminalFailureReasons` (warning WAITING_DIRECTION); do NOT treat them as dead addresses or retry manually, they resume on their own once the direction reopens. - `GET /api/campaigns/{id}/send-series` — Sending speed time series. Buckets sends by day or week (bucket=day|week) in the given IANA timezone (tz, default UTC). Counts each unique recipient in the bucket of its FIRST SMTP-accepted attempt (append-only mail_send_attempts), so historic bars never shrink after a soft-bounce retry. Gaps are returned as zero buckets. `total + undated` equals the `sent` metric; `undated` are legacy rows without a datable trace. Also returns peak, activeBuckets and avgPerActiveBucket. - `POST /api/campaigns/{id}/sync-events` — Sync open/click/bounce events from Dialog records back to recipients. Side effects by bounce nature (#345): hard/inactive/unknown → suppression + GLOBAL stop-list; policy/routing → relay→provider direction pause (TTL block rule) instead of burning the address; soft → scheduled retry. - `GET /api/campaigns/{id}/task-history` — Paginated durable delivery-task history for a campaign - `POST /api/campaigns/{id}/test-task` - `GET /api/campaigns/{id}/test-task/{taskId}` - `POST /api/campaigns/backfill-historic-unsubscribes` — One-off backfill: mark campaign_recipients as UNSUBSCRIBED for addresses already in suppressed_emails (reason=UNSUBSCRIBED), using the #762 matching criterion. dryRun (default true) only lists rows, no write. - `GET /api/campaigns/conveyor-health` — Campaign conveyor health for this tenant (#347), read-only. Check `not_sending` (#375) reports campaigns that have due READY emails but have not handed a single one to the transport for longer than CAMPAIGN_WATCHDOG_NOT_SENDING_MIN minutes, with the reason (auto_pause / breaker_stop / manual_pause / no_sender_capacity / blocked). A closed send window or a future schedule is not a stop and is not reported. - `GET /api/campaigns/overview` — Campaigns overview for dashboard mailings widget ### Canvas - `GET /api/canvas/{id}/enrollments` — List canvas enrollments (reuse campaign recipients) - `GET /api/canvas/{id}/flow` — Get the canvas flow definition of a campaign - `PUT /api/canvas/{id}/flow` — Replace the canvas flow definition (whole document) - `GET /api/canvas/{id}/stats` — Per-step canvas stats (GROUP BY canvasStage) - `POST /api/canvas/{id}/tick` — Run one orchestrator tick: claim → bring to READY → advance (does NOT send; the single send point picks up READY on its own) ### Cohort Metrics - `GET /api/cohort-metrics/cohorts` — Get cohort breakdown for a sequence/date range - `GET /api/cohort-metrics/funnel` — Get sequence funnel metrics for a date range - `GET /api/cohort-metrics/latency` — Get reply latency metrics for a sequence/date range ### Companies - `GET /api/companies` — List companies with pagination, search, and filters - `POST /api/companies` — Create a new company. Note: `email` is NOT a Company field — emails live on linked contacts as Channels. Use POST /api/contacts to create the contact, then POST /api/contacts/:id/channels { type: "EMAIL", value } to attach an email channel. Passing `email` in this body returns 400. - `GET /api/companies/{id}` — Get a single company by ID - `PATCH /api/companies/{id}` — Update an existing company - `DELETE /api/companies/{id}` — Soft-delete a company - `PATCH /api/companies/{id}/freeze` — Freeze a company (set status FROZEN). Blocks campaign sending (#369) - `DELETE /api/companies/{id}/purge` — PERMANENTLY delete a trashed company (requires isDeleted). Irreversible. - `POST /api/companies/{id}/restore` — Restore a soft-deleted company - `GET /api/companies/{id}/timeline` — Get a paginated timeline of events for a company - `PATCH /api/companies/{id}/unfreeze` — Unfreeze a company (set status ACTIVE). Does NOT resume already-skipped rows (#369) - `POST /api/companies/bulk` — Enqueue a bulk action on multiple companies (async) - `GET /api/companies/bulk/{jobId}` — Get the status of a bulk company job - `DELETE /api/companies/bulk/{jobId}` — Cancel an in-progress bulk company job - `GET /api/companies/duplicates` — Find duplicate companies in the workspace. Groups are ordered by reliability: same_inn (strong) first, then same_domain (strong), then same_name (weak — different legal entities share names). Paginated: page (1-based), pageSize (default 100, max 500); `total` is the FULL number of groups, not the page length. - `POST /api/companies/export` — Export companies to a downloadable file - `GET /api/companies/export/columns` — List exportable column definitions for companies - `POST /api/companies/import/combined` — Combined companies + contacts import. mapping fields: company (name, domain, inn, industry, size, country, city, address, state, phone, website, source), custom fields cf: → company.customFields (e.g. cf:Revenue, cf:CEO, cf:IndustryCode), contact c:firstName/c:lastName/c:email/c:phone/c:position/c:personalEmail/c:linkedin. Company dedup by DOMAIN/INN only (options.dedupeBy=auto|inn|domain). If c:email present without c:firstName → a generic company contact is created (options.genericContactName, default "Generic contact"). options.listId adds companies to a list. Returns {created,updated,skipped,errors,contactsCreated,createdIds,listId}. - `POST /api/companies/import/google-sheets` — Fetch rows from a Google Sheets URL for import. Returns { headers, rows, suggestedMapping, unmapped } (#1058): suggestedMapping proposes a column -> field guess (incl. cf:/customField: when a known export template recognises the header) with a sample value per column; unmapped lists columns nobody could confidently guess — do not invent a field for those, ask the operator. - `POST /api/companies/import/preview` — Preview a companies import with proposed mapping - `DELETE /api/companies/import/rollback/{batchId}` — Roll back a company import batch by ID - `POST /api/companies/import/start` — Start a companies import. Body: { rows: object[], mapping: {csvColumn -> field}, options }. Mappable fields: name (required), domain, inn, industry, size, country, city, address, state, phone, website, description, source, custom fields cf: → company.customFields. Deduplication is by DOMAIN or INN only (never by name — different legal entities share names): options.dedupeBy = "auto" (default: INN first, then domain) | "inn" | "domain"; options.duplicateHandling = "skip" (default) | "update" | "enrich" | "create". "enrich" (#1057) fills only EMPTY fields of the matched duplicate, never overwrites non-empty ones (customFields merge the same way). Also dedupes within the same batch. options.listId (#1057): adds EVERY row that matched a card — created, updated, or a found duplicate (skip/enrich) — to this list; must be an existing list id in this tenant or the call fails. Returns { batchId, created, updated, enriched, skipped, errors, total, createdIds, existingIds, listId }; roll back with DELETE /import/rollback/{batchId}. - `POST /api/companies/merge` — Merge multiple companies into one keeper record - `GET /api/companies/stats` — Get company statistics, optionally scoped to a list ### Companies / Custom Fields - `GET /api/companies/custom-fields` — List custom field definitions for the given entity (company/contact/lead) - `POST /api/companies/custom-fields` — Create a custom field definition - `GET /api/companies/custom-fields/{companyId}/values` — Get custom field values for a company - `POST /api/companies/custom-fields/{companyId}/values` — Set custom field values for a company - `PUT /api/companies/custom-fields/{id}` — Update a custom field definition - `DELETE /api/companies/custom-fields/{id}` — Delete a custom field definition (requires confirm=true) - `PUT /api/companies/custom-fields/reorder` — Reorder custom field definitions by id array ### Companies / Duplicates - `POST /api/companies/duplicates/delete` — Delete duplicate company records identified by scan - `GET /api/companies/duplicates/fields` — List fields available for duplicate-scan matching - `POST /api/companies/duplicates/merge` — Merge duplicate company records - `POST /api/companies/duplicates/move-to-list` — Move duplicate companies into a target list - `POST /api/companies/duplicates/scan` — Scan companies for duplicates by configured fields ### Company List Groups - `GET /api/company-list-groups` — List company list groups (flat, with parentId for tree) - `POST /api/company-list-groups` — Create a company list group - `PATCH /api/company-list-groups/{id}` — Update a company list group (name/description/color/parent/sort) - `DELETE /api/company-list-groups/{id}` — Delete a company list group (children + lists are detached) - `POST /api/company-list-groups/reorder` — Reorder company list groups by id/sortOrder array ### Company Lists - `GET /api/company-lists` — List all company lists for the tenant - `POST /api/company-lists` — Create a new company list - `PATCH /api/company-lists/{id}` — Update a company list - `DELETE /api/company-lists/{id}` — Delete a company list - `POST /api/company-lists/{id}/companies` — Add companies to a list - `DELETE /api/company-lists/{id}/companies` — Remove companies from a list - `POST /api/company-lists/{id}/duplicate` — Duplicate a company list - `POST /api/company-lists/{id}/merge` — Merge this list into another list: move or copy all member companies atomically, duplicates in the target are skipped - `GET /api/company-lists/contacts-counts` — Sum of contacts of member companies, per list id (?ids=csv) - `POST /api/company-lists/reorder` — Reorder company lists by id/sortOrder array ### Connectors - `GET /api/connectors/dadata/name` — Normalize Russian full name via DaData API - `POST /api/connectors/tilda/webhook` — Tilda form webhook (disabled — requires authorization, see #193) ### Contact List Groups - `GET /api/contact-list-groups` — List contact list groups (flat, with parentId for tree) - `POST /api/contact-list-groups` — Create a contact list group - `PATCH /api/contact-list-groups/{id}` — Update a contact list group (name/description/color/parent/sort) - `DELETE /api/contact-list-groups/{id}` — Delete a contact list group (children + lists are detached) - `POST /api/contact-list-groups/reorder` — Reorder contact list groups by id/sortOrder array ### Contact Lists - `GET /api/contact-lists` — List all contact lists for the tenant - `POST /api/contact-lists` — Create a new contact list - `PATCH /api/contact-lists/{id}` — Update a contact list - `DELETE /api/contact-lists/{id}` — Delete a contact list - `POST /api/contact-lists/{id}/contacts` — Add contacts to a list - `DELETE /api/contact-lists/{id}/contacts` — Remove contacts from a list - `POST /api/contact-lists/{id}/duplicate` — Duplicate a contact list - `GET /api/contact-lists/{id}/statistics` — Get statistics for a single contact list - `GET /api/contact-lists/all-statistics` — Get statistics across all contact lists - `GET /api/contact-lists/compare` — Compare statistics between two contact lists - `POST /api/contact-lists/reorder` — Reorder contact lists by id/sortOrder array ### Contacts - `GET /api/contacts` — List contacts with pagination, search, and filters - `POST /api/contacts` — Create a new contact. Note: `email`/`phone` are NOT direct Contact fields — they live as Channels. After creating the contact, attach the email/phone via POST /api/contacts/:id/channels { type: "EMAIL" | "PHONE", value }. Passing `email`/`phone` in this body returns 400. - `PATCH /api/contacts/{contactId}/channels/{channelId}` — Update a contact channel - `DELETE /api/contacts/{contactId}/channels/{channelId}` — Delete a contact channel - `GET /api/contacts/{id}` — Get a single contact by ID - `PATCH /api/contacts/{id}` — Update an existing contact - `DELETE /api/contacts/{id}` — Soft-delete a contact - `POST /api/contacts/{id}/channels` — Add a communication channel to a contact - `POST /api/contacts/{id}/dig` — Manually trigger DIG email enrichment for a contact - `GET /api/contacts/{id}/duplicates` — Check for duplicates of a specific contact - `DELETE /api/contacts/{id}/purge` — PERMANENTLY delete a trashed contact (requires isDeleted). Irreversible. - `POST /api/contacts/{id}/restore` — Restore a soft-deleted contact - `GET /api/contacts/{id}/timeline` — Get a paginated timeline of events for a contact - `POST /api/contacts/bulk` — Bulk action on contacts (async, BullMQ). Pass either contactIds[] OR selectAll:true (the server resolves contacts by the search/emailValidStatus/dig* filter). Actions: delete | addTags | removeTags | addToList | removeFromList | updateField; the list target = the listId parameter. WARNING: for addToList the listId parameter serves BOTH as the target AND (with selectAll) as the source filter — so selectAll will NOT add "everyone into a separate list" (filtering by the target list returns that list's own members, 0 for an empty one). Flow for "collect the whole base into a list for a mailing": (1) list contactIds page by page (GET /contacts?hasEmail=true&page..) → addToList in batches (or contact-lists POST :id/contacts); (2) campaigns pour(listId). A contact in a list is a membership row, NOT a copy of the contact. - `GET /api/contacts/bulk/{jobId}` — Get the status of a bulk contact job - `DELETE /api/contacts/bulk/{jobId}` — Cancel an in-progress bulk contact job - `GET /api/contacts/duplicates` — Find duplicate contacts across the workspace - `GET /api/contacts/duplicates/by-email` — Find contacts that share duplicate email addresses - `POST /api/contacts/export` — Export contacts to a downloadable file - `POST /api/contacts/import/preview` — Preview a contacts import with proposed mapping - `DELETE /api/contacts/import/rollback/{batchId}` — Roll back a contact import batch by ID - `POST /api/contacts/import/start` — Start a contacts import job - `POST /api/contacts/merge` — Merge multiple contacts into one keeper record - `GET /api/contacts/stats` — Get contact statistics, optionally scoped to a list ### Content Analyzer - `POST /api/content-analyzer/check` — Analyze email subject/body for spam triggers and return score 0..100. customRules (#100) lets a caller preview the effect of a not-yet-saved global/campaign Tier-1 rule before writing it via /settings/auditor-rules or campaign auditorConfig.extraRules. ### Creatives - `GET /api/creatives` — List creatives with pagination and search - `POST /api/creatives` — Create a new creative - `GET /api/creatives/{id}` — Get a single creative by ID - `PATCH /api/creatives/{id}` — Update a creative - `DELETE /api/creatives/{id}` — Delete a creative - `GET /api/creatives/{id}/autopilot/all-field-definitions` — Get all field definitions available to Autopilot - `GET /api/creatives/{id}/autopilot/auditor` — Get the Autopilot auditor configuration - `PATCH /api/creatives/{id}/autopilot/auditor` — Update the Autopilot auditor configuration (proxies `enabled` to canon; see AuditorService JSDoc for the rest) - `GET /api/creatives/{id}/autopilot/auditor/logs` — Get logs for the Autopilot auditor - `DELETE /api/creatives/{id}/autopilot/auditor/logs` — Clear logs for the Autopilot auditor - `POST /api/creatives/{id}/autopilot/auditor/pause` — Pause the Autopilot auditor - `POST /api/creatives/{id}/autopilot/auditor/run` — Run the Autopilot auditor once - `POST /api/creatives/{id}/autopilot/auditor/start` — Start the Autopilot auditor - `POST /api/creatives/{id}/autopilot/auditor/stop` — Stop the Autopilot auditor - `GET /api/creatives/{id}/autopilot/bank` — List Autopilot bank templates - `POST /api/creatives/{id}/autopilot/bank/approve-all` — Approve all pending Autopilot bank templates - `PATCH /api/creatives/{id}/autopilot/bank/config` — Update Autopilot bank configuration - `POST /api/creatives/{id}/autopilot/bank/generate` — Trigger Autopilot bank generation - `GET /api/creatives/{id}/autopilot/bank/pipeline/{jobId}` — Get the status of an Autopilot bank pipeline job - `POST /api/creatives/{id}/autopilot/bank/preview` — Preview Autopilot bank output - `POST /api/creatives/{id}/autopilot/bank/preview-with-qc` — Preview Autopilot bank output with QC checks - `POST /api/creatives/{id}/autopilot/bank/regenerate` — Regenerate Autopilot bank templates - `DELETE /api/creatives/{id}/autopilot/bank/templates` — Delete all Autopilot bank templates - `POST /api/creatives/{id}/autopilot/bank/templates` — Create a new Autopilot bank template - `PATCH /api/creatives/{id}/autopilot/bank/templates/{templateId}` — Update an Autopilot bank template - `DELETE /api/creatives/{id}/autopilot/bank/templates/{templateId}` — Delete a single Autopilot bank template - `GET /api/creatives/{id}/autopilot/bank/templates/{templateId}` — Get a single Autopilot bank template - `PUT /api/creatives/{id}/autopilot/bank/templates/{templateId}/content` — Replace the content of a bank template - `GET /api/creatives/{id}/autopilot/bank/templates/{templateId}/stats` — Get stats for a single Autopilot bank template - `POST /api/creatives/{id}/autopilot/bank/templates/generate-one` — Generate a single Autopilot bank template - `GET /api/creatives/{id}/autopilot/bank/usage` — Get Autopilot bank usage stats - `GET /api/creatives/{id}/autopilot/checklist` — Get the Autopilot launch checklist - `POST /api/creatives/{id}/autopilot/checklist/cost-estimate` — Estimate cost for the Autopilot launch - `POST /api/creatives/{id}/autopilot/checklist/lock` — Lock the Autopilot checklist before launch - `POST /api/creatives/{id}/autopilot/checklist/qc` — Run the QC step of the Autopilot checklist - `POST /api/creatives/{id}/autopilot/checklist/sandbox` — Run the sandbox step of the Autopilot checklist - `POST /api/creatives/{id}/autopilot/checklist/test-send` — Run the test-send step of the Autopilot checklist - `POST /api/creatives/{id}/autopilot/checklist/unlock` — Unlock the Autopilot checklist - `GET /api/creatives/{id}/autopilot/dashboard` — Get the Autopilot dashboard for a creative - `POST /api/creatives/{id}/autopilot/dashboard/backfill-t2` — Backfill Tier-2 QC results on the Autopilot dashboard - `POST /api/creatives/{id}/autopilot/generate-prompt` — Generate an Autopilot meta-prompt for a creative - `GET /api/creatives/{id}/autopilot/meta-prompt` — Get the meta-prompt for a creative - `PUT /api/creatives/{id}/autopilot/meta-prompt` — Replace the meta-prompt for a creative - `DELETE /api/creatives/{id}/autopilot/meta-prompt` — Delete the meta-prompt of a creative - `POST /api/creatives/{id}/autopilot/pipeline/run` — Run the full Autopilot generation pipeline - `POST /api/creatives/{id}/autopilot/preview-meta-prompt` — Preview the meta-prompt for a creative - `GET /api/creatives/{id}/autopilot/qc/config` — Get the Autopilot QC configuration - `PATCH /api/creatives/{id}/autopilot/qc/rules` — Update Autopilot QC Tier-1 rules - `POST /api/creatives/{id}/autopilot/qc/test` — Run a QC test on sample content - `PATCH /api/creatives/{id}/autopilot/qc/tier2` — Update Autopilot QC Tier-2 configuration (proxies enabled/threshold/samplingRate/model to canon; see JSDoc for the `checks` exception) - `POST /api/creatives/{id}/autopilot/sandbox/apply` — Apply the sandbox result to the creative - `GET /api/creatives/{id}/autopilot/sandbox/available-vars` — List variables available to the sandbox - `POST /api/creatives/{id}/autopilot/sandbox/cancel` — Cancel a running sandbox run - `POST /api/creatives/{id}/autopilot/sandbox/cancel-observable` — Cancel an observable sandbox run - `GET /api/creatives/{id}/autopilot/sandbox/history` — Get the sandbox run history - `DELETE /api/creatives/{id}/autopilot/sandbox/history` — Clear the sandbox run history - `DELETE /api/creatives/{id}/autopilot/sandbox/history/{idx}` — Delete one entry from the sandbox history - `POST /api/creatives/{id}/autopilot/sandbox/match-contacts` — Match contacts against a sandbox segment definition - `DELETE /api/creatives/{id}/autopilot/sandbox/observable` — Delete the observable sandbox state - `GET /api/creatives/{id}/autopilot/sandbox/observable-status` — Get the status of an observable sandbox run - `POST /api/creatives/{id}/autopilot/sandbox/run` — Run the Autopilot sandbox pipeline - `POST /api/creatives/{id}/autopilot/sandbox/run-observable` — Start an observable Autopilot sandbox run - `GET /api/creatives/{id}/autopilot/sandbox/status` — Get the current sandbox status - `POST /api/creatives/{id}/autopilot/sandbox/test-selection` — Run a test selection against the sandbox - `GET /api/creatives/{id}/autopilot/sandbox/test-selection` — Get the cached test selection result - `POST /api/creatives/{id}/autopilot/segment-count` — Count contacts matching a sandbox segment - `PATCH /api/creatives/{id}/autopilot/segment/{segmentId}/meta-prompt` — Update the meta-prompt of a segment - `POST /api/creatives/{id}/autopilot/test-batch` — Run a test batch through Autopilot - `POST /api/creatives/{id}/autopilot/v2/generate-prompt` — Generate an Autopilot v2 meta-prompt - `GET /api/creatives/{id}/autopilot/v2/meta-prompt-base` — Get the base meta-prompt for Autopilot v2 - `PATCH /api/creatives/{id}/autopilot/variable-rules` — Update the Autopilot variable rules - `GET /api/creatives/{id}/autopilot/variable-rules` — Get the Autopilot variable rules - `POST /api/creatives/{id}/autopilot/variable-rules/preview` — Preview the effect of variable rules - `GET /api/creatives/{id}/autopilot/variable-values` — Get computed variable values for a creative - `GET /api/creatives/{id}/brief-intake` — Canonical brief intake email and localized help for customer materials linked to this creative - `POST /api/creatives/{id}/copy` — Duplicate a creative - `POST /api/creatives/{id}/generate-for-contact` — Generate a creative for a specific contact - `POST /api/creatives/{id}/generate-pdf` — Generate a PDF from a creative - `POST /api/creatives/{id}/inbox-placement-test` — Start an inbox placement test for a creative - `GET /api/creatives/{id}/inbox-placement-test/{token}` — Get the result of an inbox placement test - `POST /api/creatives/{id}/moderate` — Approve/reject a channel creative (SUPER) - `GET /api/creatives/{id}/pdf-generations/{generationId}` — Get the status of an asynchronous PDF generation - `POST /api/creatives/{id}/preview` — Render a preview of a creative for a sample contact - `PATCH /api/creatives/{id}/rules/bank/{tid}` — Update a single bank template (rules v2) - `POST /api/creatives/{id}/rules/bank/{tid}/action` — Run an action on a bank template (approve/reject/etc.) - `POST /api/creatives/{id}/rules/bank/generate` — Enqueue a bank-generation job for a creative - `GET /api/creatives/{id}/rules/bank/jobs` — List bank-generation jobs for a creative - `GET /api/creatives/{id}/rules/bank/jobs/{jobId}` — Get the status of a bank-generation job - `POST /api/creatives/{id}/rules/bank/jobs/{jobId}/cancel` — Cancel a bank-generation job - `GET /api/creatives/{id}/spintax` — Analyze spintax patterns in a creative - `GET /api/creatives/{id}/stats` — Get stats for a creative - `POST /api/creatives/{id}/test-send` — Send a test copy of this creative. sender="account" (default, unchanged behaviour) needs a connected email account: pass accountId AND toEmail. sender="platform" needs NO connected account and NO toEmail at all — omit toEmail and it sends from the LDM platform mailbox (same transport as signup email) straight to the address the TENANT OWNER registered with; this is the intended zero-config call. If you DO pass toEmail with sender="platform" it must equal that same registration address exactly or the call is refused (not an open relay, #801) — passing any other address never sends anywhere else, it just fails closed. Use "platform" the moment a creative exists, before any mailbox is connected, to see the rendered email + attachment land for real. Rate-limited to 5 platform sends/hour per tenant. The response carries a disclaimer: placement via the platform mailbox says NOTHING about the client's own sending domain reputation — it only proves the creative renders and the attachment attaches; re-test with sender="account" before trusting placement for a real campaign. Zero connected accounts AND no sender given → the 400 body lists BOTH ways forward (connect one, or use sender="platform" with no toEmail). - `GET /api/creatives/{id}/test-sends` — List test sends of a creative - `GET /api/creatives/{id}/variables` — Get template variables used in a creative - `GET /api/creatives/{id}/versions` — List versions of a creative - `POST /api/creatives/{id}/versions` — Create a new version of a creative - `POST /api/creatives/{id}/versions/{versionId}/restore` — Restore a creative to a prior version - `POST /api/creatives/ai-generate` — Generate a creative draft via AI - `POST /api/creatives/autopilot-v2` — Create a new Autopilot v2 creative - `GET /api/creatives/moderation/pending` — List channel creatives pending moderation (SUPER) - `GET /api/creatives/pdf-link/{token}` — Download a generated PDF via a signed, tenant-bound, time-limited link (#1015, no auth headers needed) - `GET /api/creatives/pdf-status` — Get the PDF generation queue status - `GET /api/creatives/pdf/{filename}` — Download a generated PDF by filename - `GET /api/creatives/variables/available` — List available template variables ### Creatives / On-Demand - `POST /api/creative-on-demand/{creativeId}/generate` — Enqueue an on-demand generation request for a creative - `GET /api/creative-on-demand/{creativeId}/qc-config` — Get QC configuration for a creative - `PUT /api/creative-on-demand/{creativeId}/qc-config` — Upsert QC configuration for a creative - `DELETE /api/creative-on-demand/{creativeId}/qc-config` — Reset QC configuration to defaults - `GET /api/creative-on-demand/{creativeId}/requests` — List on-demand requests for a creative - `GET /api/creative-on-demand/{creativeId}/requests/{requestId}` — Get a single on-demand request by id - `POST /api/creative-on-demand/{creativeId}/requests/{requestId}/cancel` — Cancel an in-flight on-demand request - `GET /api/creative-on-demand/{creativeId}/stats` — Get aggregated on-demand stats for a creative ### Custom Events - `POST /api/custom-events` — Track a single custom event - `GET /api/custom-events` — List custom events with filters and pagination - `GET /api/custom-events/aggregates` — Get aggregated custom event counts by date range - `POST /api/custom-events/bulk` — Track up to 500 custom events in one request ### Custom Extract - `POST /api/custom-extract` — #972 custom extraction over ALREADY supplied material: a word/word list (KEYWORD), a regular expression (REGEX, via compileSafeRegex — the same barrier as inbound-rules), or an AI prompt (PROMPT, a custom prompt can be supplied via promptOverride). The response distinguishes three outcomes: found / not_found / cannot_search (no material, broken rule, AI unavailable, no funds, tenant queue full) — see reason. KEYWORD/REGEX return immediately (200, mode=sync); PROMPT is enqueued (202, mode=queued, jobId) — AI balance and the tenant pending-task limit are checked BEFORE enqueueing. - `GET /api/custom-extract/{jobId}` — #972 status/result of a PROMPT custom-extraction job enqueued by the POST above. The job is checked for ownership by the CURRENT tenant (the job carries the tenantId from enqueueing) — a foreign jobId is indistinguishable from a nonexistent one (status=unknown), the job number does not leak foreign data (pipeline gate per MR !1195: the same defect class as incident_tenant_isolation_idor). ### Custom Fields - `GET /api/custom-fields` — List custom field definitions, optionally filtered by entity - `POST /api/custom-fields` — Create a custom field definition - `PATCH /api/custom-fields/{id}` — Update a custom field definition - `DELETE /api/custom-fields/{id}` — Delete a custom field definition - `GET /api/custom-fields/inventory` — Read-only report: divergence between JSONB and table storage - `POST /api/custom-fields/params/{entity}/{entityId}` — Legacy bulk set of params as key-value pairs - `GET /api/custom-fields/params/{entity}/{entityId}` — Legacy get of params as flat key-value object - `POST /api/custom-fields/reorder` — Reorder custom field definitions - `GET /api/custom-fields/values/{entity}/{entityId}` — Get all custom field values for an entity instance - `POST /api/custom-fields/values/{entity}/{entityId}` — Set a custom field value for an entity instance ### Custom Objects - `GET /api/custom-objects/{slug}/records` — List records of a custom object - `POST /api/custom-objects/{slug}/records` — Create a record of a custom object - `GET /api/custom-objects/{slug}/records/{id}` — Get a custom object record by id or externalId - `PATCH /api/custom-objects/{slug}/records/{id}` — Update a custom object record - `DELETE /api/custom-objects/{slug}/records/{id}` — Delete a custom object record - `GET /api/custom-objects/defs` — List custom object definitions - `POST /api/custom-objects/defs` — Create a custom object definition - `GET /api/custom-objects/defs/{slug}` — Get a custom object definition by slug - `PATCH /api/custom-objects/defs/{slug}` — Update a custom object definition - `DELETE /api/custom-objects/defs/{slug}` — Delete a custom object definition ### DSAR - `POST /api/dsar` — Submit a data-subject request (public): erasure erases+suppresses immediately - `GET /api/dsar` — Data-subject request registry (SUPER) - `POST /api/dsar/{id}/resolve` — Resolve a data-subject request (SUPER) - `GET /api/dsar/lookup` — Look up a subject by email: global suppression status + request history (SUPER) ### Deliverability - `POST /api/deliverability/block-rules` — Create a manual block rule (defaults to category=should_not_send, reason=manual) - `GET /api/deliverability/block-rules` — List block rules for the tenant — active only by default; includeExpired=true adds expired ones, flagged with active:false - `DELETE /api/deliverability/block-rules/{id}` — Delete a block rule (manual unblock) — narrower scope required - `GET /api/deliverability/matrix` — Get the deliverability matrix for the tenant (baseline / creative / block-rule per relay × provider) - `GET /api/deliverability/probe/{jobId}` — Get aggregate status for a probe batch (jobId) - `DELETE /api/deliverability/probe/{jobId}` — Cancel pending probes in a batch (in-flight workers no-op safely) - `POST /api/deliverability/probe/creative-through-relay` — C2: probe whether a specific creative reaches inbox through (relay × provider) - `POST /api/deliverability/probe/creative-tune` — C3: iteratively tune a creative (remove markers) until it reaches inbox via (relay, provider) - `POST /api/deliverability/probe/full-pipeline` — Full pipeline: run C1 across all (relay × provider) and auto-chain C2/C3 via worker - `POST /api/deliverability/probe/relay-reach` — C1: probe whether the given relay can deliver to the given providers (Layer 1 neutral baseline) - `GET /api/deliverability/probes` — List probe history with filters (raw audit-style log) - `GET /api/deliverability/tuned-variants` — List Layer-3 tuned creative variants (optionally filtered by parent creativeId) - `POST /api/deliverability/tuned-variants/{id}/promote` — Promote a tuned variant to a new Creative row in the tenant ### Development Pipeline - `GET /api/development-pipeline/attention` — "Needs an owner" block + delivery strip (issue #1004): red tasks/deploys, findings without an issue, agent questions, approve branches waiting for merge, "no priority" counter, main-pipeline/smoke-prod/merge-queue status, main→smoke stall past the threshold (issue #999, atom A7: isHanging/ageMinutes on mainPipeline), wall/acceptance verdict on the MR head SHA (issue #999, atom A5: mergeAcceptance[].hasVerdictOnHeadSha/verdict, reading MR notes in the acceptance_watch.py format, no GitLab mutations) - `GET /api/development-pipeline/board` — Board across 4 delivery stages (queue/agents/merge/deploy) - `GET /api/development-pipeline/ci-runs-per-merge` — "CI runs per merge" KPI over a window (issue #999, atom A9), read-only: the same calculation as warden/runs_per_merge.py. There are no pipeline/deploy launch buttons here and never will be — GitLab reads only. - `PATCH /api/development-pipeline/issues/{iid}/close` — Close an issue on behalf of the owner (issue #439) — label `closed::by-owner` + close + comment + audit record - `PATCH /api/development-pipeline/issues/{iid}/model` — Executor model — sonnet (default) / opus / fable / codex-sol / codex-terra - `PATCH /api/development-pipeline/issues/{iid}/pause` — Pause an issue — do not pick it up for work while the `paused` label is set - `PATCH /api/development-pipeline/issues/{iid}/priority` — Issue priority — P1-critical / P2-integrity / P3-ux - `PATCH /api/development-pipeline/issues/{iid}/take-next` — "Take next" — unpause and move the issue to the front of the queue (P1) - `POST /api/development-pipeline/issues/priority-triage` — One-off idempotent triage: set priority on every open issue without a P label - `GET /api/development-pipeline/journal` — Tester journal !505 as source of truth (issue #500): block 3 rules, roles, block 2 findings and the integrity gate. GitLab being unavailable does not fail the response — `available:false` + `reason`. - `POST /api/development-pipeline/journal/findings` — Append one finding line to Block 2 of journal !505 (issue #1152) — an atomic write via API with CAS by length+sha256, instead of a client PUT of the whole description. Visible on the MCP surface. - `POST /api/development-pipeline/journal/findings/{findingId}/create-issue` — Create an issue from a journal finding that has no issue number (issue #500 p.2) — label `from::journal`, in the chosen project. Only on an explicit owner click, there is no auto-creation without a human. - `GET /api/development-pipeline/prod-counter` — "IN PROD" showcase (issue #1010): issues that shipped via a successful main pipeline with green smoke-prod; done/merged do not count as prod ### Development Pipeline — Conveyor Bridge - `POST /api/development-pipeline/conveyor/issues` — Create a new conveyor task (issue #1001) via the bridge; on an empty/unclear description — 422 with validator questions - `POST /api/development-pipeline/conveyor/issues/{num}/action` — retry/cancel a conveyor task from blocked/failed (issue #1001) - `PATCH /api/development-pipeline/conveyor/issues/{num}/pause` — Pause/unpause a conveyor task from the console in one click (issue #1001, owner request 17.08) - `PATCH /api/development-pipeline/conveyor/issues/{num}/priority` — Set conveyor task priority P1/P2/P3 in one click (issue #1001) - `GET /api/development-pipeline/conveyor/template` — Live TEMPLATE.md + docs/pipeline/*.md from the bridge for the "Rules" tab — no copy in frontend code (issue #1001) ### Dialog Linker - `POST /api/dialog-linker/backfill` — Run dialog linker backfill across the tenant (admin) - `GET /api/dialog-linker/failure-summary` — Get failed linker reason counts and LinkLog coverage - `GET /api/dialog-linker/logs` — Get all linker logs (admin tech log) - `GET /api/dialog-linker/logs/{dialogId}` — Get linker logs for a specific dialog - `POST /api/dialog-linker/relink/{dialogId}` — Manually re-run linker for a specific dialog - `GET /api/dialog-linker/stats` — Get linker statistics by status ### Dialogs - `GET /api/dialogs` — List dialogs with filters (channel, status, folder, etc.) - `GET /api/dialogs/{id}` — Get a single dialog by ID - `PATCH /api/dialogs/{id}` — Update dialog metadata or edit a queued outbox message - `DELETE /api/dialogs/{id}` — Delete a dialog - `POST /api/dialogs/{id}/action/bounce` — Process a bounce action (hard or soft) on a dialog - `POST /api/dialogs/{id}/action/spam` — Mark a dialog as spam and apply side effects - `POST /api/dialogs/{id}/action/stop-list` — Add the dialog sender to the stop-list - `POST /api/dialogs/{id}/action/unsubscribe` — Process an unsubscribe action triggered by a dialog - `POST /api/dialogs/{id}/auto-link` — Auto-link a dialog to matching contact/company/lead - `POST /api/dialogs/{id}/classify-reply` — Run AI reply classification on a dialog - `POST /api/dialogs/{id}/create-lead` — Create a new lead from a dialog - `POST /api/dialogs/{id}/extract-entities` — Run AI entity extraction (company/contact) on a dialog - `POST /api/dialogs/{id}/forward` — Forward an existing dialog to a new recipient. Attachments can be passed inline as `attachmentsJson: [{ filename, contentBase64, contentType }]` (base64-encoded, JSON body) or as multipart files. (#175 — parity with /compose) - `POST /api/dialogs/{id}/generate-reply` — Generate an AI reply draft for a dialog - `POST /api/dialogs/{id}/mark-read` — Mark a dialog as read - `POST /api/dialogs/{id}/mark-type` — Manually mark the message type / marking of a dialog - `DELETE /api/dialogs/{id}/purge` — PERMANENTLY delete a trashed dialog (requires status DELETED). Irreversible. - `POST /api/dialogs/{id}/reminder` — Set a reminder on a dialog - `POST /api/dialogs/{id}/reminder/clear` — Clear an existing reminder on a dialog - `POST /api/dialogs/{id}/reply` — Reply to a dialog (optionally send via SMTP with attachments). Attachments can be passed inline as `attachmentsJson: [{ filename, contentBase64, contentType }]` (base64-encoded, JSON body) or as multipart files. (#175 — parity with /compose) - `POST /api/dialogs/{id}/restore` — Restore a deleted dialog (status DELETED → NEW) - `POST /api/dialogs/{id}/share` — Generate a public share token for a dialog (30-day TTL) - `POST /api/dialogs/{id}/snooze` — Snooze a dialog until a future timestamp - `POST /api/dialogs/{id}/star` — Toggle the starred flag on a dialog - `POST /api/dialogs/{id}/track/set-token` — Set the tracking token on a dialog (internal use) - `POST /api/dialogs/{id}/unsnooze` — Unsnooze a dialog immediately - `GET /api/dialogs/accounts` — List email accounts used in dialogs - `POST /api/dialogs/badges` — Get bulk dialog badges for a set of leads - `POST /api/dialogs/bulk` — Perform a bulk action on multiple dialogs - `POST /api/dialogs/compose` — Compose and send a new outbound email. Provide exactly one sender binding: `accountId` (a fixed relay) OR `accountListId` (a virtual-relay pool — the live relay is chosen from the pool at dispatch time, #40). Attachments are inline only: `attachmentsJson: [{ filename, contentBase64, contentType }]` (base64-encoded). No multipart upload on this endpoint — use `:id/reply` or `:id/forward` for that. (#3 Block 3 — inline attachments) - `GET /api/dialogs/folders` — List folders, optionally scoped to an account - `POST /api/dialogs/imap/delete` — Permanently delete dialogs from IMAP - `GET /api/dialogs/imap/folders` — List IMAP folders live from the mail server - `POST /api/dialogs/imap/folders` — Create an IMAP folder - `PATCH /api/dialogs/imap/folders` — Rename an IMAP folder - `POST /api/dialogs/imap/folders/delete` — Delete an IMAP folder - `POST /api/dialogs/imap/move` — Move dialogs to an IMAP folder - `GET /api/dialogs/outbox` — List outbox entries with status and scheduling info - `PATCH /api/dialogs/outbox/{id}` — Reschedule (or clear schedule on) a queued outbox send - `DELETE /api/dialogs/outbox/{id}` — Cancel a queued or failed outbox entry - `GET /api/dialogs/outbox/{id}/history` — Read safe, paginated history for one durable outbox task - `POST /api/dialogs/outbox/{id}/refresh-control` — Poll inbox-check for the placement status of a control email - `POST /api/dialogs/outbox/{id}/resend-control` — Re-enqueue the control-copy send for an outbox entry - `POST /api/dialogs/outbox/{id}/retry` — Manually retry a failed outbox entry - `GET /api/dialogs/outbox/stats` — Get outbox queue statistics - `GET /api/dialogs/stats` — Get aggregate dialog statistics - `GET /api/dialogs/thread/{threadId}` — Get a full dialog thread by thread ID - `POST /api/dialogs/track/click` — Record an email-click tracking event - `POST /api/dialogs/track/open` — Record an email-open tracking event ### Domains - `POST /api/domains/dns-check` — Run DNS health check for a single domain - `POST /api/domains/dns-check/bulk` — Run DNS health check for up to 20 domains ### Email Accounts - `GET /api/email-account-lists` — List email account lists - `POST /api/email-account-lists` — Create an email account list - `PATCH /api/email-account-lists/{id}` — Update an email account list - `DELETE /api/email-account-lists/{id}` — Delete an email account list - `GET /api/email-account-lists/{id}/relay-health` — Relay pool health (#40) for an email-account-list / relay-pool id (not an individual account id): live eligibility of each account in the pool, next relay, starving flag, recent dispatches and sticky bindings - `GET /api/email-accounts` — List email accounts with filters and pagination - `POST /api/email-accounts` — Create a new email account. Lookup is advisory and client-invoked: POST does not auto-detect transport. Call GET /email-accounts/lookup first for the email address, then pass the returned SMTP/IMAP/service fields explicitly. Override host/port/credentials for custom domains. The legacy `provider` field is normalized to the `service` enum (no separate column). - `GET /api/email-accounts/{id}` — Get an email account by id - `PATCH /api/email-accounts/{id}` — Update an email account. Note on status (#558): an account already stopped by a diagnosis (ERROR / AUTO_PAUSED / RATE_LIMITED / SUSPENDED) cannot be moved to PAUSED — that would hide the reason and drop it out of automatic recovery (ERROR re-probing, auto-resume cron), and it is rejected with code PAUSE_WOULD_MASK_DIAGNOSIS. Fix the account and set ACTIVE, or stop it deliberately with POST /email-accounts/:id/suspend, which records a reason. Read handles return stopKind/stopReason/pauseAllowed so you can tell in advance - `DELETE /api/email-accounts/{id}` — Delete an email account - `GET /api/email-accounts/{id}/dns-check` — Run DNS health check for the account domain - `POST /api/email-accounts/{id}/fetch-imap` — Trigger an IMAP fetch for an account (queue or fast-lane) - `PATCH /api/email-accounts/{id}/gas/disable` — Disable GAS integration for an account - `GET /api/email-accounts/{id}/gas/labels` — List Gmail labels available via GAS - `POST /api/email-accounts/{id}/gas/ping` — Ping the GAS web app to verify connectivity - `POST /api/email-accounts/{id}/gas/setup` — Generate GAS setup instructions for the account - `POST /api/email-accounts/{id}/gas/test-fetch` — Run a test fetch via GAS - `POST /api/email-accounts/{id}/gas/test-send` — Send a test email via GAS - `POST /api/email-accounts/{id}/gas/verify` — Verify a deployed GAS web app URL for the account - `POST /api/email-accounts/{id}/reset-counters` — Reset daily send counters for an account - `GET /api/email-accounts/{id}/stats` — Get per-account email statistics - `POST /api/email-accounts/{id}/suspend` — Suspend an email account - `POST /api/email-accounts/{id}/test` — Test SMTP and/or IMAP connectivity for an account - `POST /api/email-accounts/{id}/test-receive` — Universal Receive Test (#4). Probes the real receive transport for this account — Microsoft Graph, Gmail API (planned), IMAP direct, IMAP via Agent, or GAS WebApp — and returns a single honest result with transport label, per-step log, and (when available) inbox counts. Rate-limited per account (1 per 5s). - `POST /api/email-accounts/{id}/test-send` — Universal Send Test (#5). Dry-run probe of the real send transport — Microsoft Graph sendMail (planned), Gmail API drafts (planned), SMTP XOAUTH2 verify (planned), SMTP password, SMTP via Agent, or GAS WebApp health (planned). Never sends a real message. Rate-limited per account (1 per 5s). - `POST /api/email-accounts/{id}/unsuspend` — Unsuspend an email account - `GET /api/email-accounts/{id}/warmup` — Get mailbox warm-up stats for an account - `PATCH /api/email-accounts/{id}/warmup` — Configure mailbox warm-up parameters - `POST /api/email-accounts/{id}/warmup/start` — Start mailbox warm-up for an account - `POST /api/email-accounts/{id}/warmup/stop` — Stop mailbox warm-up for an account - `PATCH /api/email-accounts/{id}/work-status` — Update the work status (IDLE / IN_PROGRESS) of an account - `GET /api/email-accounts/check-duplicate` — Check if an email address is already registered - `POST /api/email-accounts/import-oauth` — Import an Outlook/Hotmail account from an OAuth combo (email:password:refresh_token:client_id). The platform mints and auto-refreshes the access token itself (public-client refresh, no secret). Probes IMAP receive + SMTP send and reports capability; receive-only when the mailbox has SMTP disabled. - `POST /api/email-accounts/import-oauth/bulk` — Bulk-import Outlook/Hotmail accounts from OAuth combo strings (one per array item). - `GET /api/email-accounts/lookup` — Look up provider SMTP/IMAP settings for an email address - `GET /api/email-accounts/relay-health-summary` — Relay health across the whole tenant (#337): how many relays are sending right now, how many dropped out and why (AUTH_ERROR, BOUNCE_LIMIT, RATE_LIMITED, SEND_LIMIT, SPAM_PLACEMENT, PROVIDER_BLOCK), per-relay counters, daily limit usage and inbound freshness. Per-relay provider breakdown (#345, #507): rejectsByProvider (policy/routing rejects from CampaignRecipient.bounceNature, tenant-wide, windowed by the blockGuard.windowDays tenant setting — same source as campaign policyRejectedByProvider/policyRejectedByRelay, not a second aggregation), campaignBounceNatures (hard/inactive = dead addresses, policy/routing = route reputation; durable (#546, same predicate as campaign metrics #396): finalStatus=BOUNCED UNION rows currently awaiting soft-bounce retry (finalStatus reset, bounceNature/bounceRetries are not), windowed by the same blockGuard.windowDays as rejectsByProvider — see bounceNaturesWindowDays (null means the aggregate failed to compute, fail-soft)), plus tenant-wide pausedDirections — relay→provider pairs closed by TTL block rules (relay alive, direction paused). Read-only, no pool id needed — use it to answer "is sending healthy right now" - `GET /api/email-accounts/relay-health/attention` — Relays that automation can no longer recover (#356): status=ERROR relays where re-probing kept failing, so a human has to look. Returns probeFailures, lastErrorClass, when the relay was escalated and staleDays — how long it has been waiting. This is the "what do I have to fix myself" list, as opposed to relay-health-summary ("what is broken right now") - `POST /api/email-accounts/relay-health/probe` — Re-probe ERROR relays of this tenant right now (#356) instead of waiting for the 10-min cron. Probes SMTP with verify() (no test mail is sent), returns how many recovered into the pool and how many were escalated for manual review. Safe to call repeatedly - `GET /api/email-accounts/stats` — Get aggregated stats across all email accounts - `POST /api/email-accounts/test-unsaved` — Test SMTP/IMAP credentials without saving the account ### Email Accounts / GAS Webhook - `POST /api/gas/webhook` — Receive a GAS push notification and trigger IMAP fetch ### Email Accounts / Inbox Ingest - `POST /api/internal/mail-arrived` — Internal: notify that new mail arrived and trigger immediate IMAP fetch ### Email Accounts — Deliverability - `GET /api/email-accounts/{id}/deliverability` — Embedded deliverability snapshot for this email account (providers × {baseline, creative, blocked}) - `DELETE /api/email-accounts/{id}/deliverability/block-rules/{provider}` — Unblock a (this account × provider) block rule — narrower scope required - `POST /api/email-accounts/{id}/deliverability/probe` — "Probe now" — convenience C1 trigger scoped to this account ### Email Verification - `POST /api/verify` — Verify a single email address synchronously - `POST /api/verify/batch` — Verify a batch of email addresses synchronously ### Enrichment - `POST /api/enrichment/company` — Enrich a company by domain, INN or name - `POST /api/enrichment/email` — Find an email address from name and domain ### Exports - `POST /api/exports` — Create an export job - `GET /api/exports` — List my export jobs - `GET /api/exports/{id}` — Get the status of an export job - `GET /api/exports/{id}/download` — Download a completed export file - `GET /api/exports/columns` — List exportable columns and groups for an entity ### Files - `GET /api/files` — List file attachments for a contact, company or lead - `POST /api/files` — Upload a file attachment for a contact, company or lead (max 30 MB) - `DELETE /api/files/{id}` — Delete a file attachment record - `GET /api/files/{id}/download` — Download a file attachment (authorized, streams real bytes) - `POST /api/files/assets` — Upload a tenant asset (signature, PDF photo, max 30 MB) - `GET /api/files/assets` — List tenant assets - `DELETE /api/files/assets/{filename}` — Delete a tenant asset - `GET /api/files/assets/quota` — Get tenant asset storage usage and quota - `GET /api/files/images` — List uploaded creative images for the tenant - `DELETE /api/files/images/{filename}` — Delete a tenant-scoped creative image - `POST /api/files/images/import` — Import images from a source attachment into tenant storage - `GET /api/files/library` — List the unified files-and-links library, optionally filtered by brief/lead/contact/company/purpose/kind/search - `POST /api/files/library` — Add a library item: EITHER a file (multipart form field "file", max 100 MiB, whitelist pdf/doc(x)/xls(x)/ppt(x)/csv/txt/md/rtf/odt/ods/jpg/jpeg/png/webp/gif/svg/zip) OR a link (no file — send {"kind":"LINK","url":"https://..."}). Optional {title, description, briefId, leadId, contactId, companyId} attach it right away. MCP callers: this route is multipart-declared — pass {"file":{"filename","contentBase64","mimeType"}} to upload bytes, or omit "file" and pass "body":{"kind":"LINK","url":...} for a link. - `PATCH /api/files/library/{id}` — Rename / reattach / detach a library item (title, description, briefId/leadId/contactId/companyId; null = detach, keeps it in the library) - `DELETE /api/files/library/{id}` — Delete a library item — removes the DB row AND the bytes on disk (link: DB row only) - `GET /api/files/library/{id}/content` — Read a library document as bounded source-backed text/structure/image pages; imageRef yields a real MCP image block (#768) - `GET /api/files/library/{id}/download` — Download a library file with authorization (never public — Content-Disposition: attachment) - `GET /api/files/library/quota` — Library storage usage and quota (500 MB, purpose=LIBRARY, separate from the 30 MB branding quota) - `POST /api/files/upload` — Upload an image for creatives (max 5 MB) ### Files / Serve - `GET /api/uploads/avatars/{filename}` — Serve a user avatar image - `GET /api/uploads/dialog-attachments/{tenantDbName}/{filename}` — Download an inbound dialog attachment (#176) - `GET /api/uploads/images/{tenantDbName}/{filename}` — Serve a tenant-scoped uploaded image - `GET /api/uploads/tenant-assets/{tenantDbName}/{filename}` — Serve a tenant-scoped asset (signature, PDF photo) ### Health - `GET /api/health` — Liveness probe - `GET /api/health/capacity` — Resource capacity check for load admission ### ICP Extension - `GET /api/icp-tasks` — List ICP tasks with pagination and status filter - `POST /api/icp-tasks` — Create a new ICP task - `GET /api/icp-tasks/{id}` — Get an ICP task by id - `PATCH /api/icp-tasks/{id}` — Update an ICP task - `DELETE /api/icp-tasks/{id}` — Delete an ICP task - `GET /api/icp-tasks/{id}/download-extension` — Download a pre-built Chrome extension zip for an ICP task - `GET /api/icp-tasks/{id}/items` — List items belonging to an ICP task - `POST /api/icp-tasks/{id}/regenerate-key` — Regenerate the project key for an ICP task - `GET /api/icp-tasks/{id}/scoring-prompt` — Preview the ACTUAL scoring prompt for this task (brief-driven / custom / default) - `GET /api/icp-tasks/{id}/statistics` — Get statistics for an ICP task - `POST /api/icp-tasks/companies/scrape` — #298/#835 server-side crawl fetcher: enqueued in the background, the response is a taskId immediately. Input is companyIds OR listId (#955, the whole list with no 500-item cap on the request body — SiteEnrichmentService resolves it in full); with an explicit limit, the first limit are taken into work (defaults to 50 for companyIds input), how many were actually enqueued is visible in the queued field. - `GET /api/icp-tasks/companies/scrape/{taskId}` — #835 status and progress of a site-crawl task. #957 (fix): includeResults=false removes the results field from the response — on large tasks (thousands of cards) the full result list stops fitting in the response and gets in the way of working with the task via MCP; by default results is returned (the old contract), an explicit includeResults=false returns only aggregates (total/processed/ok/down/noSite). To page through the results themselves — GET .../companies/scrape/:taskId/items or the canonical .../site-enrichment/tasks/:id/items. #957 (continued): the response NOW carries a stats key — the same enriched statistics byte-for-byte as the canonical GET .../site-enrichment/tasks/:id/stats (cards AND domains funnel counted separately, breakdown of collected data by field, pace/forecast, launch parameters echoed back — listId/listName/domainField/visitPages/timeoutMs/maxAttempts/mode/surface/startedBy, direct links to the scoring-task card and this same task's journal); this is a legacy route (ldm_icp_scrape_status) that remains available. The canonical ldm_site_enrichment_tasks_* tools exist in the client catalog but may return 403 insufficient_scope. The legacy alias stays available for clients that lack scope for the new tools. - `POST /api/icp-tasks/companies/scrape/{taskId}/cancel` — #835 cancel a site-crawl task - `GET /api/icp-tasks/companies/scrape/{taskId}/items` — #952 crawl task items with a filter by outcome/reason code (alias of .../items on the canonical site-enrichment) - `POST /api/icp-tasks/companies/scrape/{taskId}/pause` — #952 pause a crawl task (alias of .../pause on the canonical site-enrichment) - `POST /api/icp-tasks/companies/scrape/{taskId}/resume` — #952 resume a paused crawl task (alias of .../resume on the canonical site-enrichment) - `GET /api/icp-tasks/companies/scrape/preview` — #955 preview list coverage WITHOUT starting a crawl: how many cards, how many have the domain field filled, how many unique domains among them (alias of .../preview on the canonical site-enrichment) ### ICP Extension / API - `GET /api/ext/auth` — Authorize the Chrome extension by project key - `GET /api/ext/company/lookup` — Lookup CRM company by site host (widget company card) - `POST /api/ext/formassist/ai` — Run AI form-fill suggestion for the extension - `POST /api/ext/formassist/complete` — Report form-assist completion from the extension - `GET /api/ext/formassist/config` — Get form assist configuration for the extension - `GET /api/ext/formassist/profile` — Resolved profile values for manual form fill (context menu) - `POST /api/ext/icpreview/ai` — Analyze an ICP review item with AI - `POST /api/ext/icpreview/complete` — Mark an ICP review item as completed - `POST /api/ext/icpreview/enrich` — Auto-enrich the company card from the visited site (#281 item 7) - `GET /api/ext/icpreview/next` — Get the next ICP review item for the extension - `POST /api/ext/messenger/complete` — Report messenger send completion (creates/links a Contact by url) - `POST /api/ext/messenger/generate` — Generate a messenger message (channel = vk|telegram|whatsapp|linkedin) - `POST /api/ext/presentation` — Generate a presentation PDF (mode = attach → base64 | link → OG url) - `GET /api/ext/presentation/{token}` — Public OG preview page for a presentation link (no key) - `GET /api/ext/presentation/pdf/{filename}` — Serve a presentation PDF by filename (no key, public link) - `POST /api/ext/sendemail/capture` — Capture an email address from the extension - `GET /api/ext/sendemail/stats` — Get send-email stats for the extension ### IMAP Orchestrator - `GET /api/imap-orchestrator/admin/health` — IMAP orchestrator kill-switch state + queue counts (#6 Layer 4). Returns whether the cron is currently paused and why. - `POST /api/imap-orchestrator/admin/kill` — Emergency stop for IMAP cron triggers (#6 Layer 4). All enqueueAll cycles short-circuit until /resume is called. Idempotent — repeated calls only update the reason field. - `POST /api/imap-orchestrator/admin/resume` — Resume IMAP cron after a kill (#6 Layer 4). - `GET /api/imap-orchestrator/attention` — List email accounts needing manual intervention - `GET /api/imap-orchestrator/conveyor-health` — Per-step inbound conveyor health for this tenant (#336): fetch quarantine, relay inbound silence, staging backlog, delivery reports the classify pass never covered (`classifyNotRun`; a NULL business label on ordinary mail is normal and is NOT backlog), link starvation, bounces not matched to ANY send path (`unresolvedBounces24h`; `alreadyHandled24h` and `foreignMailboxNdr24h` are split out), failed lead rules. `survivedHealing` counts objects that were healed a tick earlier and are still stuck — only those raise alerts. Read-only — no healing, no alerts. - `GET /api/imap-orchestrator/conveyor-watchdog/last` — Result of the last conveyor watchdog tick across all tenants (#336), without recomputing. - `POST /api/imap-orchestrator/conveyor-watchdog/run` — Run the conveyor watchdog tick across all tenants NOW (#336): measure every step, self-heal (re-enqueue stalled links, rerun pipeline for staging/classify backlog) and send alerts where healing did not help. Same routine the */10 cron runs. - `POST /api/imap-orchestrator/fetch-all` — Trigger an IMAP fetch for every active account in the tenant - `POST /api/imap-orchestrator/fetch/{accountId}` — Manually trigger an IMAP fetch for a specific account - `POST /api/imap-orchestrator/heal` — Auto-heal stuck GAS accounts and IMAP locks for the current tenant - `POST /api/imap-orchestrator/heal-all` — Auto-heal stuck IMAP/GAS state across all tenants - `GET /api/imap-orchestrator/health-accounts` — Admin: list all email accounts across every tenant for health checks - `POST /api/imap-orchestrator/health-test` — Admin: run SMTP/IMAP connection test (or round-trip) for a specific account - `GET /api/imap-orchestrator/jobs` — List recent IMAP orchestrator jobs - `DELETE /api/imap-orchestrator/jobs/{id}` — Cancel an IMAP orchestrator job - `GET /api/imap-orchestrator/marking-overrides` — List dialog marking override rules - `POST /api/imap-orchestrator/marking-overrides` — Create a dialog marking override rule - `POST /api/imap-orchestrator/marking-overrides/{id}` — Update a dialog marking override rule - `DELETE /api/imap-orchestrator/marking-overrides/{id}` — Delete a dialog marking override rule - `GET /api/imap-orchestrator/marking-patterns` — List dialog marking regex patterns - `POST /api/imap-orchestrator/marking-patterns` — Create a dialog marking regex pattern - `POST /api/imap-orchestrator/marking-patterns/{id}` — Update a dialog marking regex pattern - `DELETE /api/imap-orchestrator/marking-patterns/{id}` — Delete a dialog marking regex pattern - `GET /api/imap-orchestrator/recent-dialogs` — List recently inserted inbound dialogs - `GET /api/imap-orchestrator/stats` — Get IMAP orchestrator dashboard stats - `GET /api/imap-orchestrator/sync-health` — Get lossless mail sync cursor, staging and quarantine health - `POST /api/imap-orchestrator/sync-health/{id}/retry` — Retry a failed durable inbound message, or force a re-fetch of an exhausted quarantine row - `GET /api/imap-orchestrator/tech-log` — Get the latest N IMAP job log entries for monitoring ### Import - `POST /api/import/from-url` — Import a list from a link (Google Sheets or a direct .csv/.xlsx URL) — the "link -> file" link, does not start the import itself (#805). Response includes suggestedMapping/unmapped (#1058): a proposed column -> field mapping (from the detected template when one matched, or a header-name heuristic otherwise) with a sample value per column, plus the columns nobody could confidently guess. - `POST /api/import/tasks` — Create a new import task - `GET /api/import/tasks/{id}` — Get an import task by id - `POST /api/import/tasks/{id}/cancel` — Cancel an in-progress import task - `GET /api/import/tasks/{id}/log` — Get the row-level log for an import task - `GET /api/import/tasks/{id}/log/csv` — Download the import task log as CSV - `POST /api/import/tasks/{id}/rollback` — Rollback the changes made by an import task - `GET /api/import/templates` — List available import templates - `POST /api/import/templates/apply` — Apply an import template to CSV headers - `POST /api/import/templates/detect` — Detect the best matching import template from CSV headers ### Inbound Rules - `GET /api/inbound-rules` — List all inbound rules - `POST /api/inbound-rules` — Create a new inbound rule - `GET /api/inbound-rules/{id}` — Get an inbound rule by id - `PATCH /api/inbound-rules/{id}` — Update an inbound rule - `DELETE /api/inbound-rules/{id}` — Delete an inbound rule - `PATCH /api/inbound-rules/{id}/toggle` — Toggle the active flag of an inbound rule - `GET /api/inbound-rules/events` — List recent inbound-rule automation events - `POST /api/inbound-rules/reapply` — Re-apply keyword routing to leads in the intake stage - `PATCH /api/inbound-rules/reorder` — Reorder inbound rules (sortOrder) - `POST /api/inbound-rules/test` — Dry-run synthetic inbound message against active rules ### Jobs - `GET /api/jobs/{queue}/{id}` — Get the status of any background job by queue + id (Wave 15 unified poll) ### Landing - `POST /api/landing/apply` — Submit a public landing-page application form ### Leads - `GET /api/leads` — List leads with pagination, search, and pipeline/stage filters - `POST /api/leads` — Create a new lead - `GET /api/leads/{id}` — Get a single lead by ID - `PATCH /api/leads/{id}` — Update an existing lead - `DELETE /api/leads/{id}` — Soft-delete a lead - `GET /api/leads/{id}/deal` — Get the deal projection (amount/probability/weighted) for a lead - `PATCH /api/leads/{id}/deal` — Update deal fields (amount/currency/probability/close date) - `GET /api/leads/{id}/dossier` — Get the full dossier (company + contact + activity) for a lead - `GET /api/leads/{id}/duplicates` — Check for duplicates of a specific lead - `GET /api/leads/{id}/interest` — Get the AI interest classification for a lead - `PATCH /api/leads/{id}/interest` — Manually set the interest status of a lead - `POST /api/leads/{id}/move` — Move a lead to a different stage (and optionally pipeline) - `DELETE /api/leads/{id}/purge` — PERMANENTLY delete a trashed lead (requires isDeleted). Irreversible. - `POST /api/leads/{id}/restore` — Restore a soft-deleted lead - `POST /api/leads/bulk` — Enqueue a bulk action on multiple leads (async) - `GET /api/leads/bulk/{jobId}` — Get the status of a bulk lead job - `DELETE /api/leads/bulk/{jobId}` — Cancel an in-progress bulk lead job - `GET /api/leads/deal/forecast` — Compute the weighted-amount forecast across leads in scope - `GET /api/leads/duplicates` — Find duplicate leads across the workspace (by title/contact/company within a pipeline) - `POST /api/leads/export` — Export leads to a downloadable file - `POST /api/leads/import/preview` — Preview a leads import with proposed mapping - `POST /api/leads/import/start` — Start a leads import job - `GET /api/leads/interest/breakdown` — Get the breakdown of leads by interest status - `GET /api/leads/kanban/{pipelineId}` — Get the kanban board for a pipeline - `GET /api/leads/kanban/{pipelineId}/stage/{stageId}` — Load more leads for a single kanban stage - `GET /api/leads/stages/{pipelineId}` — List stages of a pipeline (for lead filters) - `GET /api/leads/stats` — Get lead statistics, optionally scoped to a pipeline ### Legal - `GET /api/legal` — List all legal documents (SUPER only) - `POST /api/legal/acceptances` — Record the current user's acceptance of a legal document - `GET /api/legal/acceptances` — Acceptance registry — who accepted which version when (SUPER) - `GET /api/legal/acceptances/me` — The current user's own acceptances - `GET /api/legal/documents` — List all legal documents (key/title/summary) — public - `GET /api/legal/documents/{key}` — Get one legal document by key (terms|aup|privacy|dpa|mode2|campaign_guarantee|security) — public - `POST /api/legal/documents/{key}/publish` — Publish a new version of a legal document (SUPER) - `GET /api/legal/documents/{key}/versions` — List versions of a legal document (SUPER) - `GET /api/legal/terms` — Get the current terms of service text (public). Web default is ru; the ldm_terms agent tool requests locale=en explicitly. - `PUT /api/legal/terms` — Update the terms of service text (SUPER only) ### LinkedIn - `GET /api/linkedin/accounts` — List of LinkedIn accounts (without cookies) - `GET /api/linkedin/accounts/{id}` — Account by id (without cookies) - `PATCH /api/linkedin/accounts/{id}` — Change account limits/windows/pause - `POST /api/linkedin/accounts/{id}/disconnect` — Revoke session (erase cookies) - `GET /api/linkedin/campaigns` — List of campaigns - `POST /api/linkedin/campaigns` — Create a campaign - `PATCH /api/linkedin/campaigns/{id}` — Change campaign (name/status/scenario) - `POST /api/linkedin/campaigns/{id}/leads` — Pour leads into a campaign - `GET /api/linkedin/integration` — LinkedIn integration status (toggle, defaults) - `PATCH /api/linkedin/integration` — Enable/disable the module, kill-switch, defaults - `GET /api/linkedin/scenarios` — List of scenarios - `POST /api/linkedin/scenarios` — Create a scenario - `GET /api/linkedin/stats` — Dashboard: funnel + acceptance/reply-rate ### LinkedIn / Extension API - `GET /api/ext/linkedin/config` — Client config: toggles + guard-rails - `POST /api/ext/linkedin/events` — Widget events (accept/reply/warning/challenge) - `POST /api/ext/linkedin/heartbeat` — Widget heartbeat (+optionally fresh cookies) - `GET /api/ext/linkedin/next` — Claim the next due action (atomically). Empty → 204 - `POST /api/ext/linkedin/report` — Report on an action execution - `POST /api/ext/linkedin/session` — Upload/update a session (cookies from an authorized browser) ### Mail Agent - `POST /api/mail-agent/disconnect` — Disconnect the mail agent session - `GET /api/mail-agent/download/windows` — Download the Windows mail agent bundle with token pre-configured - `GET /api/mail-agent/qr` — Get an SVG QR code for pairing the Android mail agent - `GET /api/mail-agent/settings` — Get current mail agent settings - `POST /api/mail-agent/settings` — Toggle the mail agent enabled flag - `POST /api/mail-agent/stats/reset` — Reset mail agent statistics counters - `GET /api/mail-agent/status` — Get the mail agent connection status and stats - `POST /api/mail-agent/token/regenerate` — Regenerate the mail agent auth token ### Mail Agent / Update - `GET /api/mail-agent/update/check` — Check for the latest mail agent version (public) ### Mail Routing Rules - `GET /api/mail-routing-rules` — List all mail routing rules - `POST /api/mail-routing-rules` — Create a new mail routing rule - `GET /api/mail-routing-rules/{id}` — Get a mail routing rule by id - `PATCH /api/mail-routing-rules/{id}` — Update a mail routing rule - `DELETE /api/mail-routing-rules/{id}` — Delete a mail routing rule - `PATCH /api/mail-routing-rules/{id}/toggle` — Toggle the active flag of a mail routing rule - `POST /api/mail-routing-rules/test` — Dry-run a synthetic email against active routing rules ### Mail Transport - `GET /api/mail-transport/stats` — Transport-usage breakdown from mail_outbox.sent_via. Useful for spotting unused or broken transports (e.g. relay=0 implies the option can be disabled). ### Mailing - `GET /api/mailing/{taskId}/ab-test` — Get A/B test statistics for a mailing campaign - `GET /api/mailing/{taskId}/analytics` — Get per-domain analytics for a mailing campaign - `POST /api/mailing/{taskId}/approve` — Approve a pending mailing campaign (admin) - `GET /api/mailing/{taskId}/config` — Get the mailing configuration for a campaign - `PATCH /api/mailing/{taskId}/config` — Update the mailing configuration for a campaign - `POST /api/mailing/{taskId}/control-email-autopause/evaluate` — Manually evaluate control-email auto-pause rules for a campaign - `GET /api/mailing/{taskId}/control-email-stats` — Get control-email tracking stats for a mailing campaign - `GET /api/mailing/{taskId}/export.csv` — Export a mailing campaign as CSV - `GET /api/mailing/{taskId}/items` — List mailing items (recipients) for a campaign - `GET /api/mailing/{taskId}/items/{itemId}` — Get a single mailing item by id - `PATCH /api/mailing/{taskId}/items/{itemId}` — Update fields of a single mailing item - `PATCH /api/mailing/{taskId}/items/{itemId}/status` — Update the status of a single mailing item - `DELETE /api/mailing/{taskId}/logs` — Delete all logs for a mailing campaign (SUPER only) - `POST /api/mailing/{taskId}/reject` — Reject a pending mailing campaign (admin) - `GET /api/mailing/{taskId}/report.pdf` — Export a client-ready PDF report for a mailing campaign - `POST /api/mailing/{taskId}/reset-errors` — Reset all failed mailing items in a campaign back to pending - `POST /api/mailing/{taskId}/reset-limits` — Reset limit counters for a whole mailing: un-pause blocked accounts, KEEP block_events/stats (#289) - `POST /api/mailing/{taskId}/reset-status/{status}` — Reset all items with a given status back to pending (admin recovery only) - `GET /api/mailing/{taskId}/speed` — Get sending speed metrics for a mailing campaign - `POST /api/mailing/{taskId}/start` — Start a mailing campaign - `GET /api/mailing/{taskId}/start-list-stats` — Get start-list statistics for a mailing campaign - `POST /api/mailing/{taskId}/start-sending` — Issue #180: start sending an APPROVED campaign (status=APPROVED). A separate step after approval — launches streams → ACTIVE. 400 if not approved or the creative changed after approval (needs re-approval). - `GET /api/mailing/{taskId}/stats` — Get aggregate stats for a mailing campaign - `GET /api/mailing/{taskId}/stats/today` — Get today's stats for a mailing campaign - `GET /api/mailing/{taskId}/streams` — List sending streams for a mailing campaign - `GET /api/mailing/{taskId}/streams/{streamId}/detail` — Get detailed information for a specific mailing stream - `GET /api/mailing/{taskId}/streams/{streamId}/logs` — Get logs for a specific mailing stream - `GET /api/mailing/{taskId}/streams/{streamId}/logs-filtered` — Get filtered and paginated logs for a mailing stream - `GET /api/mailing/{taskId}/timeline` — Get the activity timeline for a mailing campaign - `POST /api/mailing/batch-stats` — Get stats for a batch of mailing tasks in one call - `GET /api/mailing/block-guard/account/{accountId}` — Get block-event history for an email account - `POST /api/mailing/block-guard/account/{accountId}/reset-limits` — Reset limit counters for one account: clear auto-pause + zero working counters, KEEP stats (#289) - `GET /api/mailing/block-guard/dashboard` — Get the BlockGuard dashboard (blocks, barked templates, paused accounts) - `GET /api/mailing/block-guard/providers` — Get block statistics aggregated per email provider - `GET /api/mailing/block-guard/template/{templateId}` — Get block-event history for a template - `POST /api/mailing/block-guard/template/{templateId}/unblock` — Manually unblock a previously barked template - `GET /api/mailing/bounce-overview` — Get global bounce statistics: legacy mailing tasks (per-task + per-domain) AND the campaign conveyor (#345) — campaignBounces.byNature splits bounces into hard/inactive (dead addresses), soft (retryable), policy/routing (route reputation, address alive), plus top bounced domains - `GET /api/mailing/creative-preview/{creativeId}` — Preview a creative for approval (cross-tenant for SUPER) - `GET /api/mailing/overview` — Get a global overview of all mailing campaigns (admin dashboard) — merges legacy mailing tasks AND the campaign conveyor (#539); each row is tagged source: legacy | campaign - `GET /api/mailing/pending-approvals` — List campaigns awaiting admin approval (cross-tenant for SUPER) - `GET /api/mailing/warmup-overview` — Get an overview of all email accounts currently in warmup mode ### Marking - `POST /api/marking/classify` — Classify a message against the marking patterns - `POST /api/marking/dialogs/{id}/classify` — Classify a specific dialog against the marking patterns - `GET /api/marking/patterns` — List all marking patterns - `POST /api/marking/patterns` — Create a new marking pattern - `DELETE /api/marking/patterns/{id}` — Delete a marking pattern - `PATCH /api/marking/patterns/{id}/toggle` — Enable or disable a marking pattern ### Nalog ID - `POST /api/nalog-id/lookup` — Look up Russian tax/company data (Nalog) by company info - `GET /api/nalog-id/test` — Debug/test endpoint for Nalog lookup via query parameters ### Names - `POST /api/names/decline` — Decline a Russian full name (FIO) by grammatical case - `GET /api/names/ping` — Ping the names service (health check) ### Notifications - `GET /api/notifications` — List notifications for the current user - `POST /api/notifications/{id}/dismiss` — Dismiss a notification (hide from widget) - `POST /api/notifications/{id}/read` — Mark a notification as read - `POST /api/notifications/dismiss-all` — Dismiss all notifications for the current user - `GET /api/notifications/push/public-key` — Get the tenant VAPID public key for Web Push - `POST /api/notifications/push/subscribe` — Register a browser Web Push subscription - `POST /api/notifications/push/unsubscribe` — Remove a browser Web Push subscription - `POST /api/notifications/read-all` — Mark all unread notifications as read - `POST /api/notifications/self-note` — Create an agent self-note notification - `GET /api/notifications/unread-count` — Get the unread notifications badge count ### OAuth - `POST /api/oauth/{accountId}/refresh` — Manually refresh the OAuth access token for an account - `POST /api/oauth/{accountId}/revoke` — Revoke OAuth tokens for an account - `GET /api/oauth/{accountId}/status` — Get OAuth connection status for an account - `GET /api/oauth/auth-url/{accountId}` — Get the OAuth consent URL for an email account - `GET /api/oauth/callback` — OAuth provider callback endpoint (public, used by provider redirect) - `GET /api/oauth/connect` — Public OAuth connect page for anti-detect browser flow - `GET /api/oauth/connect/{accountId}` — Public OAuth consent redirect for a specific account ### Observability / API Monitor - `GET /api/admin/api-monitor` — List API request log entries with filters (SUPER only) - `GET /api/admin/api-monitor/aggregates` — Get aggregated API monitor metrics (RPS, latency percentiles, top paths) - `GET /api/admin/api-monitor/mcp/keys` — Per-bearer-key usage — count, error rate, top-3 tools per key. SUPER only. - `GET /api/admin/api-monitor/mcp/timeseries` — MCP requests time series bucketed by N minutes (default 60). Optional toolName filter for per-tool charts. - `GET /api/admin/api-monitor/mcp/tools` — MCP tools usage breakdown — per-tool counts, latency p95, error rate. SUPER only. Used by the MCP tab on the admin API monitor page. - `GET /api/me/api-monitor/calls` — Read your own API/MCP call journal — time, MCP tool name, method, path, HTTP status, duration, error, resourceId (object a create call produced), costUsd (mirrors _billing.cost) and correlationId. Scoped to your own tenant; foreign tenantId cannot be requested. #1212: the SDK's own /api-keys/whoami preflight before every tool call is hidden by default — pass includeInternal=true to see it. ### Observability / API Tests - `GET /api/admin/api-tests/catalog` — List the catalog of synthetic API test suites - `POST /api/admin/api-tests/run` — Run a synthetic API test suite (SUPER only) - `GET /api/admin/api-tests/runs` — List recent synthetic API test runs - `GET /api/admin/api-tests/runs/{id}` — Get details of a single synthetic API test run ### Phantom Browser - `GET /api/phantom-browser/admin/stats` — Get Phantom browser admin stats (SUPER only) - `GET /api/phantom-browser/client/status` — Get the Phantom desktop client connection status - `POST /api/phantom-browser/client/token/regenerate` — Regenerate the Phantom client auth token - `GET /api/phantom-browser/download/config` — Download the Phantom desktop client config.json with the user's token - `GET /api/phantom-browser/launches` — List Phantom browser profile launches - `GET /api/phantom-browser/profiles` — List all Phantom browser profiles - `POST /api/phantom-browser/profiles` — Create a new Phantom browser profile - `GET /api/phantom-browser/profiles/{id}` — Get a single Phantom browser profile by id - `PUT /api/phantom-browser/profiles/{id}` — Update a Phantom browser profile - `DELETE /api/phantom-browser/profiles/{id}` — Delete a Phantom browser profile - `POST /api/phantom-browser/profiles/{id}/click` — Click an element in the Phantom browser by CSS selector - `GET /api/phantom-browser/profiles/{id}/cookies` — Get cookies for the current Phantom browser page - `POST /api/phantom-browser/profiles/{id}/cookies` — Set cookies on the current Phantom browser page — lets the worker browse a site already logged-in. Accepts the Cookie-Editor / EditThisCookie export format directly (or the plain CDP shape); the server normalizes it (expirationDate→expires, sameSite no_restriction→None, drops hostOnly/session/storeId). Example body: { "cookies": [ { "domain": ".linkedin.com", "name": "li_at", "value": "AQ...", "path": "/", "expirationDate": 1814817974.7, "secure": true, "httpOnly": true, "sameSite": "no_restriction" } ] } - `POST /api/phantom-browser/profiles/{id}/execute` — Execute JavaScript in the Phantom browser page context - `GET /api/phantom-browser/profiles/{id}/html` — Get the current Phantom browser page's HTML - `POST /api/phantom-browser/profiles/{id}/launch` — Launch a profile on the user's Phantom desktop client - `POST /api/phantom-browser/profiles/{id}/navigate` — Navigate the Phantom browser profile to a URL - `GET /api/phantom-browser/profiles/{id}/page-info` — Get the current Phantom browser page URL and title - `PUT /api/phantom-browser/profiles/{id}/proxy` — Set the proxy configuration on a Phantom browser profile - `DELETE /api/phantom-browser/profiles/{id}/proxy` — Remove the proxy configuration from a Phantom browser profile - `POST /api/phantom-browser/profiles/{id}/proxy/test` — Test a Phantom browser profile proxy (launch and report IP) - `POST /api/phantom-browser/profiles/{id}/screenshot` — Take a screenshot in the Phantom browser profile (base64 JPEG) - `GET /api/phantom-browser/profiles/{id}/screenshot.jpg` — Get the latest Phantom browser screenshot as a JPEG image - `POST /api/phantom-browser/profiles/{id}/scroll` — Scroll the current Phantom browser page - `POST /api/phantom-browser/profiles/{id}/solve-captcha` — Solve a captcha on the current Phantom browser page (via RuCaptcha) - `POST /api/phantom-browser/profiles/{id}/stop` — Stop a running profile on the Phantom desktop client - `POST /api/phantom-browser/profiles/{id}/type` — Type text into a focused element or selector in the Phantom browser - `POST /api/phantom-browser/profiles/{id}/wait` — Wait for a selector to appear in the Phantom browser page - `POST /api/phantom-browser/profiles/sync` — Re-send the profiles list to the connected Phantom client - `GET /api/phantom-browser/scenarios` — List Phantom browser scenarios - `POST /api/phantom-browser/scenarios` — Create a new Phantom browser scenario - `GET /api/phantom-browser/scenarios/{id}` — Get a single Phantom browser scenario by id - `PUT /api/phantom-browser/scenarios/{id}` — Update a Phantom browser scenario - `DELETE /api/phantom-browser/scenarios/{id}` — Delete a Phantom browser scenario - `GET /api/phantom-browser/schedules` — List Phantom browser schedules - `POST /api/phantom-browser/schedules` — Create a new Phantom browser schedule - `PUT /api/phantom-browser/schedules/{id}` — Update a Phantom browser schedule - `DELETE /api/phantom-browser/schedules/{id}` — Delete a Phantom browser schedule ### Phantom Browser / Download - `GET /api/phantom-browser/download/{platform}` — Download the Phantom client binary for a given platform - `POST /api/phantom-browser/logs` — Receive logs from the Phantom desktop client - `POST /api/phantom-browser/sync` — Sync Phantom desktop client profiles using its token - `GET /api/phantom-browser/update/bundle` — Download the Phantom client update bundle (gzipped source files) - `GET /api/phantom-browser/update/check` — Check for available Phantom client updates ### Pipeline Events - `POST /api/pipeline/events` — Ingest a conveyor event from the orchestrator host (upsert task/step, append log, file snapshot) - `GET /api/pipeline/files/{name}` — Snapshot of a conveyor service file (GATES.md/questions.md/TEMPLATE.md) - `GET /api/pipeline/speed` — "Speed" block: lead time 7/30d, per-stage breakdown, throughput, WIP (issue #1009) - `GET /api/pipeline/speed/stage/{name}/tasks` — Tasks in a specific stage, sorted by time spent in it (issue #1009, click on the stage bar) - `GET /api/pipeline/steps/{id}/logs` — Conveyor step logs (pagination, total is a separate count, without all row bodies) - `GET /api/pipeline/summary` — Task counters by status + how many reached prod per hour/shift (issue #998) - `GET /api/pipeline/tasks` — List of conveyor tasks (status filter, pagination) - `GET /api/pipeline/tasks/{id}` — A conveyor task together with its steps - `GET /api/pipeline/tasks/{id}/stage-timeline` — Per-stage breakdown of one task (issue #998): queued→in_progress→review→…→prod ### Pipelines - `GET /api/pipelines` — List all pipelines - `POST /api/pipelines` — Create a new pipeline - `GET /api/pipelines/{id}` — Get a single pipeline by ID - `PATCH /api/pipelines/{id}` — Update an existing pipeline - `DELETE /api/pipelines/{id}` — Delete a pipeline - `POST /api/pipelines/{id}/migrate-legacy-stages` — Migrate legacy denormalized stage JSON into PipelineStage rows - `POST /api/pipelines/{id}/stages` — Create a new stage in a pipeline - `GET /api/pipelines/{id}/stats` — Get stats and conversion metrics for a pipeline - `PATCH /api/pipelines/{pipelineId}/stages/{stageId}` — Update a pipeline stage - `DELETE /api/pipelines/{pipelineId}/stages/{stageId}` — Delete a pipeline stage - `PATCH /api/pipelines/{pipelineId}/stages/reorder` — Reorder pipeline stages - `GET /api/pipelines/automation-logs` — Get global automation logs across pipelines - `PATCH /api/pipelines/automations/{automationId}` — Update a stage automation - `DELETE /api/pipelines/automations/{automationId}` — Delete a stage automation - `GET /api/pipelines/automations/{automationId}/logs` — Get execution logs for an automation - `GET /api/pipelines/stages/{stageId}/automations` — List automations attached to a stage - `POST /api/pipelines/stages/{stageId}/automations` — Create an automation on a stage ### Profi Orders - `GET /api/profi-orders` — Profi.ru order feed (pagination, keyword filter) - `POST /api/profi-orders/ingest` — Receive orders from the external Profi.ru monitor - `GET /api/profi-orders/status` — Monitor status: last ingest and per-keyword counters ### Project Templates - `GET /api/project-templates` — List project templates - `POST /api/project-templates` — Create a project template - `GET /api/project-templates/{id}` — Get a template with steps and runs - `PATCH /api/project-templates/{id}` — Update a template (name, description, isArchived) - `DELETE /api/project-templates/{id}` — Delete a template (409 if it has runs — archive instead) - `GET /api/project-templates/{id}/brief-questions` — All brief questions of a template as one list - `POST /api/project-templates/{id}/runs` — Create a run from a template (steps are snapshotted) - `POST /api/project-templates/{id}/steps` — Add a step to a template - `PATCH /api/project-templates/{id}/steps/{stepId}` — Update a template step - `DELETE /api/project-templates/{id}/steps/{stepId}` — Delete a template step - `POST /api/project-templates/{id}/steps/reorder` — Reorder template steps (full ordered id list) - `GET /api/project-templates/{id}/summary` — Plan vs avg fact per step over DONE runs (scope: all|test|real) - `GET /api/project-templates/runs/{runId}` — Get a project run with steps and totals - `PATCH /api/project-templates/runs/{runId}` — Update a project run (name, status, isTest, notes) - `DELETE /api/project-templates/runs/{runId}` — Delete a project run - `PATCH /api/project-templates/runs/{runId}/steps/{stepId}` — Update a run step (fact, status, issues/questions/ideas) ### Prompt Templates - `GET /api/prompt-templates` — List prompt templates with optional filters - `POST /api/prompt-templates` — Create a new prompt template - `GET /api/prompt-templates/{id}` — Get a single prompt template by id - `PATCH /api/prompt-templates/{id}` — Update a prompt template - `DELETE /api/prompt-templates/{id}` — Delete a prompt template - `POST /api/prompt-templates/{idOrName}/render` — Render a prompt template (by id or name) with caller variables ### Provider Config - `GET /api/provider-configs` — List all email provider configurations - `GET /api/provider-configs/{provider}` — Get a single provider configuration by provider key - `PUT /api/provider-configs/{provider}` — Upsert a provider configuration - `DELETE /api/provider-configs/{provider}` — Delete a provider configuration - `POST /api/provider-configs/{provider}/domains` — Add a domain mapping to a provider configuration - `DELETE /api/provider-configs/{provider}/domains/{pattern}` — Remove a domain mapping from a provider configuration - `PATCH /api/provider-configs/{provider}/toggle` — Enable or disable a provider configuration - `GET /api/provider-configs/resolve` — Resolve the email provider by domain ### Public - `GET /api/public/pricing` — Public platform pricing (USD): operations (inbox_placement / inbox_placement_byoc / creative_approval / ...) + starting balance on signup (signupCreditUsd). No authorization required; tenant overrides do not apply. ### RPA - `POST /api/rpa/job-report` — Report the result of an RPA send job - `GET /api/rpa/job-status` — Poll RPA job status until ready - `GET /api/rpa/poll` — Poll for available RPA work (queued jobs or unprocessed contacts) - `POST /api/rpa/report` — Legacy: submit a batch report of RPA item send results - `POST /api/rpa/system-report` — Report a process-level RPA error not tied to a job - `GET /api/rpa/system-status` — Get RPA system health: errors, paused mailings, account stats - `GET /api/rpa/task-status` — Legacy: get mailing-level RPA status with item counts ### RPA service protocol - `GET /api/rpa/v1/attachments/{token}` — Download a lease-bound signed attachment with its original filename; legacy token-only URLs remain supported - `GET /api/rpa/v1/attachments/{token}/{filename}` — Download a lease-bound signed attachment with its original filename; legacy token-only URLs remain supported - `POST /api/rpa/v1/claim` — Poll preparation: task:null while validating; placement-gated tasks begin CONTROL-only and receive REAL only after server evidence. Deduplicate by idempotencyKey. - `POST /api/rpa/v1/current` — Read the same task and lease after a CONTROL report or server placement poll; never creates a second claim. - `POST /api/rpa/v1/error` — Report a deduplicated execution error; obey the returned action, never replay sending after an uncertain outcome - `POST /api/rpa/v1/heartbeat` — Service heartbeat; include the complete task lease identity to extend a live CLAIMED/STARTED/CONTROL_REPORTED/REAL_READY task by 60 minutes. Stop at the last acknowledged expiresAt. - `POST /api/rpa/v1/placement` — Attach late seed-copy placement evidence; never evidence of recipient inbox placement - `POST /api/rpa/v1/profile-release` — Optional terminal-task browser-close acknowledgement. CONTROL/placement/REAL continuation never releases the profile. Without this acknowledgement legacy reports remain valid and ownership lasts until the last issued expiresAt. Not permission to resend. - `GET /api/rpa/v1/readiness` — Read scoped queue and campaign metadata without claiming, preparing or authorizing sending; follow pagination for a complete snapshot - `POST /api/rpa/v1/reconcile` — Resolve an uncertain execution using positive sent evidence or retained executor proof of no send. Not a blind retry or a lease renewal. - `POST /api/rpa/v1/reconciliation` — Read this worker’s unresolved tasks, up to 20 per authorized target. Never permission to resend. Resolve items then read again. - `POST /api/rpa/v1/refresh` — Refresh HTTPS attachment links and scoped relay context without changing canonical envelope bytes - `POST /api/rpa/v1/renew` — Extend this worker’s live CLAIMED/STARTED/CONTROL_REPORTED/REAL_READY lease by 60 minutes; never revive an expired or terminal task - `POST /api/rpa/v1/report` — Report one immutable CONTROL or REAL operation. Gated tasks must echo operationId, taskVersion and contentFingerprint; legacy three-field reports remain for one-step tasks. - `POST /api/rpa/v1/return` — Return an execution known not to have sent. Keeps the same task, retries through canonical gates within its attempt budget. - `POST /api/rpa/v1/start` — Revalidate canonical send gates for the claimed operationId; send only when sendEnabled=true. CONTROL and REAL use different operation idempotency keys on the same task. ### RUM - `POST /api/rum/vitals` — Accept a batch of Core Web Vitals from an anonymous client (sendBeacon). Public, no authorization. ### Reports - `GET /api/reports` — List tenant reports (paginated) + storage quota - `POST /api/reports` — Create a report from base64 content (agent/MCP path) - `GET /api/reports/{id}` — Get one report - `PATCH /api/reports/{id}` — Update report title/description - `DELETE /api/reports/{id}` — Delete a report (file + record) - `GET /api/reports/{id}/download` — Download the report file (authorized) - `POST /api/reports/{id}/publish` — Publish: open a direct public download link (no auth!) - `POST /api/reports/{id}/rotate-token` — Rotate the public token — old link dies immediately - `POST /api/reports/{id}/unpublish` — Unpublish: close the public link (token kept — re-publish restores the same url) - `POST /api/reports/from-export/{exportJobId}` — Register a completed export job as a report (idempotent) - `GET /api/reports/limits` — Reports storage limits and current usage - `POST /api/reports/upload` — Upload a report file (multipart, UI path) ### Rules / Creative - `GET /api/creatives/{id}/rules` — Get the creative rules document - `PATCH /api/creatives/{id}/rules` — Patch the creative rules document - `GET /api/creatives/{id}/rules/context` — Get supporting context used to evaluate creative rules - `GET /api/creatives/{id}/rules/history` — Get audit history of creative rule changes - `GET /api/creatives/{id}/rules/schema` — Get the JSON Schema for creative rules ### Rules / Creative Bootstrap - `GET /api/creatives/{id}/bootstrap` — Get unified creative bootstrap snapshot for page render - `PATCH /api/creatives/{id}/meta` — Update creative meta fields (non-rule) ### Rules / Discovery - `GET /api/contract` — Get the agent contract version and core principles - `GET /api/discovery` — Get agent discovery document with resources and endpoints ### Rules / Sub-resource - `GET /api/creatives/{id}/rules-sub` — List available autopilot sub-resource keys - `GET /api/creatives/{id}/rules/{sub}` — Get an autopilot sub-resource rules document - `PATCH /api/creatives/{id}/rules/{sub}` — Patch an autopilot sub-resource rules document - `GET /api/creatives/{id}/rules/{sub}/history` — Get audit history for an autopilot sub-resource - `GET /api/creatives/{id}/rules/{sub}/schema` — Get the JSON Schema for an autopilot sub-resource ### SEO Index - `GET /api/seo-index/coverage` — URLs in the sitemap versus the number of pages with impressions - `GET /api/seo-index/history` — Daily series of clicks/impressions/ctr/position for charts - `GET /api/seo-index/index-coverage` — Total sitemap page count per surface (landing/blog/check/email-stats); index status is unknown until URL Inspection (#770) is wired — deprecated GSC sitemap.indexed is not reported as a page count - `GET /api/seo-index/indexnow` — IndexNow ping status: whether a key is configured, time/mode/URL count of the last ping - `POST /api/seo-index/indexnow` — Force IndexNow ping — pings the ENTIRE sitemap list (not just the diff from the cron run). Use after bulk sitemap edits, when waiting for the next cron tick (12h) is not an option. - `GET /api/seo-index/overview` — Summary of clicks/impressions/CTR/position over 7/28/90d with delta vs the previous period - `GET /api/seo-index/pages` — Top pages by clicks (snapshot, filter by section, pagination) - `GET /api/seo-index/queries` — Top search queries by clicks (snapshot, pagination) - `GET /api/seo-index/segments` — Breakdown of metrics by site sections - `GET /api/seo-index/sitemaps` — Sitemap status: type, upload date, submitted/indexed, errors - `GET /api/seo-index/yandex/overview` — Yandex.Webmaster: clicks/impressions/position with delta + Yandex site quality index (ICS) and pages in search per host - `GET /api/seo-index/yandex/queries` — Yandex.Webmaster: top search queries (snapshot, pagination) - `GET /api/seo-index/yandex/sitemaps` — Yandex.Webmaster: sitemaps — type, URL, errors, last crawl date ### Saved Views - `GET /api/saved-views` — List saved views for an entity for the current user - `POST /api/saved-views` — Create a saved view - `PATCH /api/saved-views/{id}` — Update a saved view (only the owner may edit it) - `DELETE /api/saved-views/{id}` — Delete a saved view (only the owner may delete it) ### Search - `GET /api/search` — Universal cross-entity search by free-text query. Returns matching contacts, companies, and leads in a single response. ### Sequences - `GET /api/sequences` — List email sequences - `POST /api/sequences` — Create an email sequence - `GET /api/sequences/{id}` — Get a sequence by id - `PATCH /api/sequences/{id}` — Update a sequence - `DELETE /api/sequences/{id}` — Delete a sequence - `POST /api/sequences/{id}/enroll` — Enroll contacts into a sequence - `GET /api/sequences/{id}/enrollments` — List enrollments for a sequence - `POST /api/sequences/{id}/steps` — Add a step to a sequence - `PATCH /api/sequences/{id}/steps/{stepId}` — Update a sequence step - `DELETE /api/sequences/{id}/steps/{stepId}` — Delete a sequence step - `GET /api/sequences/{id}/steps/{stepId}/variants` — List A/B variants on a step - `POST /api/sequences/{id}/steps/{stepId}/variants` — Add an A/B variant to a step - `PATCH /api/sequences/{id}/steps/{stepId}/variants/{variantId}` — Update an A/B variant - `DELETE /api/sequences/{id}/steps/{stepId}/variants/{variantId}` — Delete an A/B variant - `GET /api/sequences/{id}/steps/{stepId}/variants/stats` — Get A/B variant statistics for a step - `POST /api/sequences/enrollments/{eid}/cancel` — Cancel an enrollment - `POST /api/sequences/enrollments/{eid}/pause` — Pause an enrollment - `POST /api/sequences/enrollments/{eid}/resume` — Resume an enrollment ### Settings - `GET /api/settings` — Get the full workspace settings document - `POST /api/settings/account/deactivate` — Deactivate the tenant account (irreversible self-service action). Confirmation = workspace name or the initiator's email, not a bare "OK". Data is retained; final purge stays admin-scheduled and out of scope here. - `GET /api/settings/account/deactivation-preview` — Preview before account deactivation: confirmation phrase + blockers (active campaigns) - `PATCH /api/settings/ai-providers` — Update AI provider keys and defaults - `GET /api/settings/auditor-rules` — Get workspace send window (#194): { enabled, workDays[1-7], startTime, endTime, timezone } or null (24/7) - `PATCH /api/settings/auditor-rules` — Partial PATCH of the tenant auditor canon (#100): { minScore?, tier2?: {enabled?, model?}, rules?: [{id,phrase,severity?,message?,weight?,scope?,enabled?}] }. rules — if sent, replaces the list ENTIRELY (send the whole list, not just the new item). Fields/groups not sent are left untouched — seeded from the current effective document, not the schema defaults. SUPER only. - `GET /api/settings/crm-event-rules` — Get CRM event → notification bridge rules - `PATCH /api/settings/crm-event-rules` — Update CRM event → notification bridge rules - `DELETE /api/settings/delete-data` — Delete all tenant data (irreversible danger-zone action) - `GET /api/settings/integrations/bo-nalog` — Get bo.nalog.gov.ru integration state: enabled, our rate limit, requests today, last error - `PATCH /api/settings/integrations/bo-nalog` — Update bo.nalog.gov.ru integration: enable/disable, requests-per-minute cap - `GET /api/settings/integrations/dadata` — Get DaData integration configuration - `PATCH /api/settings/integrations/dadata` — Update DaData integration configuration - `POST /api/settings/integrations/dadata/reset-counter` — Reset the DaData daily request counter - `GET /api/settings/integrations/inbox-check` — Get Inbox Check integration configuration - `PATCH /api/settings/integrations/inbox-check` — Update Inbox Check integration configuration - `POST /api/settings/integrations/inbox-check/test` — Test the Inbox Check connection - `GET /api/settings/integrations/smartlead` — Get Smartlead integration configuration - `PATCH /api/settings/integrations/smartlead` — Update Smartlead integration configuration - `GET /api/settings/integrations/smartlead/placement-history` — Smartlead placement history for one mailbox × provider cell (#1040) - `GET /api/settings/integrations/smartlead/placement-matrix` — Smartlead placement matrix — mailbox × provider (#1040) - `POST /api/settings/integrations/smartlead/test` — Test the Smartlead connection - `POST /api/settings/ldm-ai/request` — Request LDM AI access / balance increase (tenant → admin) - `GET /api/settings/mailing-auto-approve` — List users with mailing auto-approve enabled (SUPER only). Pass userId to resolve the TARGET USER tenant instead of the calling SUPER session tenant. - `GET /api/settings/mailing-auto-approve-tenant` — Get tenant-level mailing auto-approve — whether the WHOLE workspace sends without manual approval (SUPER only). - `PATCH /api/settings/mailing-auto-approve-tenant` — Set tenant-level mailing auto-approve. enabled=true → the whole workspace sends immediately after QC, no manual admin approval (overrides per-user). enabled=false → manual approval (or per-user trusted senders). SUPER only. - `GET /api/settings/memory-notes` — Get the current user memory notes for AI features - `PATCH /api/settings/memory-notes` — Update the current user memory notes - `GET /api/settings/min-send-interval` — Get workspace default min send interval per account, seconds (#194). 0 = off - `PATCH /api/settings/min-send-interval` — Set workspace default min send interval per account, seconds 0..86400 (#194). Per-account override lives on emailAccount.minSendIntervalSeconds. SUPER only. - `PATCH /api/settings/notifications` — Update notification preferences (push and email) - `GET /api/settings/notify-rules` — Get the notification routing rules - `PATCH /api/settings/notify-rules` — Update the notification routing rules - `GET /api/settings/platform-tracking-allowed` — Get whether SUPER allowed this tenant to serve the open-pixel via the SHARED platform domain (pixelSource=ldm). SUPER only. (#237) - `PATCH /api/settings/platform-tracking-allowed` — Allow/deny this tenant to serve the open-pixel via the SHARED platform domain (pixelSource=ldm). Default false — anti-spam gate so an unapproved tenant cannot burn the shared domain. SUPER only. (#237) - `GET /api/settings/profile` — Get the current user profile - `PATCH /api/settings/profile` — Update the current user profile - `POST /api/settings/profile/avatar` — Upload a new avatar for the current user - `DELETE /api/settings/profile/avatar` — Delete the current user avatar - `GET /api/settings/relay-health` — Relay health thresholds of the whole workspace (#337): block-guard, bounce auto-pause, relay cooldowns, limit policy and ERROR re-probe (#356: backoff and when a dead relay is escalated for manual review). These are the DEFAULTS every campaign inherits — a campaign senderConfig only overrides them. Returns effective values, stored overrides and code defaults - `PATCH /api/settings/relay-health` — Set workspace relay health defaults (#337). Partial patch: send only the groups you change (blockGuard, bounce, cooldownSec, limits, probe, inbound). Values are clamped to safe bounds. inbound.requireChannel=true (#357) stops relays without a working receive channel from being picked for sending at all. SUPER only - `POST /api/settings/security/change-password` — Change the current user password - `GET /api/settings/security/sessions` — List active sessions for the current user - `DELETE /api/settings/security/sessions` — Revoke all active sessions for the current user - `DELETE /api/settings/security/sessions/{sessionId}` — Revoke a single user session - `GET /api/settings/send-window` - `PATCH /api/settings/send-window` — Set workspace send window (#194). enabled=false → 24/7 (default). SUPER only. - `GET /api/settings/transports` — Get enabled mail transport flags - `PATCH /api/settings/transports` — Update enabled mail transport flags - `GET /api/settings/trash` — Trash counters: soft-deleted dialogs/leads/contacts/companies awaiting restore or purge - `POST /api/settings/trash/empty` — Empty trash — PERMANENTLY delete soft-deleted records. Body: {confirmation:"EMPTY TRASH", entities?:["dialogs"|"leads"|"contacts"|"companies"]} (default: all). Irreversible. - `GET /api/settings/ui-preferences` — Get current user UI preferences (dashboard blocks, left menu) - `PATCH /api/settings/ui-preferences` — Replace current user UI preferences - `PATCH /api/settings/users/{userId}/mailing-auto-approve` — Enable/disable mailing auto-approve for a user (SUPER only). - `PATCH /api/settings/workspace` — Update workspace metadata (name, currency, language) ### Share Links - `POST /api/share-links` — Create a share link for an entity - `GET /api/share-links` — List share links for an entity - `DELETE /api/share-links/{id}` — Revoke a share link ### Share Public - `GET /api/s/{code}` — Get publicly-shared entity by short code (no auth) - `GET /api/share-og/{code}` — Render OG meta-tag HTML for bot crawlers by short code - `GET /api/share-public/{token}` — Get publicly-shared entity by legacy full token (no auth) ### Site Enrichment - `GET /api/site-enrichment/backlog/down-wait-timeout` — #1126: count and ids of tenant cards where customFields.site_status='down' AND customFields.site_last_reason is one of a NARROW list of transient failures of OUR OWN infrastructure (currently only 'wait_timeout'), AND customFields.site_status_http is empty (navigation never reached a response). NOT cards with dns_error/tls_error/conn_refused — those are responses FROM THE WORLD about the site, not a failure of our attempt, and are deliberately excluded (see SiteEnrichmentService.TRANSIENT_DOWN_REASONS). The condition is live — a card that has already actually been recrawled drops out of the list by itself. - `POST /api/site-enrichment/backlog/down-wait-timeout/requeue` — #1126: a controlled BATCH re-crawl of cards from backlog/down-wait-timeout (batchSize, defaults to 50, cap 200) — via the same create()/run() as a normal task enqueue; not a separate crawl engine, only a real crawl changes a card's status. - `GET /api/site-enrichment/backlog/stale-http-status` — #969 backfill: count and ids of tenant cards where customFields.site_status='ok' but a real crawl HTTP code was never recorded (customFields.site_status_http is empty). The condition is live (the same as the migration itself), NOT the write-once flag site_needs_recrawl_969 — a card that has already actually been recrawled drops out of the list by itself, with no separate cleanup step. Not retroactive code guessing. - `POST /api/site-enrichment/backlog/stale-http-status/requeue` — #969 backfill: a controlled BATCH re-crawl of flagged cards (batchSize, defaults to 50, cap 200) — via the same create()/run() as a normal task enqueue; not a separate crawl engine. - `POST /api/site-enrichment/detect-forms` — #968 detect forms on a page from HTML: hasForm — whether any form is present at all; hasContactForm — a CONTACT-US form (excluding search/login/subscribe/catalog filter); forms[] — submit address (absolute if pageUrl is supplied), method, kind (contact|search|login|subscribe|catalog_filter|other), confidence (0..1), and reasons[] — a human-readable basis for the decision. - `POST /api/site-enrichment/tasks` — #903/#889 enqueue the canonical site-crawl task (durable, DB is the source of truth) - `GET /api/site-enrichment/tasks` — #953 list of site-crawl tasks: both linked to a scoring task (scoringTaskId) and unlinked (owned=false — e.g. launched via MCP without a task) - `GET /api/site-enrichment/tasks/{id}` — Status/counters of a site-crawl task - `POST /api/site-enrichment/tasks/{id}/cancel` — Cancel a site-crawl task (durable — status in the DB, visible to any process) - `GET /api/site-enrichment/tasks/{id}/events` — #903 task transition log, read from the DB, cursor=last received seq - `POST /api/site-enrichment/tasks/{id}/events` — #1144 atom B: an agent note into the task journal (kind=agent_note, author taken from the request key/JWT, NOT from the body). Written through the SINGLE existing journal writer (SiteEnrichmentService.appendEvent) — there is no second one. Does NOT touch the task's updatedAt/status/processed/counters and does not count towards MAX_RESUME_ATTEMPTS: an observer note must not affect the observed thing (otherwise it would delay the watchdog's staleness pickup). The body is ONLY {text}; the system kind cannot be supplied from outside (forbidNonWhitelisted → 400 before the controller, see CreateAgentNoteDto). - `GET /api/site-enrichment/tasks/{id}/items` — #952/#969 crawl task items: paginated, with a filter by outcome (status=OK|DOWN|NO_SITE|EMPTY|CAPTCHA) and/or reason code (reasonCode) - `POST /api/site-enrichment/tasks/{id}/pause` — #952 pause a crawl task (durable, non-terminal — resumed via .../resume) - `GET /api/site-enrichment/tasks/{id}/raw` — #965 diagnostic snapshot of the RAW visited page text(s) for ONE company (homepage + "Contacts", BEFORE contact extraction) — TTL 12h, only while diagnostic mode is on; an empty data is legitimate, not an error - `GET /api/site-enrichment/tasks/{id}/reason-summary` — #949 summary of failure reasons (for a clickable filter above the journal) - `POST /api/site-enrichment/tasks/{id}/resume` — #952 resume a paused crawl task - `POST /api/site-enrichment/tasks/{id}/retry` — Retry still-not-OK companies of a terminal task (FAILED/PARTIAL/CANCELLED) - `GET /api/site-enrichment/tasks/{id}/stats` — #957/#969 unified task statistics read from durable rows: funnel (cards AND domains counted separately, including sourceTotal/visitsSaved from dedup), collected data (substrate-writer receipt — emails/phones/socials/text/native fields, per-map and total), acquisition method, pace/forecast state by domain (plus elapsedSeconds/cardsPerHour/etaAt), breakdown by reason (byReason), breakdown by outcome (byStatus: OK/DOWN/NO_SITE/EMPTY/CAPTCHA) and actual homepage HTTP codes (byHttpStatus, httpStatus:null — no response), launch (launch parameters echoed back — listId/listName/domainField/visitPages/timeoutMs/maxAttempts/mode/surface/startedBy), and links (direct URLs to the scoring-task card and this task's journal) — WITHOUT the full result list (use tasks/:id/items for paginated inspection). The same method serves the legacy alias GET /icp-tasks/companies/scrape/:taskId (stats key) — the only route that actually reaches MCP. - `GET /api/site-enrichment/tasks/preview` — #955 preview list coverage WITHOUT starting a crawl: how many cards are in the list, how many have the domain field filled, how many unique domains among them — plus coverage of ALL eligible fields at once (website/domain/URL custom fields) ### Stop List - `GET /api/stop-lists` — List stop list entries (user scope) - `POST /api/stop-lists` — Add a stop list entry (user scope) - `DELETE /api/stop-lists/{id}` — Remove a stop list entry (user scope) - `POST /api/stop-lists/bulk` — Bulk-add stop list entries (user scope) - `POST /api/stop-lists/check` — Check a batch of emails against the stop list - `GET /api/stop-lists/global` — List global stop list entries (SUPER only) - `POST /api/stop-lists/global` — Add a global stop list entry (SUPER only) - `DELETE /api/stop-lists/global/{id}` — Remove a global stop list entry (SUPER only) - `POST /api/stop-lists/global/bulk` — Bulk-add global stop list entries (SUPER only) - `GET /api/stop-lists/global/stats` — Get global stop list statistics (SUPER only) - `GET /api/stop-lists/stats` — Get stop list statistics (user scope) ### Support - `GET /api/support/attachments/{id}` — Serve a support attachment (thread owner or SUPER only) - `POST /api/support/messages` — Send a message to support (optional JPG/PNG attachments) - `POST /api/support/read` — Mark support replies as read - `POST /api/support/status` — Close or reopen own support ticket - `GET /api/support/thread` — Get own support thread with message history - `GET /api/support/unread-count` — Unread support messages badge count ### Suppression - `GET /api/suppression` — List suppressed emails - `POST /api/suppression` — Add an email to the suppression list - `DELETE /api/suppression/{email}` — Remove an email from the suppression list - `POST /api/suppression/bulk` — Bulk-add emails to the suppression list - `POST /api/suppression/check` — Check a batch of emails against the suppression list - `GET /api/suppression/domains` — List suppressed domains - `POST /api/suppression/domains` — Add a domain to the suppression list - `DELETE /api/suppression/domains/{domain}` — Remove a domain from the suppression list ### System - `GET /api/system/operator` — Operator (legal entity) details from config — null fields mean "not filled by owner" (public) - `GET /api/system/version` — Deployed build version: app semver + commit SHA + build timestamp (public) ### SystemBriefIntake - `POST /api/internal/brief-intake` - `GET /api/internal/brief-intake/limits` ### Tags - `GET /api/tags` — List all tags in the workspace - `POST /api/tags` — Create a new tag - `PATCH /api/tags/{id}` — Update an existing tag - `DELETE /api/tags/{id}` — Delete a tag - `POST /api/tags/companies/{companyId}` — Attach a tag to a company - `DELETE /api/tags/companies/{companyId}/{tagId}` — Detach a tag from a company - `POST /api/tags/contacts/{contactId}` — Attach a tag to a contact - `DELETE /api/tags/contacts/{contactId}/{tagId}` — Detach a tag from a contact - `POST /api/tags/leads/{leadId}` — Attach a tag to a lead - `DELETE /api/tags/leads/{leadId}/{tagId}` — Detach a tag from a lead ### Tasks - `GET /api/tasks` — List tasks with pagination and status/method filters - `POST /api/tasks` — Create a task. SITE SCORING (lead gen): methodId=22 + methodName="scoring" + companyListId (items are populated from the company list on start) → POST /tasks/:id/steps (SITE_AVAILABILITY, then AI_CONTENT) → POST /tasks/:id/start. Other methodId values: 11=email validation, 14=ICP review, 15=DIG, 12=mailing (legacy — prefer /campaigns). - `GET /api/tasks/{id}` — Get a single task by ID - `PATCH /api/tasks/{id}` — Update task description, configuration, or schedule - `DELETE /api/tasks/{id}` — Delete a task - `GET /api/tasks/{id}/ai-settings` — Get the AI provider/model settings for a task - `PATCH /api/tasks/{id}/ai-settings` — Update the AI provider/model settings for a task - `GET /api/tasks/{id}/analytics` — Get analytics for a task (open/click/reply rates) - `GET /api/tasks/{id}/branches` — List sub-sequence branches of a task - `POST /api/tasks/{id}/branches` — Create a sub-sequence branch off a task - `POST /api/tasks/{id}/branches/{branchId}/enroll` — Enroll contacts into a task branch - `GET /api/tasks/{id}/circuit-breaker` — Get the circuit-breaker state for a task - `POST /api/tasks/{id}/circuit-breaker/reset` — Reset a tripped circuit breaker for a task - `GET /api/tasks/{id}/cron` — Get the cron scheduling settings for a task - `PATCH /api/tasks/{id}/cron` — Update the cron scheduling settings for a task - `POST /api/tasks/{id}/cron/trigger` — Manually trigger a task cron run - `GET /api/tasks/{id}/error-settings` — Get the error-handling settings for a task - `PATCH /api/tasks/{id}/error-settings` — Update error-handling settings for a task - `GET /api/tasks/{id}/errors` — List errors encountered during a task run - `GET /api/tasks/{id}/items` — List items processed by a task with pagination - `GET /api/tasks/{id}/items/{itemId}` — Get a single task item by ID - `GET /api/tasks/{id}/memory` — Get AI memory instructions for a task - `PATCH /api/tasks/{id}/memory` — Update AI memory instructions for a task - `GET /api/tasks/{id}/monitor` — Get real-time monitor data for a running task - `POST /api/tasks/{id}/pause` — Pause a running task - `GET /api/tasks/{id}/report.csv` — Download a CSV report of all task items - `POST /api/tasks/{id}/restart` — Resume a task: resets ERROR items to PENDING and sets status=ACTIVE. Does NOT re-process already-scored items — response names preservedItems (untouched) and resetToRetryCount. For a targeted re-score of e.g. SCORED_BELOW_THRESHOLD, use POST :id/scoring-results/retry instead (#1099). - `GET /api/tasks/{id}/scoring-results` — List scoring results for a task with pagination. contradictory=true narrows to results whose stepResults carry a contradiction flag (data.contradictoryFields non-empty on any step) — same detection as POST :id/scoring-results/retry contradictoryOnly (#1113). - `GET /api/tasks/{id}/scoring-results.csv` — Download a CSV of scoring results - `DELETE /api/tasks/{id}/scoring-results/{resultId}` — Delete a single scoring result - `GET /api/tasks/{id}/scoring-results/{resultId}/events` — #975: paginated step journal for a single scoring card (requested/success/fail/error/interrupted/resumed_after_restart, seq by time) - `POST /api/tasks/{id}/scoring-results/retry` — #1099: retry SELECTION of scoring results — by statuses (ERROR|UNAVAILABLE|NO_WWW|SCORED_BELOW_THRESHOLD|SKIPPED|STUB_MATERIAL, #1146) and/or the processedAfter/processedBefore window. UNAVAILABLE (dead domains) is excluded by default — includeUnavailable=true to explicitly include it. No body — ERROR only. WARNING: behavior change — a call without a body used to take ERROR+UNAVAILABLE (hard-coded); now dead domains require an explicit includeUnavailable=true. #1128: OR a targeted selection by resultIds/companyIds (specific record ids, regardless of status) — the only way to recompute chosen cards without re-evaluating the rest via the window. ACTIVE (an already-accepted honest verdict) in this list is unreachable without includeActive=true — the same device as includeUnavailable for statuses; without the flag such a record is silently skipped. Records with a foreign taskId in resultIds/companyIds are silently ignored. Statuses/window are not used in selection when resultIds/companyIds are passed. includeActive=true writes a persistent journal event for EVERY reset ACTIVE record (who/when/how many) — see GET /tasks/:id/scoring-results/:resultId/events, kind=active_reset_by_retry. #1128 round 3: preview=true — a read-only preview of the SAME selection: returns wouldRetryCount/resultIds WITHOUT mutating ScoringResult/TaskItem and without a journal entry; the retry does not run. #1113 goal 2: contradictoryOnly=true narrows the selection to cards where a contradiction flag fired (data.contradictoryFields at any step) — an additional AND condition on top of the other filters. - `GET /api/tasks/{id}/scoring-stats` — Get aggregate scoring statistics for a task - `POST /api/tasks/{id}/start` — Start (or resume) a task. If items are empty, they are populated from the task's contactListId/companyListId. Progress: GET /tasks/:id (processedItems/totalItems); for scoring: GET /tasks/:id/scoring-stats and /tasks/:id/scoring-results?status=ACTIVE (score, companyType, contacts found on the site). - `GET /api/tasks/{id}/stats` — Get aggregate stats for a task - `GET /api/tasks/{id}/steps` — List pipeline steps configured on a task - `POST /api/tasks/{id}/steps` — Add a pipeline step to a task. Types: SITE_AVAILABILITY, AI_CONTENT, KEYWORDS, EMAIL_EXTRACT, PHONE_EXTRACT, SOCIAL_LINKS, TECH_DETECT, GEO_FILTER, NALOG_LOOKUP, AI_CARD_FIELD, and others. For AI_CONTENT: config.messages=[{role:system,...},{role:user,...}] with placeholders {-Variable.page_text-}/{-Variable.domain-}, config.min_score (success when score>=min_score). For AI_CARD_FIELD (#1035, requires item.contactId; or item.companyId when config.targetEntity=COMPANY — a company with no single contact is covered too): config.promptTemplate with the resolver's canonical variables ({{firstName}}, {company.name}, ccf_...), config.targetEntity=CONTACT|COMPANY|LEAD (LEAD requires item.contactId — TaskItem does not store leadId, the target becomes that contact's single lead; 0 leads → no_lead, more than one → ambiguous_lead, the step does not guess), config.targetFieldId=id of an existing CustomFieldDefinition, config.fallback — writes the result to that custom field WITHOUT hitting the site; a repeat on an already-computed entity×field pair does not invoke AI again (dedup by non-empty field value). Routing: onSuccess/onFail/onError = CONTINUE|STOP|MOVE_TO_LIST (+onSuccessTarget=list id)|DELETE. Typical scoring: step 1 SITE_AVAILABILITY (onFail=STOP), step 2 AI_CONTENT (onSuccess=MOVE_TO_LIST into the target list). - `PATCH /api/tasks/{id}/steps/{stepId}` — Update a single pipeline step - `DELETE /api/tasks/{id}/steps/{stepId}` — Delete a pipeline step from a task - `PUT /api/tasks/{id}/steps/reorder` — Reorder pipeline steps in a task - `POST /api/tasks/{id}/stop` — Stop a task (mark as DONE) ### Tracking - `GET /api/r/{slug}` — Short redirect by slug (/r/:slug) — click path for directly-created links (createLink). Applies the same cloaking (bot_split / captcha via ?v) but does NOT log tracking events, so this path produces no analytics. - `GET /api/t/c/{trackingId}/{slug}` — Redirect a tracked click (/t/c/:trackingId/:slug) to target and log a CLICK. Cloaking: cloakMode=bot_split → bots go to botDecoyUrl (logs BOT_CLICK), humans to target; cloakMode=captcha → HTML interstitial unless a valid ?v token is present. This mapped path is produced by campaign sends and feeds analytics. - `GET /api/t/o/{trackingId}.gif` — Serve a 1x1 tracking pixel GIF and log an OPEN event. Injected into campaign emails; drives open-rate analytics. - `GET /api/tracking/analytics/devices` — Get device type distribution from tracking events - `GET /api/tracking/analytics/email-clients` — Get email client distribution from tracking events - `GET /api/tracking/analytics/stats` — Aggregated tracking analytics (open rate, click rate, unique opens/clicks). Populated only from campaign-sent tracking (pixel + mapped click path); directly-created createLink links produce no analytics. - `GET /api/tracking/analytics/timeline` — Get opens/clicks timeline - `GET /api/tracking/analytics/top-links` — Top clicked links with per-link click counts (from campaign-sent CLICK events). - `GET /api/tracking/events` — List raw tracking events (type filter: OPEN | CLICK | BOT_CLICK). Populated by campaign-sent links (mapped /t/c path + pixel); directly-created links clicked via /r do not appear here. - `POST /api/tracking/links` — Create a tracking link. Optional cloakMode (off|bot_split|captcha) + botDecoyUrl enable cloaking immediately, no send required. Such links are clicked via /r/:slug (legacyRedirect) and do NOT log events — for open/click-rate analytics send links through a campaign (senderConfig.tracking). - `GET /api/tracking/links/{slug}` — Get tracking link info by slug - `PATCH /api/tracking/links/{slug}` — Update a tracking link: cloakMode (off|bot_split|captcha), botDecoyUrl and/or targetUrl. Only provided fields change. - `DELETE /api/tracking/links/{slug}` — Delete a tracking link by slug - `GET /api/tracking/pixel-settings` — Get tenant pixel tracking settings - `PATCH /api/tracking/pixel-settings` — Update tenant pixel tracking settings - `POST /api/tracking/simulate-click` — Dry-run the cloaking decision for a link given userAgent/ip/v — returns target|decoy|captcha + bot verdict WITHOUT logging an event or moving counters. Lets an agent test cloakMode (off/bot_split/captcha) without spoofing HTTP headers. - `GET /api/tracking/stats` — Get aggregated tracking stats for a task ### Tracking / Bot List - `GET /api/bot-list` — Bot-list stats (feed count, custom CIDR/UA, last refresh). GLOBAL cross-tenant list for the cloaking redirect layer (#214 phase 3, SUPER-only): matched entries mark a click as a bot on cloakMode=bot_split links (→ botDecoyUrl). Changes here affect bot detection for ALL tenants. - `POST /api/bot-list/cidr` — Add a custom bot CIDR to the GLOBAL bot-list (e.g. 203.0.113.0/24). Clicks from these IPs on cloakMode=bot_split links are deflected to botDecoyUrl (+BOT_CLICK). SUPER-only; affects all tenants — use narrow ranges. - `DELETE /api/bot-list/cidr` — Remove a custom bot CIDR from the GLOBAL bot-list. SUPER-only. - `POST /api/bot-list/refresh` — Reload external bot-feeds now (BOT_FEED_URLS) into the in-memory matcher. SUPER-only, global. Gated by BOT_FEED_ENABLED + RUN_CRON_JOBS; normally refreshed on a schedule. - `POST /api/bot-list/ua` — Add a custom bot User-Agent regex to the GLOBAL bot-list (e.g. MyScanner). UA match → bot → deflect to botDecoyUrl on cloakMode=bot_split links. SUPER-only; affects all tenants. - `DELETE /api/bot-list/ua` — Remove a custom bot User-Agent regex from the GLOBAL bot-list. SUPER-only. ### Tracking / Domain - `GET /api/tracking-domains` — List tracking domains - `POST /api/tracking-domains` — Create a tracking domain (starts as PENDING). External step: add a DNS CNAME → cnameTarget (default track.pixel.ldm-app.com) in the customer DNS zone, then run health-check/verify to make it ACTIVE. An ACTIVE domain is required for campaign link rewriting; set one as default for the open pixel. - `GET /api/tracking-domains/{id}` — Get a tracking domain by id - `PUT /api/tracking-domains/{id}` — Update a tracking domain - `DELETE /api/tracking-domains/{id}` — Delete a tracking domain - `POST /api/tracking-domains/{id}/health-check` — Run a health check for a tracking domain (verifies the DNS CNAME → cnameTarget and SSL). Passing DNS moves a PENDING domain toward ACTIVE. - `POST /api/tracking-domains/{id}/report-spam` — Report spam for a tracking domain (auto-pause at threshold) - `POST /api/tracking-domains/{id}/set-default` — Set a tracking domain as the default (used for the open pixel; without a default the pixel falls back to APP_URL/api). - `GET /api/tracking-domains/monitoring` — Get tracking domain monitoring overview with click/send stats - `GET /api/tracking-domains/stats` — Get tracking domain statistics ### Tracking / Redirect Source - `GET /api/redirect-sources` — List redirect sources - `POST /api/redirect-sources` — Create a redirect source - `GET /api/redirect-sources/{id}` — Get a redirect source by id - `PUT /api/redirect-sources/{id}` — Update a redirect source - `DELETE /api/redirect-sources/{id}` — Delete a redirect source - `GET /api/redirect-sources/stats` — Get redirect source statistics ### Unsubscribe - `GET /api/unsubscribe/{token}` — Handle unsubscribe click from email link (public) - `POST /api/unsubscribe/{token}` — RFC 8058 one-click unsubscribe endpoint ### User Settings - `GET /api/user-settings` — Get current user settings - `PATCH /api/user-settings` — Update current user settings - `GET /api/user-settings/available-transports` — List available transports admin has enabled ### Users - `GET /api/users` — List users (SUPER/MANAGER) - `POST /api/users` — Create a user (SUPER only) - `GET /api/users/{id}` — Get a user by id (SUPER/MANAGER) - `DELETE /api/users/{id}` — Delete a user with tenant cleanup: sole-member tenants + their DBs are dropped (SUPER only) - `GET /api/users/{id}/comments` — List admin comments on a user (SUPER/MANAGER) - `POST /api/users/{id}/comments` — Add an admin comment on a user (SUPER/MANAGER) - `DELETE /api/users/{id}/comments/{commentId}` — Delete an admin comment on a user (SUPER only) - `PATCH /api/users/{id}/password` — Update a user password (SUPER only) - `PATCH /api/users/{id}/profile` — Update a user profile (SUPER only) - `PATCH /api/users/{id}/role` — Update a user role (SUPER/MANAGER) - `PATCH /api/users/{id}/status` — Update a user status (SUPER/MANAGER) - `POST /api/users/me/accept-terms` — Accept legal terms for the current user - `POST /api/users/system/cleanup-orphans` — Cleanup orphan system resources (SUPER only) - `POST /api/users/system/kill-process/{pid}` — Kill a system process by pid (SUPER only) - `GET /api/users/system/stats` — Get system stats (SUPER only) ### Utils - `POST /api/utils/timezone-lookup` — Look up timezone by city or country - `GET /api/utils/timezones` — List IANA timezones with cities, country, offset ### Variable Resolver - `GET /api/variable-resolver/available-variables` — List all available template variables (canonical — same source as /creatives/variables/available) - `POST /api/variable-resolver/resolve` — Resolve contact context and variable map for templates ### Webhooks - `GET /api/webhooks` — List webhook endpoints - `POST /api/webhooks` — Create a webhook endpoint - `PATCH /api/webhooks/{id}` — Update a webhook endpoint - `DELETE /api/webhooks/{id}` — Delete a webhook endpoint - `GET /api/webhooks/{id}/deliveries` — List delivery history for a webhook endpoint - `POST /api/webhooks/{id}/retrigger` — Retrigger the last N deliveries for a webhook endpoint - `POST /api/webhooks/deliveries/{id}/retry` — Retry a single webhook delivery ## Conventions - Base URL: https://api.live-direct-marketing.online - Auth: Bearer token in `Authorization` header. The same workspace API key (copied from Settings → API & Integrations in the LDM web app) works for direct REST calls and for MCP/A2A clients — there is no separate MCP key. - Tenant scope: include `X-Tenant-Id: ` for tenant-scoped endpoints. - Status: closed beta.