Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.enginy.ai/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Use POST /v1/campaigns to create a draft campaign from the public steps format. This endpoint is designed for external integrations. You send a single ordered steps array and Enginy compiles it into the internal campaign graph.

Quick Start

  1. Start steps with the first action that should happen.
  2. Add each next action in the exact order it should run.
  3. Use condition to branch on lead data.
  4. Use linkedin_connection to send a connection request and then branch on acceptance.
  5. Put shared continuation steps after the branch instead of duplicating them inside both sides.

Rules

  • Build the campaign as an ordered steps array from first action to last action.
  • Use condition and linkedin_connection when you need branches.
  • Put shared follow-up work after the branching step instead of duplicating the same steps in both branches.
  • Use { "type": "end" } only when that branch should stop completely.
  • waitForAcceptance.unit must always be days.
  • waitForAcceptance.value must be an integer greater than or equal to 1.
  • Validation errors return full paths such as steps[0].onTrue[1].subject.

Step Types

Step typeRequired fieldsOptional fields
emailcontentsubject, delay
linkedin_messagecontent or attachmentattachment, attachmentName, contentType, delay
linkedin_message_bundlemessagesdelay
linkedin_voice_messagecontent+voiceId or attachmentvoiceSettings, delay
linkedin_inmailsubject, contentdelay
linkedin_visit_profileNonedelay
linkedin_like_last_postNonedelay, reactionType
whatsapp_messagecontentdelay
conditionconditiononTrue, onFalse
linkedin_connectionNonewaitForAcceptance, delay, content, onAccepted, onNotAccepted
tasktaskType, titlenote, ownerId, delay
add_to_another_campaigncampaignIddelay
endNoneNone
email.subject is required for the first email on every live path; follow-up emails in the same thread can omit it. linkedin_connection.waitForAcceptance is optional — omit it (and the branches) for a fire-and-forget connection request that just sends the invite and continues with whatever follows. add_to_another_campaign.campaignId must reference a campaign owned by the same client as the API key.

linkedin_message_bundle

Sends several LinkedIn messages back-to-back in the same step. Each entry in messages can carry text, an attachment, or both:
FieldTypeNotes
contentstringOptional text body. At least one of content or attachment required.
attachmentstringOptional URL or storage key of a file to send.
attachmentNamestringOptional display name for the attachment.
contentType"AUDIO" | "IMAGE" | "FILE" | "VIDEO"Optional. Recommended when attachment is set.

linkedin_voice_message

Sends a LinkedIn voice message. You can either generate audio from text via text-to-speech (content + voiceId) or provide a pre-recorded audio attachment (attachment with contentType: "AUDIO"). Use GET /v1/voices to discover valid voiceId values (system voices and identity-cloned voices owned by the client).
FieldTypeNotes
contentstringOptional. Text that will be synthesized to speech using the configured voiceId.
voiceIdstringOptional. ID of a system voice or an identity-cloned voice. Discoverable via GET /v1/voices.
attachmentstringOptional. URL or storage key of a pre-recorded audio file to send instead of synthesized text.
attachmentNamestringOptional. Display name for the audio attachment.
contentType"AUDIO"Optional. Recommended when attachment is set.
voiceSettingsobjectOptional. Fine-tuning of the synthesized voice and background audio (see below).
The step must satisfy one of: (a) both content and voiceId are provided so we synthesize speech, or (b) attachment is provided to send a pre-recorded audio file. voiceSettings only applies in mode (a). voiceSettings fields (all optional):
FieldTypeNotes
voiceSpeednumberSpeech rate multiplier. Range 0.52.0.
voiceStabilitynumberVoice stability percentage. Range 0100. Higher is steadier.
backgroundNoisestringBackground noise preset (e.g. none, office).
backgroundVolumenumberBackground noise volume. Range 0100. Ignored when backgroundNoise is none.

Branching Step Types

Use this stepWhen you need itBranch fields
conditionBranch based on what Enginy already knows about the leadonTrue, onFalse
linkedin_connectionSend a connection request and branch based on acceptanceonAccepted, onNotAccepted

Condition Types

condition.type supports exactly these values:
Condition typeExtra fieldsonTrue branch runs whenonFalse branch runs when
has_linkedin_profileThe lead has a LinkedIn profileThe lead does not have a LinkedIn profile
has_professional_emailThe lead has a professional emailThe lead does not have a professional email
is_already_connectedThe lead is already a LinkedIn connectionThe lead is not already a LinkedIn connection
has_been_contactedThe lead has already been contactedThe lead has not been contacted yet
has_whatsapp_accountThe lead has a WhatsApp accountThe lead does not have a WhatsApp account
email_openedwaitFor (required)The lead opens the preceding email within waitForwaitFor elapses without the email being opened
email_clickedwaitFor (required)The lead clicks a link in the preceding email within waitForwaitFor elapses without a link being clicked
task_completedwaitFor (required)The preceding task step is marked complete within waitForwaitFor elapses without the task being marked complete
connection_acceptedwaitFor (required)The lead accepts an earlier LinkedIn connection request within waitForwaitFor elapses without the connection being accepted
lead_fieldfield, operator, value (mostly)The contact’s field matches operator (and value, where applicable)The match fails
lead_field lets you branch on any built-in column or custom field. operator accepts: EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUALS, LESS_THAN_OR_EQUALS, IS_EMPTY, IS_NOT_EMPTY. value is required for every operator except IS_EMPTY and IS_NOT_EMPTY.

Timed Conditions (waitFor)

email_opened, email_clicked, task_completed, and connection_accepted require a waitFor object on the condition:
{
  "type": "email_opened",
  "waitFor": { "value": 3, "unit": "days" }
}
  • waitFor.value must be a positive number.
  • waitFor.unit must be one of seconds, minutes, hours, or days.
  • email_opened and email_clicked must be placed immediately after an email step.
  • task_completed must be placed immediately after a task step.
  • connection_accepted checks whether the lead has accepted a LinkedIn connection request that was sent earlier in the campaign. Use this when you want to fan out work after the request without inlining the acceptance check on the linkedin_connection step itself.

Validation Checklist

  • steps must contain at least one step.
  • Every branch array you provide must contain at least one step.
  • Use { "type": "end" } if a branch should stop explicitly.
  • waitForAcceptance.unit must always be days.
  • waitForAcceptance.value must be a whole integer greater than or equal to 1.
  • If GET /v1/tasks/owners returns owners, every task step must include a valid ownerId.
  • Every linkedin_voice_message step must reference a voiceId returned by GET /v1/voices.

Task Owners

If your campaign contains task steps, call GET /v1/tasks/owners first.
  • If that endpoint returns owners, every task step must include a valid ownerId.
  • If no owners are returned, ownerId can be omitted.

Voices

If your campaign contains linkedin_voice_message steps, call GET /v1/voices to discover the available voiceId values:
  • source: "system" voices are available to every client.
  • source: "identity" voices are cloned from one of the client’s identities.
  • A voiceId that is not in the list will fail validation when creating or validating the campaign.
  • POST /v1/campaigns creates the draft campaign.
  • GET /v1/campaigns lists campaigns after creation.
  • GET /v1/tasks/owners returns the valid task owners for task steps.
  • GET /v1/voices returns the valid voices for linkedin_voice_message steps.
  • POST /v1/add-contact-to-campaign adds one contact to an existing campaign.
  • POST /v1/add-contact-group-to-campaign adds every contact in a contact group to an existing campaign.

Adding Contacts After Creation

Use the campaign creation endpoint to build the sequence first, then use one of the audience endpoints below to start adding contacts to it.
  • POST /v1/add-contact-to-campaign is for one contact at a time.
  • POST /v1/add-contact-group-to-campaign is for an entire contact group and returns counts for newly added, reactivated, already-present, and skipped contacts.

Example: Add a Contact Group to a Campaign

{
  "campaignId": 456,
  "contactGroupId": 123
}

Example: Condition First

{
  "name": "Email or fallback task",
  "identityId": 323,
  "steps": [
    {
      "type": "condition",
      "condition": {
        "type": "has_professional_email"
      },
      "onTrue": [
        {
          "type": "email",
          "subject": "Great to connect",
          "content": "Sharing a quick overview."
        }
      ],
      "onFalse": [
        {
          "type": "task",
          "taskType": "TODO",
          "title": "Find email first",
          "ownerId": "owner-1"
        }
      ]
    }
  ]
}

Example: LinkedIn Acceptance Branch

{
  "name": "LinkedIn acceptance flow",
  "identityId": 323,
  "steps": [
    {
      "type": "linkedin_connection",
      "delay": {
        "value": 1,
        "unit": "days"
      },
      "waitForAcceptance": {
        "value": 5,
        "unit": "days"
      },
      "onAccepted": [
        {
          "type": "linkedin_message",
          "content": "Thanks for accepting.",
          "delay": {
            "value": 2,
            "unit": "hours"
          }
        }
      ],
      "onNotAccepted": [
        {
          "type": "task",
          "taskType": "TODO",
          "title": "Review no-response path",
          "ownerId": "owner-1"
        }
      ]
    },
    {
      "type": "email",
      "subject": "Following up",
      "content": "Continuing after the LinkedIn decision.",
      "delay": {
        "value": 1,
        "unit": "days"
      }
    }
  ]
}

Example: LinkedIn Message Bundle

Send a short text message followed by a file attachment in the same LinkedIn step.
{
  "name": "LinkedIn bundle intro",
  "identityId": 323,
  "steps": [
    {
      "type": "linkedin_message_bundle",
      "delay": {
        "value": 2,
        "unit": "hours"
      },
      "messages": [
        {
          "content": "Hi {firstName}, great to connect! Sending along a short intro."
        },
        {
          "content": "Here is the deck we discussed.",
          "attachment": "https://cdn.example.com/deck.pdf",
          "attachmentName": "intro-deck.pdf",
          "contentType": "FILE"
        }
      ]
    }
  ]
}

Example: Branch on Email Click

Follow up on LinkedIn only if the lead clicks the tracked email link within three days, otherwise bump via email.
{
  "name": "Click-gated follow-up",
  "identityId": 323,
  "steps": [
    {
      "type": "email",
      "subject": "Thought you might find this useful",
      "content": "Here is the landing page with the details."
    },
    {
      "type": "condition",
      "condition": {
        "type": "email_clicked",
        "waitFor": {
          "value": 3,
          "unit": "days"
        }
      },
      "onTrue": [
        {
          "type": "linkedin_message",
          "content": "Saw you checked out the page. Happy to jump on a quick call."
        }
      ],
      "onFalse": [
        {
          "type": "email",
          "subject": "Still interested?",
          "content": "Quick bump in case this got buried."
        }
      ]
    }
  ]
}

Example: Task Completion Branch

Wait up to five days for a task to be marked complete; otherwise fall back to another channel.
{
  "name": "Task completion fallback",
  "identityId": 323,
  "steps": [
    {
      "type": "task",
      "taskType": "CALL",
      "title": "Initial discovery call",
      "ownerId": "owner-1"
    },
    {
      "type": "condition",
      "condition": {
        "type": "task_completed",
        "waitFor": {
          "value": 5,
          "unit": "days"
        }
      },
      "onTrue": [
        {
          "type": "email",
          "subject": "Great talking to you",
          "content": "Following up with a quick summary of what we discussed."
        }
      ],
      "onFalse": [
        {
          "type": "linkedin_message",
          "content": "Hey {firstName}, still keen to chat whenever works for you."
        }
      ]
    }
  ]
}

Example: Explicitly Stop One Branch

{
  "name": "Accepted-or-stop flow",
  "identityId": 323,
  "steps": [
    {
      "type": "linkedin_connection",
      "content": "Would love to connect and share a few ideas.",
      "waitForAcceptance": {
        "value": 3,
        "unit": "days"
      },
      "onAccepted": [
        {
          "type": "linkedin_message",
          "content": "Thanks for accepting. Sharing a quick follow-up.",
          "delay": {
            "value": 4,
            "unit": "hours"
          }
        }
      ],
      "onNotAccepted": [
        {
          "type": "end"
        }
      ]
    },
    {
      "type": "email",
      "subject": "Resources after connecting",
      "content": "Continuing only for contacts who accepted the connection request.",
      "delay": {
        "value": 1,
        "unit": "days"
      }
    }
  ]
}