A simple HTTP + JSON API for syncing contacts, deals, invoices, projects and email sequences with ImpactiveAI CRM. Designed for Zapier, Make, n8n, custom backends, and AI agents like Claude Code or Cloud Co-Work.
Sequence engagement filtering: sequence report fields uniqueOpens, uniqueClicks, totalOpens, totalClicks, openRate and clickRate, including contact drill-downs, use filtered activity. Recognisable bots, HEAD/prefetch probes, identifiable image-proxy opens and rapid sweeps of three distinct links within two seconds are excluded. These are estimates, not verified human engagement. Reports also expose rawUniqueOpens, rawUniqueClicks, rawTotalOpens and rawTotalClicks. Legacy per-email tracking counters remain raw. Filtering starts with requests recorded after the update; historical requests cannot be reliably reclassified. Rate denominators remain provider-accepted sends. Scanner requests still receive the normal image or link redirect.
All endpoints are served from your workspace host. If you use ImpactiveAI's standard domain, the base URL is:
If your workspace uses a custom domain (white-label setup), swap https://app.impactiveai.com for that domain — for example https://crm.yourcompany.com. Both the Agent API and Zapier API are reachable from any host that serves your workspace.
Every request must include an x-api-key header. API keys look like icrm_ followed by 32 hex characters and are scoped to a single workspace.
In the app, open Workspace Settings → Integrations → API Keys. Click Create Key, give it a name, and copy the full key — it is only shown once. Revoke a key any time from the same screen; the change takes effect immediately on the next request.
401 Unauthorized — missing or invalid x-api-key header.400 Bad Request — invalid body, missing required fields, or a referenced ID doesn't belong to your workspace.404 Not Found — the resource ID doesn't exist in your workspace.429 Too Many Requests — rare; back off and retry with exponential delay if you see this./api/agent/*A higher-level surface for AI agents and bespoke integrations. All endpoints require x-api-key, are restricted to that key's workspace, and never return mailbox credentials or OAuth tokens. Inbox actions are explicit. Delete moves mail to Trash, never permanently deletes it. Agents can create and edit reply drafts but cannot send them; sending requires a signed-in Hub user or your own mail client.
/api/agent/inbox/batchIMAP only. JSON body: {ids: string[], action: "archive"|"delete"|"done"}. Sequential, workspace-scoped per-account locking. Duplicate IDs run once. Returns {results:[{id,success,status,email?,error?,imapError?}],processed,total,failed}; failures do not stop later IDs. Archive moves to archiveFolder; delete moves to trashFolder, retaining the local row and appending “deleted to Trash”; done only changes local triageStatus. Failed moves do not update the email. Already moved rows succeed idempotently. Existing single-message agent /archive and /done are unchanged: /done still archives as well as completing.
/api/agent/inbox/:id/draftIMAP inbound reply draft. Body: {bodyText,bodyHtml?,to?,cc?,subject?,pushToImap?:true}. To/Cc accept comma-separated addresses or arrays. Defaults to the sender, original Cc and Re: subject. bodyText is the reply content; the Hub rebuilds both MIME alternatives with blank-line-separated paragraphs, Calibri 11pt, and an Outlook-style inline quote of the original message and its quoted history. Any supplied bodyHtml is replaced by the generated formatting. The full account signature is not appended to replies. Adds original In-Reply-To and full References chain; APPENDs with \Draft. Returns 201 with the draft, including replyBodyText (unquoted editable text); original becomes drafted. One active draft per original: 409 if one already exists. A failed APPEND returns 502 plus imapError and draftId when a local draft was saved: edit that draft to retry, do not create another. No sending occurs.
/api/agent/draftsOptional query status=draft,approved|sent|discarded (comma-separated), since (ISO, inclusive createdAt), emailId. Returns a draft array, newest updated first. GET /api/agent/drafts/:id returns one draft. Shape: id, workspaceId, emailAccountId, inReplyToEmailId, toAddresses, ccAddresses, subject, replyBodyText (editable reply only), bodyText (plain MIME part including quoted original), bodyHtml (formatted HTML MIME part including quoted original), status, createdBy, imapUid, imapFolder, sentEmailId, createdAt, updatedAt; also messageId, sendState, sendError, smtpAcceptedAt and inlineImages. No account credentials.
/api/agent/drafts/:idBody: any edited bodyText/bodyHtml/to/cc/subject and optional status draft|approved|discarded. Supplying bodyText regenerates the paragraph-formatted HTML and plain-text reply, including the original quoted thread; bodyHtml is rebuilt rather than used as-is. Returns the updated draft. Edits APPEND the replacement first, then move the old draft to Trash (no EXPUNGE); discard moves the draft to Trash. Failed mailbox changes preserve the previous draft content. 409 for terminal/sending/uncertain drafts. No agent send route exists.
/api/agent/drafts/:idMoves the corresponding IMAP Drafts message to Trash (never EXPUNGE), deletes the local draft row, and changes the original email's triageStatus from drafted back to triaged. Returns {id,deleted:true}. Only editable draft/approved rows can be deleted; IMAP failure leaves the local row intact.
Open queue tabs exclude done/archived messages. Needs you also includes every drafted message, without changing its classification, so drafts from unclassified mail remain visible. On an unclassified message, choose a type before saving classification (the inline priority control prompts for it). Hub replies use paragraph-preserving Calibri 11pt formatting, quote the original inline, and do not append the account's full signature; SMTP/Gmail replies send the full References chain, while Microsoft uses Graph's native threading. Optional send body {expectedUpdatedAt: "ISO timestamp"} protects the reviewed draft version: a stale version returns 409 before SMTP. The Hub supplies it automatically. Repeated requests for a sent draft return alreadySent=true.
Browser-session routes, not API-key routes: PATCH /api/emails/:id/triage accepts partial {type,priority,summary,followUpAt:"YYYY-MM-DD"|null}, sets triagedBy=user and triagedAt, and changes new to triaged. Waiting defaults to three working days ahead. POST /api/emails/:id/reclassify clears classification and returns the email as new; agents cannot overwrite user classification, even with override=true. POST /api/emails/:id/action {action} returns {email}. POST /api/emails/:id/sender-trust {status:trusted|blocked,kind:address|domain} returns a manualOverride whitelist entry and refreshes matching senders. GET /api/emails/:id/activity returns the latest 100 audit records with action/source/actor/createdAt/result (JSON string).
Phase C task/support routes: POST /api/agent/inbox/:id/task (x-api-key) accepts optional {name,projectGid,sectionGid,dueOn:"YYYY-MM-DD"|null,notes,assigneeGid} (assigneeGid may be a workspace user GID or email) and returns {asanaTaskGid,asanaTaskUrl,projectGid,projectName,sectionGid,assigneeGid,assigneeName}; it returns 409 if a task is already linked and 422 if the selected project, section, or assignee is invalid. GET /api/agent/asana/projects?accountId=… returns the non-archived projects for that account's configured Asana workspace as an array of Asana project objects. GET /api/agent/asana/users?accountId=… returns workspace users ({gid,name,email}). Both require x-api-key and an account in the caller's workspace. POST /api/agent/inbox/:id/to-support returns {ticketId,ticketUrl}, or 409 if already linked. GET /api/agent/inbox/brief?accountId=…&date=YYYY-MM-DD returns {accountId,date,carriedOverCount,byType,draftsWaitingCount,tasksCreatedToday,openCount,priorityOne,waitingDue,headline}. Browser-session equivalents are POST /api/emails/:id/task (accepts projectGid, sectionGid and assigneeGid), POST /api/emails/:id/to-support, POST /api/emails/batch-create-tasks {ids} (uses account defaults), and GET /api/emails/triage-brief?accountId=…&date=YYYY-MM-DD.
POST /api/emails/batch-action has the same body and JSON response as the agent batch route. Optional Accept: application/x-ndjson streams {processed,total,result} after every ID and a final {complete:true,...results}. Browser draft equivalents: POST /api/emails/:id/draft, GET /api/drafts and /api/drafts/:id, PATCH /api/drafts/:id. POST /api/drafts/:id/send is session-only and sends exactly the stored reviewed draft; returns {draft,email?,warnings,alreadySent?}. SMTP acceptance is persisted before Sent APPEND (\Seen), stale-draft cleanup and original completion/archive. Post-send failures produce warnings, never a resend. Ambiguous SMTP failures become sendState=uncertain and block retries; verify your mail-client Sent folder. Incoming messages' isRead is untouched by these actions.
IMAP settings now include editable archiveFolder, draftsFolder and trashFolder. Trash discovery prefers \Trash, then Trash, then Deleted Items. MOVE requires native IMAP MOVE support; unsupported servers return 502 rather than using ImapFlow's destructive EXPUNGE fallback. Action tables: email_drafts and email_actions. Classification/action errors: 404 outside workspace, 409 conflicts, 422 validation/provider errors, 502 mailbox errors with imapError.
The Hub classifies only inbound new messages on IMAP accounts. User- and agent-classified rows are protected, including a conditional recheck at write time. Deterministic rules run first; remaining messages use one gpt-4o-mini JSON call each. Invalid or failed AI classifications leave the message new. Successful classifications set status triaged and triagedBy rule. Nothing is archived, moved, drafted, sent, marked read, or turned into a Support ticket.
/api/emails/triage-runBrowser-session route. JSON body { "accountId": "account-id" }. Returns 202 { "runId": "uuid", "status": "running" }. An active run returns its existing runId rather than creating a duplicate.
/api/agent/inbox/run-triageSame start behavior, authenticated with x-api-key and scoped to that key's workspace. JSON body { "accountId": "account-id" }. Returns 202 with runId and status. Returns 404 for an account outside the workspace, 422 for invalid input or a non-IMAP account.
/api/emails/triage-run/:accountIdBrowser-session run status. Returns only accountId, status (idle/running/complete/failed), and parsed progress. No credentials or OAuth tokens.
/api/agent/inbox/run-status?accountId=account-idAPI-key-authenticated status with the same response shape, workspace scoped. Returns 404 for an unknown/out-of-workspace account; 422 if accountId is absent.
IMAP account settings on the existing browser-session POST/PATCH email-accounts routes: triageAutoEnabled (default false), triageRunTimes (JSON string or array of 1–8 HH:MM times, default ["07:00","16:00"], Europe/London), triageOnSync (default false), triageRunCap (1–1000, default 300), triageLlmCallCap (0–1000, default 300). Scheduled runs are morning/afternoon; button and on-sync runs are manual. Interrupted runs with no heartbeat for ten minutes become failed; untouched messages remain new.
Account storage adds triageRunStatus and JSON-string triageRunProgress. Completed runs are recorded in email_triage_runs: llmCalls counts attempted AI calls, llmTokens records reported total token usage, typeCounts is JSON with counts of successfully classified types (including support/calendar). newCount is remaining new inbound mail; fyiCount includes calendar, while reply/task/noise/waiting retain existing aggregate fields. carriedOverCount counts inbound before today in Europe/London not done/archived. The classifier uses existing calendar subject/system hints and calendar text when headers are unavailable; it does not fetch IMAP headers.
Triage values: status new | triaged | drafted | tasked | archived | done; type reply | task | fyi | noise | waiting | calendar | support; priority 1 (highest) through 3. Messages classified as support are labeled only in this phase; they do not create a Support ticket.
The emails table stores nullable imapUid (the message UID in its synced IMAP folder) and repliedAt (the date of an outbound mail-client reply linked through In-Reply-To or References). These are sync metadata and are not added to the agent inbox JSON shapes. Incremental sync updates existing messages' isRead and fills a missing UID; it marks local INBOX rows done when no longer present in the recent server window, and links replies by message-id.
/api/agent/inboxList inbound messages, newest first. Query parameters: accountId, comma-separated status (default new), type, priority, inclusive since (ISO timestamp), includeArchived (default false), inInbox=true (only rows confirmed by that account's most recent completed header refresh), limit (default 50, max 200), offset, and bodyChars (default 2000, max 50000). Each item includes headersRefreshedAt, which is null until refreshed, alongside message/thread IDs, dates, sender/recipient fields, truncated bodyText, read/automation/trust flags, CRM contact/company summaries, all triage fields, references header, and asanaTaskUrl. The inInbox filter requires a non-null refresh timestamp on or after the most recent completed refresh's startedAt; with no completed refresh, it returns no rows.
/api/agent/inbox/refresh-headersRefresh existing local messages from an IMAP account without fetching message bodies or attachments, changing the remote Seen flag, or writing local isRead. JSON body: accountId (required), days (integer 1–365, default 180). Walks INBOX and discovered Sent folders, matches by messageId, skips messages not held locally, and updates referencesHeader, isAutomated, hasListUnsubscribe, toMeDirect and senderTrust (blocked > trusted > contact > unknown). It does not insert, send, archive or delete messages.
Returns 202 with { "status": "running", "progress": "{...}" }; progress is a JSON string. Repeating the request while a run is active resumes it without resetting the cursor. Processes up to 500 UIDs per page, checkpoints each 50-message batch, and records totalInFolder. IMAP operations are serialized per account, connections close after each page, and there is a 2-second pause between pages. On errors it reconnects with 5s/15s/60s backoff, or 120s for connection-limit errors (up to 10 attempts); progress records connectionLimitRetries. Exhaustion sets status failed and saves lastError.
/api/agent/inbox/refresh-headers/cancelCancel a running header refresh. JSON body: {"accountId":"your-account-id"}. Returns {"status":"cancelled"}, or the account's current non-running status. Returns 404 for an account outside the workspace.
/api/agent/email-accounts/:id/backfillAPI-key-authenticated version of the browser backfill route. Required JSON body: { "days": 180 } (integer 1–365). Background, resumable, paged 500 UIDs across INBOX and Sent, deduplicated by messageId. It imports missing messages only; use refresh-headers to refresh existing rows. IMAP operations are serialized per account, connections close after each page, and there is a 2-second pause between pages. Errors reconnect with 5s/15s/60s backoff, or 120s for connection-limit errors for up to 10 attempts; progress includes connectionLimitRetries and totalInFolder, and exhausted retries set status to failed.
Returns 202 with { "status": "running", "progress": "{...}" }, with progress as a JSON string. Returns 400 for invalid days, 404 for an unknown/out-of-workspace account, or 422 for a non-IMAP account. No browser session is required.
/api/agent/email-accounts/:id/backfill/cancelCancel a running backfill for the account in the path. No request body is required. Returns {"status":"cancelled"}, or the account's current non-running status.
/api/agent/email-accounts/:id/sync-statusRead only the sync/job status for an account in the API key's workspace. No query parameters or request body. Returns exactly the fields below, with both progress values parsed as objects; never returns credentials or OAuth tokens. Returns 404 for an unknown/out-of-workspace account. Job statuses include idle, running, complete, failed and cancelled. Progress includes folder totals, cursor, retryAttempt and an optional lastError; after a completed refresh, lastCompletedStartedAt preserves that run's start time even while a newer refresh is running.
/api/agent/inbox/reconcileManually archive local inbound messages that are missing from INBOX after a completed header refresh. JSON body: accountId and optional positive-integer olderThanDays (default 14). Only rows with status new, emailDate older than the cutoff, and headersRefreshedAt null are changed. Sets status archived, triagedBy rule, and the reason Not in INBOX at reconcile; it makes no IMAP changes and is never scheduled automatically. Requires the account's header refresh status to be complete. Returns {"count":N}.
/api/agent/inbox/:idFetch one inbound message with full, untruncated bodyText, bodyHtml, its headersRefreshedAt, and a thread array. Thread messages are oldest first, include direction, and each includes headersRefreshedAt. Returns 404 when the message is not in the key's workspace.
/api/agent/inbox/carried-overList inbound messages dated before 00:00 Europe/London today whose status is neither done nor archived. A waiting message with followUpAt is excluded. Supports account, type, priority, pagination, and body truncation filters; returns the same item shape as the inbox list.
/api/agent/inbox/:id/triageSet triage fields. Required JSON: type, priority, summary, reason; optional status and date followUpAt. Sets triagedBy=agent and triagedAt. A message last changed by a user returns 409 even with override:true; use Reclassify in the Hub first. Invalid enum values return 422.
/api/agent/inbox/triageApply the same triage fields to a JSON array of objects, each with an id. Returns a per-message result array; user-override conflicts are reported as 409 entries without stopping other updates.
/api/agent/inbox/:id/archiveMove an IMAP message to its account's configured archive folder, creating that folder if needed, then set local folder to Archive and status to archived. Optional JSON body: {"reason":"..."}. Idempotent when already archived. IMAP move failures return 502 with imapError; the local row is not changed on failure. Does not delete the message.
/api/agent/inbox/:id/doneArchive the IMAP message and set status to done. Optional JSON body: {"reason":"..."}. Uses the same 502 behavior as archive when the remote move fails.
/api/agent/inbox/summaryOptional date in YYYY-MM-DD (default today, Europe/London) and accountId. Returns byTriageStatus, byTriageType, carriedOverCount, confirmedInInbox (inbound rows confirmed by the latest completed refresh and not done or archived, across dates), and priorityOne items containing id, from, subject, and summary.
/api/agent/triage-runsInsert an email triage run. JSON requires accountId and runKind (morning | afternoon | eod_snapshot | bankruptcy); count fields and note are optional. Count fields: newCount, repliedDraftedCount, taskedCount, fyiCount, noiseCount, waitingCount, and carriedOverCount. Returns the created row.
/api/agent/triage/whitelistList whitelist entries for required accountId; optional status filters trusted, blocked, or neutral rows. Each row includes its email/domain kind, status, and manualOverride.
/api/agent/triage/whitelist/:idSet JSON {"status":"trusted"} (or blocked/neutral). Marks the row manualOverride:true, so scheduled rebuilds preserve it, and recomputes sender trust only for open triage messages matching this address or domain.
/api/agent/triage/whitelistCreate or update a manual override. JSON: accountId, either email or domain, kind (address/domain), and status (trusted/blocked/neutral). Recomputes sender trust only for messages matching that address or domain.
/api/agent/contactsList contacts in the workspace. Optional limit query parameter (default 100, max 500).
/api/agent/contacts/searchFind existing contacts before creating a new one. Provide email, phone, or both as query parameters. Email matching is case-insensitive and exact; phone matching compares digits only, ignoring spaces and punctuation. When both are supplied, a match on either value is returned. Results are limited to the API key's workspace.
Returns { "contacts": [...], "count": N }; no matches returns an empty array and count 0. At least one valid lookup value is required.
/api/agent/contactsCreate one or many contacts. Send a single object, or an array of objects for batch create.
Required: firstName, lastName. Returns the created contact (or an array of contacts for batch).
/api/agent/contacts/:idUpdate a contact. Send only the fields you want to change.
/api/agent/contacts/:id/notesAdd a note activity to a contact in the API key's workspace. JSON body: {"body":"Caller asked about the renewal date."}. The body is required (1–10000 characters). Returns the created activity with entityType="contact" and type="note"; returns 404 if the contact is not in this workspace.
/api/agent/contacts/:id/repliesList inbound emails received from this contact (up to 100, most recent first). Useful for agents that want to read replies before deciding next steps.
/api/agent/sending-accountsList the SMTP sending accounts configured for this workspace. Use the returned id as sendingAccountId when creating or updating a sequence, so the agent can pick which "from" address the sequence sends from.
Each row contains: id, name, fromName, fromEmail, smtpHost, syncEnabled. Credentials are never returned. If the workspace has no SMTP accounts the list is empty and sequences fall back to the platform default sender (Resend).
/api/agent/sequencesCreate a new email sequence with optional inline steps. Each step needs a subject, body, and a delayDays (0 for the first send). status defaults to active. sendingAccountId is optional — call GET /api/agent/sending-accounts to pick one, or omit (or set to null) to fall back to the workspace default sender (Resend if no SMTP account is connected).
Returns 201 with the created sequence and its steps array.
/api/agent/sequencesList every sequence in the workspace. Each row includes stepCount and an enrollmentCounts object (total, plus a count per status — active / paused / completed / unsubscribed).
/api/agent/sequences/:idFetch a single sequence with its full step list (ordered by position) and enrollment counts.
/api/agent/sequences/:idUpdate a sequence's name, description, status (draft / active / paused / archived), or sendingAccountId (must be an id returned by GET /api/agent/sending-accounts; set to null to send via the platform default).
/api/agent/sequences/:id/stepsAppend a new step (or insert at a specific zero-based position; existing steps at or after that index shift down). delayDays is the wait before this step is sent relative to the previous send.
/api/agent/sequences/:id/steps/:stepIdEdit any field on a step. Setting position reorders the sequence so positions stay contiguous.
/api/agent/sequences/:id/steps/:stepIdDelete a step and compact the remaining positions back to 0..n-1.
/api/agent/sequences/:id/enrollEnroll contacts into a sequence by any combination of contactIds, tagIds, and listIds (at least one must be provided). The resolved audience is deduped against people already enrolled; skipped rows include a reason (already_enrolled or not_found).
Using tagIds or listIds requires the Audience module to be enabled for the workspace; plain contactIds always works.
Returns 201 with:
/api/agent/sequences/:id/enrollmentsList who is on the sequence plus their state. Each row contains: id, contactId, a denormalised contact summary (id, firstName, lastName, email), status (active / paused / completed / unsubscribed), currentStep, startedAt, nextSendAt, completedAt, pausedAt, pauseReason, lastSendError, lastSendErrorAt, consecutiveFailures, and updatedAt.
Supports ?since=ISO_8601 to only return enrollments updated strictly after that timestamp (exclusive boundary).
/api/agent/sequences/:id/sendsList outbound emails sent by this sequence (up to 500, newest first). Each row contains: id, contactId, sequenceStepId, sequenceEnrollmentId, subject, fromAddress, toAddresses, body, emailDate. Sends remain visible here even after a contact is unenrolled (sequenceEnrollmentId may then be null). Sends that happened before this linkage was introduced are not returned.
Supports ?since=ISO_8601 filtered on emailDate >= since (inclusive boundary).
/api/agent/sequences/:id/repliesList inbound replies from contacts currently enrolled in this sequence (up to 500, newest first). Each row contains: id, contactId, enrollmentId, subject, fromAddress, body, emailDate. Replies from contacts who have been fully unenrolled are not returned by this endpoint — use GET /api/agent/contacts/:id/replies if you need reply history per contact.
Supports ?since=ISO_8601 filtered on emailDate >= since (inclusive boundary).
/api/agent/sequences/:id/pausePause active enrollments. Pass contactId to pause a single person, or contactIds for several. To pause everyone currently active on the sequence you must explicitly pass { "all": true } — an empty body pauses nothing and returns a 400, so a sequence is never paused by accident. Returns { "paused": <count> }.
/api/agent/sequences/:id/resumeResume a single paused enrollment. Pass contactId; the enrollment is set back to active and continues from its current step and next send time — it is never restarted from the first email. Returns { "resumed": 1, "enrollment": {...} } when reactivated, { "resumed": 0 } if the enrollment was not paused (already active/completed), and 404 if the contact is not enrolled in the sequence.
/api/agent/sequences/:id/contacts/:contactIdUnenroll a contact from a sequence.
/enrollments, /sends and /replies endpoints, remember the highest updatedAt (enrollments) or emailDate (sends/replies) you've seen and pass it back as ?since= on the next poll to get only new rows. Note the boundary is exclusive for enrollments (>) and inclusive for sends/replies (>=), so for sends/replies de-dupe on the row id when the cursor matches the previous poll's max emailDate. A 5–15 minute poll interval is usually plenty.
/api/agent/bookingsList all bookings in the workspace.
/api/agent/bookingsCreate a booking, optionally linked to an existing contact.
/api/zapier/*Designed for poll-based integrations (Zapier, Make, n8n). List endpoints return up to 100 records sorted by updatedAt descending and support a ?since=ISO_8601 filter for incremental polling. Action endpoints (POST) create new records on demand.
/api/zapier/auth/testVerify an API key. Returns the workspace's id and name. Used by Zapier's Auth Test step.
/api/zapier/companiesList companies. Optional ?since=2026-05-01T00:00:00Z returns only companies updated after that timestamp.
/api/zapier/companiesCreate a company. name is required.
/api/zapier/contactsList contacts with optional ?since= filter. Each row includes companyName for convenience.
/api/zapier/contactsCreate a contact. firstName and lastName are required.
/api/zapier/listsCreate a static contact list. name is required; description is optional. These endpoints require the Audience module to be enabled and are scoped to the workspace belonging to the supplied API key. Names are matched case-insensitively within the workspace: a new list returns 201 with created: true; an existing list is returned with 200 and created: false.
Response: { "list": { "id", "workspaceId", "name", "description", "listType", "createdBy", "createdAt", "updatedAt" }, "created": true }.
/api/zapier/lists/:id/membersAdd contacts to an existing list without removing its current members. Pass either contactId or contactIds (up to 500 IDs). Repeating an addition is safe; existing memberships are counted as skipped. Every list and contact must belong to the API key's workspace.
Response: { "listId": "LIST_ID", "added": 1, "skipped": 0, "addedContactIds": ["CONTACT_ID"] }.
/api/zapier/tagsCreate a workspace tag. name is required; optional color is a display color. Names are matched case-insensitively within the workspace: a new tag returns 201 with created: true; an existing tag is returned with 200 and created: false.
Response: { "tag": { "id", "workspaceId", "name", "color", "createdBy", "createdAt" }, "created": true }.
/api/zapier/contacts/:contactId/tagsAdd an existing workspace tag to a contact without replacing any other tags. Pass tagId. Repeating the same assignment returns added: false and does not create another membership.
Response: { "contactId": "CONTACT_ID", "tag": { "id", "name", "color" }, "added": true }. A new assignment returns 201; an existing assignment returns 200.
/api/zapier/dealsList deals with stage name, company name, and primary contact name resolved.
/api/zapier/dealsCreate a deal. name is required; if stageId is omitted, the deal is placed in the first stage of the pipeline.
/api/zapier/invoicesList invoices with totals, status, due/paid dates, and resolved company/contact names.
/api/zapier/projectsList projects with stage name and company name.
For poll-based integrations, store the highest updatedAt you've seen and pass it back as ?since= on the next poll. Records are returned newest-first, capped at 100 per request. Typical poll interval: 5–15 minutes.
If you hit something unexpected, get in touch via the contact page. For Zapier-specific setup, see the Integrations page.
Create a free workspace and generate your first API key in under a minute.
Create your free account