/*
* Zernio API
*
* API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal. Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema). Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
*
* The version of the OpenAPI document: 1.522.1
* Contact: support@zernio.com
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::{Deserialize, Serialize};
/// WorkflowNode : A node in a workflow graph. `config` shape depends on `type`.
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct WorkflowNode {
/// Stable node id referenced by edges
#[serde(rename = "id")]
pub id: String,
/// Node kind. The 17 supported types break into four groups: messaging (send_message), control flow (trigger, condition, delay, wait_for_reply, a_b_split, end), data ops (set_variable, set_field, add_tag, remove_tag, enroll_sequence), integrations (webhook, hubspot_upsert_contact, ai, handoff, start_call).
#[serde(rename = "type")]
pub r#type: Type,
/// Type-specific settings. Every string field supports `{{variable}}` interpolation against the run's variable bag, resolved at execution time. **trigger**: `{ triggerType: inbound_message|api_call|whatsapp_event|referral|story_mention|comment|reaction|lead_received, keywords:[string], matchType: any|contains|exact|regex, onlyFirstMessage:boolean, eventType: message_sent|message_delivered|message_read|message_failed|reaction, referral:{ ref, matchType: exact|prefix|any }, reaction:{ emojis:[string] }, comment:{ keywords:[string], matchType: any|contains|exact|regex, platformPostId }, leadReceived:{ formIds:[string], pageAccountIds:[string], consentCheckboxKey, defaultCountry, allowCrossProfile:boolean }, cooldownHours:int (1-720) }`. Default `triggerType` is `inbound_message` for legacy nodes. A workflow starts only on events of its own trigger type: - `inbound_message`: a message. - `referral`: an ig.me / m.me / click-to-message ad referral (standalone, or the first message carrying one). The two are deduplicated per conversation within 60 seconds. - `story_mention`: an Instagram story mention. - `reaction`: a reaction (Instagram, Facebook, WhatsApp, Telegram groups). - `comment`: an Instagram or Facebook comment (see below). - `whatsapp_event`: a WhatsApp status of a message we sent, filtered by `eventType`. - `api_call`: only via `POST /v1/workflows/{workflowId}/trigger`. - `lead_received` (WhatsApp workflows only): a Meta lead ad form is submitted. **`comment` runs** start with no conversation. The first `send_message` is sent as a private reply to the comment, and the run continues in the conversation that reply opens. That first `send_message` must follow the trigger directly and be text (optionally with buttons or quick replies) or cards (checked on save). On some Instagram accounts Meta refuses buttons and cards to commenters who do not follow the account. When an active comment automation's keywords match the comment, the automation answers and the workflow does not start. **`lead_received`** needs `leadReceived.consentCheckboxKey` and at least one of `formIds` or `pageAccountIds`. A form scope wins over a Page scope, then the earliest activated workflow. It starts only for leads that: - were submitted in the last hour and after the workflow was activated; - have the consent checkbox ticked (the form's custom disclaimer, see `POST /v1/ads/lead-forms`); - have a phone number that parses to a valid international number (`defaultCountry`, ISO 3166 alpha-2, defaults to the country of the workflow's WhatsApp number). Otherwise the lead is marked `unreachable`. The Page and the WhatsApp number must be in the same profile unless `allowCrossProfile` is true. Every send or call reached first from the trigger must be a WhatsApp template (checked on save). **Other trigger rules**: - `referral.matchType` defaults to `any`. - `cooldownHours` stops the same contact from starting the workflow again inside the window. It applies to inbound_message, referral, story_mention, reaction, whatsapp_event and lead_received, where it defaults to 24. - A `whatsapp_event` workflow never starts on the status of a message a workflow, sequence or comment automation sent. - A redelivered platform event never starts the same workflow twice. **Variables** available from the start of every run (absent values are `''`): - Message: `lastMessage`, `inboundText`, `triggerText`, `conversationId`. - Contact: `contact.name`, `contact.phone`, `contact.handle`, `contact.tags` (array), `contact.fields` (the contact's custom fields object), `contact.lastInteractionAt` (ISO date), `contact.lastInteractionHoursAgo` (number), `contact.isFollower` (Instagram, from the stored profile: true, false or `''` when unknown). - Referral: `referral.ref`, `referral.source`, `referral.type`, `referral.adId`, `referral.adTitle` (the ad title or headline Meta sent), `referral.adName` (the synced ad's name, else `referral.adTitle`), `referral.campaignName` (the synced ad's campaign). - Taps: `postback.payload`, `postback.title`, `quickReply.payload`, `quickReply.title`. - Story and comment: `story.id`, `story.url`, `comment.id`, `comment.text`, `comment.platformPostId`. - Reaction: `reaction.emoji`, `reaction.action` (added|removed). `api_call` runs also get the request's `variables`, merged over these. `lead_received` runs also get `lead.id`, `lead.leadgenId`, `lead.formId`, `lead.formName`, `lead.adId`, `lead.adsetId`, `lead.campaignId` and every form answer by its key (`lead.full_name`, `lead.email`, `lead.phone_number`). Template `variableMapping` entries with `field: custom` interpolate `{{...}}` in `customValue`. An array or object interpolated into text renders as JSON. **send_message**: `{ messageType: text|template|media|interactive|cards, text, template, media:{mediaType:image|video|audio|document, url,caption}, interactive, buttons, quickReplies, messageTag }`. - `template` is `{name,language,variableMapping}` for `messageType: template`, and `{ type: generic, elements:[{ title, subtitle, imageUrl, buttons }] }` (1 to 10 cards) for `messageType: cards`. - `template` and `interactive` are WhatsApp-only. `interactive.type` is inferred from the payload shape when omitted; payloads with neither `type` nor an inferable shape are rejected. - Facebook and Instagram only (a 400 on other platforms; WhatsApp keeps `interactive`): `cards`, `buttons` on `text` (cards carry their own buttons), and `quickReplies` / `messageTag` on `text`, `media` and `cards`. - `media` takes no `buttons` (a 400 at save). - `buttons:[{ type: url|postback|phone, title, url, payload, phone, workflowId }]`: 1 to 3, text only (the text then 640 characters at most), sent as Meta's button_template, not inside cards. `phone` is Facebook-only. - `quickReplies:[{ title, payload, imageUrl, workflowId }]`: 1 to 13, not combined with `buttons`. - `messageTag` is not available: a workflow only sends within the 24h window, and `HUMAN_AGENT` is rejected with a 400 because it is only for replies typed by a person. - Titles are capped at 20 characters (80 for card titles and subtitles). - A postback button or quick reply with no `payload` gets `zernio:workflow:<this workflow id>`, so a tap answers this workflow's pending `wait_for_reply` or restarts it. With `workflowId` (a 24-hex workflow id, not combined with `payload`) the tap starts that workflow instead. **wait_for_reply**: `{ timeoutMinutes:int (max 43200), saveAs:string }`. Resumes via the `'reply'` edge on inbound, or the `'timeout'` edge after `timeoutMinutes` of silence. On Facebook and Instagram a button tap sets `{{postback.payload}}` / `{{postback.title}}` and a quick reply tap sets `{{quickReply.payload}}` / `{{quickReply.title}}`. Each reply clears the previous tap. **condition**: `{ rules:[{ id, variable, operator: equals|not_equals|contains|not_contains|starts_with|ends_with|exists|not_exists|matches|greater_than|less_than|greater_or_equal|less_or_equal|before|after|has_tag|not_has_tag|is_true|is_false, value }] }`. The first matching rule takes its `id` as the sourceHandle; otherwise `'default'`. - Text operators compare case-insensitively. - `greater_than`, `less_than`, `greater_or_equal` and `less_or_equal` compare numbers (a non-numeric side is false). - `before` and `after` compare ISO dates. - `has_tag` and `not_has_tag` test whether an array variable (such as `contact.tags`) contains `value`. - `is_true` and `is_false` take no value. **set_variable**: `{ assignments:[{ name, value }] }`. Run-scoped: lives only for this execution (use `set_field` for persistent values). **delay**: `{ delayMinutes:int (max 43200) }`. Suspends the run, resumes via timer. **webhook**: `{ url, method: GET|POST|PUT|PATCH|DELETE, headers, bodyTemplate, saveAs }`. SSRF-guarded (private/loopback/metadata IPs rejected). The response is saved as `{ status, ok, body }` to `vars[saveAs]`. Edges: `'success'` on 2xx, `'error'` otherwise. **hubspot_upsert_contact**: `{ email, phone, firstName, lastName, properties, saveAs }`. Creates or updates the run contact in the profile's connected HubSpot portal. - Updates the contact already linked by inbox sync, else one matching by email, then phone; otherwise creates one. - An existing contact gets only the fields the node sets (a value that resolves to an empty string is skipped). A new one also takes the Zernio contact's name, email and phone for anything left out. - `properties` maps HubSpot internal property names to values (at most 50). - Saves `{ id, created }` (or `{ error }`) to `vars[saveAs]` (default `hubspotContact`). - Edges: `'success'` | `'error'` (HubSpot not connected, or HubSpot refused the write). **ai**: `{ provider: anthropic|openai|google|mistral|groq|openrouter, model, preset: smart|tools|cheap, systemPrompt, userPromptTemplate, saveAs, temperature, maxTokens, outputType: text|json, tools:[{ name, description, parameters }] }`. Set `provider` + `model` for BYOK (uses your stored API key); omit `provider` for the legacy Telnyx path. Edges: `'success'`, `'tool:<name>'` (model picked a tool), `'error'`. **handoff**: `{ note, assignTo }`. Terminates the run as `exited` and flags the conversation for a human operator. **start_call**: `{ to, forwardTo, requirePermissionFirst, recordingEnabled, saveAs }`. WhatsApp-only. `forwardTo` can be `tel:+E164`, `sip:user@host`, or `wss://…` (AI voice agent). Edges: `'success'`, `'permission_required'`, `'failed'`. **a_b_split**: `{ percentage: number 0-100 (default 50) }`. Random branch picker. Edges: `'a'` (with probability `percentage/100`), `'b'`. **set_field**: `{ field, value }`. Persistent custom field on the Contact (unlike `set_variable`, which is run-scoped). The field name is sanitized to `[A-Za-z0-9_]`. No-op on `api_call` runs (no contact). **enroll_sequence**: `{ sequenceId, saveAs }`. Enrolls the run's contact into a Sequence. Edges: `'success'`, `'error'`. **add_tag** / **remove_tag**: `{ tag }`. Push or pull a tag on the Contact. No-op on `api_call` runs. **end**: no config. Terminates the run as `completed`.
#[serde(rename = "config", skip_serializing_if = "Option::is_none")]
pub config: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(rename = "position", skip_serializing_if = "Option::is_none")]
pub position: Option<Box<models::WorkflowNodePosition>>,
/// Optional display name shown on the builder canvas and inspector, falling back to the node type when absent. The nodes array is replaced wholesale on update, so it must be resent to be kept.
#[serde(rename = "label", skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
impl WorkflowNode {
/// A node in a workflow graph. `config` shape depends on `type`.
pub fn new(id: String, r#type: Type) -> WorkflowNode {
WorkflowNode {
id,
r#type,
config: None,
position: None,
label: None,
}
}
}
/// Node kind. The 17 supported types break into four groups: messaging (send_message), control flow (trigger, condition, delay, wait_for_reply, a_b_split, end), data ops (set_variable, set_field, add_tag, remove_tag, enroll_sequence), integrations (webhook, hubspot_upsert_contact, ai, handoff, start_call).
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Type {
#[serde(rename = "trigger")]
Trigger,
#[serde(rename = "send_message")]
SendMessage,
#[serde(rename = "wait_for_reply")]
WaitForReply,
#[serde(rename = "condition")]
Condition,
#[serde(rename = "set_variable")]
SetVariable,
#[serde(rename = "delay")]
Delay,
#[serde(rename = "webhook")]
Webhook,
#[serde(rename = "hubspot_upsert_contact")]
HubspotUpsertContact,
#[serde(rename = "ai")]
Ai,
#[serde(rename = "handoff")]
Handoff,
#[serde(rename = "start_call")]
StartCall,
#[serde(rename = "a_b_split")]
ABSplit,
#[serde(rename = "set_field")]
SetField,
#[serde(rename = "enroll_sequence")]
EnrollSequence,
#[serde(rename = "add_tag")]
AddTag,
#[serde(rename = "remove_tag")]
RemoveTag,
#[serde(rename = "end")]
End,
}
impl Default for Type {
fn default() -> Type {
Self::Trigger
}
}