{"version":"1.0","title":"Salesbase CRM API — Integration Reference","baseUrl":"/api/v1","sections":{"auth":{"overview":{"title":"Authentication","description":"Integration endpoints use a workspace API key (Bearer sb_…). API keys are created in the Salesbase app under Settings → Workspace → Automation → API Keys. Mobile app endpoints (voice, notifications, timers, assistant, devices, workspaces switch) require JWT login and are not part of this integration reference."},"apiKey":{"title":"API Key Authentication","description":"Use your workspace API key in the Authorization header","format":"Authorization: Bearer <YOUR_API_KEY>","alternativeFormat":"Query parameter: ?api_key=<YOUR_API_KEY> (for tools that cannot set headers)","keyPrefix":"API keys start with 'sb_' prefix","note":"The full API key is only shown once when created. It is stored as a SHA-256 hash — Salesbase cannot recover lost keys."},"mcpOAuth":{"title":"MCP OAuth (AI agents)","description":"Claude and other MCP clients can connect via OAuth (Settings → Workspace → Automation → MCP). The consent screen grants scopes that gate entire API groups. Call GET /api/v1/whoami (or the MCP whoami tool) to see the workspace name, user, and active scopes for the current session.","scopes":{"read":"Read CRM data (companies, people, leads, deals, activities, notes, tasks, search, docs, …)","write":"Create/update/delete on CRM resources","inbound":"POST /api/v1/inbound","webhooks":"Webhook subscription management","sms":"SMS send/list/threads (required — SMS is not included in read)","export":"CSV/export endpoints","import":"Import endpoints"},"note":"Missing a scope returns 403 for that entire group. Re-connect the MCP app to grant additional scopes. List endpoints reject unknown query parameters with HTTP 400 so agents can tell missing filters from empty results. Prefer view=summary (MCP default) and updatedSince for incremental sync. CRM writes (POST/PUT on deals, leads, companies, people, projects, support tickets) accept preview:true (body or ?preview=true) and return current/proposed/diff without saving — call again without preview to persist."},"rateLimits":{"title":"Rate Limits","description":"All API keys share fixed rate limits to ensure fair usage and platform stability","defaults":{"minuteLimit":"120 requests per minute","dailyLimit":"50,000 requests per day"},"headers":{"X-RateLimit-Limit-Minute":"Maximum requests allowed per minute (120)","X-RateLimit-Remaining-Minute":"Remaining requests in the current minute window","X-RateLimit-Limit-Day":"Maximum requests allowed per day (50,000)","X-RateLimit-Remaining-Day":"Remaining requests in the current day window"},"exceeded":"Returns HTTP 429 Too Many Requests when limits are exceeded"},"errors":{"title":"Error Responses","codes":{"400":{"description":"Bad Request — invalid parameters or missing required fields"},"401":{"description":"Unauthorized — missing or invalid API key","body":"{ \"error\": \"Invalid API key\" }"},"403":{"description":"Forbidden — API key is revoked or workspace is suspended"},"404":{"description":"Not Found — resource does not exist or belongs to another workspace"},"429":{"description":"Too Many Requests — rate limit exceeded"},"500":{"description":"Internal Server Error — unexpected server error"}}},"responseFormat":{"title":"Standard Response Format","success":{"description":"Successful responses wrap data in a 'data' key","single":"{ \"data\": { \"_id\": \"...\", \"name\": \"...\", ... } }","list":"{ \"data\": [...], \"page\": 1, \"limit\": 20, \"total\": 150 }"},"error":{"description":"Error responses include an 'error' key with a message string","format":"{ \"error\": \"Description of what went wrong\" }"}},"quickStart":{"title":"Quick Start","steps":[{"step":1,"title":"Create an API key","description":"Go to Settings → Workspace → Automation → API Keys and create a new key. Copy the key immediately — it won't be shown again."},{"step":2,"title":"Test the connection","description":"Make a GET request to /api/v1/whoami to verify your key and see workspace name, user, and scopes","example":{"curl":"curl -H \"Authorization: Bearer sb_your_api_key_here\" https://app.salesbase.dk/api/v1/leads"}},{"step":3,"title":"Explore the API","description":"Use GET /api/v1/docs to get the full API documentation as JSON"}]},"bestPractices":["Store API keys in environment variables, never in code","Use the minimum required permissions/scopes for each key","Rotate keys periodically and revoke unused keys","Handle rate limit errors (429) with exponential backoff","Always include error handling for network failures","Use the workspace-scoped endpoints — all data is automatically filtered by your workspace"],"inboundWebhooks":{"title":"Inbound webhooks (external → Salesbase)","description":"These endpoints receive data from external systems. They are outside /api/v1 but documented here for integrators.","endpoints":[{"method":"POST","path":"/api/webhooks/automation/:automationId","description":"Trigger a webhook-type automation. JSON body is available as webhookData.* placeholders in the flow.","authentication":"Header x-webhook-secret must match the secret configured on the automation trigger node.","example":{"curl":"curl -X POST -H \"Content-Type: application/json\" -H \"x-webhook-secret: YOUR_SECRET\" -d '{\"email\":\"lead@example.com\"}' https://app.salesbase.dk/api/webhooks/automation/AUTOMATION_ID"}},{"method":"POST","path":"/api/v1/webhooks/zapier","description":"Zapier-specific inbound — creates person, lead, and deal. Uses Bearer API key (inbound scope).","authentication":"Authorization: Bearer sb_…"},{"method":"POST","path":"/api/v1/inbound","description":"Unified inbound creation for forms and integrations. Uses Bearer API key (inbound scope)."}]}},"automations":{"overview":{"title":"Automations","description":"Automations are event-driven flows that execute actions when specific triggers fire. Each automation has exactly one trigger node and one or more action/condition/delay/AI nodes connected by edges. Automations are stored as ReactFlow graphs (nodes + edges) and executed by a cron-based step processor.","concepts":{"flow":"An automation flow is a directed graph of nodes connected by edges. It always starts with a single trigger node.","trigger":"The entry point — fires when a specific event occurs (e.g. lead_created, deal_stage_changed).","action":"Performs an operation. Action settings live under node.data.config.","condition":"Branches via sourceHandle 'true'|'false'.","delay":"Pauses execution (rounded to 5-minute cron intervals).","ai":"AI model step; output as {{ai_text}} / {{ai_json.*}}.","lookup":"Queries records; sourceHandle found/empty/error. mode=all fans out one child execution per match.","edge":"Condition/AI condition: true|false. Lookup: found|empty|error. Else null."}},"endpoints":[{"method":"GET","path":"/api/v1/automations","description":"List all automations in the workspace","queryParams":{"page":{"type":"number","default":1,"description":"Page number"},"limit":{"type":"number","default":20,"max":100,"description":"Items per page"},"isActive":{"type":"string","enum":["true","false"],"description":"Filter by active status"}},"response":{"data":"Automation[]","page":"number","limit":"number","total":"number"}},{"method":"POST","path":"/api/v1/automations","description":"Create a new automation","body":{"name":{"type":"string","required":true,"description":"Automation name"},"description":{"type":"string","required":false,"description":"Description of what this automation does"},"nodes":{"type":"Node[]","required":false,"description":"Array of flow nodes (see Node Structure below)"},"edges":{"type":"Edge[]","required":false,"description":"Array of edges connecting nodes (see Edge Structure below)"}},"response":{"data":"Automation"},"statusCode":201},{"method":"GET","path":"/api/v1/automations/:id","description":"Get a single automation by ID","response":{"data":"Automation"}},{"method":"PUT","path":"/api/v1/automations/:id","description":"Update an automation","body":{"name":{"type":"string","required":false},"description":{"type":"string","required":false},"nodes":{"type":"Node[]","required":false},"edges":{"type":"Edge[]","required":false},"executionSettings":{"type":"ExecutionSettings","required":false},"rateLimits":{"type":"RateLimits","required":false}},"response":{"data":"Automation"}},{"method":"DELETE","path":"/api/v1/automations/:id","description":"Delete an automation. Fails if there are running executions.","response":{"data":{"deleted":true}}},{"method":"POST","path":"/api/v1/automations/:id/toggle","description":"Toggle automation active/inactive","response":{"data":{"_id":"string","isActive":"boolean"}}},{"method":"GET","path":"/api/v1/automations/:id/executions","description":"List executions for an automation","queryParams":{"page":{"type":"number","default":1},"limit":{"type":"number","default":20,"max":100},"status":{"type":"string","enum":["running","completed","failed","cancelled"]}},"response":{"data":"AutomationExecution[]","page":"number","limit":"number","total":"number"}},{"method":"GET","path":"/api/v1/automations/executions/:executionId","description":"Get a single execution with full step log","response":{"data":"AutomationExecution"}},{"method":"GET","path":"/api/v1/automations/stats","description":"Get aggregate automation statistics for the workspace","response":{"data":{"totalAutomations":"number","activeAutomations":"number","totalExecutions":"number","executionsByStatus":"{ running: number, completed: number, failed: number, cancelled: number }","avgDuration":"number|null (milliseconds)"}}},{"method":"POST","path":"/api/v1/automations/:id/duplicate","description":"Duplicate an automation (creates inactive copy)","response":{"data":"Automation"}},{"method":"POST","path":"/api/v1/automations/:id/test","description":"Run automation in test mode with sample context","body":{"testContext":{"type":"object","description":"Optional context overrides"}}},{"method":"POST","path":"/api/v1/automations/:id/trigger","description":"Manually trigger an active automation","body":{"context":{"type":"object","description":"Trigger context payload"}}},{"method":"POST","path":"/api/v1/automations/executions/:executionId/cancel","description":"Cancel a running automation execution","response":{"data":{"cancelled":true}}}],"nodeStructure":{"description":"Every node in the flow has this base structure:","schema":{"id":{"type":"string","required":true,"description":"Unique node ID. Convention: {type}_{timestamp}_{index}"},"type":{"type":"string","required":true,"enum":["trigger","action","condition","delay","ai","lookup"],"description":"Canvas node type"},"position":{"type":"object","required":true,"description":"Canvas position { x, y }. Vertical flows: x=500, increment y by 200 per row."},"data":{"type":"object","required":true,"description":"Node config. Triggers: triggerType + config. Actions: actionType + config."}}},"triggerTypes":{"description":"Each automation must have exactly ONE trigger node. Set data.triggerType and optional data.config on the trigger node.","uiGuide":"Triggeren er den øverste node. Klik på den → panelet til højre viser 'Trigger type' øverst. Vælg event i dropdownen; eventuelle ekstra felter (fx tidsplan, keyword, webhook) vises under.","types":{"lead_created":{"label":"Lead oprettet","description":"Starter når et nyt lead oprettes.","category":"leads","placeholders":["lead.*","person.*","company.*","owner.*"]},"lead_updated":{"label":"Lead opdateret","description":"Starter når et lead opdateres.","category":"leads","placeholders":["lead.*","person.*","company.*","owner.*"]},"company_updated":{"label":"Virksomhed opdateret","description":"Starter når en virksomhed opdateres.","category":"general","placeholders":["company.*","owner.*"]},"person_updated":{"label":"Person opdateret","description":"Starter når en person opdateres.","category":"general","placeholders":["person.*","company.*","owner.*"]},"deal_created":{"label":"Deal oprettet","description":"Starter når en deal oprettes.","category":"deals","placeholders":["deal.*","company.*","person.*","owner.*"]},"deal_updated":{"label":"Deal opdateret","description":"Starter når en deal opdateres.","category":"deals","placeholders":["deal.*","company.*","person.*","owner.*"]},"deal_won":{"label":"Deal vundet","description":"Starter når en deal markeres som vundet.","category":"deals","placeholders":["deal.*","company.*","person.*","owner.*"]},"deal_lost":{"label":"Deal tabt","description":"Starter når en deal markeres som tabt.","category":"deals","placeholders":["deal.*","company.*","person.*","owner.*"]},"deal_stage_changed":{"label":"Deal stage ændret","description":"Starter når en deal flyttes til et nyt stage.","category":"deals","config":{"stageId":{"type":"string","description":"Begræns til et bestemt stage (valgfrit — tom = alle stage-skift)","storage":"node.data.config"}},"placeholders":["deal.*","company.*","person.*","owner.*"]},"call_completed":{"label":"Opkald afsluttet","description":"Starter når et opkald afsluttes.","category":"calls","placeholders":["call.*","lead.*","deal.*","owner.*"]},"call_answered":{"label":"Opkald besvaret","description":"Starter når et opkald bliver besvaret.","category":"calls","placeholders":["call.*","lead.*","deal.*","owner.*"]},"email_received":{"label":"Email modtaget","description":"Starter når en email modtages.","category":"email","placeholders":["email.*","lead.*","person.*"]},"email_opened":{"label":"Email åbnet","description":"Starter når en sendt email åbnes.","category":"email","placeholders":["email.*","lead.*","person.*"]},"email_replied":{"label":"Email besvaret","description":"Starter når en email besvares.","category":"email","placeholders":["email.*","lead.*","person.*"]},"sms_received":{"label":"SMS modtaget","description":"Starter når en SMS modtages.","category":"sms","config":{"keyword":{"type":"string","description":"Valgfrit keyword-filter — kun beskeder der indeholder dette ord udløser","storage":"node.data.config"}},"placeholders":["sms.*","lead.*","person.*"]},"sms_replied":{"label":"SMS besvaret","description":"Starter når en SMS besvares.","category":"sms","config":{"keyword":{"type":"string","description":"Valgfrit keyword-filter","storage":"node.data.config"}},"placeholders":["sms.*","lead.*","person.*"]},"event_created":{"label":"Begivenhed oprettet","description":"Starter når en kalenderbegivenhed oprettes.","category":"calendar","placeholders":["event.*","owner.*"]},"agent_field_change":{"label":"Agent: feltændring","description":"Starter når en AI-agent ændrer et felt.","category":"agents","config":{"agentId":{"type":"string","description":"Hvilken agent","storage":"node.data.config"}},"placeholders":["agent.*","company.*","person.*","owner.*"]},"agent_new_company":{"label":"Agent: ny virksomhed","description":"Starter når en AI-agent opretter en ny virksomhed.","category":"agents","config":{"agentId":{"type":"string","description":"Hvilken agent","storage":"node.data.config"}},"placeholders":["agent.*","company.*","owner.*"]},"agent_any":{"label":"Agent: enhver hændelse","description":"Starter ved enhver AI-agent-hændelse.","category":"agents","placeholders":["agent.*","company.*","person.*","owner.*"]},"sales_analytics_done":{"label":"Salgsanalyse færdig","description":"Starter når en salgsanalyse er færdig.","category":"analytics","placeholders":["call.*","analytics.*","owner.*"]},"transcription_ready":{"label":"Transskription klar","description":"Starter når en opkaldstransskription er klar.","category":"analytics","placeholders":["call.*","transcript.*"]},"offer_created":{"label":"Tilbud oprettet","description":"Starter når et tilbud oprettes.","category":"offers","placeholders":["offer.*","deal.*","company.*"]},"offer_sent":{"label":"Tilbud sendt","description":"Starter når et tilbud sendes.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"offer_viewed":{"label":"Tilbud set","description":"Starter når et tilbud åbnes af modtageren.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"offer_accepted":{"label":"Tilbud accepteret","description":"Starter når et tilbud accepteres.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"offer_rejected":{"label":"Tilbud afvist","description":"Starter når et tilbud afvises.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"offer_expired":{"label":"Tilbud udløbet","description":"Starter når et tilbud udløber.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"offer_pdf_downloaded":{"label":"Tilbuds-PDF hentet","description":"Starter når tilbuds-PDF'en downloades.","category":"offers","placeholders":["offer.*","deal.*","company.*","owner.*"]},"booking_created":{"label":"Booking oprettet","description":"Starter når en booking oprettes.","category":"bookings","config":{"bookingConfigId":{"type":"string","description":"Begræns til en bestemt bookingside (valgfrit)","storage":"node.data.config"}},"placeholders":["booking.*","person.*"]},"booking_cancelled":{"label":"Booking aflyst","description":"Starter når en booking aflyses.","category":"bookings","config":{"bookingConfigId":{"type":"string","description":"Bookingside (valgfrit)","storage":"node.data.config"}},"placeholders":["booking.*","person.*"]},"booking_rescheduled":{"label":"Booking flyttet","description":"Starter når en booking flyttes.","category":"bookings","config":{"bookingConfigId":{"type":"string","description":"Bookingside (valgfrit)","storage":"node.data.config"}},"placeholders":["booking.*","person.*"]},"booking_approved":{"label":"Booking godkendt","description":"Starter når en booking godkendes.","category":"bookings","placeholders":["booking.*","person.*"]},"booking_rejected":{"label":"Booking afvist","description":"Starter når en booking afvises.","category":"bookings","placeholders":["booking.*","person.*"]},"calculator_submission":{"label":"Beregner indsendt","description":"Starter når en beregner indsendes.","category":"calculators","config":{"calculatorTemplateId":{"type":"string","description":"Begræns til en bestemt beregner (valgfrit)","storage":"node.data.config"}},"placeholders":["calculator.*","lead.*"]},"calculator_submission_processed":{"label":"Beregner behandlet","description":"Starter når en beregner-indsendelse er færdigbehandlet.","category":"calculators","placeholders":["calculator.*","lead.*"]},"reply_intent_detected":{"label":"Svar-intention registreret","description":"Starter når AI registrerer en intention i et svar.","category":"ai","placeholders":["email.*","lead.*","person.*","ai.*"]},"lifecycle_stage_changed":{"label":"Livscyklus-stage ændret","description":"Starter når en kontakts livscyklus-stage ændres.","category":"ai","placeholders":["person.*","company.*","lead.*","owner.*"]},"engagement_risk_detected":{"label":"Engagement-risiko","description":"Starter når der registreres risiko for lavt engagement.","category":"ai","placeholders":["person.*","company.*","lead.*","owner.*"]},"winback_opportunity":{"label":"Winback-mulighed","description":"Starter når der opstår en winback-mulighed.","category":"ai","placeholders":["person.*","company.*","lead.*","owner.*"]},"support_ticket_created":{"label":"Supportsag oprettet","description":"Starter når en supportsag oprettes.","category":"support","placeholders":["ticket.*","person.*","company.*"]},"support_ticket_updated":{"label":"Supportsag opdateret","description":"Starter når en supportsag opdateres.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"support_ticket_resolved":{"label":"Supportsag løst","description":"Starter når en supportsag løses.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"support_ticket_closed":{"label":"Supportsag lukket","description":"Starter når en supportsag lukkes.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"support_ticket_reopened":{"label":"Supportsag genåbnet","description":"Starter når en supportsag genåbnes.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"support_ticket_stage_changed":{"label":"Supportsag stage ændret","description":"Starter når en supportsag skifter stage.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"support_ticket_assigned":{"label":"Supportsag tildelt","description":"Starter når en supportsag tildeles en bruger.","category":"support","placeholders":["ticket.*","person.*","company.*","owner.*"]},"tag_added":{"label":"Tag tilføjet","description":"Starter når et tag tilføjes til en entity.","category":"general","config":{"entityType":{"type":"enum","description":"Hvilken entitetstype skal lyttes på","enum":["all","Lead","People","Company"],"default":"all","storage":"node.data.config"},"tagId":{"type":"string","description":"Specifikt tag-id, eller 'any' for alle tags","storage":"node.data.config"}},"placeholders":["lead.*","person.*","company.*","owner.*"]},"tag_removed":{"label":"Tag fjernet","description":"Starter når et tag fjernes fra en entity.","category":"general","config":{"entityType":{"type":"enum","description":"Entitetstype","enum":["all","Lead","People","Company"],"default":"all","storage":"node.data.config"},"tagId":{"type":"string","description":"Tag-id eller 'any'","storage":"node.data.config"}},"placeholders":["lead.*","person.*","company.*","owner.*"]},"user_joined_workspace":{"label":"Bruger tilføjet","description":"Starter når en bruger tilføjes til workspacet.","category":"users","placeholders":["owner.*"]},"meta_lead_form_submitted":{"label":"Meta lead-formular","description":"Starter når en Meta/Facebook lead-formular indsendes. Lead oprettes automatisk medmindre »Opret leads automatisk« er slået fra under Meta-indstillinger.","category":"integrations","placeholders":["lead.*","meta.*","person.*","company.*"]},"custom_object_created":{"label":"Brugerdefineret objekt oprettet","description":"Starter når en record oprettes i et brugerdefineret objekt.","category":"custom_objects","config":{"customObjectId":{"type":"string","description":"Begræns til specifikt objekt (valgfrit)","storage":"node.data.config"}},"placeholders":["custom_record.*","owner.*"]},"custom_object_updated":{"label":"Brugerdefineret objekt opdateret","description":"Starter når en record opdateres i et brugerdefineret objekt.","category":"custom_objects","config":{"customObjectId":{"type":"string","description":"Begræns til specifikt objekt (valgfrit)","storage":"node.data.config"}},"placeholders":["custom_record.*","owner.*"]},"record_created":{"label":"Record oprettet","description":"Starter når en valgt record-type oprettes (lead, deal, tilbud, event, support ticket, booking).","category":"deals","config":{"entityTypes":{"type":"enum","description":"Hvilke record-typer der overvåges ved oprettelse","enum":["all","lead","deal","offer","event","support_ticket","booking"],"default":"all","storage":"node.data.config"}},"placeholders":["lead.*","deal.*","offer.*","event.*","ticket.*"],"note":"Preferred over legacy single-entity triggers (lead_created, deal_created, etc.) for new flows."},"record_changed":{"label":"Record ændret","description":"Starter når en valgt record opdateres eller tags ændres.","category":"leads","config":{"entityTypes":{"type":"enum","description":"Hvilke entiteter der overvåges","enum":["all","lead","deal","company","person"],"default":"all","storage":"node.data.config"},"changeTypes":{"type":"enum","description":"Hvilke ændringstyper der udløser","enum":["all","updated","tag_added","tag_removed"],"default":"all","storage":"node.data.config"},"tagId":{"type":"string","description":"Specifikt tag-id ved tag-ændringer, eller 'any'","storage":"node.data.config"}},"placeholders":["lead.*","deal.*","company.*","person.*","owner.*"],"note":"Preferred over legacy single-entity triggers (lead_created, deal_created, etc.) for new flows."},"scheduled":{"label":"Tidsplan","description":"Starter på en fast tidsplan (cron-lignende).","category":"scheduling","config":{"frequency":{"type":"enum","description":"Hvor ofte den kører. interval = hvert 5., 10., 15. eller 30. minut","enum":["interval","hourly","daily","weekly","monthly"],"default":"daily","storage":"node.data.config"},"intervalMinutes":{"type":"number","description":"Minutter mellem kørsler (5, 10, 15 eller 30). Kun ved frequency=interval","storage":"node.data.config"},"dayOfWeek":{"type":"number","description":"Ugedag 0-6 (kun weekly)","storage":"node.data.config"},"dayOfMonth":{"type":"number","description":"Dag i måneden 1-28 (kun monthly)","storage":"node.data.config"},"time":{"type":"string","description":"Tidspunkt HH:mm (ved hourly bruges kun minuttet. Ved interval ignoreres feltet)","default":"09:00","storage":"node.data.config"}},"example":"Hvert 5. minut: frequency=interval, intervalMinutes=5. Daglig kl. 09:00: frequency=daily, time=09:00. Kombinér interval med HTTP-kald for at polle et API.","note":"No entity context by itself — pair with a lookup node (optionally changeDetection) to load records."},"webhook":{"label":"Webhook","description":"Starter når en ekstern webhook kalder automationens unikke URL.","category":"scheduling","config":{"secret":{"type":"string","description":"Valgfri hemmelighed til at validere kald","storage":"node.data.config"},"entityMapping":{"type":"object","description":"Mapping af payload til entity: { entityType: Lead|People|Company|Deal|none, entityIdField }","storage":"node.data.config"}},"placeholders":["webhookData.*"],"example":"Webhook-URL'en findes i trigger-panelet og kaldes udefra. Payload-felter bliver til {{webhookData.<felt>}}.","note":"Webhook URL: POST /api/webhooks/automation/{automationId}. Payload fields available as {{webhookData.fieldName}}."}}},"actionTypes":{"description":"Action nodes perform operations. Set data.actionType and data.config on the node.","types":{"send_email":{"label":"Send email","description":"Sender en email til en kontakt på entityen.","group":"communication","config":{"subject":{"type":"string","description":"Emnelinje — kritisk indhold brugeren skal angive (medmindre template vælges)","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"body":{"type":"string","description":"Email-brødtekst (HTML) — kritisk indhold (medmindre template vælges)","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"templateId":{"type":"string","description":"Brug en eksisterende email-template i stedet for at skrive emne/body","storage":"node.data.config"},"recipientField":{"type":"enum","description":"Hvilken email-adresse: primary=Primær (første tilgængelige), secondary=Sekundær, work/privat/cvr/ai/faktura/support=type-markeret email, person/company=via forbindelse, priority=Person→Virksomhed, any=første tilgængelige, all=alle (multi-send), custom=fast adresse i feltet 'to'.","enum":["primary","secondary","work","privat","cvr","ai","faktura","support","person","company","priority","any","all","custom"],"default":"primary","storage":"node.data.config"},"recipientType":{"type":"enum","description":"KUN relevant når entityen er et Lead: 'auto'=kontakt først, ellers firma; 'contact'=personRef; 'company'=companyRef. Bestemmer om mailen går til kontakten eller firmaet.","enum":["auto","contact","company"],"default":"auto","storage":"node.data.config"},"to":{"type":"string","description":"KUN når recipientField=custom: fast email eller {{placeholder}}.","supportsPlaceholders":true,"storage":"node.data.config"},"senderAccountId":{"type":"string","description":"Afsenderkonto — auto-detekteres hvis tom; spørg ikke","storage":"node.data.config"}},"produces":["emailSent.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"Spørg ALTID om emne+indhold hvis ingen template er valgt. Slå afsender op — spørg aldrig om den. recipientType gælder kun Leads (kontakt vs. firma)."},"send_sms":{"label":"Send SMS","description":"Sender en SMS til et telefonnummer på entityen.","group":"communication","config":{"message":{"type":"string","description":"SMS-tekst — kritisk indhold brugeren skal angive","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"phoneField":{"type":"enum","description":"Hvilket telefonfelt der bruges","enum":["primary","mobile","work","all","custom"],"default":"primary","storage":"node.data.config"},"fromNumber":{"type":"string","description":"Afsendernummer — bruger workspace-default hvis tom","storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"Spørg om beskedteksten. Slå afsendernummer op via listTwilioNumbers."},"create_task":{"label":"Opret kalenderbegivenhed","description":"IMPORTANT: Despite the key name create_task, this creates a calendar Event (møde/opkald/opgave i kalenderen) — not a timeline log. Use create_activity to log on the entity timeline without a calendar slot.","group":"crm","config":{"title":{"type":"string","description":"Begivenhedens titel (NOT taskTitle — that field is sequences-only)","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"description":{"type":"string","description":"Beskrivelse","supportsPlaceholders":true,"storage":"node.data.config"},"activityType":{"type":"enum","description":"Type i kalenderen","enum":["task","call","meeting","follow_up","email","deadline","reminder"],"default":"meeting","storage":"node.data.config"},"priority":{"type":"enum","description":"Prioritet","enum":["low","medium","high"],"default":"medium","storage":"node.data.config"},"dateScheduleType":{"type":"enum","description":"Hvordan dato/tid vælges","enum":["relative","fixed","placeholder"],"default":"relative","storage":"node.data.config"},"durationMinutes":{"type":"number","description":"Varighed i minutter","default":30,"storage":"node.data.config"},"assignTo":{"type":"string","description":"Hvem begivenheden tildeles: owner | creator | placeholder | konkret bruger-ID fra teamlisten","default":"owner","storage":"node.data.config"},"assignToPlaceholder":{"type":"string","description":"Kun når assignTo=placeholder: bruger-ID","supportsPlaceholders":true,"storage":"node.data.config"},"calendarAccountId":{"type":"string","description":"Kalenderkonto — tom = auto (ansvarliges egen eller første tilgængelige). Listen viser brugernavn + kalender.","storage":"node.data.config"},"conflictPolicy":{"type":"enum","description":"Ved dobbeltbooking: flyt til ledig, undlad oprettelse, eller opret alligevel","enum":["find_next","skip","create_anyway"],"default":"find_next","storage":"node.data.config"}},"produces":["taskCreated.*","taskCreated.date","taskCreated.time","taskCreated.created"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"Spørg om titel og tidspunkt. Ved dobbeltbooking anbefales find_next. Downstream kan bruge {{taskCreated.date}} / {{taskCreated.time}}. Do NOT confuse with create_activity (timeline-only)."},"create_recall":{"label":"Opret genopkald","description":"Opretter et planlagt genopkald (recall).","group":"crm","config":{"title":{"type":"string","description":"Recall-titel","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"message":{"type":"string","description":"Noter til opkaldet","storage":"node.data.config"},"phoneField":{"type":"enum","description":"Telefonfelt","enum":["primary","mobile","work","custom"],"default":"primary","storage":"node.data.config"},"delayDays":{"type":"number","description":"Dage til genopkald","default":0,"storage":"node.data.config"},"assignTo":{"type":"string","description":"Tildeles til — default ejer","storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"create_note":{"label":"Opret note","description":"Tilføjer en note på entityen.","group":"crm","config":{"noteContent":{"type":"string","description":"Notens indhold. Field name is noteContent (NOT content) — the executor reads config.noteContent.","required":true,"supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"config: { noteContent: \"Lead score: {{lead.score}}\" }"},"create_activity":{"label":"Opret aktivitet","description":"Logs an Activity on the entity timeline only — does NOT create a calendar Event. Use create_task for calendar meetings/calls/tasks.","group":"crm","config":{"activityType":{"type":"string","description":"Aktivitetstype (timeline)","storage":"node.data.config"},"description":{"type":"string","description":"Beskrivelse af aktiviteten","required":true,"supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"update_record":{"label":"Opdater felt","description":"Opdaterer ét eller flere felter på entityen via fieldUpdates-arrayet.","group":"crm","config":{"recordType":{"type":"enum","description":"Hvilken record-type der opdateres (resolver entityId fra trigger-kontekst). custom_object kræver customRecord i kontekst.","enum":["lead","deal","company","person","custom_object"],"default":"lead","storage":"node.data.config"},"fieldUpdates":{"type":"array","description":"Array of { id?, fieldKey, value, isCustomField?, customFieldType? }. fieldKey is the field name (e.g. status, score) — NOT fieldPath. Legacy single field+value is still accepted by the executor but do not use it for new flows.","required":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"config: { recordType: \"lead\", fieldUpdates: [{ fieldKey: \"score\", value: \"{{ai_json.score}}\" }] }"},"assign_user":{"label":"Tildel adgang (legacy)","description":"Legacy — brug set_access i stedet.","group":"crm","config":{"assignmentType":{"type":"enum","description":"","enum":["owner","ownerGroup","share","shareGroup"],"default":"owner","storage":"node.data.config"},"userId":{"type":"string","description":"Bruger","storage":"node.data.config"},"groupId":{"type":"string","description":"Gruppe","storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"set_access":{"label":"Tildel adgang","description":"Sæt ejer, synlighed (gruppe/privat) og deling.","group":"crm","config":{"recordType":{"type":"enum","description":"","enum":["trigger","lead","deal","company","person","project","supportTicket","customObject"],"default":"trigger","storage":"node.data.config"},"accessState":{"type":"object","description":"Adgangskonfiguration (ejer, synlighed, delinger)","required":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"move_deal_stage":{"label":"Flyt deal stage","description":"Flytter dealen til et nyt pipeline-stage. Efter Slå op (Find alle) bruges den fundne deal automatisk.","group":"crm","config":{"flowRef":{"type":"string","description":"Pipeline (flowRef)","storage":"node.data.config"},"stageId":{"type":"string","description":"Mål-stage","storage":"node.data.config"},"dealId":{"type":"string","description":"Valgfri deal-id eller {{placeholder}} fra Slå op. Tom = deal fra trigger/opslag.","supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["deal.*"],"uiGuide":"Klik på Flyt deal stage → vælg Pipeline og Stage. Deal-feltet kan stå tomt efter en Slå op-node, eller du kan indsætte en placeholder via {}."},"create_lead":{"label":"Opret lead","description":"Opretter et nyt lead. (Deprecated — use create_record.)","group":"crm","config":{"title":{"type":"string","description":"Lead-titel/navn","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"email":{"type":"string","description":"Email","supportsPlaceholders":true,"storage":"node.data.config"},"phone":{"type":"string","description":"Telefon","supportsPlaceholders":true,"storage":"node.data.config"},"status":{"type":"string","description":"Status — default workspace-standard","storage":"node.data.config"}},"produces":["createdLead.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","deprecated":true},"create_deal":{"label":"Opret deal","description":"Opretter en ny deal i en pipeline. (Deprecated — use create_record.)","group":"crm","config":{"title":{"type":"string","description":"Deal-titel","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"value":{"type":"number","description":"Værdi","storage":"node.data.config"},"flowRef":{"type":"string","description":"Pipeline (flowRef)","storage":"node.data.config"},"stageId":{"type":"string","description":"Start-stage","storage":"node.data.config"}},"produces":["createdDeal.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","deprecated":true},"create_contact":{"label":"Opret kontakt","description":"Opretter en ny person/kontakt. (Deprecated — use create_record.)","group":"crm","config":{"firstName":{"type":"string","description":"Fornavn","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"lastName":{"type":"string","description":"Efternavn","supportsPlaceholders":true,"storage":"node.data.config"},"email":{"type":"string","description":"Email","supportsPlaceholders":true,"storage":"node.data.config"}},"produces":["createdContact.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","deprecated":true},"add_to_sequence":{"label":"Tilmeld sequence","description":"Tilmelder entityen til en sequence. There is no remove_from_sequence action — use sequence exit conditions or manual unenroll. When already enrolled: forceRestart=false skips (already_enrolled); forceRestart=true restarts. Sequence-level duplicateHandling is a separate enrollment-API setting.","group":"communication","config":{"sequenceId":{"type":"string","description":"Hvilken sequence","storage":"node.data.config"},"entityType":{"type":"enum","description":"'trigger' = use the triggering entity; otherwise override target type (ID inferred from context).","enum":["trigger","Lead","People","Company","Deal"],"default":"trigger","storage":"node.data.config"},"forceRestart":{"type":"boolean","description":"If true and an active/paused enrollment exists, restart it. If false (default), skip when already enrolled. Independent of sequence.duplicateHandling.","default":false,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"create_offer":{"label":"Opret tilbud","description":"Opretter et tilbud. (Deprecated — use create_record.)","group":"crm","config":{"templateId":{"type":"string","description":"Tilbudsskabelon","storage":"node.data.config"},"title":{"type":"string","description":"Tilbudstitel","required":true,"supportsPlaceholders":true,"storage":"node.data.config"}},"produces":["createdOffer.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","deprecated":true},"http_request":{"label":"HTTP-kald","description":"Sender et HTTP-request til et eksternt endpoint og gør svaret tilgængeligt som variabler.","group":"general","config":{"method":{"type":"enum","description":"HTTP-metode","enum":["GET","POST","PUT","PATCH","DELETE"],"default":"GET","storage":"node.data.config"},"url":{"type":"string","description":"Endpoint-URL — kritisk","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"headers":{"type":"object","description":"HTTP-headers som key/value","storage":"node.data.config"},"body":{"type":"json","description":"Request-body (JSON, kan indeholde placeholders)","supportsPlaceholders":true,"storage":"node.data.config"},"responseMapping":{"type":"object","description":"Map felter fra svaret til navngivne variabler","storage":"node.data.config"}},"produces":["httpResponse.*","httpResponse.status","httpResponse.body"],"consumes":["lead.*","deal.*","ai_json.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"Svaret tilgås som {{httpResponse.body.<felt>}}. Spørg om URL og metode; resten kan defaultes."},"send_webhook":{"label":"Send webhook","description":"Sender en webhook (POST) til en URL.","group":"general","config":{"url":{"type":"string","description":"Webhook-URL — kritisk","required":true,"supportsPlaceholders":true,"storage":"node.data.config"},"payload":{"type":"json","description":"Payload (JSON)","supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"ordrestyring_create_debtor":{"label":"Opret debitor","description":"Opretter en debitor i økonomisystemet.","group":"integrations","config":{"mapping":{"type":"object","description":"Mapping af entity-felter til debitor-felter (navn, CVR, email)","storage":"node.data.config"}},"produces":["debtor.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"ordrestyring_update_debtor":{"label":"Opdater debitor","description":"Opdaterer en eksisterende debitor.","group":"integrations","config":{"mapping":{"type":"object","description":"Felter der opdateres","storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"ordrestyring_create_case":{"label":"Opret sag","description":"Opretter en sag i økonomisystemet.","group":"integrations","config":{"mapping":{"type":"object","description":"Sags-felter","storage":"node.data.config"}},"produces":["case.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"ordrestyring_create_order_confirmation":{"label":"Opret ordrebekræftelse","description":"Opretter en ordrebekræftelse i økonomisystemet.","group":"integrations","config":{"mapping":{"type":"object","description":"Ordre-felter og linjer","storage":"node.data.config"}},"produces":["orderConfirmation.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"create_record":{"label":"Opret record","description":"Opretter lead, deal, kontakt, tilbud eller brugerdefineret objekt-record i ét samlet trin.","group":"crm","config":{"recordType":{"type":"enum","description":"Hvilken record-type der oprettes","enum":["lead","deal","contact","offer","custom_object"],"default":"lead","required":true,"storage":"node.data.config"},"fieldValues":{"type":"object","description":"Standardfelter for record-typen (fx title, email, value, stageId)","supportsPlaceholders":true,"storage":"node.data.config"},"customFields":{"type":"array","description":"Brugerdefinerede felter [{ name, value, type }]","storage":"node.data.config"},"customObjectId":{"type":"string","description":"Påkrævet når recordType=custom_object — ID på objektdefinition","storage":"node.data.config"}},"produces":["createdRecord.*","createdLead.*","createdDeal.*","createdContact.*","createdOffer.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder.","example":"Brug create_record i stedet for legacy create_lead/create_deal/create_contact/create_offer."},"add_to_list":{"label":"Tilføj til liste","description":"Tilføjer entityen til en statisk liste.","group":"lists","config":{"listId":{"type":"string","description":"Mål-liste (statisk liste-ID)","required":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"remove_from_list":{"label":"Fjern fra liste","description":"Fjerner entityen fra en statisk liste.","group":"lists","config":{"listId":{"type":"string","description":"Liste entityen fjernes fra","required":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"billy_create_contact":{"label":"Billy: Opret kontakt","description":"Opretter en kontakt i Billy.","group":"integrations","config":{"mapping":{"type":"object","description":"Mapping af entity-felter til Billy-kontakt","storage":"node.data.config"}},"produces":["billyContact.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"apacta_create_contact":{"label":"Apacta: Opret kontakt","description":"Opretter en kontakt i Apacta.","group":"integrations","config":{"mapping":{"type":"object","description":"Mapping af entity-felter til Apacta-kontakt","storage":"node.data.config"}},"produces":["apactaContact.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"economic_create_customer":{"label":"e-conomic: Opret kunde","description":"Opretter en kunde i e-conomic.","group":"integrations","config":{"mapping":{"type":"object","description":"Mapping til e-conomic kunde-felter (navn, CVR, email)","storage":"node.data.config"}},"produces":["economicCustomer.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"economic_create_invoice":{"label":"e-conomic: Opret faktura","description":"Opretter en faktura i e-conomic.","group":"integrations","config":{"mapping":{"type":"object","description":"Fakturalinjer og kunde-reference","storage":"node.data.config"}},"produces":["economicInvoice.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"economic_book_invoice":{"label":"e-conomic: Bogfør faktura","description":"Bogfører en faktura i e-conomic.","group":"integrations","config":{"invoiceId":{"type":"string","description":"Faktura-ID eller {{placeholder}}","supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"uniconta_create_customer":{"label":"Uniconta: Opret kunde","description":"Opretter en kunde i Uniconta.","group":"integrations","config":{"mapping":{"type":"object","description":"Mapping til Uniconta kunde-felter","storage":"node.data.config"}},"produces":["unicontaCustomer.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"uniconta_create_invoice":{"label":"Uniconta: Opret faktura","description":"Opretter en faktura i Uniconta.","group":"integrations","config":{"mapping":{"type":"object","description":"Fakturalinjer og kunde-reference","storage":"node.data.config"}},"produces":["unicontaInvoice.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"uniconta_book_invoice":{"label":"Uniconta: Bogfør faktura","description":"Bogfører en faktura i Uniconta.","group":"integrations","config":{"invoiceId":{"type":"string","description":"Faktura-ID eller {{placeholder}}","supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"dinero_create_contact":{"label":"Dinero: Opret kontakt","description":"Opretter en kontakt i Dinero.","group":"integrations","config":{"contactName":{"type":"string","description":"Kontaktnavn i feltet Navn (config.contactName). Alternativt config.mapping.name.","supportsPlaceholders":true,"storage":"node.data.config"},"contactCountryKey":{"type":"string","description":"Valgfri landekode til Dinero CountryKey (ISO-2). Tomt bliver DK.","supportsPlaceholders":true,"storage":"node.data.config"},"mapping":{"type":"object","description":"Valgfri mapping (name/email/cvr/countryKey) hvis contact*-felter mangler","storage":"node.data.config"}},"produces":["dineroContact.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"dinero_create_invoice":{"label":"Dinero: Opret faktura","description":"Opretter en kladdefaktura i Dinero.","group":"integrations","config":{"useOfferLines":{"type":"boolean","description":"Sæt true for at kopiere linjer fra det udløsende tilbud (kræver en tilbuds-trigger). Uden linje-config falder runtime også tilbage til tilbuddets linjer.","storage":"node.data.config"},"lines":{"type":"array","description":"Statiske fakturalinjer: [{ description, quantity, unitPrice, accountNumber?, unit? }]. Placeholders tilladt i description. Alternativt config.mapping.lines/lineItems/invoiceLines.","storage":"node.data.config"},"contactGuid":{"type":"string","description":"Dinero kontakt-GUID. Tomt bliver {{dinero_contact_guid}} fra et tidligere Dinero: Opret kontakt-trin. Alternativt config.mapping.contactGuid.","supportsPlaceholders":true,"storage":"node.data.config"},"mapping":{"type":"object","description":"Valgfri mapping-aliaser: mapping.lines/lineItems/invoiceLines (array eller '{{offer.lineItems}}'), mapping.contactGuid, mapping.useOfferLines","storage":"node.data.config"}},"produces":["dineroInvoice.*"],"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."},"dinero_book_invoice":{"label":"Dinero: Bogfør faktura","description":"Bogfører en faktura i Dinero.","group":"integrations","config":{"invoiceId":{"type":"string","description":"Faktura-ID eller {{placeholder}}","supportsPlaceholders":true,"storage":"node.data.config"}},"consumes":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Klik på action-noden → panelet til højre viser action-typen øverst og dens felter nedenunder."}}},"conditionNode":{"description":"Condition nodes evaluate rules and branch the flow. Edges use sourceHandle 'true' (met) or 'false' (not met).","dataSchema":{"label":{"type":"string","description":"Display label"},"conditions":{"type":"array","description":"Array of { id, field, operator, value, dataType? }"},"logic":{"type":"string","enum":["and","or"],"description":"Combine multiple conditions"}},"operators":[{"key":"equals","label":"equals"},{"key":"not_equals","label":"not equals"},{"key":"contains","label":"contains"},{"key":"not_contains","label":"not contains"},{"key":"starts_with","label":"starts with"},{"key":"ends_with","label":"ends with"},{"key":"matches_regex","label":"matches regex"},{"key":"greater_than","label":"greater than"},{"key":"less_than","label":"less than"},{"key":"greater_than_or_equal","label":"greater than or equal"},{"key":"less_than_or_equal","label":"less than or equal"},{"key":"between","label":"between"},{"key":"is_empty","label":"is empty"},{"key":"is_not_empty","label":"is not empty"},{"key":"exists","label":"exists"},{"key":"not_exists","label":"not exists"},{"key":"is_true","label":"is true"},{"key":"is_false","label":"is false"},{"key":"array_contains","label":"array contains"},{"key":"array_length_equals","label":"array length equals"},{"key":"array_length_greater","label":"array length greater"},{"key":"date_before","label":"date before"},{"key":"date_after","label":"date after"},{"key":"date_equals","label":"date equals"},{"key":"date_within_days","label":"date within days"},{"key":"date_in_exactly_days","label":"date in exactly days"}],"fields":{"conditions":{"type":"array","description":"Liste af { id, field, operator, value }. field er en sti (fx lead.score, deal.value, ai_json.intent).","required":true,"storage":"node.data"},"logic":{"type":"enum","description":"Hvordan flere betingelser kombineres","enum":["and","or"],"default":"and","storage":"node.data"}},"uiGuide":"Klik på betingelses-noden → panelet 'Betingelser' åbner. Hver betingelse har felt-vælger, operator-dropdown og værdi. Husk at trække BÅDE den grønne (true) og røde (false) udgang videre."},"delayNode":{"description":"Delay nodes pause execution for a specified duration before continuing.","dataSchema":{"label":{"type":"string","description":"Display label"},"delayAmount":{"type":"number","required":true,"description":"Amount of time to wait"},"delayUnit":{"type":"string","required":true,"enum":["minutes","hours","days","weeks"],"description":"Time unit (stored in data.config)"}},"note":"Delays are rounded to 5-minute cron intervals.","fields":{"delayAmount":{"type":"number","description":"Antal tidsenheder","default":1,"storage":"node.data.config"},"delayUnit":{"type":"enum","description":"Tidsenhed","enum":["minutes","hours","days","weeks"],"default":"days","storage":"node.data.config"}}},"aiNode":{"description":"AI nodes send execution context to an AI model. Settings live on node.data (not under config). Output available as {{ai_text}} and {{ai_json.*}}.","dataSchema":{"label":{"type":"string","description":"Display label"},"prompt":{"type":"string","required":true,"description":"AI prompt with {{placeholders}}"},"outputMode":{"type":"string","enum":["text","json_schema"],"default":"text"},"reasoningEffort":{"type":"string","enum":["none","low","medium","high"],"default":"low"},"schema":{"type":"object","description":"JSON Schema when outputMode is json_schema"}},"outputContext":{"ai_text":"string","ai_json.*":"structured fields from schema or freeform JSON"},"fields":{"prompt":{"type":"string","description":"Instruktion til AI'en — kritisk. Kan referere {{lead.*}} osv.","required":true,"supportsPlaceholders":true,"storage":"node.data"},"outputMode":{"type":"enum","description":"'text' = fritekst, 'json_schema' = struktureret output der kan forgrenes på","enum":["text","json_schema"],"default":"text","storage":"node.data"},"reasoningEffort":{"type":"enum","description":"Hvor meget AI'en 'tænker' — højere = langsommere/dyrere","enum":["none","low","medium","high"],"default":"low","storage":"node.data"},"schema":{"type":"json","description":"JSON schema for struktureret output (kun ved json_schema). Hvert top-level felt bliver til {{ai_json.<felt>}}. Brug schema-builderen eller en SCHEMA_TEMPLATE.","storage":"node.data"}},"uiGuide":"Klik på AI-noden → skriv prompten i det store felt. For struktureret output: skift 'Output-format' til JSON og byg felterne i schema-sektionen. Output bruges senere som {{ai_json.<felt>}}."},"lookupNode":{"description":"Lookup nodes query records and branch via sourceHandle found/empty/error. Settings live on node.data (NOT under config). mode=first continues once with the record; mode=count only counts; mode=all FANS OUT — one child AutomationExecution per match (downstream runs per record). Fan-out caps are plan-based (no user-editable limit field).","dataSchema":{"label":{"type":"string","description":"Display label; also slugified into an optional lookup.<alias> placeholder namespace"},"entityType":{"type":"string","required":true,"enum":["Lead","Deal","Company","People","Offer","Activity","Task","Booking","Event","SupportTicket","CustomObjectRecord"],"description":"Entity type to query"},"customObjectId":{"type":"string","description":"Required when entityType is CustomObjectRecord"},"mode":{"type":"string","enum":["first","count","all"],"default":"first","description":"first=findOne; count=countDocuments; all=fan-out child executions per entity (plan-capped)"},"filters":{"type":"array","description":"Filter rules [{ field, operator, value }] relative to trigger context"},"sortField":{"type":"string","default":"createdAt","description":"Sort field for first/all"},"sortOrder":{"type":"number","enum":[-1,1],"default":-1,"description":"Mongo sort order (-1 desc, 1 asc). Named sortOrder, not sortDirection."},"changeDetection":{"type":"boolean","default":false,"description":"Snapshot watched fields; unchanged records take the empty handle"},"watchedFields":{"type":"array","description":"Field paths watched when changeDetection is true"}},"entityTypes":["Lead","Deal","Company","People","Offer","Activity","Task","Booking","Event","SupportTicket","CustomObjectRecord"],"branching":{"handles":["found","empty","error"],"describe":"Connect edges with sourceHandle 'found', 'empty', and optionally 'error'. With changeDetection enabled, unchanged records emit 'empty' so the found branch only runs on real changes."},"iteration":"mode=all = fan-out: N child AutomationExecutions (one per record); parent completes. mode=first/count continue once in the same execution.","fanOutLimits":"Effective cap = min(planLimit, 1000). Plan table: Trial/Free 100 · Basic/Starter 500 · Pro+ 1000 (hard safety). No per-node limit field.","outputPlaceholders":["lookup.<nodeId>.<field>","lookup.<nodeId>.count","lookup.<nodeId>.found","lookup.<alias>.<field>","lookup.<nodeId>.changed","lookup.<nodeId>.previous.*"],"fields":{"entityType":{"type":"enum","description":"Hvilken type records der slås op","enum":["Lead","Deal","Company","People","Offer","Activity","Task","Booking","Event","SupportTicket","CustomObjectRecord"],"storage":"node.data"},"customObjectId":{"type":"string","description":"Påkrævet når entityType er CustomObjectRecord — ID på brugerdefineret objekt","storage":"node.data"},"mode":{"type":"enum","description":"first = findOne + continue once with that record in context. count = count only (found if count>0). all = FAN-OUT: creates one child AutomationExecution per matching record (parent completes); downstream nodes run PER record, not once with an array. Cap is plan-based (Free/Trial 100, Basic/Starter 500, Pro 5000, Enterprise 50000), hard-capped at 1000 except higher plans.","enum":["first","count","all"],"default":"first","storage":"node.data"},"filters":{"type":"array","description":"Filtre der afgrænser opslaget ({ field, operator, value }) relative to trigger context","storage":"node.data"},"sortField":{"type":"string","description":"Sort field for first/all modes (entity-specific; default createdAt)","default":"createdAt","storage":"node.data"},"sortOrder":{"type":"number","description":"Mongo sort order: -1 = descending (newest first), 1 = ascending. Field name is sortOrder (NOT sortDirection).","default":-1,"storage":"node.data"},"changeDetection":{"type":"boolean","description":"When true (typically with scheduled + mode=first), compares watchedFields against a stored snapshot. Unchanged → outputHandle empty; changed → found + previous values in context.","default":false,"storage":"node.data"},"watchedFields":{"type":"array","description":"Field paths to watch when changeDetection is on. Empty = first 10 non-internal keys on the document.","storage":"node.data"}},"uiGuide":"Klik på opslag-noden → vælg entityType, mode og filtre. Forbind found/empty (og evt. error) handles. mode=all faner ud til N child-executions — efterfølgende noder kører pr. record. Placeholders: {{lookup.<nodeId>.field}} eller via label-alias."},"flowBuildingGuide":{"layout":"Automations flow top-down (vertical). Sequences flow left-to-right (horizontal).","nodeIds":"Use stable IDs: trigger_1, action_1, condition_1, delay_1, lookup_1, ai_1","branching":"Condition / AI condition: sourceHandle 'true'|'false'. Lookup: sourceHandle 'found'|'empty'|'error'.","storageConvention":"Automation action settings live under node.data.config. Logic nodes (condition, delay, ai, lookup) store settings on node.data (delay also accepts config.*). Sequences store ALL step settings on node.data — never under config.","placeholders":"Use {{entity.field}} syntax. AI output: {{ai_json.field}}, {{ai_text}}. Lookup: {{lookup.<nodeId>.field}} or {{lookup.<alias>.field}}.","compositeTriggers":"Prefer record_created and record_changed over legacy lead_created/deal_created/tag_added triggers.","createRecord":"Prefer create_record action over legacy create_lead/create_deal/create_contact/create_offer.","namingGotcha":"create_task = calendar Event. create_activity = timeline Activity only. create_note uses config.noteContent (not content). update_record uses config.fieldUpdates[{fieldKey,value}] + recordType (not fieldPath+value).","knownLimitations":["No remove_from_sequence (exit via sequence exit conditions).","No merge node; failureBehavior is global (no per-node catch edge).","No delete/archive entity action.","Lookup mode=all is fan-out to child executions — not an in-graph array iterator.","No business-hours gate on automation sends (use sequences for outreach timing)."]},"edgeStructure":{"description":"Edges connect nodes in the flow. Branching nodes require sourceHandle.","schema":{"id":{"type":"string","required":true,"description":"Unique edge ID, e.g. 'edge_trigger_1-action_1'"},"source":{"type":"string","required":true,"description":"Source node ID"},"target":{"type":"string","required":true,"description":"Target node ID"},"sourceHandle":{"type":"string|null","required":false,"description":"Branching handle. Condition / AI condition: 'true' | 'false'. Lookup: 'found' | 'empty' | 'error'. null for non-branching nodes.","enum":["true","false","found","empty","error",null]},"targetHandle":{"type":"string|null","required":false,"description":"Target handle (usually null)"}},"examples":[{"id":"e_cond_true","source":"condition_1","target":"action_1","sourceHandle":"true"},{"id":"e_lookup_found","source":"lookup_1","target":"action_1","sourceHandle":"found"},{"id":"e_lookup_empty","source":"lookup_1","target":"action_2","sourceHandle":"empty"}]},"executionSettings":{"description":"Configure how the automation handles failures","schema":{"failureBehavior":{"type":"string","enum":["stop","continue","retry"],"default":"stop","description":"'stop' halts the flow, 'continue' skips the failed node, 'retry' retries"},"retryMaxAttempts":{"type":"number","default":3,"min":1,"max":10},"retryIntervalMinutes":{"type":"number","default":5,"min":1,"max":1440}}},"rateLimits":{"description":"Limit how often an automation can run","schema":{"maxRunsPerHour":{"type":"number","default":0,"description":"0 = unlimited"},"maxRunsPerDay":{"type":"number","default":0,"description":"0 = unlimited"},"maxRunsPerWeek":{"type":"number","default":0,"description":"0 = unlimited"},"maxTotalRuns":{"type":"number","default":0,"description":"0 = unlimited. Auto-deactivates after this many total runs."}}},"placeholders":{"description":"Placeholders use double curly braces: {{field.path}}. They are resolved from the execution context at runtime.","categories":{"lead":["lead._id","lead.title","lead.email","lead.phone","lead.status","lead.source","lead.score","lead.value","lead.notes","lead.customFields.*"],"company":["company._id","company.name","company.cvr","company.email","company.phone","company.address","company.city","company.zip","company.industry","company.employees","company.website"],"contact":["contact._id","contact.firstName","contact.lastName","contact.fullName","contact.email","contact.phone","contact.mobile","contact.title"],"deal":["deal._id","deal.title","deal.value","deal.stage","deal.probability","deal.priority","deal.customFields.*"],"owner":["owner._id","owner.firstName","owner.lastName","owner.fullName","owner.email","owner.phone"],"call":["call._id","call.duration","call.outcome","call.direction","call.notes"],"email":["email.subject","email.from","email.fromName","email.body"],"sms":["sms.body","sms.from"],"webhook":["webhookData.* (any field from the incoming webhook payload)"],"lookup":["lookup.<nodeId>.items","lookup.<nodeId>.count","lookup.<nodeId>.<field>"],"system":["triggeredAt","entityId","entityType","workspaceId"],"previousSteps":["httpResponse.status","httpResponse.data.*","ai_text","ai_json.*","aiOutput","aiOutput.*"]}},"examples":[{"name":"New lead welcome email + follow-up task","description":"When a lead is created, send a welcome email and create a follow-up task in 3 days","flow":{"name":"Welcome & Follow-up","description":"Sends welcome email on lead creation and creates a 3-day follow-up task","nodes":[{"id":"trigger_1","type":"trigger","position":{"x":500,"y":50},"data":{"triggerType":"lead_created","label":"Lead created"}},{"id":"action_1","type":"action","position":{"x":500,"y":250},"data":{"actionType":"send_email","label":"Send welcome email","config":{"subject":"Welcome {{contact.firstName}}!","body":"<p>Hi {{contact.firstName}},</p><p>Thank you for your interest. We will be in touch soon.</p><p>Best regards,<br>{{owner.firstName}}</p>"}}},{"id":"action_2","type":"action","position":{"x":500,"y":450},"data":{"actionType":"create_task","label":"Follow-up task","config":{"taskTitle":"Follow up with {{contact.firstName}} {{contact.lastName}}","taskDescription":"New lead from {{lead.source}}. Score: {{lead.score}}","dueInDays":3,"assignTo":"owner"}}}],"edges":[{"id":"e1","source":"trigger_1","target":"action_1"},{"id":"e2","source":"action_1","target":"action_2"}]}},{"name":"Deal stage change with condition","description":"When a deal moves to 'Proposal' stage, check if value > 50000 and send different emails","flow":{"name":"High-value deal notification","description":"Notifies the owner differently based on deal value when deal reaches proposal stage","nodes":[{"id":"trigger_1","type":"trigger","position":{"x":500,"y":50},"data":{"triggerType":"deal_stage_changed","label":"Deal stage changed"}},{"id":"condition_1","type":"condition","position":{"x":500,"y":250},"data":{"label":"Deal value > 50000?","conditions":[{"id":"c1","field":"deal.value","operator":"greater_than","value":"50000"}],"logic":"and"}},{"id":"action_1","type":"action","position":{"x":300,"y":450},"data":{"actionType":"send_email","label":"High-value alert","config":{"to":"{{owner.email}}","subject":"High-value deal: {{deal.title}}","body":"<p>Deal {{deal.title}} worth {{deal.value}} has moved to proposal stage!</p>"}}},{"id":"action_2","type":"action","position":{"x":700,"y":450},"data":{"actionType":"create_task","label":"Standard follow-up","config":{"taskTitle":"Follow up on {{deal.title}}","dueInDays":2,"assignTo":"owner"}}}],"edges":[{"id":"e1","source":"trigger_1","target":"condition_1"},{"id":"e2","source":"condition_1","target":"action_1","sourceHandle":"true"},{"id":"e3","source":"condition_1","target":"action_2","sourceHandle":"false"}]}},{"name":"Webhook-triggered lead creation","description":"External system sends webhook data, automation creates a lead and notifies the team","flow":{"name":"Webhook lead import","description":"Creates leads from incoming webhook data and sends a Slack/webhook notification","nodes":[{"id":"trigger_1","type":"trigger","position":{"x":500,"y":50},"data":{"triggerType":"webhook","label":"Webhook received"}},{"id":"action_1","type":"action","position":{"x":500,"y":250},"data":{"actionType":"create_lead","label":"Create lead","config":{"title":"{{webhookData.company_name}} — {{webhookData.contact_name}}","email":"{{webhookData.email}}","phone":"{{webhookData.phone}}","source":"webhook"}}},{"id":"action_2","type":"action","position":{"x":500,"y":450},"data":{"actionType":"send_webhook","label":"Notify team","config":{"webhookUrl":"https://hooks.slack.com/services/YOUR/WEBHOOK/URL","webhookBody":"{\"text\": \"New lead from webhook: {{webhookData.company_name}}\"}"}}}],"edges":[{"id":"e1","source":"trigger_1","target":"action_1"},{"id":"e2","source":"action_1","target":"action_2"}]}},{"name":"AI-powered email response handler","description":"When an email reply is received, use AI to analyze sentiment and route accordingly","flow":{"name":"AI Email Router","description":"Analyzes email replies with AI and creates appropriate tasks based on sentiment","nodes":[{"id":"trigger_1","type":"trigger","position":{"x":500,"y":50},"data":{"triggerType":"email_replied","label":"Email replied"}},{"id":"ai_1","type":"ai","position":{"x":500,"y":250},"data":{"label":"Analyze reply","prompt":"Analyze this email reply and determine the sentiment and intent.\n\nFrom: {{email.fromName}}\nSubject: {{email.subject}}\nBody: {{email.body}}\n\nReturn JSON with sentiment (positive/negative/neutral) and intent (interested/not_interested/question/meeting_request).","outputMode":"json_schema","schema":{"type":"object","properties":{"sentiment":{"type":"string","enum":["positive","negative","neutral"]},"intent":{"type":"string","enum":["interested","not_interested","question","meeting_request"]},"summary":{"type":"string"}},"required":["sentiment","intent","summary"]}}},{"id":"condition_1","type":"condition","position":{"x":500,"y":450},"data":{"label":"Positive sentiment?","conditions":[{"id":"c1","field":"aiOutput.sentiment","operator":"equals","value":"positive"}],"logic":"and"}},{"id":"action_1","type":"action","position":{"x":300,"y":650},"data":{"actionType":"create_task","label":"Priority follow-up","config":{"taskTitle":"Priority: {{contact.firstName}} replied positively — {{aiOutput.summary}}","dueInDays":0,"assignTo":"owner"}}},{"id":"action_2","type":"action","position":{"x":700,"y":650},"data":{"actionType":"create_task","label":"Review reply","config":{"taskTitle":"Review reply from {{contact.firstName}}: {{aiOutput.intent}}","dueInDays":1,"assignTo":"owner"}}}],"edges":[{"id":"e1","source":"trigger_1","target":"ai_1"},{"id":"e2","source":"ai_1","target":"condition_1"},{"id":"e3","source":"condition_1","target":"action_1","sourceHandle":"true"},{"id":"e4","source":"condition_1","target":"action_2","sourceHandle":"false"}]}}],"webhookTriggerUrl":{"description":"To trigger a webhook-based automation externally, send a POST request to:","url":"POST /api/webhooks/automation/{automationId}","body":"Any JSON payload — all fields become available as webhookData.* placeholders","note":"The automation must be active and have a webhook trigger type configured."}},"sequences":{"overview":{"title":"Sequences","description":"Sequences are entity-centric outreach flows for personalized sales communication. Unlike automations (which are event-triggered), sequences are started by enrolling an entity (Lead, People, Deal, or Company). Each sequence has a Start node, communication nodes (email, SMS), timing nodes (wait), action nodes (tasks, recalls, field updates), logic nodes (conditions, AI), and Stop nodes.","sequencesVsAutomations":{"sequences":{"start":"Enrollment (manual or automatic)","purpose":"Outreach campaigns & nurturing","focus":"Email/SMS communication sequences","exit":"Exit conditions (reply, meeting booked, etc.)","timing":"Smart scheduling with business hours"},"automations":{"start":"Trigger (event-based)","purpose":"React to CRM events","focus":"Broad automation of any action","exit":"Runs to completion","timing":"Event-driven, immediate execution"}},"entityTypes":{"Lead":"Leads in the system — cold outreach, nurturing","People":"Contact persons — personal outreach, follow-up","Company":"Companies — account-based outreach","Deal":"Deals in pipeline — deal acceleration, engagement"}},"endpoints":[{"method":"GET","path":"/api/v1/sequences","description":"List all sequences in the workspace","queryParams":{"page":{"type":"number","default":1},"limit":{"type":"number","default":20,"max":100},"isActive":{"type":"string","enum":["true","false"],"description":"Filter by active status"}},"response":{"data":"Sequence[]","page":"number","limit":"number","total":"number"}},{"method":"POST","path":"/api/v1/sequences","description":"Create a new sequence","body":{"name":{"type":"string","required":true,"description":"Sequence name"},"description":{"type":"string","required":false},"nodes":{"type":"Node[]","required":false,"description":"Array of step nodes (see Step Types below). A default Start node is created if nodes are omitted."},"edges":{"type":"Edge[]","required":false,"description":"Array of edges connecting nodes"}},"response":{"data":"Sequence"},"statusCode":201},{"method":"GET","path":"/api/v1/sequences/:id","description":"Get a single sequence by ID","response":{"data":"Sequence"}},{"method":"PUT","path":"/api/v1/sequences/:id","description":"Update a sequence","body":{"name":{"type":"string"},"description":{"type":"string"},"nodes":{"type":"Node[]"},"edges":{"type":"Edge[]"},"allowedEntityTypes":{"type":"string[]","description":"Array of entity types that can be enrolled: 'Lead', 'People', 'Deal', 'Company'"},"exitConditions":{"type":"ExitConditions","description":"See Exit Conditions section"},"sendSettings":{"type":"SendSettings","description":"See Send Settings section"},"trackingSettings":{"type":"TrackingSettings"},"aiSettings":{"type":"AiSettings"},"executionSettings":{"type":"ExecutionSettings"},"triggers":{"type":"Trigger[]","description":"Auto-enrollment triggers"},"defaultSenderType":{"type":"string","enum":["fixed","owner"]},"defaultSenderId":{"type":"string","description":"Email account ID for fixed sender"},"defaultSmsNumber":{"type":"string","description":"Phone number for SMS"}},"response":{"data":"Sequence"}},{"method":"DELETE","path":"/api/v1/sequences/:id","description":"Delete a sequence. Fails if there are active enrollments.","response":{"data":{"deleted":true}}},{"method":"POST","path":"/api/v1/sequences/:id/toggle","description":"Toggle sequence active/inactive. Activation requires a start node and at least one action node.","response":{"data":{"_id":"string","isActive":"boolean"}}},{"method":"POST","path":"/api/v1/sequences/:id/duplicate","description":"Duplicate a sequence (creates inactive copy)","response":{"data":"Sequence"}},{"method":"GET","path":"/api/v1/sequences/:id/enroll-eligibility","description":"Check if an entity can be enrolled before calling enroll","queryParams":{"entityType":{"type":"string","required":true,"enum":["Lead","People","Deal","Company"]},"entityId":{"type":"string","required":true}},"response":{"data":"{ eligible: boolean, reason?: string }"}},{"method":"POST","path":"/api/v1/sequences/:id/enroll","description":"Enroll one or more entities into the sequence","body":{"entityType":{"type":"string","required":true,"enum":["Lead","People","Deal","Company"]},"entityId":{"type":"string","description":"Single entity ID (for single enrollment)"},"entityIds":{"type":"string[]","description":"Array of entity IDs (for bulk enrollment)"},"source":{"type":"string","default":"api","description":"Enrollment source identifier"},"forceRestart":{"type":"boolean","default":false,"description":"Re-enroll even if entity was previously enrolled"}},"response":{"singleEnrollment":{"data":"{ ok: boolean, action: string, enrollmentId: string }"},"bulkEnrollment":{"data":"{ enrolled: number, failed: number, results: [{ entityId, success, error? }] }"}},"statusCode":201,"note":"Use entityId for single enrollment, entityIds (array) for bulk enrollment. The sequence must be active."},{"method":"GET","path":"/api/v1/sequences/:id/enrollments","description":"List enrollments for a sequence","queryParams":{"page":{"type":"number","default":1},"limit":{"type":"number","default":20,"max":100},"status":{"type":"string","enum":["active","paused","completed","exited","switched"]}},"response":{"data":"SequenceEnrollment[]","page":"number","limit":"number","total":"number"}},{"method":"GET","path":"/api/v1/sequences/:id/stats","description":"Get aggregate statistics for a sequence","response":{"data":{"stats":"Sequence.stats object (totalEnrolled, active, completed, exited, etc.)","enrollmentCounts":"[{ _id: status, count: number }]"}}},{"method":"POST","path":"/api/v1/sequences/enrollments/:enrollmentId/pause","description":"Pause an active enrollment","body":{"durationDays":{"type":"number","description":"Optional: auto-resume after X days"}},"response":{"data":{"paused":true}}},{"method":"POST","path":"/api/v1/sequences/enrollments/:enrollmentId/resume","description":"Resume a paused enrollment","response":{"data":{"resumed":true}}},{"method":"POST","path":"/api/v1/sequences/enrollments/:enrollmentId/unenroll","description":"Remove an entity from the sequence (only if active or paused)","response":{"data":{"unenrolled":true}}}],"stepTypes":{"description":"Each sequence node has a type on node.type. Settings live on node.data (not nested under config). Flows are horizontal (left to right).","nodeWidths":{"start":200,"stop":180,"wait":220,"send_email":280,"send_sms":280,"create_task":280,"create_recall":280,"update_field":280,"condition":280,"ai_analyzer":320,"ai_condition":280},"types":{"start":{"label":"Start","description":"Indgangspunktet for sequencen. Præcis én pr. flow.","produces":["lead.*","person.*","company.*","deal.*","owner.*"],"uiGuide":"Start-noden er fast og kan ikke fjernes. Træk den første step ud fra dens højre udgang."},"send_email":{"label":"Send email","description":"Sender en email-step i sequencen.","dataSchema":{"subject":{"type":"string","description":"Emnelinje — kritisk (medmindre template)","required":true},"body":{"type":"string","description":"Email-brødtekst (HTML) — kritisk (medmindre template). Brug rich text med toolbar: vedhæft fil, indsæt billede, variabel, AI Composer.","required":true},"aiBlocks":{"type":"array","description":"Inline AI-blokke [{ id, prompt }] — indsættes som {{ai:id}} i body og genereres pr. modtager ved afsendelse. Prompt kan indeholde variabler."},"attachments":{"type":"array","description":"Vedhæftede filer [{ id, name, s3Key, workspaceFileId }]"},"templateId":{"type":"string","description":"Brug template i stedet for emne/body"},"recipientType":{"type":"enum","description":"Trin 1 'Hvem sender du til?': 'contact'=Person, 'company'=Virksomhed, 'auto'=prøv kontaktperson først, ellers virksomhed. KUN relevant når sequencen accepterer Lead/Deal eller en blanding (Company+People) — Company-only låses til virksomhed, People-only til person. Bekræft valget med brugeren for Lead/Deal/blandede sequences.","enum":["contact","company","auto"],"default":"contact"},"companyRecipientTarget":{"type":"enum","description":"KUN ved virksomheds-flow (recipientType company/auto eller Company-entity). 'contact_person'=send til en person tilknyttet virksomheden, 'company_direct'=send til virksomhedens egen email (fx info@, salg@).","enum":["contact_person","company_direct"],"default":"contact_person"},"companyContactSelection":{"type":"object","description":"KUN når companyRecipientTarget=contact_person. Objekt: { mode: 'first'|'beslutningstagere'|'all'|'byTitle'|'byTag', titleFilter, tagFilter, fallback: 'company_email'|'skip' }. mode vælger hvilke(n) kontaktperson(er) (byTitle kræver titleFilter, byTag kræver tagFilter); fallback bestemmer hvad der sker hvis ingen kontaktperson matcher."},"recipientField":{"type":"enum","description":"Hvilken email-adresse på den valgte modtager: primary=Første email (uanset type), work=Arbejde, cvr=CVR, privat=Privat, faktura=Faktura, support=Support, ai=AI-fundet, all=send separat til ALLE emails, custom=fast adresse/placeholder i customRecipient.","enum":["primary","work","cvr","privat","faktura","support","ai","all","custom"],"default":"primary"},"customRecipient":{"type":"string","description":"KUN når recipientField=custom: fast email eller {{placeholder}} (fx {{contact.email}} eller salg@firma.dk)."},"senderType":{"type":"enum","description":"'fixed' = bestemt konto, 'owner' = entity-ejers konto. Default = sequencens afsender","enum":["fixed","owner"]},"senderAccountId":{"type":"string","description":"Afsenderkonto — sæt via setEmailSender eller emailAccountId i updateNodeConfig (auto-resolver email-adresse)"},"emailMode":{"type":"enum","description":"'manual' = emne+body, 'template' = brug skabelon","enum":["manual","template"],"default":"manual"},"ignoreBusinessHours":{"type":"boolean","description":"Send uden for åbningstid hvis true","default":false},"sendCondition":{"type":"object","description":"Valgfrit: spring email over hvis felt matcher { enabled, field, operator: equals|not_equals, value }"},"unsubscribeLinkEnabled":{"type":"boolean","description":"Tilføj afmeldingslink i footer","default":false},"unsubscribeLinkPreset":{"type":"string","description":"Footer-preset for afmelding","default":"attio_default"}},"produces":["stepHistory.lastEmail.*"],"uiGuide":"Lineære sekvenser vises som vertikal trin-liste (Attio-stil): emne + rich body-kort med bund-toolbar (Vedhæft, Billede, Variabel, AI Composer). Brug AI Composer til personaliserede snippets med {{ai:id}}-markører. Modtageren konfigureres i dialogen 'Opsæt modtager' (åbnes via ⋯ → Leveringsindstillinger): trinnene er 1) 'Hvem sender du til?' (Person/Virksomhed/Auto — kun for Lead/Deal/blandet), 2) ved virksomhed 'Hvem modtager emailen?' (virksomhedens email vs. en kontaktperson), 3) 'Hvilken kontaktperson?' + fallback, 4) 'Hvilken email bruges?'. Højre side viser en live forhåndsvisning når man søger en rigtig person/virksomhed.","example":"Spørg om emne+indhold hvis ingen template. Brug aiBlocks til dynamisk tekst — ikke skriv alt manuelt. Afsender slås op — spørg ikke. Modtager: for Lead/Deal/blandede sequences, bekræft recipientType (person/virksomhed/auto) med brugeren og forklar den beregnede modtager."},"send_sms":{"label":"Send SMS","description":"Sender en SMS-step i sequencen.","dataSchema":{"message":{"type":"string","description":"SMS-tekst — kritisk. Kan indeholde {{ai:id}} AI-blokke.","required":true},"aiBlocks":{"type":"array","description":"Inline AI-blokke [{ id, prompt }] for SMS — genereres pr. modtager"},"recipientType":{"type":"enum","description":"Trin 1 'Hvem sender du til?': 'contact'=Person, 'company'=Virksomhed, 'auto'=prøv kontaktperson først, ellers virksomhed. KUN relevant for Lead/Deal/blandede sequences (Company-only=virksomhed, People-only=person). Bekræft valget med brugeren ved Lead/Deal/blandet.","enum":["contact","company","auto"],"default":"contact"},"companyRecipientTarget":{"type":"enum","description":"KUN ved virksomheds-flow. 'contact_person'=brug telefon fra en person tilknyttet virksomheden, 'company_direct'=brug virksomhedens eget telefonnummer.","enum":["contact_person","company_direct"],"default":"contact_person"},"companyContactSelection":{"type":"object","description":"KUN når companyRecipientTarget=contact_person. { mode: 'first'|'beslutningstagere'|'all'|'byTitle'|'byTag', titleFilter, tagFilter, fallback: 'company_phone'|'skip' }."},"phoneField":{"type":"enum","description":"Hvilket telefonnummer: primary=Første telefon, mobile=Mobil, work=Arbejde, privat=Privat, cvr=CVR, direkte=Direkte, all=alle numre, custom=fast nummer/placeholder i customPhone.","enum":["primary","mobile","work","privat","cvr","direkte","all","custom"],"default":"primary"},"customPhone":{"type":"string","description":"KUN når phoneField=custom: fast nummer eller {{placeholder}} (fx {{contact.phone}} eller +4512345678)."},"smsMode":{"type":"enum","description":"'manual' = besked, 'template' = SMS-skabelon","enum":["manual","template"],"default":"manual"},"templateId":{"type":"string","description":"SMS-skabelon (template mode)"},"ignoreBusinessHours":{"type":"boolean","description":"Send uden for åbningstid hvis true","default":false},"sendCondition":{"type":"object","description":"Valgfrit: spring SMS over hvis felt matcher { enabled, field, operator, value }"},"senderType":{"type":"enum","description":"Afsender-type for SMS","enum":["fixed","owner","alphanumeric"],"default":"fixed"},"fromNumber":{"type":"string","description":"Telefonnummer (fast afsender)"},"senderId":{"type":"string","description":"Alfanumerisk afsender-ID (max 11 tegn)"}},"produces":["stepHistory.lastSms.*"],"uiGuide":"SMS vises som kompakt kort i trin-listen med besked + toolbar (Variabel, AI Composer — ingen vedhæftninger). Modtager konfigureres i 'Opsæt modtager (telefon)' (⋯ → Leveringsindstillinger): samme trin-flow som email, men med telefonnumre og fallback til virksomhedens telefon. Højre side viser live forhåndsvisning."},"wait":{"label":"Vent","description":"Venter et tidsrum før næste step. Canonical fields: delayAmount + delayUnit on node.data (NOT under config). Aliases accepted by the executor: waitAmount/waitUnit, duration/unit, amount, and waitDays/waitHours.","dataSchema":{"delayAmount":{"type":"number","description":"Antal tidsenheder (canonical)","default":1},"delayUnit":{"type":"enum","description":"Tidsenhed (canonical)","enum":["minutes","hours","days","weeks"],"default":"days"}},"uiGuide":"Klik på vent-step'et → angiv delayAmount + delayUnit. Do not use nested config.","example":"data: { delayAmount: 3, delayUnit: \"days\" }"},"create_task":{"label":"Opret kalenderbegivenhed","description":"Creates a calendar Event for the enrolled entity (same calendar model as automation create_task). Field names differ from automations: use taskTitle / taskDescription on node.data (NOT config, NOT title — though title is accepted as a legacy alias).","dataSchema":{"taskTitle":{"type":"string","description":"Event title. Canonical key is taskTitle (legacy alias: title).","required":true},"taskDescription":{"type":"string","description":"Beskrivelse (canonical; not description)"},"activityType":{"type":"enum","description":"Kalender-type","enum":["task","call","meeting","follow_up","email","deadline","reminder"],"default":"task"},"priority":{"type":"enum","description":"Prioritet","enum":["low","medium","high"],"default":"medium"},"dueInDays":{"type":"number","description":"Dage til deadline. Default/runtime: 1. Note: stored 0 currently coerces to 1 (historical || 1) — use 1+ explicitly; do not rely on 0 for 'today'.","default":1},"assignTo":{"type":"enum","description":"Hvem begivenheden tildeles","enum":["owner","creator"],"default":"owner"}},"uiGuide":"Klik på opgave-step'et → udfyld taskTitle, type og dueInDays. Settings on node.data.","example":"data: { taskTitle: \"Ring til {{contact.firstName}}\", dueInDays: 1, priority: \"high\" }"},"create_recall":{"label":"Opret genopkald","description":"Planlægger et genopkald som en step.","dataSchema":{"title":{"type":"string","description":"Recall-titel","required":true},"message":{"type":"string","description":"Noter"},"phoneField":{"type":"enum","description":"Telefonfelt","enum":["primary","mobile","work","custom"],"default":"primary"},"recipientType":{"type":"enum","description":"Modtagertype","enum":["contact","company","auto"],"default":"auto"},"delayDays":{"type":"number","description":"Dage til genopkald","default":0},"callTime":{"type":"string","description":"Tidspunkt HH:mm"},"assignTo":{"type":"string","description":"Tildeles til"}},"uiGuide":"Klik på genopkalds-step'et → udfyld titel, telefonfelt og forsinkelse."},"update_field":{"label":"Opdater felt","description":"Opdaterer ét felt på den enrolled entity. Uses fieldPath + value on node.data (sequences do NOT use automation's fieldUpdates array).","dataSchema":{"fieldPath":{"type":"string","description":"Felt der opdateres (fx status, customFields.score)","required":true},"value":{"type":"string","description":"Ny værdi","required":true}},"uiGuide":"Klik på step'et → vælg felt og angiv ny værdi. Settings on node.data — not under config."},"condition":{"label":"Betingelse","description":"Forgrener sequencen så den udfører FORSKELLIGE handlinger (fx ud fra lead-score eller åben deal). For blot at stoppe ved svar/booking, brug exit conditions i stedet — ikke denne node.","dataSchema":{"field":{"type":"string","description":"Felt der tjekkes (fx lead.score, deal.stage)","required":true},"operator":{"type":"enum","description":"Operator","enum":["equals","not_equals","contains","not_contains","starts_with","ends_with","matches_regex","greater_than","less_than","greater_than_or_equal","less_than_or_equal","between","is_empty","is_not_empty","exists","not_exists","is_true","is_false","array_contains","array_length_equals","array_length_greater","date_before","date_after","date_equals","date_within_days","date_in_exactly_days"],"default":"is_true"},"value":{"type":"string","description":"Sammenligningsværdi (hvis relevant)"}},"branching":{"handles":["true","false"],"describe":"Begge grene bør forbindes."},"uiGuide":"Klik på betingelses-step'et → vælg felt, operator og værdi. Forbind både true- og false-grenen.","example":"field=lead.score, operator=greater_than, value=70 (forgren på score). Brug IKKE til at stoppe ved svar — det gør exit conditions automatisk."},"ai_analyzer":{"label":"AI-analyse","description":"Analyserer kontekst med AI (fx et email-svar) og producerer output til efterfølgende betingelser.","dataSchema":{"prompt":{"type":"string","description":"Analyse-prompt — kritisk","required":true},"outputMode":{"type":"enum","description":"Output-format","enum":["text","json_schema"],"default":"json_schema"},"reasoningEffort":{"type":"enum","description":"Reasoning effort","enum":["none","low","medium","high"],"default":"low"},"schema":{"type":"json","description":"Schema for struktureret output → {{ai_json.<felt>}}"}},"produces":["ai_json.*","ai_text"],"uiGuide":"Klik på AI-analyse-step'et → skriv prompt og byg schema. Forgren derefter med en betingelse på ai_json.<felt>."},"ai_condition":{"label":"AI-betingelse","description":"AI træffer en ja/nej-beslutning og forgrener sequencen. (Manglede tidligere helt i flow-schemas.)","dataSchema":{"prompt":{"type":"string","description":"Kriterie AI skal vurdere — kritisk","required":true},"confidenceThreshold":{"type":"number","description":"Sikkerhedstærskel (0-1)","default":0.7},"useKnowledgeBase":{"type":"boolean","description":"Brug vidensbase","default":false}},"produces":["ai_json.decision","ai_json.confidence"],"branching":{"handles":["true","false"],"describe":"Begge grene bør forbindes."},"uiGuide":"Klik på AI-betingelse-step'et → formulér kriteriet. Forbind true- og false-grenen."},"set_access":{"label":"Tildel adgang","description":"Sætter ejer, synlighed og deling på den enrolled entity.","dataSchema":{"recordType":{"type":"enum","description":"","enum":["trigger"],"default":"trigger"},"accessState":{"type":"object","description":"Adgangskonfiguration","required":true}}},"stop":{"label":"Stop","description":"Afslutter sequencen for entityen.","uiGuide":"Forbind et step til stop-noden for eksplicit at afslutte den gren."}}},"sequenceSettingsGuide":{"exitConditions":"onEmailReply, onSmsReply, onMeetingBooked, onDealStageChange.{enabled,stages[]}, onLeadStatusChange.{enabled,statuses[]}","sendSettings":"businessHoursOnly, businessStart, businessEnd, timezone, skipWeekends, smsQuietHours*, emailIntervalSeconds. Defaults if omitted: businessHoursOnly=false, skipWeekends=false (emails can send on weekends). Recommended DK defaults: businessHoursOnly=true, businessStart=08:00, businessEnd=16:30, timezone=Europe/Copenhagen, skipWeekends=true.","autoEnrollTriggers":"field_change | sequence_event | entity_created — see triggers[] on sequence document","duplicateHandling":"block | block_with_restart (default) | allow_multiple. Independent of automation add_to_sequence.forceRestart: when forceRestart=false and an enrollment is active, the automation skips; sequence duplicateHandling applies to enrollment API / auto-enroll paths.","storageConvention":"ALL sequence step settings live on node.data (never nested under config). Automations put action settings under node.data.config — opposite convention.","waitFields":"Canonical: delayAmount + delayUnit. Aliases: waitAmount/waitUnit, duration/unit, waitDays/waitHours.","createTaskFields":"Canonical: taskTitle, taskDescription, dueInDays, assignTo, priority, activityType. Legacy alias for title: title.","placeholders":"Namespaced only: {{contact.firstName}} — bare {{firstName}} does not resolve."},"exitConditions":{"description":"Exit conditions automatically remove an entity from the sequence when certain events occur.","schema":{"onEmailReply":{"type":"boolean","default":true,"description":"Exit when the entity replies to a sequence email"},"onSmsReply":{"type":"boolean","default":true,"description":"Exit when the entity replies to a sequence SMS"},"onMeetingBooked":{"type":"boolean","default":true,"description":"Exit when a meeting/booking is created for the entity"},"onDealStageChange":{"enabled":{"type":"boolean","default":false},"stages":{"type":"number[]","description":"Array of stage IDs that trigger exit"}},"onLeadStatusChange":{"enabled":{"type":"boolean","default":false},"statuses":{"type":"string[]","description":"Array of status values that trigger exit (e.g. ['won', 'lost'])"}}},"exitReasons":["email_reply","sms_reply","meeting_booked","deal_stage","lead_status","manual","ai_decision"]},"sendSettings":{"description":"Control when messages are sent in the sequence","schema":{"businessHoursOnly":{"type":"boolean","default":false,"description":"Only send during business hours"},"businessStart":{"type":"string","default":"08:00","description":"Business hours start (HH:mm)"},"businessEnd":{"type":"string","default":"16:30","description":"Business hours end (HH:mm)"},"timezone":{"type":"string","default":"Europe/Copenhagen","description":"Timezone for scheduling"},"skipWeekends":{"type":"boolean","default":false,"description":"Skip Saturday and Sunday"},"smsQuietHoursEnabled":{"type":"boolean","default":false,"description":"Enforce SMS quiet hours"},"smsQuietHoursStart":{"type":"string","default":"20:00","description":"SMS quiet hours start"},"smsQuietHoursEnd":{"type":"string","default":"08:00","description":"SMS quiet hours end"}}},"trackingSettings":{"schema":{"trackOpens":{"type":"boolean","default":true,"description":"Track email opens via pixel"},"trackClicks":{"type":"boolean","default":false,"description":"Track link clicks"}}},"executionSettings":{"schema":{"failureBehavior":{"type":"string","enum":["stop","continue","retry"],"default":"stop"},"retryMaxAttempts":{"type":"number","default":3,"min":1,"max":10},"retryIntervalMinutes":{"type":"number","default":5,"min":1,"max":1440}}},"autoEnrollmentTriggers":{"description":"Sequences can automatically enroll entities when specific conditions are met. Configure via the 'triggers' array on the sequence.","triggerTypes":{"field_change":{"description":"Enroll when a field on an entity changes to a specific value","config":{"entityType":{"type":"string","enum":["Lead","People","Deal","Company"]},"field":{"type":"string","description":"Field path to watch"},"operator":{"type":"string","enum":["equals","not_equals","contains"]},"value":{"type":"string"}}},"entity_created":{"description":"Enroll when a new entity is created matching filters","config":{"entityType":{"type":"string","enum":["Lead","People","Deal","Company"]},"filters":{"type":"object","description":"Field-value filters the new entity must match"}}},"sequence_event":{"description":"Enroll when an entity completes or exits another sequence","config":{"sourceSequenceId":{"type":"string","description":"The other sequence ID"},"event":{"type":"string","enum":["completed","exited","any"],"description":"Which event to trigger on"}}}}},"duplicateHandling":{"description":"Controls what happens when an entity is already enrolled","options":{"block":"Reject the enrollment if the entity is already active in this sequence","block_with_restart":"Unenroll from the current run and start fresh (default)","allow_multiple":"Allow multiple concurrent enrollments of the same entity"}},"placeholders":{"description":"Placeholders use double curly braces: {{namespace.field}}. Available in subject, body, message, titles, and descriptions. IMPORTANT: bare {{firstName}} does NOT resolve — use {{contact.firstName}} (or company.*/lead.*/deal.*/owner.*). This differs from some email-template shorthand; sequence context is namespaced.","categories":{"contact":["contact.firstName","contact.lastName","contact.fullName","contact.email","contact.phone","contact.mobile","contact.title"],"company":["company.name","company.cvr","company.email","company.phone","company.address","company.city","company.zip","company.industry","company.employees","company.website"],"lead":["lead._id","lead.title","lead.email","lead.phone","lead.status","lead.source","lead.score","lead.value"],"deal":["deal._id","deal.title","deal.value","deal.stage","deal.probability","deal.priority"],"owner":["owner.firstName","owner.lastName","owner.fullName","owner.email","owner.phone"],"workspace":["workspace.name"],"system":["currentDate","currentTime","enrolledAt"],"customFields":["customFields.field_key (any custom field defined on the entity)"],"aiOutput":["aiOutput (full AI response)","aiOutput.fieldName (structured field from AI json_schema output)"],"stepHistory":["stepHistory.lastEmail.replied","stepHistory.lastEmail.opened","stepHistory.lastSms.replied"]},"formatting":{"description":"Placeholders support default values and formatting","syntax":["{{contact.firstName}} — basic placeholder (required namespace)","{{contact.firstName|default:Customer}} — fallback value if empty","{{deal.value|format:currency}} — format as currency","{{currentDate|format:date}} — format as date"]},"commonMistakes":["{{firstName}} → use {{contact.firstName}}","Do not use automation-style {{person.firstName}} unless that namespace is present in enrollment context"]},"examples":[{"name":"3-email cold outreach","description":"Send 3 emails spaced 3 days apart with a stop at the end","flow":{"name":"Cold Outreach 3-Touch","description":"3-email cold outreach sequence with 3-day intervals","nodes":[{"id":"start_1","type":"start","position":{"x":50,"y":300},"data":{"label":"Start"}},{"id":"email_1","type":"send_email","position":{"x":330,"y":300},"data":{"label":"First email","subject":"Hi {{contact.firstName}} — quick question","body":"<p>Hi {{contact.firstName}},</p><p>I noticed {{company.name}} might benefit from our solution. Would you be open to a quick chat this week?</p><p>Best,<br>{{owner.firstName}}</p>"}},{"id":"wait_1","type":"wait","position":{"x":690,"y":300},"data":{"label":"Wait 3 days","delayAmount":3,"delayUnit":"days"}},{"id":"email_2","type":"send_email","position":{"x":990,"y":300},"data":{"label":"Follow-up","subject":"Re: Hi {{contact.firstName}} — quick question","body":"<p>Hi {{contact.firstName}},</p><p>Just following up on my last email. I'd love to show you how we've helped similar companies in {{company.industry}}.</p><p>Best,<br>{{owner.firstName}}</p>"}},{"id":"wait_2","type":"wait","position":{"x":1350,"y":300},"data":{"label":"Wait 3 days","delayAmount":3,"delayUnit":"days"}},{"id":"email_3","type":"send_email","position":{"x":1650,"y":300},"data":{"label":"Final attempt","subject":"Last try — {{contact.firstName}}","body":"<p>Hi {{contact.firstName}},</p><p>I don't want to be a bother. If now isn't the right time, no worries at all. Just let me know if you'd like to connect in the future.</p><p>Best,<br>{{owner.firstName}}</p>"}},{"id":"stop_1","type":"stop","position":{"x":2010,"y":300},"data":{"label":"Stop"}}],"edges":[{"id":"e1","source":"start_1","target":"email_1"},{"id":"e2","source":"email_1","target":"wait_1"},{"id":"e3","source":"wait_1","target":"email_2"},{"id":"e4","source":"email_2","target":"wait_2"},{"id":"e5","source":"wait_2","target":"email_3"},{"id":"e6","source":"email_3","target":"stop_1"}],"exitConditions":{"onEmailReply":true,"onMeetingBooked":true},"sendSettings":{"businessHoursOnly":true,"skipWeekends":true,"timezone":"Europe/Copenhagen"}}},{"name":"Multi-channel with condition","description":"Email then SMS with a condition check for email open","flow":{"name":"Multi-channel Follow-up","description":"Sends email, waits 2 days, checks if opened, then sends SMS or second email","nodes":[{"id":"start_1","type":"start","position":{"x":50,"y":300},"data":{"label":"Start"}},{"id":"email_1","type":"send_email","position":{"x":330,"y":300},"data":{"label":"Initial email","subject":"{{contact.firstName}}, quick intro","body":"<p>Hi {{contact.firstName}},</p><p>I wanted to introduce myself and our solution for {{company.name}}.</p><p>{{owner.firstName}}</p>"}},{"id":"wait_1","type":"wait","position":{"x":690,"y":300},"data":{"label":"Wait 2 days","delayAmount":2,"delayUnit":"days"}},{"id":"condition_1","type":"condition","position":{"x":990,"y":300},"data":{"label":"Email opened?","conditions":[{"field":"stepHistory.lastEmail.opened","operator":"is_true"}],"logic":"and"}},{"id":"sms_1","type":"send_sms","position":{"x":1350,"y":150},"data":{"label":"SMS follow-up","message":"Hi {{contact.firstName}}, I sent you an email about {{company.name}}. Would love to chat briefly — {{owner.firstName}}","phoneField":"primary"}},{"id":"email_2","type":"send_email","position":{"x":1350,"y":450},"data":{"label":"Re-engage email","subject":"Did you see my email, {{contact.firstName}}?","body":"<p>Hi {{contact.firstName}},</p><p>I sent an email a couple of days ago but wanted to make sure it didn't get lost. Happy to chat whenever works for you.</p><p>{{owner.firstName}}</p>"}},{"id":"task_1","type":"create_task","position":{"x":1710,"y":300},"data":{"label":"Manual follow-up","taskTitle":"Call {{contact.firstName}} at {{company.name}}","taskDescription":"Sequence complete — time for a personal touch","dueInDays":1,"assignTo":"owner"}},{"id":"stop_1","type":"stop","position":{"x":2070,"y":300},"data":{"label":"Stop"}}],"edges":[{"id":"e1","source":"start_1","target":"email_1"},{"id":"e2","source":"email_1","target":"wait_1"},{"id":"e3","source":"wait_1","target":"condition_1"},{"id":"e4","source":"condition_1","target":"sms_1","sourceHandle":"true"},{"id":"e5","source":"condition_1","target":"email_2","sourceHandle":"false"},{"id":"e6","source":"sms_1","target":"task_1"},{"id":"e7","source":"email_2","target":"task_1"},{"id":"e8","source":"task_1","target":"stop_1"}],"exitConditions":{"onEmailReply":true,"onSmsReply":true,"onMeetingBooked":true},"sendSettings":{"businessHoursOnly":true,"skipWeekends":true}}},{"name":"AI-powered reply analysis","description":"Send email, wait for reply, use AI to analyze and route","flow":{"name":"AI Reply Analyzer","description":"Sends outreach email, waits for reply, uses AI to determine next action","nodes":[{"id":"start_1","type":"start","position":{"x":50,"y":300},"data":{"label":"Start"}},{"id":"email_1","type":"send_email","position":{"x":330,"y":300},"data":{"label":"Outreach email","subject":"{{contact.firstName}} — idea for {{company.name}}","body":"<p>Hi {{contact.firstName}},</p><p>I have an idea that could help {{company.name}} save time. Worth a 15-min call?</p><p>{{owner.firstName}}</p>"}},{"id":"wait_1","type":"wait","position":{"x":690,"y":300},"data":{"label":"Wait 5 days","delayAmount":5,"delayUnit":"days"}},{"id":"ai_1","type":"ai_condition","position":{"x":990,"y":300},"data":{"label":"Got a reply?","prompt":"Has the contact {{contact.firstName}} at {{company.name}} replied to our last email? Check the step history for any email reply.","reasoningEffort":"low"}},{"id":"ai_2","type":"ai_analyzer","position":{"x":1350,"y":150},"data":{"label":"Analyze reply","prompt":"Analyze the reply from {{contact.firstName}}. Determine if they are interested, need more info, or are declining. Reply history: {{stepHistory.lastEmail.replyBody}}","outputMode":"json_schema","schema":{"type":"object","properties":{"intent":{"type":"string","enum":["interested","needs_info","declining","out_of_office"]},"suggestedAction":{"type":"string"}},"required":["intent","suggestedAction"]}}},{"id":"task_1","type":"create_task","position":{"x":1750,"y":150},"data":{"label":"Act on reply","taskTitle":"{{contact.firstName}} replied: {{aiOutput.intent}} — {{aiOutput.suggestedAction}}","dueInDays":0,"assignTo":"owner"}},{"id":"email_2","type":"send_email","position":{"x":1350,"y":450},"data":{"label":"Bump email","subject":"Re: {{contact.firstName}} — idea for {{company.name}}","body":"<p>Hi {{contact.firstName}},</p><p>Just bumping this to the top of your inbox. Let me know if you'd like to chat.</p><p>{{owner.firstName}}</p>"}},{"id":"stop_1","type":"stop","position":{"x":2110,"y":300},"data":{"label":"Stop"}}],"edges":[{"id":"e1","source":"start_1","target":"email_1"},{"id":"e2","source":"email_1","target":"wait_1"},{"id":"e3","source":"wait_1","target":"ai_1"},{"id":"e4","source":"ai_1","target":"ai_2","sourceHandle":"true"},{"id":"e5","source":"ai_1","target":"email_2","sourceHandle":"false"},{"id":"e6","source":"ai_2","target":"task_1"},{"id":"e7","source":"task_1","target":"stop_1"},{"id":"e8","source":"email_2","target":"stop_1"}],"exitConditions":{"onEmailReply":false,"onMeetingBooked":true}}}],"completeWorkflow":{"title":"Building a sequence via API — step by step","steps":[{"step":1,"title":"Create the sequence","description":"POST /api/v1/sequences with name and the full flow (nodes + edges)","note":"Include all nodes and edges in the initial creation, or create empty and update later."},{"step":2,"title":"Configure settings","description":"PUT /api/v1/sequences/:id with exitConditions, sendSettings, allowedEntityTypes","note":"Set exit conditions (onEmailReply, onMeetingBooked) and send settings (business hours, timezone)."},{"step":3,"title":"Activate the sequence","description":"POST /api/v1/sequences/:id/toggle to activate","note":"The sequence must have a start node and at least one action node."},{"step":4,"title":"Enroll entities","description":"POST /api/v1/sequences/:id/enroll with entityType and entityId or entityIds","note":"Entities begin at the start node and progress through the flow automatically."},{"step":5,"title":"Monitor progress","description":"GET /api/v1/sequences/:id/enrollments and /api/v1/sequences/:id/stats","note":"Check enrollment statuses and aggregate statistics."}]}},"endpoints":{"overview":{"baseUrl":"/api/v1","authentication":"All endpoints require Authorization: Bearer <API_KEY> header (or ?api_key= query param)","responseFormat":{"success":"{ data: <result>, page?, limit?, total? }","error":"{ error: <message string>, ...details }"},"pagination":"Most list endpoints accept page (default 1) and limit (default 20, max 100) query params","integrationOnly":"Denne reference dækker workspace API-nøgler til integrationer. Mobilapp-, session- og platform-endpoints er ikke dokumenteret her."},"groups":{"inbound":{"title":"Inbound (unified creation)","description":"Create companies, people, leads, and deals in one request. Ideal for form submissions and integrations.","endpoints":[{"method":"POST","path":"/api/v1/inbound","description":"Create or find-and-update company/person/lead/deal from a single payload. Supports JSON, form-urlencoded, and Elementor webhook formats.","body":{"companyName":{"type":"string","description":"Company name (for company lookup/creation)"},"companyCvr":{"type":"string","description":"Danish CVR number (for exact company match)"},"companyEmail":{"type":"string"},"companyPhone":{"type":"string"},"personFirstName":{"type":"string"},"personLastName":{"type":"string"},"personEmail":{"type":"string"},"personPhone":{"type":"string"},"personTitle":{"type":"string"},"leadTitle":{"type":"string","description":"Lead title (auto-generated if not provided)"},"leadSource":{"type":"string"},"leadStatus":{"type":"string"},"leadNotes":{"type":"string"},"dealTitle":{"type":"string"},"dealValue":{"type":"number"},"dealFlowId":{"type":"string","description":"Pipeline ID"},"dealStageId":{"type":"string","description":"Stage ID within pipeline"},"customFields":{"type":"object","description":"Key-value pairs for custom fields"},"tags":{"type":"string[]","description":"Array of tag names to apply"}},"response":"{ data: { company, person, lead, deal, flow }, created: { company: bool, person: bool, lead: bool, deal: bool } }"},{"method":"GET","path":"/api/v1/inbound","description":"Returns API documentation for the inbound endpoint"}]},"session":{"title":"Session","description":"Identity for the authenticated API key or MCP session.","endpoints":[{"method":"GET","path":"/api/v1/whoami","description":"Return workspace name, user name, API key prefix, and effective scopes for the current session. Any valid key or MCP token works (no extra scope required). MCP clients should call this first.","response":"{ data: { workspaceId, workspaceName, workspace: { id, name, companyName, plan }, userId, userName, userEmail, user: { id, firstName, lastName, name, email }, apiKeyPrefix, apiKeyName, authType, scopes, scopeLabels, scopeDescriptions, rateLimits } }"},{"method":"GET","path":"/api/v1/docs","description":"API documentation index or a section. Query: section, subsection, type, index=true, full=true. No authentication required."},{"method":"GET","path":"/api/v1/docs/markdown","description":"Full API documentation as Markdown."}]},"search":{"title":"Search","endpoints":[{"method":"GET","path":"/api/v1/search","description":"Search across CRM entities, projects, offers, and tasks","queryParams":{"q":{"type":"string","required":true,"description":"Search query (minimum 2 characters)"},"types":{"type":"string","description":"Comma-separated entity types: company,person,lead,deal,support-ticket,project,offer,task (default: all)"},"limit":{"type":"number","default":10,"description":"Max results per entity type (max 50)"}}}]},"companies":{"title":"Companies","endpoints":[{"method":"GET","path":"/api/v1/companies","description":"List companies","queryParams":{"page":{},"limit":{},"search":{"type":"string"}}},{"method":"POST","path":"/api/v1/companies","description":"Create a company","body":{"name":{"type":"string","required":true},"cvr":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"city":{"type":"string"},"zip":{"type":"string"},"country":{"type":"string"},"website":{"type":"string"},"industry":{"type":"string"},"employees":{"type":"number"},"customFields":{"type":"array"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/companies/:id","description":"Get company by ID"},{"method":"PUT","path":"/api/v1/companies/:id","description":"Update company"},{"method":"DELETE","path":"/api/v1/companies/:id","description":"Delete company"},{"method":"GET","path":"/api/v1/companies/:id/people","description":"List people connected to company"},{"method":"GET","path":"/api/v1/companies/:id/deals","description":"List deals connected to company"},{"method":"GET","path":"/api/v1/companies/:id/activities","description":"List activities for company"},{"method":"GET","path":"/api/v1/companies/:id/activity-feed","description":"Unified activity feed for company"},{"method":"POST","path":"/api/v1/companies/:id/people","description":"Link person to company"},{"method":"DELETE","path":"/api/v1/companies/:id/people","description":"Unlink person from company"}]},"people":{"title":"People (contacts)","endpoints":[{"method":"GET","path":"/api/v1/people","description":"List people"},{"method":"POST","path":"/api/v1/people","description":"Create a person","body":{"firstName":{"type":"string","required":true},"lastName":{"type":"string"},"emails":{"type":"array","description":"[{ email, label }]"},"phones":{"type":"array","description":"[{ phone, label }]"},"title":{"type":"string"},"companyId":{"type":"string"},"customFields":{"type":"array"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/people/:id","description":"Get person by ID"},{"method":"PUT","path":"/api/v1/people/:id","description":"Update person"},{"method":"DELETE","path":"/api/v1/people/:id","description":"Delete person"},{"method":"GET","path":"/api/v1/people/:id/companies","description":"List companies connected to person"},{"method":"GET","path":"/api/v1/people/:id/deals","description":"List deals connected to person"},{"method":"GET","path":"/api/v1/people/:id/activities","description":"List activities for person"},{"method":"GET","path":"/api/v1/people/:id/activity-feed","description":"Unified activity feed for person"}]},"leads":{"title":"Leads","endpoints":[{"method":"GET","path":"/api/v1/leads","description":"List leads","queryParams":{"page":{},"limit":{},"status":{"type":"string"},"source":{"type":"string"},"search":{"type":"string"}}},{"method":"POST","path":"/api/v1/leads","description":"Create a lead","body":{"title":{"type":"string","required":true},"email":{"type":"string"},"phone":{"type":"string"},"status":{"type":"string"},"source":{"type":"string"},"score":{"type":"number"},"value":{"type":"number"},"notes":{"type":"string"},"companyRef":{"type":"string","description":"Company ID"},"personRef":{"type":"string","description":"Person ID"},"customFields":{"type":"object"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/leads/:id","description":"Get lead by ID"},{"method":"PUT","path":"/api/v1/leads/:id","description":"Update lead"},{"method":"DELETE","path":"/api/v1/leads/:id","description":"Delete lead"},{"method":"GET","path":"/api/v1/leads/:id/activity-feed","description":"Unified activity feed for lead"},{"method":"GET","path":"/api/v1/leads/:id/activities","description":"List activities for lead"}]},"deals":{"title":"Deals","endpoints":[{"method":"GET","path":"/api/v1/deals","description":"List deals. Unknown query params return 400. Use view=summary for slim payloads; updatedSince for incremental sync.","queryParams":{"page":{},"limit":{},"q":{"type":"string"},"status":{"type":"string","enum":["open","won","lost"]},"flowId":{"type":"string","description":"Pipeline id (alias: flowRef)"},"stageId":{"type":"string","description":"Stage id/number (alias: stage)"},"ownerId":{"type":"string","description":"Owner user id (alias: owner)"},"sortBy":{"type":"string","description":"title|value|updatedAt|createdAt|status|lostDate|wonDate|stage"},"sortOrder":{"type":"string","enum":["asc","desc"]},"view":{"type":"string","enum":["summary","full"],"default":"full"},"fields":{"type":"string"},"updatedSince":{"type":"string","description":"ISO — updatedAt >="},"createdAfter":{"type":"string"},"dateFrom":{"type":"string","description":"createdAt >= (aliases: startDate, from)"},"dateTo":{"type":"string","description":"createdAt <= (aliases: endDate, to)"}},"response":"{ data, page, limit, total, view }"},{"method":"POST","path":"/api/v1/deals","description":"Create a deal","body":{"title":{"type":"string","required":true},"value":{"type":"number"},"flowId":{"type":"string"},"stageId":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high"]},"probability":{"type":"number"},"company":{"type":"string","description":"Company ID"},"personRef":{"type":"string","description":"Person ID"},"leadRef":{"type":"string","description":"Lead ID"},"customFields":{"type":"object"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/deals/:id","description":"Get deal by ID"},{"method":"PUT","path":"/api/v1/deals/:id","description":"Update deal (logs field/stage changes to activity feed, same as UI)"},{"method":"DELETE","path":"/api/v1/deals/:id","description":"Delete deal"},{"method":"POST","path":"/api/v1/deals/:id/move","description":"Move deal to a different stage","body":{"stageId":{"type":"string","required":true},"flowId":{"type":"string"}}},{"method":"GET","path":"/api/v1/deals/:id/activities","description":"List activities for deal"},{"method":"GET","path":"/api/v1/deals/:id/activity-feed","description":"Unified activity feed for deal"}]},"activities":{"title":"Activities","description":"Aktiviteter / activity feed — list and create feed rows (noter, opkald, deals m.m.)","endpoints":[{"method":"GET","path":"/api/v1/activities","description":"List activities (aktiviteter). Unknown query params return 400.","queryParams":{"page":{},"limit":{},"type":{"type":"string"},"companyId":{},"personId":{},"dealId":{},"leadId":{},"dateFrom":{"type":"string","description":"Activity date >= (aliases: startDate, from)"},"dateTo":{"type":"string","description":"Activity date <= (aliases: endDate, to)"},"updatedSince":{},"createdAfter":{},"view":{"type":"string","enum":["summary","full"]},"q":{},"ownerId":{}},"response":"{ data, page, limit, total, view }"},{"method":"POST","path":"/api/v1/activities","description":"Create activity / aktivitet (creates a feed row). Body: type (required), summary or title, description, companyId/personId/dealId/leadId, entityType+entityId, noteRef, date, metadata"},{"method":"GET","path":"/api/v1/activities/:id","description":"Get activity by ID (aktivitet)"}]},"tasks":{"title":"Tasks","endpoints":[{"method":"GET","path":"/api/v1/tasks","description":"List tasks. status enum includes to_do (not open).","queryParams":{"page":{},"limit":{},"status":{"type":"string","enum":["to_do","in_progress","done","cancelled"]},"priority":{},"ownerId":{"description":"Assignee (aliases: owner, assignee)"},"project":{},"q":{},"view":{},"updatedSince":{},"dateFrom":{},"dateTo":{}}},{"method":"POST","path":"/api/v1/tasks","description":"Create task"},{"method":"GET","path":"/api/v1/tasks/:id","description":"Get task"},{"method":"PUT","path":"/api/v1/tasks/:id","description":"Update task"},{"method":"DELETE","path":"/api/v1/tasks/:id","description":"Delete task"}]},"notes":{"title":"Notes","endpoints":[{"method":"GET","path":"/api/v1/notes","description":"List notes. Supports q search; unknown params → 400.","queryParams":{"page":{},"limit":{},"q":{},"personRef":{},"company":{},"leadRef":{},"dealRef":{},"dateFrom":{},"dateTo":{},"updatedSince":{},"createdAfter":{},"view":{"type":"string","enum":["summary","full"]}}},{"method":"POST","path":"/api/v1/notes","description":"Create note (also creates linked activity for Noter tab / activity feed). Requires entity ref: company, personRef, leadRef, dealRef, or customObjectRecord"},{"method":"GET","path":"/api/v1/notes/:id","description":"Get note"},{"method":"PUT","path":"/api/v1/notes/:id","description":"Update note (syncs linked activity)"},{"method":"DELETE","path":"/api/v1/notes/:id","description":"Delete note (removes linked activity)"}]},"calls":{"title":"Calls","endpoints":[{"method":"GET","path":"/api/v1/calls","description":"List calls"},{"method":"POST","path":"/api/v1/calls","description":"Create call record (also creates call activity in feed)"},{"method":"GET","path":"/api/v1/calls/:id","description":"Get call by ID including transcriptText, transcriptSummary, and slim conversation turns (speaker/text/timing). Recording download URLs are JWT/session-only.","response":"{ data: { _id, subject, phone, status, direction, duration, transcriptText, transcriptSummary, transcriptStatus, hasTranscript, conversation[], conversationTruncated, languageCode, hasRecording, recordingPending, … } }"},{"method":"PUT","path":"/api/v1/calls/:id","description":"Update call (syncs linked activity)"},{"method":"DELETE","path":"/api/v1/calls/:id","description":"Delete call (removes linked activity)"}]},"events":{"title":"Events (calendar)","endpoints":[{"method":"GET","path":"/api/v1/events","description":"List events"},{"method":"POST","path":"/api/v1/events","description":"Create event"},{"method":"GET","path":"/api/v1/events/:id","description":"Get event"},{"method":"PUT","path":"/api/v1/events/:id","description":"Update event"},{"method":"DELETE","path":"/api/v1/events/:id","description":"Delete event"}]},"tags":{"title":"Tags","endpoints":[{"method":"GET","path":"/api/v1/tags","description":"List unique tags","queryParams":{"entityType":{"type":"string","enum":["company","person","deal","lead"]}}},{"method":"POST","path":"/api/v1/tags/bulk","description":"Bulk add/remove tags on multiple entities"}]},"customFields":{"title":"Custom Fields","endpoints":[{"method":"GET","path":"/api/v1/custom-fields","description":"List custom field definitions","queryParams":{"entityType":{"type":"string","enum":["lead","deal","company","person"]}}},{"method":"POST","path":"/api/v1/custom-fields","description":"Create custom field definition"},{"method":"GET","path":"/api/v1/custom-fields/:id","description":"Get custom field"},{"method":"PUT","path":"/api/v1/custom-fields/:id","description":"Update custom field"},{"method":"DELETE","path":"/api/v1/custom-fields/:id","description":"Delete custom field"},{"method":"POST","path":"/api/v1/custom-fields/search","description":"Search entities by custom field values"},{"method":"GET","path":"/api/v1/custom-fields/search","description":"Returns search endpoint documentation"}]},"flows":{"title":"Flows & Stages (Pipelines)","endpoints":[{"method":"GET","path":"/api/v1/flows","description":"List pipelines"},{"method":"POST","path":"/api/v1/flows","description":"Create pipeline"},{"method":"GET","path":"/api/v1/flows/:id","description":"Get pipeline"},{"method":"PUT","path":"/api/v1/flows/:id","description":"Update pipeline"},{"method":"DELETE","path":"/api/v1/flows/:id","description":"Delete pipeline"},{"method":"POST","path":"/api/v1/flows/:id/stages","description":"Create stage in pipeline"},{"method":"PUT","path":"/api/v1/flows/:id/stages","description":"Update stage","body":{"id":{"type":"string","required":true},"title":{"type":"string"},"order":{"type":"number"}}},{"method":"DELETE","path":"/api/v1/flows/:id/stages","description":"Delete stage","body":{"id":{"type":"string","required":true}}}]},"lists":{"title":"Lists & Items","endpoints":[{"method":"GET","path":"/api/v1/lists","description":"List all lists"},{"method":"POST","path":"/api/v1/lists","description":"Create list"},{"method":"GET","path":"/api/v1/lists/:id","description":"Get list"},{"method":"PUT","path":"/api/v1/lists/:id","description":"Update list"},{"method":"DELETE","path":"/api/v1/lists/:id","description":"Delete list"},{"method":"GET","path":"/api/v1/lists/:id/items","description":"List items in a list"},{"method":"POST","path":"/api/v1/lists/:id/items","description":"Add items to list"},{"method":"DELETE","path":"/api/v1/lists/:id/items","description":"Remove items from list"}]},"emails":{"title":"Emails","endpoints":[{"method":"GET","path":"/api/v1/emails","description":"List emails"},{"method":"POST","path":"/api/v1/emails","description":"Send email"},{"method":"GET","path":"/api/v1/emails/:id","description":"Get email"},{"method":"GET","path":"/api/v1/emails/:id/calendar-invite","description":"Get calendar invite for email"},{"method":"POST","path":"/api/v1/emails/:id/calendar-invite","description":"RSVP to calendar invite"},{"method":"GET","path":"/api/v1/emails/threads/:threadId","description":"Get email thread"}]},"emailAccounts":{"title":"Email Accounts","endpoints":[{"method":"GET","path":"/api/v1/email-accounts","description":"List connected email accounts"}]},"emailTemplates":{"title":"Email Templates","description":"Create and manage email templates with custom HTML (compiledHtml). Supports {{contact.*}} placeholders and default filter syntax {{field | default:\"fallback\"}}.","endpoints":[{"method":"GET","path":"/api/v1/email-templates","description":"List email templates"},{"method":"POST","path":"/api/v1/email-templates","description":"Create email template (admin scope)","body":{"name":{"type":"string","required":true},"category":{"type":"string","enum":["marketing","transactional","quote","other"]},"compiledHtml":{"type":"string","description":"Full HTML body with {{placeholders}}"},"plainText":{"type":"string"},"metadata":{"type":"object","description":"{ subject, preheader }"},"layoutSettings":{"type":"object","description":"containerWidth, backgroundColor, layoutMode"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/email-templates/:id","description":"Get template by ID (includes compiledHtml, projectData)"},{"method":"PATCH","path":"/api/v1/email-templates/:id","description":"Update template (admin scope)"},{"method":"DELETE","path":"/api/v1/email-templates/:id","description":"Delete template (admin scope)"}],"aiGuide":"/api/v1/docs?section=offerTemplates"},"sms":{"title":"SMS","description":"Requires API key scope `sms` (not covered by `write` alone).","endpoints":[{"method":"GET","path":"/api/v1/sms","description":"List SMS messages. Unknown query params return 400. Use view=summary for slim payloads. Requires scope: sms."},{"method":"POST","path":"/api/v1/sms","description":"Send SMS. Requires scope: sms.","body":{"to":{"type":"string","required":true},"message":{"type":"string","required":true},"from":{"type":"string","description":"Sender number"}}},{"method":"GET","path":"/api/v1/sms/:id","description":"Get SMS by ID (Mongo ObjectId). Requires scope: sms."},{"method":"PUT","path":"/api/v1/sms/:id","description":"Update SMS flags (unread, starred). Requires scope: sms."},{"method":"GET","path":"/api/v1/sms/conversations","description":"List SMS conversation threads (one row per counterparty phone). Alias of /sms/threads. Requires scope: sms."},{"method":"GET","path":"/api/v1/sms/threads","description":"List SMS conversation threads (same as /sms/conversations). Requires scope: sms."},{"method":"POST","path":"/api/v1/sms/bulk","description":"Send bulk SMS. Requires scope: sms."},{"method":"POST","path":"/api/v1/sms/schedule","description":"Schedule SMS for later. Requires scope: sms."},{"method":"GET","path":"/api/v1/sms/scheduled","description":"List scheduled SMS. Requires scope: sms."},{"method":"DELETE","path":"/api/v1/sms/scheduled/:id","description":"Cancel scheduled SMS. Requires scope: sms."},{"method":"GET","path":"/api/v1/sms/threads/:phoneNumber","description":"Get SMS thread by phone number (messages + resolved contact). Requires scope: sms."},{"method":"PUT","path":"/api/v1/sms/threads/:phoneNumber","description":"Mark inbound unread messages in the thread as read. Body: { unread: false }. Requires scope: sms."}]},"smsTemplates":{"title":"SMS Templates","endpoints":[{"method":"GET","path":"/api/v1/sms-templates","description":"List SMS templates"}]},"webhooks":{"title":"Webhook Subscriptions","description":"Subscribe to CRM events via outbound webhooks. See the Webhooks section for available events.","endpoints":[{"method":"GET","path":"/api/v1/webhooks/subscriptions","description":"List webhook subscriptions","response":"{ data: items[], total, availableEvents: string[] }"},{"method":"POST","path":"/api/v1/webhooks/subscriptions","description":"Create webhook subscription","body":{"url":{"type":"string","required":true},"events":{"type":"string[]","required":true,"description":"Array of event names to subscribe to"},"name":{"type":"string"},"active":{"type":"boolean","default":true}},"response":"{ data: { _id, url, events, name, active, secret, createdAt }, warning: 'Store the secret...' }"},{"method":"GET","path":"/api/v1/webhooks/subscriptions/:id","description":"Get subscription (secret masked)"},{"method":"PUT","path":"/api/v1/webhooks/subscriptions/:id","description":"Update subscription"},{"method":"DELETE","path":"/api/v1/webhooks/subscriptions/:id","description":"Delete subscription"},{"method":"POST","path":"/api/v1/webhooks/zapier","description":"Zapier inbound webhook — creates person, lead, and deal in one request (Bearer API key)","body":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"leadTitle":{"type":"string"},"dealTitle":{"type":"string"},"dealValue":{"type":"number"},"source":{"type":"string","default":"zapier"}},"response":"{ data: { person, lead, deal } }"}]},"crmConnect":{"title":"CRM Connect","description":"Lookup and connect CRM data with external systems","endpoints":[{"method":"GET","path":"/api/v1/crm-connect/lookup","description":"Look up entity by phone/email"},{"method":"POST","path":"/api/v1/crm-connect/calls","description":"Log or connect call events for integration"}]},"offers":{"title":"Offers (quotes/proposals)","description":"Create draft offers linked to offer types and templates. Send creates an immutable snapshot and public link.","endpoints":[{"method":"GET","path":"/api/v1/offers","description":"List offers"},{"method":"POST","path":"/api/v1/offers","description":"Create offer","body":{"title":{"type":"string","required":true},"offerTypeId":{"type":"ObjectId"},"templateId":{"type":"ObjectId"},"companyRef":{"type":"ObjectId"},"personRef":{"type":"ObjectId"},"dealRef":{"type":"ObjectId"},"quoteInput":{"type":"object","description":"{ contact, address, facts, calculatorInputs }"},"lineItems":{"type":"array","description":"Preferred write field for offer lines. Each item: name (or description), quantity, unitPrice, optional unit/vatRate. Server generates id and totals."},"calculatorResult":{"type":"object","description":"Readback/result shape { lineItems, subtotal, vatAmount, total }. lineItems without id/name are normalized on write (same as top-level lineItems)."},"validUntil":{"type":"Date"}}},{"method":"GET","path":"/api/v1/offers/:id","description":"Get offer (by _id or publicId)"},{"method":"PUT","path":"/api/v1/offers/:id","description":"Update draft offer","body":{"title":{"type":"string"},"templateId":{"type":"ObjectId"},"offerTypeId":{"type":"ObjectId"},"quoteInput":{"type":"object"},"lineItems":{"type":"array","description":"Replace draft offer lines. Same shape as POST."},"calculatorResult":{"type":"object"}}},{"method":"DELETE","path":"/api/v1/offers/:id","description":"Delete draft offer"},{"method":"POST","path":"/api/v1/offers/:id/send","description":"Send offer — snapshot, activity, automations, offer.sent webhook. Returns publicId.","response":"{ data: { _id, status: 'sent', sentAt, publicId, snapshotId } }"},{"method":"POST","path":"/api/v1/offers/:id/cancel","description":"Cancel offer"},{"method":"POST","path":"/api/v1/offers/:id/send-email","description":"Send or resend offer email to customer","body":{"emailTo":{"type":"string","description":"Optional recipient override"},"senderAccountId":{"type":"ObjectId","description":"Optional sender email account"},"templateId":{"type":"ObjectId","description":"Optional email template"}}},{"method":"POST","path":"/api/v1/offers/:id/send-sms","description":"Send or resend offer link via SMS","body":{"phoneTo":{"type":"string","description":"Optional phone override"}}},{"method":"GET","path":"/api/v1/offers/:id/preview-token","description":"Preview URL for draft offers (short-lived token) or public link when sent"}],"aiGuide":"/api/v1/docs?section=offerTemplates"},"offerTemplates":{"title":"Offer Templates (tilbudsskabeloner)","description":"Visual page-builder templates with pages[], headerElements, footerElements, branding, and custom HTML elements. See ?section=offerTemplates for element types and A-Polering reference.","endpoints":[{"method":"GET","path":"/api/v1/offer-templates","description":"List templates (summary without full pages)"},{"method":"POST","path":"/api/v1/offer-templates","description":"Create template with full layout (admin scope)","body":{"name":{"type":"string","required":true},"pages":{"type":"array","description":"Page[] with elements (text, heading, html, columns, line_items, totals, video, …)"},"headerElements":{"type":"array"},"footerElements":{"type":"array"},"branding":{"type":"object","description":"colors, typography, logo"},"defaultTerms":{"type":"string"},"defaultPaymentTerms":{"type":"string"},"isDefault":{"type":"boolean"}}},{"method":"GET","path":"/api/v1/offer-templates/:id","description":"Get full template including pages and elements"},{"method":"PATCH","path":"/api/v1/offer-templates/:id","description":"Update template (admin scope)"},{"method":"DELETE","path":"/api/v1/offer-templates/:id","description":"Delete template (admin scope)"},{"method":"POST","path":"/api/v1/offer-templates/:id/duplicate","description":"Duplicate template (admin scope)"}],"aiGuide":"/api/v1/docs?section=offerTemplates"},"offerTypes":{"title":"Offer Types","endpoints":[{"method":"GET","path":"/api/v1/offer-types","description":"List offer types"},{"method":"POST","path":"/api/v1/offer-types","description":"Create offer type (admin scope)"},{"method":"GET","path":"/api/v1/offer-types/:id","description":"Get offer type"},{"method":"PATCH","path":"/api/v1/offer-types/:id","description":"Update offer type (admin scope)"},{"method":"DELETE","path":"/api/v1/offer-types/:id","description":"Delete offer type (admin scope)"}]},"products":{"title":"Products & Catalog","endpoints":[{"method":"GET","path":"/api/v1/products","description":"List products"},{"method":"POST","path":"/api/v1/products","description":"Create product"},{"method":"GET","path":"/api/v1/products/:id","description":"Get product"},{"method":"PUT","path":"/api/v1/products/:id","description":"Update product"},{"method":"DELETE","path":"/api/v1/products/:id","description":"Delete product"},{"method":"GET","path":"/api/v1/catalog","description":"List catalog items"},{"method":"POST","path":"/api/v1/catalog","description":"Create catalog item"},{"method":"GET","path":"/api/v1/catalog/:id","description":"Get catalog item"},{"method":"PUT","path":"/api/v1/catalog/:id","description":"Update catalog item"},{"method":"DELETE","path":"/api/v1/catalog/:id","description":"Delete catalog item"}]},"projects":{"title":"Projects","endpoints":[{"method":"GET","path":"/api/v1/projects","description":"List projects"},{"method":"POST","path":"/api/v1/projects","description":"Create project"},{"method":"GET","path":"/api/v1/projects/:id","description":"Get project"},{"method":"PUT","path":"/api/v1/projects/:id","description":"Update project"},{"method":"DELETE","path":"/api/v1/projects/:id","description":"Delete project"},{"method":"POST","path":"/api/v1/projects/:id/move","description":"Move project to different stage"},{"method":"GET","path":"/api/v1/projects/:id/stats","description":"Get project statistics"},{"method":"GET","path":"/api/v1/projects/:id/tasks","description":"List project tasks"},{"method":"POST","path":"/api/v1/projects/:id/tasks","description":"Create project task"},{"method":"GET","path":"/api/v1/projects/tasks/:taskId","description":"Get project task"},{"method":"PUT","path":"/api/v1/projects/tasks/:taskId","description":"Update project task"},{"method":"DELETE","path":"/api/v1/projects/tasks/:taskId","description":"Delete project task"},{"method":"POST","path":"/api/v1/projects/tasks/:taskId/move","description":"Move project task"},{"method":"GET","path":"/api/v1/projects/:id/time-entries","description":"List time entries"},{"method":"POST","path":"/api/v1/projects/:id/time-entries","description":"Create time entry"},{"method":"PUT","path":"/api/v1/projects/time-entries/:entryId","description":"Update time entry"},{"method":"DELETE","path":"/api/v1/projects/time-entries/:entryId","description":"Delete time entry"},{"method":"GET","path":"/api/v1/projects/:id/expenses","description":"List expenses"},{"method":"POST","path":"/api/v1/projects/:id/expenses","description":"Create expense"}]},"bookings":{"title":"Bookings","description":"Authenticated workspace API (API key). Create/cancel/reschedule/slots use the same pipeline as the public booking page. For a custom website UI without an API key, see GET /api/v1/docs?section=publicBooking.","endpoints":[{"method":"GET","path":"/api/v1/bookings","description":"List bookings"},{"method":"POST","path":"/api/v1/bookings","description":"Create booking (slot check, calendar, CRM, emails, automations, webhooks)","body":{"configId":{"type":"string","required":true},"slotStart":{"type":"string","required":true,"description":"ISO datetime"},"attendeeInfo":{"type":"object","required":true,"description":"{ name, email, phone?, notes? }"},"meetingTypeId":{"type":"string"},"selectedAgentId":{"type":"string"},"customFieldsData":{"type":"object"},"invitedParticipants":{"type":"array"}}},{"method":"GET","path":"/api/v1/bookings/:id","description":"Get booking by ID or publicId"},{"method":"POST","path":"/api/v1/bookings/:id/cancel","description":"Cancel booking (calendar + notifications)"},{"method":"POST","path":"/api/v1/bookings/:id/approve","description":"Approve pending booking","body":{"notes":{"type":"string"}}},{"method":"POST","path":"/api/v1/bookings/:id/reject","description":"Reject pending booking","body":{"reason":{"type":"string"}}},{"method":"POST","path":"/api/v1/bookings/:id/reschedule","description":"Reschedule booking","body":{"newSlotStart":{"type":"string","required":true}}},{"method":"GET","path":"/api/v1/bookings/available-slots","description":"Get available time slots for a booking config","queryParams":{"configId":{"type":"string","required":true},"startDate":{"type":"string","required":true},"endDate":{"type":"string","required":true},"meetingTypeId":{"type":"string"}}},{"method":"GET","path":"/api/v1/booking-configs","description":"List booking configurations"},{"method":"GET","path":"/api/v1/booking-configs/:id","description":"Get booking configuration"},{"method":"PATCH","path":"/api/v1/booking-configs/:id","description":"Update booking configuration (admin scope)"}]},"reports":{"title":"Reports","endpoints":[{"method":"GET","path":"/api/v1/reports/pipeline","description":"Pipeline report. byStage entries include stageId + stageName (stage keeps the name for back-compat).","response":"{ data: { total, totalValue, byStage: [{ stageId, stageName, stage, count, value }], byStatus, winRate, avgDealValue, avgDaysToClose } }"},{"method":"GET","path":"/api/v1/reports/leads","description":"Leads report"},{"method":"GET","path":"/api/v1/reports/offers","description":"Offers report"},{"method":"GET","path":"/api/v1/reports/support","description":"Support report"},{"method":"GET","path":"/api/v1/reports/history","description":"History/activity report (daily counts)","queryParams":{"stat":{"type":"string","required":true,"enum":["deals","leads","activities","companies"]},"startDate":{"type":"string","required":true,"description":"ISO date (aliases: dateFrom, from)"},"endDate":{"type":"string","required":true,"description":"ISO date (aliases: dateTo, to)"},"userId":{"type":"string"}}},{"method":"GET","path":"/api/v1/reports/organization","description":"Organization overview"},{"method":"GET","path":"/api/v1/reports/users","description":"Users report"},{"method":"GET","path":"/api/v1/reports/users/:userId","description":"Individual user report"}]},"leadScoring":{"title":"Lead Scoring","endpoints":[{"method":"GET","path":"/api/v1/lead-scoring/rules","description":"List lead scoring rules"},{"method":"POST","path":"/api/v1/lead-scoring/rules","description":"Create scoring rule"},{"method":"PUT","path":"/api/v1/lead-scoring/rules/:id","description":"Update scoring rule"},{"method":"DELETE","path":"/api/v1/lead-scoring/rules/:id","description":"Delete scoring rule"}]},"leadRouting":{"title":"Lead Routing","endpoints":[{"method":"GET","path":"/api/v1/lead-routing/rules","description":"List lead routing rules"},{"method":"POST","path":"/api/v1/lead-routing/rules","description":"Create routing rule"},{"method":"PUT","path":"/api/v1/lead-routing/rules/:id","description":"Update routing rule"},{"method":"DELETE","path":"/api/v1/lead-routing/rules/:id","description":"Delete routing rule"}]},"importExport":{"title":"Import & Export","description":"Import requires API key scope `import`; export requires `export`. Neither is covered by `write` alone. Admin/owner role is also required.","endpoints":[{"method":"POST","path":"/api/v1/import/companies","description":"Import companies from CSV/data. Requires scope: import."},{"method":"POST","path":"/api/v1/import/people","description":"Import people. Requires scope: import."},{"method":"POST","path":"/api/v1/import/leads","description":"Import leads. Requires scope: import."},{"method":"POST","path":"/api/v1/import/deals","description":"Import deals. Requires scope: import."},{"method":"POST","path":"/api/v1/import/validate","description":"Validate import data before importing. Requires scope: import."},{"method":"GET","path":"/api/v1/import/history","description":"List import history. Requires scope: import."},{"method":"POST","path":"/api/v1/export/leads","description":"Export leads (POST only — not GET). Requires scope: export. Admin/owner role required."},{"method":"POST","path":"/api/v1/export/deals","description":"Export deals (POST only — not GET). Requires scope: export. Admin/owner role required."},{"method":"POST","path":"/api/v1/export/people","description":"Export people (POST only — not GET). Requires scope: export. Admin/owner role required."},{"method":"POST","path":"/api/v1/export/companies","description":"Export companies (POST only — not GET). Requires scope: export. Admin/owner role required."},{"method":"POST","path":"/api/v1/export/lists/:listId","description":"Export list items (POST only). Requires scope: export. Admin/owner role required."},{"method":"POST","path":"/api/v1/export/custom-objects/:slug","description":"Export custom object records (POST only). Requires scope: export. Admin/owner role required."}]},"files":{"title":"Files & Folders","endpoints":[{"method":"GET","path":"/api/v1/files","description":"List files"},{"method":"POST","path":"/api/v1/files","description":"Upload file"},{"method":"GET","path":"/api/v1/files/:id","description":"Get file metadata"},{"method":"DELETE","path":"/api/v1/files/:id","description":"Delete file"},{"method":"POST","path":"/api/v1/files/:id/link","description":"Create signed download link"},{"method":"DELETE","path":"/api/v1/files/:id/link","description":"Revoke signed download link"},{"method":"GET","path":"/api/v1/folders","description":"List folders"},{"method":"POST","path":"/api/v1/folders","description":"Create folder"},{"method":"DELETE","path":"/api/v1/folders/:id","description":"Delete folder"}]},"users":{"title":"Users (Team Members)","description":"Manage workspace members","endpoints":[{"method":"GET","path":"/api/v1/users","description":"List workspace members","queryParams":{"page":{},"limit":{},"search":{"type":"string"}}},{"method":"GET","path":"/api/v1/users/:id","description":"Get user by ID"},{"method":"PUT","path":"/api/v1/users/:id","description":"Update user (name, phone)","body":{"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"},"defaultUserPhone":{"type":"string"}}}]},"supportTickets":{"title":"Support Tickets","description":"Authenticated workspace API (API key) for the agent inbox. For a custom website chat UI without an API key, see GET /api/v1/docs?section=publicChat.","endpoints":[{"method":"GET","path":"/api/v1/support-tickets","description":"List support tickets","queryParams":{"page":{},"limit":{},"status":{"type":"string"},"priority":{"type":"string"},"assignee":{"type":"string"},"category":{"type":"string"},"search":{"type":"string"}}},{"method":"POST","path":"/api/v1/support-tickets","description":"Create support ticket","body":{"title":{"type":"string","required":true},"description":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"category":{"type":"string"},"assignee":{"type":"string","description":"User ID"},"company":{"type":"string","description":"Company ID"},"personRef":{"type":"string","description":"Person ID"},"tags":{"type":"string[]"}}},{"method":"GET","path":"/api/v1/support-tickets/:id","description":"Get support ticket by ID"},{"method":"PUT","path":"/api/v1/support-tickets/:id","description":"Update support ticket","body":{"title":{"type":"string"},"description":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string","enum":["open","in_progress","waiting","resolved","closed"]},"category":{"type":"string"},"assignee":{"type":"string"},"tags":{"type":"string[]"},"resolution":{"type":"string"}}},{"method":"DELETE","path":"/api/v1/support-tickets/:id","description":"Delete support ticket"},{"method":"POST","path":"/api/v1/support-tickets/:id/reply","description":"Add reply to ticket","body":{"content":{"type":"string","required":true},"type":{"type":"string","enum":["agent_reply","internal_note"],"default":"agent_reply"},"visibility":{"type":"string","enum":["public","internal"],"default":"public"}}},{"method":"POST","path":"/api/v1/support-tickets/:id/move","description":"Move ticket to kanban stage","body":{"stageId":{"type":"number","required":true},"flowId":{"type":"string","description":"Optional support flow ID"}}},{"method":"GET","path":"/api/v1/support-tickets/:id/messages","description":"List ticket messages","queryParams":{"limit":{"type":"number","default":50},"visibility":{"type":"string","enum":["public","internal"]}}},{"method":"GET","path":"/api/v1/support-tickets/:id/time-entries","description":"List time entries on ticket"},{"method":"POST","path":"/api/v1/support-tickets/:id/time-entries","description":"Create time entry on ticket"},{"method":"GET","path":"/api/v1/support-flow","description":"Get (or lazily create) the workspace's default support flow (kanban stages)"},{"method":"GET","path":"/api/v1/support-customers","description":"List support customers (grouped identity across tickets by person, email, or chat visitor)","queryParams":{"page":{},"limit":{},"search":{"type":"string"},"q":{"type":"string","description":"Alias for search"},"sortBy":{"type":"string","enum":["lastActivityAt","totalTickets","openTickets","unread"],"default":"lastActivityAt"},"sortOrder":{"type":"string","enum":["asc","desc"],"default":"desc"}}},{"method":"GET","path":"/api/v1/support-customers/detail","description":"Get full detail for one support customer: tickets, time stats, and time entries","queryParams":{"type":{"type":"string","required":true,"enum":["person","email","visitor"]},"value":{"type":"string","required":true,"description":"personRef ID, email address, or chatVisitorRef ID depending on type"}}}]},"filterPresets":{"title":"Filter Presets","description":"Saved filter configurations for lists and views","endpoints":[{"method":"GET","path":"/api/v1/filter-presets","description":"List filter presets","queryParams":{"area":{"type":"string","description":"Filter by area (e.g. companies, leads, deals)"},"page":{},"limit":{}}},{"method":"POST","path":"/api/v1/filter-presets","description":"Create filter preset","body":{"name":{"type":"string","required":true},"area":{"type":"string"},"columnFilters":{"type":"object"},"filterTree":{"type":"object"},"filterMode":{"type":"string","enum":["and","or"]},"sorting":{"type":"array"},"visibility":{"type":"string","enum":["workspace","private","group","user"]}}},{"method":"GET","path":"/api/v1/filter-presets/:id","description":"Get filter preset"},{"method":"PUT","path":"/api/v1/filter-presets/:id","description":"Update filter preset"},{"method":"DELETE","path":"/api/v1/filter-presets/:id","description":"Delete filter preset"}]},"emailSignatures":{"title":"Email Signatures","endpoints":[{"method":"GET","path":"/api/v1/email-signatures","description":"List email signatures"},{"method":"POST","path":"/api/v1/email-signatures","description":"Create signature","body":{"name":{"type":"string","required":true},"content":{"type":"string","required":true,"description":"HTML content"},"textContent":{"type":"string"}}},{"method":"GET","path":"/api/v1/email-signatures/:id","description":"Get signature"},{"method":"PUT","path":"/api/v1/email-signatures/:id","description":"Update signature"},{"method":"DELETE","path":"/api/v1/email-signatures/:id","description":"Delete signature"}]},"clipCards":{"title":"Clip Cards (Prepaid Hours)","description":"Manage prepaid service hour cards for projects","endpoints":[{"method":"GET","path":"/api/v1/clip-cards","description":"List clip cards","queryParams":{"page":{},"limit":{},"status":{"type":"string","enum":["active","depleted","expired","cancelled"]},"projectId":{"type":"string"}}},{"method":"POST","path":"/api/v1/clip-cards","description":"Create clip card","body":{"project":{"type":"string","required":true,"description":"Project ID"},"title":{"type":"string"},"totalHours":{"type":"number","required":true},"hourlyRate":{"type":"number","required":true},"allowOverdraft":{"type":"boolean"},"overdraftRate":{"type":"number"},"expiresAt":{"type":"string","description":"ISO date"},"notes":{"type":"string"}}},{"method":"GET","path":"/api/v1/clip-cards/:id","description":"Get clip card"},{"method":"PUT","path":"/api/v1/clip-cards/:id","description":"Update clip card"},{"method":"DELETE","path":"/api/v1/clip-cards/:id","description":"Delete clip card"}]},"dashboardLayouts":{"title":"Dashboard Layouts","description":"Manage saved dashboard configurations","endpoints":[{"method":"GET","path":"/api/v1/dashboard-layouts","description":"List dashboard layouts"},{"method":"POST","path":"/api/v1/dashboard-layouts","description":"Create layout","body":{"name":{"type":"string","required":true},"layout":{"type":"object"},"visibleComponents":{"type":"string[]"},"chartWidgets":{"type":"array"},"visibility":{"type":"string","enum":["workspace","private","group","user"]}}},{"method":"GET","path":"/api/v1/dashboard-layouts/:id","description":"Get layout"},{"method":"PUT","path":"/api/v1/dashboard-layouts/:id","description":"Update layout"},{"method":"DELETE","path":"/api/v1/dashboard-layouts/:id","description":"Delete layout"}]},"customObjects":{"title":"Custom Objects","description":"Workspace-defined object types and records. Schema/field mutations require admin scope; record CRUD uses write scope.","endpoints":[{"method":"GET","path":"/api/v1/custom-objects","description":"List custom object definitions"},{"method":"POST","path":"/api/v1/custom-objects","description":"Create custom object definition (admin scope)","body":{"slug":{"type":"string","required":true},"singular":{"type":"string","required":true},"plural":{"type":"string","required":true},"description":{"type":"string"},"icon":{"type":"string"},"color":{"type":"string"}}},{"method":"GET","path":"/api/v1/custom-objects/search","description":"Search records across all searchable objects","queryParams":{"q":{"type":"string","required":true,"minLength":2},"limit":{"type":"number","default":10}}},{"method":"GET","path":"/api/v1/custom-objects/:slug","description":"Get definition with field schemas"},{"method":"PATCH","path":"/api/v1/custom-objects/:slug","description":"Update definition metadata (admin scope)"},{"method":"DELETE","path":"/api/v1/custom-objects/:slug","description":"Delete object and all records (admin scope)"},{"method":"POST","path":"/api/v1/custom-objects/:slug/fields","description":"Add field (admin scope)","body":{"slug":{"type":"string","required":true},"label":{"type":"string","required":true},"type":{"type":"string","required":true}}},{"method":"PATCH","path":"/api/v1/custom-objects/:slug/fields/:fieldSlug","description":"Update field (admin scope)"},{"method":"DELETE","path":"/api/v1/custom-objects/:slug/fields/:fieldSlug","description":"Delete field (admin scope)"},{"method":"GET","path":"/api/v1/custom-objects/:slug/records","description":"List records with pagination or cursor search","queryParams":{"page":{"type":"number","default":1},"limit":{"type":"number","default":30,"max":100},"q":{"type":"string","description":"Search query (alias: search)"},"search":{"type":"string","description":"Alias for q"},"cursor":{"type":"string","description":"Cursor for next page when searching"},"sort":{"type":"string","description":"Field slug to sort by"},"dir":{"type":"string","enum":["asc","desc"],"default":"desc"}}},{"method":"POST","path":"/api/v1/custom-objects/:slug/records","description":"Create record","body":{"type":"object","description":"Field slug keys → values"}},{"method":"POST","path":"/api/v1/custom-objects/:slug/records/bulk","description":"Bulk update records","body":{"ids":{"type":"string[]","required":true},"patch":{"type":"object","required":true}}},{"method":"GET","path":"/api/v1/custom-objects/:slug/records/:id","description":"Get record by ID"},{"method":"PATCH","path":"/api/v1/custom-objects/:slug/records/:id","description":"Partial update of record"},{"method":"PUT","path":"/api/v1/custom-objects/:slug/records/:id","description":"Replace record"},{"method":"DELETE","path":"/api/v1/custom-objects/:slug/records/:id","description":"Delete record"},{"method":"GET","path":"/api/v1/custom-objects/:slug/records/:id/activity-feed","description":"Activity feed for a record"},{"method":"GET","path":"/api/v1/custom-objects/:slug/records/:id/related","description":"Related custom object records linked via native entity refs"},{"method":"POST","path":"/api/v1/export/custom-objects/:slug","description":"Export records (export + admin scope)","body":{"from":{"type":"string"},"to":{"type":"string"},"limit":{"type":"number"}}},{"method":"POST","path":"/api/v1/import/custom-objects/:slug","description":"Import records from rows array (import + admin scope)","body":{"rows":{"type":"array","required":true}}}]},"calculators":{"title":"Calculators","description":"Read calculator templates tied to offer types and run stateless price calculations.","endpoints":[{"method":"GET","path":"/api/v1/calculator-templates","description":"List calculator templates"},{"method":"GET","path":"/api/v1/calculator-templates/:id","description":"Get template with pricing rules"},{"method":"POST","path":"/api/v1/calculator-templates/:id/calculate","description":"Run calculation without persisting","body":{"inputs":{"type":"object","required":true}}},{"method":"GET","path":"/api/v1/calculator-submissions","description":"List calculator submissions","queryParams":{"page":{},"limit":{},"templateId":{"type":"string"},"offerId":{"type":"string"}}}]},"integrations":{"title":"Integrations","description":"Read integration connection status. OAuth connect flows remain in-app only.","endpoints":[{"method":"GET","path":"/api/v1/integrations","description":"List connected providers and sync health (no secrets)"},{"method":"POST","path":"/api/v1/integrations/:provider/sync","description":"Trigger manual sync (admin scope)","body":{"fullSync":{"type":"boolean"}}}]}}},"schemas":{"overview":{"title":"Data Schemas","description":"Salesbase CRM entities and their field structures. All entities are workspace-scoped. Custom fields can be added to any entity type via the Custom Fields API."},"entities":{"Company":{"description":"A business entity. Can be linked to people, leads, and deals.","fields":{"_id":{"type":"ObjectId","description":"Unique identifier"},"name":{"type":"string","required":true,"description":"Company name"},"cvr":{"type":"string","description":"Danish CVR number (unique within workspace)"},"email":{"type":"string","description":"Primary email"},"phone":{"type":"string","description":"Primary phone"},"address":{"type":"string"},"city":{"type":"string"},"zip":{"type":"string"},"country":{"type":"string"},"website":{"type":"string"},"industry":{"type":"string"},"employees":{"type":"number"},"description":{"type":"string"},"customFields":{"type":"object","description":"Key → typed value (boolean/number/string). Responses never include customFieldsMap."},"tags":{"type":"ObjectId[]","description":"Array of tag IDs"},"owner":{"type":"ObjectId","description":"Assigned user ID"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"People":{"description":"A contact person. Can be linked to companies, leads, and deals.","fields":{"_id":{"type":"ObjectId"},"firstName":{"type":"string","required":true},"lastName":{"type":"string"},"emails":{"type":"array","description":"[{ email: string, label: string }]"},"phones":{"type":"array","description":"[{ phone: string, label: string }]"},"title":{"type":"string","description":"Job title"},"description":{"type":"string"},"companyIds":{"type":"ObjectId[]","description":"Connected company IDs"},"customFields":{"type":"object","description":"Key → typed value (boolean/number/string). Responses never include customFieldsMap."},"tags":{"type":"ObjectId[]"},"owner":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"Lead":{"description":"A sales lead. Links to a company and/or person, and can have deals.","fields":{"_id":{"type":"ObjectId"},"title":{"type":"string","required":true,"description":"Lead title (typically company/person name)"},"email":{"type":"string"},"phone":{"type":"string"},"status":{"type":"string","description":"Lead status (e.g. 'new', 'contacted', 'qualified', 'won', 'lost')"},"source":{"type":"string","description":"Where the lead came from"},"score":{"type":"number","description":"Lead score (0-100)"},"value":{"type":"number","description":"Estimated value"},"notes":{"type":"string"},"companyRef":{"type":"ObjectId","description":"Connected company"},"personRef":{"type":"ObjectId","description":"Connected person"},"customFields":{"type":"object","description":"Key → typed value (boolean/number/string)"},"tags":{"type":"ObjectId[]"},"owner":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"Deal":{"description":"A deal/opportunity in a pipeline. Linked to a company, person, and/or lead.","fields":{"_id":{"type":"ObjectId"},"title":{"type":"string","required":true},"value":{"type":"number","description":"Deal monetary value"},"flow":{"type":"ObjectId","description":"Pipeline (flow) ID"},"stage":{"type":"number","description":"Stage ID within the pipeline"},"status":{"type":"string","enum":["open","won","lost"]},"probability":{"type":"number","description":"Win probability (0-100)"},"priority":{"type":"string","enum":["low","medium","high"]},"expectedCloseDate":{"type":"Date"},"wonDate":{"type":"Date","description":"Set when status is won (synced with won stage)"},"lostDate":{"type":"Date","description":"Set when status is lost (synced with lost stage)"},"closeDate":{"type":"Date","description":"Set with wonDate for won deals"},"lostReason":{"type":"string","description":"Dialer loss-reason value (e.g. no_budget). Copied from call outcomes when deals are created from dialer."},"company":{"type":"ObjectId","description":"Connected company"},"personRef":{"type":"ObjectId","description":"Connected person"},"leadRef":{"type":"ObjectId","description":"Connected lead"},"customFields":{"type":"object","description":"Key → typed value (boolean/number/string)"},"tags":{"type":"ObjectId[]"},"owner":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"Automation":{"description":"An event-driven automation flow (see Automations section for full detail)","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"description":{"type":"string"},"isActive":{"type":"boolean"},"nodes":{"type":"Node[]","description":"ReactFlow nodes (see Automation docs)"},"edges":{"type":"Edge[]","description":"ReactFlow edges"},"triggers":{"type":"Trigger[]","description":"Extracted trigger configs for quick lookup"},"executionSettings":{"type":"ExecutionSettings"},"rateLimits":{"type":"RateLimits"},"stats":{"type":"Stats","description":"{ totalRuns, successfulRuns, failedRuns, lastRunAt }"},"createdBy":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"Sequence":{"description":"A multi-step outreach sequence (see Sequences section for full detail)","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"description":{"type":"string"},"isActive":{"type":"boolean"},"nodes":{"type":"Node[]","description":"ReactFlow nodes (see Sequence docs)"},"edges":{"type":"Edge[]"},"allowedEntityTypes":{"type":"string[]","description":"['Lead', 'People', 'Deal', 'Company']"},"exitConditions":{"type":"ExitConditions"},"sendSettings":{"type":"SendSettings"},"trackingSettings":{"type":"TrackingSettings"},"executionSettings":{"type":"ExecutionSettings"},"stats":{"type":"Stats"},"createdBy":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"Activity":{"description":"An activity log entry (call, meeting, email, note, task)","fields":{"_id":{"type":"ObjectId"},"type":{"type":"string","enum":["call","meeting","email","note","task","other"]},"title":{"type":"string"},"description":{"type":"string"},"entityType":{"type":"string","description":"Related entity type"},"entityId":{"type":"ObjectId","description":"Related entity ID"},"dueDate":{"type":"Date"},"completed":{"type":"boolean"},"owner":{"type":"ObjectId"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"}}},"Tag":{"description":"A label that can be applied to any entity","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"color":{"type":"string"},"workspaceId":{"type":"ObjectId"}}},"WebhookSubscription":{"description":"An outbound webhook subscription that fires on CRM events","fields":{"_id":{"type":"ObjectId"},"url":{"type":"string","required":true},"events":{"type":"string[]","required":true},"name":{"type":"string"},"active":{"type":"boolean"},"secret":{"type":"string","description":"HMAC signing secret (returned on creation only)"},"workspaceId":{"type":"ObjectId"},"createdAt":{"type":"Date"}}},"CustomObjectDefinition":{"description":"A workspace-defined object type (schema). Managed via admin-scoped API key.","fields":{"_id":{"type":"ObjectId"},"slug":{"type":"string","required":true,"description":"URL-safe identifier, e.g. contracts"},"singular":{"type":"string","required":true},"plural":{"type":"string","required":true},"description":{"type":"string"},"icon":{"type":"string"},"color":{"type":"string","description":"Hex accent for sidebar"},"searchable":{"type":"boolean"},"showInSidebar":{"type":"boolean"},"workspace":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"CustomObjectField":{"description":"Field definition on a custom object. Types mirror Attio-style schema fields.","fields":{"_id":{"type":"ObjectId"},"slug":{"type":"string","required":true},"label":{"type":"string","required":true},"type":{"type":"string","enum":["text","number","date","checkbox","select","multiselect","url","email","phone","textarea","record_reference","formula","rollup"]},"isRequired":{"type":"boolean"},"isUnique":{"type":"boolean"},"options":{"type":"array","description":"For select/multiselect"},"referenceObject":{"type":"string","description":"Custom object slug for record_reference"},"referenceType":{"type":"string","enum":["custom","native"]},"nativeEntity":{"type":"string","description":"company, person, deal, lead when referenceType=native"},"order":{"type":"number"}}},"CustomObjectRecord":{"description":"A record instance for a custom object. Field values live under data keyed by field slug.","fields":{"_id":{"type":"ObjectId"},"object":{"type":"ObjectId","description":"CustomObjectDefinition _id"},"recordNumber":{"type":"number","description":"Auto-increment per object type"},"data":{"type":"object","description":"Map of fieldSlug → value"},"workspace":{"type":"ObjectId"},"createdBy":{"type":"ObjectId"},"updatedBy":{"type":"ObjectId"},"createdAt":{"type":"Date"},"updatedAt":{"type":"Date"}}},"OfferTemplate":{"description":"Tilbudsskabelon with page builder layout. Mix native elements (text, line_items) and custom html blocks.","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"description":{"type":"string"},"pages":{"type":"array","description":"[{ id, order, title, elements[], settings }]"},"headerElements":{"type":"array","description":"Elements on every page header"},"footerElements":{"type":"array","description":"Elements on every page footer"},"branding":{"type":"object","description":"{ colors, typography, logo }"},"defaultTerms":{"type":"string"},"defaultPaymentTerms":{"type":"string"},"defaultValidity":{"type":"number","description":"Days"},"isDefault":{"type":"boolean"},"isActive":{"type":"boolean"},"workspaceId":{"type":"ObjectId"}},"elementTypes":["text","heading","html","columns","image","video","table","line_items","totals","divider","spacer","button"],"aiGuide":"/api/v1/docs?section=offerTemplates"},"EmailTemplate":{"description":"Email template with compiledHtml and optional GrapesJS projectData.","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"category":{"type":"string","enum":["marketing","transactional","quote","other"]},"compiledHtml":{"type":"string","description":"HTML with {{contact.firstName}} placeholders"},"plainText":{"type":"string"},"metadata":{"type":"object","description":"{ subject, preheader }"},"layoutSettings":{"type":"object"},"requiredVars":{"type":"array"},"tags":{"type":"string[]"},"workspace":{"type":"ObjectId"}},"aiGuide":"/api/v1/docs?section=offerTemplates"},"OfferType":{"description":"Tilbudstype — pricing rules, BBR mapping, default template, CRM automations on accept/submit.","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"slug":{"type":"string"},"defaultTemplateId":{"type":"ObjectId","ref":"OfferTemplate"},"requiredInputFields":{"type":"array"},"pricingRules":{"type":"object","description":"tieredPricing, conditionalRules, packages, globalSurcharges"},"offerSettings":{"type":"object","description":"OTP, deal/sequence on accept/submit"},"customPlaceholders":{"type":"array","description":"→ {{custom.fieldId}} in templates"},"isActive":{"type":"boolean"},"workspaceId":{"type":"ObjectId"}}},"OfferProduct":{"description":"Product/service for offers. pricingType: fixed | advanced (tiered pricing).","fields":{"_id":{"type":"ObjectId"},"name":{"type":"string","required":true},"unitPrice":{"type":"number"},"unit":{"type":"string","default":"stk"},"pricingType":{"type":"string","enum":["fixed","advanced"]},"tieredPricing":{"type":"array"},"conditionalRules":{"type":"array"},"variants":{"type":"array"},"category":{"type":"string"},"isActive":{"type":"boolean"},"workspaceId":{"type":"ObjectId"}}}},"customFieldTypes":{"description":"Custom fields support these data types","types":[{"type":"text","description":"Free-text string"},{"type":"number","description":"Numeric value"},{"type":"date","description":"ISO date string"},{"type":"checkbox","description":"Boolean (true/false)"},{"type":"select","description":"Single-select from predefined options"},{"type":"multi_select","description":"Multi-select from predefined options"},{"type":"url","description":"URL string"},{"type":"email","description":"Email address"},{"type":"phone","description":"Phone number (stored as text)"},{"type":"textarea","description":"Multi-line text"}],"note":"Lead and Deal custom fields use Map storage (key → value). Company and People use array storage ([{ name, value }]). When creating via API, both formats are accepted and auto-converted."}},"webhooks":{"overview":{"title":"Webhooks","description":"Subscribe to CRM events and receive HTTP POST notifications at your URL. Each webhook delivery includes an HMAC signature for verification. Manage subscriptions via the /api/v1/webhooks/subscriptions endpoints."},"events":[{"event":"company.created","description":"A company was created"},{"event":"company.updated","description":"A company was updated"},{"event":"company.deleted","description":"A company was deleted"},{"event":"person.created","description":"A person was created"},{"event":"person.updated","description":"A person was updated"},{"event":"person.deleted","description":"A person was deleted"},{"event":"deal.created","description":"A deal was created"},{"event":"deal.updated","description":"A deal was updated"},{"event":"deal.deleted","description":"A deal was deleted"},{"event":"deal.stage_changed","description":"A deal moved to a different pipeline stage"},{"event":"deal.won","description":"A deal was marked as won"},{"event":"deal.lost","description":"A deal was marked as lost"},{"event":"lead.created","description":"A lead was created"},{"event":"lead.updated","description":"A lead was updated"},{"event":"lead.deleted","description":"A lead was deleted"},{"event":"call.created","description":"A call record was created"},{"event":"call.updated","description":"A call record was updated"},{"event":"note.created","description":"A note was created"},{"event":"note.updated","description":"A note was updated"},{"event":"task.created","description":"A task was created"},{"event":"task.updated","description":"A task was updated"},{"event":"task.completed","description":"A task was marked as completed"},{"event":"activity.created","description":"An activity was logged"},{"event":"custom_object.created","description":"A custom object record was created"},{"event":"custom_object.updated","description":"A custom object record was updated"},{"event":"custom_object.deleted","description":"A custom object record was deleted"},{"event":"offer.created","description":"An offer was created"},{"event":"offer.updated","description":"An offer was updated"},{"event":"offer.deleted","description":"An offer was deleted"},{"event":"offer.sent","description":"An offer was sent to recipient"},{"event":"booking.created","description":"A booking was created"},{"event":"booking.approved","description":"A booking was approved"},{"event":"booking.rejected","description":"A booking was rejected"},{"event":"booking.cancelled","description":"A booking was cancelled"},{"event":"booking.rescheduled","description":"A booking was rescheduled"},{"event":"support_ticket.created","description":"A support ticket was created"},{"event":"support_ticket.updated","description":"A support ticket was updated"},{"event":"support_ticket.stage_changed","description":"A support ticket moved kanban stage"},{"event":"support_ticket.deleted","description":"A support ticket was deleted"}],"deliveryFormat":{"description":"Each webhook delivery sends a POST request to your URL with this JSON body:","schema":{"event":{"type":"string","description":"Event name (e.g. 'lead.created')"},"timestamp":{"type":"string","description":"ISO 8601 timestamp"},"data":{"type":"object","description":"The entity data that triggered the event"},"workspaceId":{"type":"string"}},"headers":{"Content-Type":"application/json","X-Webhook-Signature":"HMAC-SHA256 signature of the request body using your webhook secret"}},"signatureVerification":{"description":"Verify webhook authenticity by computing HMAC-SHA256 of the raw request body with your secret","example":"const crypto = require('crypto');\n\nfunction verifyWebhook(body, signature, secret) {\n  const expected = crypto\n    .createHmac('sha256', secret)\n    .update(JSON.stringify(body))\n    .digest('hex');\n  return crypto.timingSafeEqual(\n    Buffer.from(signature),\n    Buffer.from(expected)\n  );\n}"},"endpoints":{"description":"See the Webhook Subscriptions section in Endpoints for CRUD operations","quickReference":[{"method":"GET","path":"/api/v1/webhooks/subscriptions","description":"List subscriptions"},{"method":"POST","path":"/api/v1/webhooks/subscriptions","description":"Create subscription (returns secret)"},{"method":"GET","path":"/api/v1/webhooks/subscriptions/:id","description":"Get subscription"},{"method":"PUT","path":"/api/v1/webhooks/subscriptions/:id","description":"Update subscription"},{"method":"DELETE","path":"/api/v1/webhooks/subscriptions/:id","description":"Delete subscription"}]},"inboundWebhooks":{"description":"Salesbase also accepts inbound webhooks to trigger automations","endpoints":[{"method":"POST","path":"/api/webhooks/automation/:automationId","description":"Trigger a webhook-type automation. The JSON body becomes available as webhookData.* placeholders in the automation flow.","authentication":"Header x-webhook-secret must match the secret on the automation webhook trigger node."},{"method":"POST","path":"/api/v1/webhooks/zapier","description":"Zapier inbound — creates person, lead, and deal. Bearer API key with inbound scope."}]}},"offerTemplates":{"title":"Offer & Email Templates (AI Guide)","description":"How to build premium tilbudsskabeloner with custom HTML, placeholders, columns, and branding — plus email templates with compiledHtml. Reference implementation: A-Polering Standard Tilbud.","referenceDoc":"docs/OFFER-AI-PROMPT.md (full internal reference in repo)","setupOrder":["1. Workspace offer settings (companyInfo, bankInfo, defaults)","2. OfferProducts (pricingType: advanced for tiered pricing)","3. OfferTemplate (pages, headerElements, footerElements, branding)","4. OfferType (pricingRules, defaultTemplateId, offerSettings)","5. CalculatorTemplate (optional, offerTypeId)","6. Offer (templateId, offerTypeId, quoteInput, calculatorResult)","7. POST /api/v1/offers/:id/send (creates snapshot, fires automations)"],"apiEndpoints":{"offerTemplates":["GET /api/v1/offer-templates","POST /api/v1/offer-templates (admin)","GET /api/v1/offer-templates/:id","PATCH /api/v1/offer-templates/:id (admin)","DELETE /api/v1/offer-templates/:id (admin)","POST /api/v1/offer-templates/:id/duplicate (admin)"],"emailTemplates":["GET /api/v1/email-templates","POST /api/v1/email-templates (admin)","GET /api/v1/email-templates/:id","PATCH /api/v1/email-templates/:id (admin)","DELETE /api/v1/email-templates/:id (admin)"],"offerStack":["GET/POST /api/v1/offer-types, /api/v1/products","GET/POST /api/v1/offers, PUT draft fields, POST /api/v1/offers/:id/send"]},"elementTypes":{"description":"Page builder element types for offer templates (pages[].elements, headerElements, footerElements)","types":[{"type":"text","use":"Body copy with {{placeholders}}. Use \\n for line breaks. Inline styling via style object."},{"type":"heading","use":"H1-H6 via level: 1-6. Supports placeholders in content."},{"type":"html","use":"Custom HTML/CSS blocks — hero banners, info boxes, USP cards, colored dividers. Supports {{placeholders}} inside HTML."},{"type":"columns","use":"Multi-column layout. columns: [{ id, width, elements: [...] }]. Presets: 50-50, 70-30, 33-33-33."},{"type":"image","use":"url, alt, width, height. Use {{company.logo}} for workspace logo."},{"type":"video","use":"YouTube/Vimeo embed. url, provider: youtube|vimeo|direct."},{"type":"table","use":"Structured key-value data (recipient/sender, bank info). hideEmptyRows: true."},{"type":"line_items","use":"Auto-renders offer.calculatorResult.lineItems. showQuantity, showUnitPrice, showTotal."},{"type":"totals","use":"Auto-renders subtotal, VAT, total from calculatorResult."},{"type":"divider","use":"Horizontal rule with style.borderColor etc."},{"type":"spacer","use":"Vertical gap via height: '28px'|'40px' etc."},{"type":"button","use":"CTA. action: link|download. Brand-colored accept styling optional (accept handled on public /offer/[token] page)."}],"fullWidthOnly":["line_items","totals","columns"]},"placeholders":{"offer":["{{offer.number}}","{{offer.title}}","{{offer.date}}","{{offer.validUntil}}","{{offer.terms}}","{{offer.paymentTerms}}","{{offer.total}}","{{offer.subtotal}}","{{offer.vat}}"],"recipient":["{{recipient.name}}","{{recipient.firstName}}","{{recipient.email}}","{{recipient.phone}}","{{recipient.fullAddress}}","{{recipient.zip}}","{{recipient.city}}"],"company":["{{company.name}}","{{company.logo}}","{{company.phone}}","{{company.email}}","{{company.cvr}}","{{company.street}}","{{company.postalCode}}","{{company.city}}"],"bank":["{{bank.name}}","{{bank.reg}}","{{bank.account}}","{{bank.iban}}","{{bank.swift}}"],"calc":["{{calc.total}}","{{calc.subtotal}}","{{calc.vat}}","{{calc.discount}}"],"footer":["{{page}}","{{total}}","{{totalPages}}"],"email":["{{contact.firstName}}","{{contact.lastName}}","{{contact.email}}","{{company.name}}","{{offer.validUntil}}","{{sender.name}}"],"syntax":"Email templates also support {{field | default:\"fallback\"}} filter syntax."},"offerTemplateSchema":{"name":"string (required)","description":"string","branding":{"colors":{"primary":"#13143E","secondary":"#FBA212","accent":"#FBA212","background":"#ffffff","text":"#13143E","border":"#e0e0ea"},"typography":{"fontFamily":"Inter, sans-serif","headingFontFamily":"","fontSize":"base"},"logo":{"url":"","maxHeight":60,"position":"left|center|right"}},"headerElements":"Element[] shown on every page","footerElements":"Element[] shown on every page","headerSettings":{"height":80,"showOnAllPages":true,"showOnFirstPage":true},"footerSettings":{"height":40,"showPageNumbers":true,"pageNumberFormat":"Side {{page}} af {{total}}"},"pages":[{"id":"page_...","order":0,"title":"Side 1","elements":[],"settings":{"background":"#ffffff","minHeight":297,"margins":{"top":20,"right":15,"bottom":20,"left":15}}}],"defaultTerms":"string","defaultPaymentTerms":"string","defaultValidity":30,"isDefault":false,"isActive":true},"emailTemplateSchema":{"name":"string (required)","category":"marketing|transactional|reminder|follow-up|notification|quote|newsletter|other","compiledHtml":"string — full HTML body with {{placeholders}} (primary field for API-created templates)","plainText":"string (optional)","metadata":{"subject":"string with {{placeholders}}","preheader":"string"},"layoutSettings":{"layoutMode":"minimal|styled","containerWidth":600,"backgroundColor":"#ffffff","outerBackgroundColor":"#f3f4f6"},"requiredVars":[{"key":"vars.offerLink","type":"string","description":"..."}],"tags":["string[]"]},"aPoleringPattern":{"description":"Premium template mixing native elements + custom HTML (stored in production for A-Polering ApS)","techniques":["Header: columns (logo left) + html block (right-aligned company info) + html accent bar (#FBA212)","Hero: html element with dark brand background (#13143E), badge pill, headline, Trustpilot row","Meta: columns 55/45 — greeting text left, html info box right with {{offer.number}}, {{offer.date}}, {{recipient.fullAddress}}","Pricing: native line_items + totals elements","Social proof: video element (YouTube) + columns with html USP cards (border-top accent)","CTA: html dark banner + optional button element","Footer: html accent bar + columns (company | terms) + page numbers with {{page}}, {{offer.number}}"],"exampleHtmlHero":"<div style=\"background:#13143E;border-radius:10px;padding:36px;font-family:sans-serif\"><div style=\"font-size:26px;font-weight:800;color:#ffffff\">Dit personlige tilbud</div><div style=\"font-size:13px;color:rgba(255,255,255,0.75)\">Blanke ruder siden 1949</div></div>","exampleHtmlInfoBox":"<div style=\"background:#f5f5f8;border:1px solid #e0e0ea;border-radius:8px;padding:20px\"><table style=\"width:100%\"><tr><td>Tilbud nr.</td><td style=\"text-align:right;font-weight:600\">{{offer.number}}</td></tr></table></div>"},"designRules":["Mix html + native elements: html for visual design (heroes, cards, bars); text/heading for copy; line_items/totals for pricing.","Use inline CSS in html elements (flexbox, border-radius, brand colors). Placeholders work inside html content.","Vertical rhythm: spacer + divider between sections. One idea per section.","Brand colors via branding.colors AND inline html for accent bars/cards.","Do NOT put accept signature lines in templates — accept is handled on /offer/[token].","Email templates: set compiledHtml directly; subject in metadata.subject with {{contact.firstName}} etc."],"createOfferExample":{"method":"POST","path":"/api/v1/offers","body":{"title":"Tilbud vinduespolering","offerTypeId":"<OfferType _id>","templateId":"<OfferTemplate _id>","personRef":"<Person _id>","quoteInput":{"contact":{"name":"Klaus Hansen","email":"klaus@example.dk","phone":"+4512345678"},"address":{"fullAddress":"Hovedgaden 1, 2100 København Ø"}},"calculatorResult":{"lineItems":[{"name":"Vinduespolering","quantity":12,"unit":"stk","unitPrice":45,"total":540}],"subtotal":540,"vatAmount":135,"vatRate":25,"total":675}}},"sendOffer":{"method":"POST","path":"/api/v1/offers/:id/send","description":"Marks offer sent, creates immutable OfferSnapshot, logs activity, runs onOfferSent automations, dispatches offer.sent webhook. Returns publicId for customer link /offer/{publicId}.","scope":"write"}},"publicBooking":{"overview":{"title":"Public Booking API","description":"Unauthenticated HTTP API for custom booking UIs on your website. Identify the booking page with its publicId (from Indstillinger → Workspace → Booking). Do not put a workspace API key in the browser. Creates, cancels and reschedules run the same pipeline as /book: slots, calendar, CRM, emails, automations and webhooks.","baseUrl":"/api/bookings","authentication":"None. Guest actions after booking use cancellationToken.","cors":"CORS is enabled. Empty embedSettings.allowedDomains allows all origins (*). If you set allowed domains on the booking config, only those origins are accepted."},"flow":{"title":"Typical custom UI flow","steps":["GET /api/bookings/config/:publicId — meeting types, fields, hours, optional agents","GET /api/bookings/:publicId/slots?start=&end=&meetingTypeId= — available slots (ISO start)","POST /api/bookings/:publicId/book — create; store publicId + cancellationToken","GET /api/bookings/booking/:publicId/details?token= — manage page","POST /api/bookings/booking/:publicId/cancel — action cancel or reschedule"]},"endpoints":[{"method":"GET","path":"/api/bookings/config/:publicId","description":"Public booking page config (no secrets).","response":"{ ok, data: { publicId, title, meetingTypes, agentSelectionMode, agents?, businessHours, branding, defaultFields, customFields, advanceBookingDays, minimumNoticeHours, slotInterval } }"},{"method":"GET","path":"/api/bookings/:publicId/slots","description":"Available time slots for a date range.","queryParams":{"start":{"type":"string","required":true,"description":"ISO date"},"end":{"type":"string","required":true,"description":"ISO date"},"meetingTypeId":{"type":"string","description":"Meeting type id or name"}}},{"method":"POST","path":"/api/bookings/:publicId/book","description":"Create a booking. Rate limited (~10/hour per IP+email).","body":{"slotStart":{"type":"string","required":true,"description":"ISO datetime from slots"},"attendeeInfo":{"type":"object","required":true,"description":"{ name, email, phone?, notes? }"},"meetingTypeId":{"type":"string"},"selectedAgentId":{"type":"string"},"customFieldsData":{"type":"object"},"invitedParticipants":{"type":"array","description":"[{ name, email }]"},"selectedCvrCompanyId":{"type":"string"},"groupBooking":{"type":"object"}},"response":"{ ok, data: { _id, publicId, status, startDateTime, endDateTime, meetingType, meetingChannel, assignedAgent, cancellationToken, manageUrl, googleMeetLink } }"},{"method":"GET","path":"/api/bookings/:publicId/cvr-search","description":"CVR lookup when the config has CVR enabled.","queryParams":{"q":{"type":"string","required":true,"description":"Min 3 characters"}}},{"method":"GET","path":"/api/bookings/booking/:publicId/details","description":"Guest booking details. Requires ?token=cancellationToken."},{"method":"POST","path":"/api/bookings/booking/:publicId/cancel","description":"Cancel or reschedule with cancellationToken.","body":{"cancellationToken":{"type":"string","required":true},"action":{"type":"string","description":"cancel | reschedule"},"newSlotStart":{"type":"string","description":"Required for reschedule"},"meetingTypeId":{"type":"string","description":"Required for reschedule"}}},{"method":"GET","path":"/api/bookings/reschedule-suggestions/:bookingId","description":"Suggested slots for reschedule. Requires ?token=."},{"method":"GET","path":"/api/bookings/group/:groupBookingId","description":"Group booking details. Requires ?token=."},{"method":"POST","path":"/api/bookings/group/:groupBookingId/join","description":"Join a group booking.","body":{"token":{"type":"string","required":true},"attendeeInfo":{"type":"object","required":true}}},{"method":"POST","path":"/api/bookings/feedback/:bookingPublicId","description":"Submit post-meeting feedback."},{"method":"POST","path":"/api/bookings/booking/:publicId/late","description":"Notify the host that the guest is late."}],"tokens":{"title":"cancellationToken","description":"Returned once on create. Store it securely (your backend) if you offer cancel/reschedule. Treat it like a password. manageUrl is /book/cancel/{publicId}?token=…"},"webhooks":{"description":"Public create/cancel/reschedule emit the same outbound events as the product: booking.created, booking.cancelled, booking.rescheduled. Subscribe via /api/v1/webhooks/subscriptions."},"authenticatedV1":{"description":"Workspace API keys use /api/v1/bookings (list, create, cancel, reschedule, available-slots) and /api/v1/booking-configs. Create/cancel/reschedule/slots share the same pipeline as this public API."}},"publicChat":{"overview":{"title":"Public Chat Widget API","description":"Unauthenticated HTTP API for a custom website chat UI. Identify the widget with its widgetId (from Indstillinger → Support & chat → Chat-widget). Do not put a workspace API key in the browser. Messages land as support tickets (channel: chat) in the inbox — same pipeline as the official embed.","baseUrl":"/api/chat-widget","authentication":"None on bootstrap. After POST /session, send x-visitor-token on every request. Treat the token like a password.","cors":"CORS is enabled. Empty security.allowedDomains allows all origins (*). If you set allowed domains on the widget, only those hosts (and subdomains) are accepted. Send Origin (browser) or ?origin= for the iframe parent page."},"flow":{"title":"Typical custom UI flow","steps":["GET /api/chat-widget/:widgetId/bootstrap — branding, texts, pre-chat, isOnline, file upload flags","POST /api/chat-widget/:widgetId/session — create or resume; store visitorToken","POST /api/chat-widget/:widgetId/conversations — firstMessage + optional preChatFields","GET .../conversations/:id/messages — poll ~4s for agent replies, agentTyping, isClosed","POST .../conversations/:id/messages — { content, attachments? }","Optional: POST read, typing, csat; uploads via /api/uploads/public/* (purpose: chatWidgetVisitor)"]},"endpoints":[{"method":"GET","path":"/api/chat-widget/:widgetId/bootstrap","description":"Public widget config (no secrets). Inactive or blocked domain returns { active: false }.","queryParams":{"origin":{"type":"string","description":"Embedding page origin (iframe). Custom UIs can rely on the Origin header."}},"response":"{ ok, data: { active, widgetId, isOnline, branding, texts, preChatForm, notifications, fileUpload } }"},{"method":"POST","path":"/api/chat-widget/:widgetId/session","description":"Create or refresh a visitor session. Rate limited (~180/hour per IP).","body":{"pageUrl":{"type":"string","description":"Page the visitor is on"}},"response":"{ ok, data: { visitorToken, visitor: { name, email, phone }, conversations: [{ conversationId, title, status, ticketNumber, lastMessagePreview, lastMessageAt, unread }] } }"},{"method":"POST","path":"/api/chat-widget/:widgetId/conversations","description":"Start a conversation (creates a support ticket). Requires x-visitor-token. Rate limited (~5/hour per IP).","body":{"firstMessage":{"type":"string","required":true},"preChatFields":{"type":"object","description":"{ name?, email?, phone?, customFields? }"},"pageUrl":{"type":"string"}},"response":"{ ok, data: { conversationId, ticketNumber, message } }"},{"method":"GET","path":"/api/chat-widget/:widgetId/conversations/:conversationId/messages","description":"Public messages + thread state. Poll about every 4 seconds. Requires x-visitor-token.","response":"{ ok, data: { messages, status, isClosed, permanentlyClosed, customerSatisfaction, agentTyping, fileUploadEnabled } }"},{"method":"POST","path":"/api/chat-widget/:widgetId/conversations/:conversationId/messages","description":"Send a visitor message. Requires x-visitor-token.","body":{"content":{"type":"string","description":"Required unless attachments are present"},"attachments":{"type":"array","description":"[{ url, filename, size, contentType }] from /api/uploads/public"}}},{"method":"POST","path":"/api/chat-widget/:widgetId/conversations/:conversationId/read","description":"Mark the thread read for the visitor (clears unread)."},{"method":"POST","path":"/api/chat-widget/:widgetId/conversations/:conversationId/typing","description":"Visitor typing indicator for agents.","body":{"isTyping":{"type":"boolean"}}},{"method":"POST","path":"/api/chat-widget/:widgetId/conversations/:conversationId/csat","description":"Rate a resolved/closed conversation 1–5.","body":{"rating":{"type":"number","required":true,"description":"Integer 1–5"}}},{"method":"POST","path":"/api/uploads/public/presign","description":"Presign a visitor file. purpose: chatWidgetVisitor. Headers: x-visitor-token. Body meta.widgetId required."}],"tokens":{"title":"visitorToken","description":"Returned from POST /session. Persist it (localStorage is fine for a website widget). Send as header x-visitor-token. Conversations are scoped to this visitor — do not expose it to other users."},"polling":{"description":"There is no visitor WebSocket. Poll GET messages about every 4s while a thread is open, and POST /session about every 12s for the conversation list / unread badge."},"authenticatedV1":{"description":"Agent inbox uses /api/v1/support-tickets (API key). Website visitors must use this public API, not v1."}}},"integrationScope":"Workspace API key integrations. Mobile app, session, and platform endpoints are intentionally omitted."}