Pipelines
A pipeline is the path a conversation, called a case, follows. It has stages in order, fields that hold the data of the case, routing rules that send a case to another pipeline, settings such as business hours and automatic close, and quick replies your team sends from the inbox. A case lives in one stage at a time, and the agent linked to that stage works it.
The examples use this client:
Code
Create a pipeline
Send stages to create them in the same call, in order. The entry stage is the one marked entry, or the first. Link an agent to a stage with agentId. The pipeline is created active.
Expected: 201 with status: "active" and stages holding the stage ids in order. The pipeline is this object:
Pipelineobject, 10 fields
objectenumrequiredpipelineidstringrequirednamestringrequireddescriptionstring or nullrequiredstatusenumrequireddraftactivepausedarchivedstagesstring[]requiredThe ids of its stages, in order.
quickRepliesQuickReply[]requiredQuickReply2 fields
shortcutstringrequired1 to 30 lowercase letters, numbers, hyphens or underscores. Unique in the pipeline.
textstringrequiredUp to 4096 characters.
settingsPipelineSettingsResponserequiredPipelineSettingsResponse7 fields
timezonestring or nullrequiredaskForenumrequiredone_at_a_timein_orderautoCloseAutoClose or nullrequiredAutoClose6 fields
enabledbooleanrequiredTurns the automatic close on or off.
inactivityMinutesintegerrequiredMinutes of silence before the case closes, from 5 to 525600. Counted inside the business hours when the pipeline has them.
messagestring or nulloptionalFarewell sent when the case closes, up to 1000 characters. It needs inactivityMinutes under 1440, because after 24 hours the WhatsApp window is closed.
closeStageIdstring or nulloptionalThe stage where the case is left when it closes. A stage of this pipeline.
includeTakenbooleanoptionalAlso close the chats a person has taken over.
reopenWindowHoursintegeroptionalHours after the close when a new message reopens the case, from 0 to 720. 24 when omitted.
businessHoursPipelineBusinessHours or nullrequiredPipelineBusinessHours3 fields
timezonestringoptionalIANA time zone. Santiago when omitted.
daysPipelineBusinessDaysoptionalRanges per weekday. A day that is not here is closed. Without days the pipeline attends 24/7.
PipelineBusinessDays7 fields
monTimeRange[]optionalRanges on Monday. An empty list closes the day.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
tueTimeRange[]optionalRanges on Tuesday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
wedTimeRange[]optionalRanges on Wednesday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
thuTimeRange[]optionalRanges on Thursday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
friTimeRange[]optionalRanges on Friday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
satTimeRange[]optionalRanges on Saturday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
sunTimeRange[]optionalRanges on Sunday.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
holidaysHoliday[]optionalHoliday2 fields
datestringrequiredYYYY-MM-DD.
rangesTimeRange[]optionalRanges that replace the ones of that weekday. Without ranges the day is closed.
TimeRange2 fields
fromstringrequiredHH:MM, 00:00 to 23:59.
tostringrequiredHH:MM, after from. 24:00 is the end of the day.
waitThresholdsWaitThresholds or nullrequiredWaitThresholds2 fields
yellowMinutesnumberoptionalMinutes without an answer before the wait turns yellow.
redMinutesnumberoptionalMinutes without an answer before the wait turns red. Greater than yellowMinutes.
stageOnTakeControlstring or nullrequiredstageOnHandoffstring or nullrequired
createdAtstring (date-time)requiredupdatedAtstring (date-time)required
Stages
A stage is a step of the path. It has a name, a position, an optional agentId and the requiredFields a case needs before it can enter, and it can close the conversation, name the stage that follows and carry its own settings. List them in funnel order with GET /v2/pipelines/{id}/stages.
The entry stage
Cases enter a pipeline through its entry stage, so the agent that answers new conversations belongs there. Exactly one stage is the entry. Mark another one with entry: true and the previous one stops being the entry. You cannot unmark the entry stage, and you cannot delete it: make another stage the entry first.
Add, order and change stages
position is the place in the funnel, starting at 0. Creating or moving a stage to a position shifts the others.
DELETE /v2/pipelines/{id}/stages/{stageId} answers 409 resource_in_use while cases are in the stage (move or close them first) and 409 conflict for the entry stage.
Stage kinds
A stage has no kind in the API. Its role comes from how you use it:
| Role | How you make it |
|---|---|
| Entry | entry: true |
| Worked by an agent | agentId set |
| Worked by a person | No agentId. Your team answers the cases here |
| Handoff | The stage you name in config.stageId when you grant transfer_to_human to an agent |
| Gate | requiredFields set: a case cannot enter without those fields |
| Closing | closes: true: a case that arrives there ends. See Stages that close |
Stages that close
Mark a stage with closes: true and a case that arrives there ends: it is closed, and the next message from the person starts a new case. Give the stage a closingOutcome of up to 50 characters, such as Resolved, and that text is written in the case as its outcome. A stage with statusType: "done" closes too. statusType says what the stage means on the board: todo, in_progress or done; null leaves it without one.
You can also close a case yourself, wherever it is, with POST /v2/cases/{id}/close. See Move and close cases.
The next stage and transitions
nextStageId is the stage that follows when no transition holds, and transitions lists where a case goes from this stage. Each transition has a targetStageId, a priority (lower numbers are tried first) and conditions that must all hold. A condition compares a field path with a value using operator: eq, neq, gt, gte, lt, lte, contains, exists or in. Every target must be a stage of the same pipeline, or the call answers 400 invalid_request with param naming it. transitions replaces the whole list; nextStageId: null removes the next stage.
Code
Settings of a stage
config holds the settings of a stage. Only the keys you send change, and null removes a key.
| Key | Use |
|---|---|
agentMode | When the agent answers: always, paused (never) or outside_business_hours (only outside the business hours of the organization) |
followUp | Messages for a person who went silent: enabled, delays (minutes of silence before each one), maxAttempts, templateSequence (reengagement, reminder, last_chance, breakup, demo_reminder or no_show, in order), and optionally allowTemplates and maxTemplates (1 to 5). Templates sent after the 24-hour window cost money, so allowTemplates is false unless you turn it on. It replaces the whole block |
maxDurationSeconds, minDurationSeconds | The longest and the shortest duration of the stage, in seconds |
delayMinutes | Minutes a case waits in the stage before it moves on |
noProgress | A task for your team when a case stays too long in the stage: minutes (counted inside the business hours of the pipeline, when it has them), title, and optionally description, fallbackAssigneeId (the member who gets the task when the case has no assignee) and tag (added to the contact). It creates a reminder of type call, one per pass of the case through the stage, and closes it by itself when the case leaves the stage or closes. A case with no assignee and no fallbackAssigneeId gets no task. It replaces the whole block |
attentionRole | The part the stage plays in the attention queue: escalation (cases here wait for the technical team and show as waiting for a reply to whoever escalated them) or answer (cases here came back with an answer and the customer still needs a reply; the owner of the case gets a notification when one enters). null removes it. Read the queue of a member with GET /v2/case-queue |
Code
A key that is not in the table answers 400 unknown_field.
Escalate a case to your technical team
A case that your agents cannot solve can become an issue in your Linear, and the answer of the team comes back to the case. Connect Linear once per organization, mark the stage where those cases wait, and say which stage receives the answer.
- Connect Linear.
PUT /v2/integrations/linear(linear.connect) with a Linear personal API key and the key or id of the team that receives the issues (and optionally aproject). Lelemon checks them with Linear, keeps the key encrypted and never returns it. It needs thelinear:writescope, which belongs to admins. The answer carrieswebhook.url. - Register the webhook in Linear. In Linear, Settings, API, Webhooks, create a webhook with that URL, the Issues resource and the team. Copy the signing secret Linear shows and send it with
PATCH /v2/integrations/linearaswebhookSecret. Until you do, Lelemon rejects every notification (webhook.secretConfiguredsays whether it is saved). - Mark the stages. Set
config.attentionRoletoescalationon the stage where those cases wait and toansweron the stage that receives the answer. They are the same marks the attention queue uses, so there is nothing else to configure.
When a case enters the escalation stage, Lelemon creates one issue with the contact, the pipeline, the last messages (sensitive values covered) and the link to the case, and saves the link in the case. A case that already has an open issue never gets a second one. If Linear does not answer, the escalation stays pending and Lelemon keeps trying every minute with growing waits. When the team completes the issue, Lelemon reads the last comment of the team, moves the case to the answer stage of the pipeline if it is still in the stage where it waited, and leaves the answer as an internal note. Entering the answer stage notifies the owner of the case as always; when the case ends elsewhere or has no owner, Lelemon notifies the owner or the admins itself. If the team cancels the issue, the case stays where it was with a note.
To escalate a case that is not in an escalation stage, call POST /v2/cases/{id}/escalate-to-team (cases.escalateToTeam) with an optional answerStageId (the answer stage of the pipeline by default). GET /v2/cases/{id}/team-escalations lists the escalations of a case with their issue, status and answer. DELETE /v2/integrations/linear disconnects.
Code
Start the cases of a WhatsApp number in a stage
whatsappPhoneNumberId is the id of a WhatsApp number of the organization, from GET /v2/whatsapp/phone-numbers. A message that arrives at that number opens its case in this stage and the pipeline of the stage works it. Linking an agent to a number with PATCH /v2/whatsapp/phone-numbers/{id} does not let you choose: Lelemon uses the entry stage of your oldest pipeline that has no number yet, or creates a pipeline. Use whatsappPhoneNumberId to choose the stage yourself. A number feeds one pipeline only: linking it to a stage of another pipeline answers 409 conflict, and moving it to another stage of the same pipeline is fine. Send null to release it. Disconnecting the number releases it too.
Link an agent to a stage
An agent works the cases of the stages it is linked to. Send agentId to link one and null to unlink.
The agent must be linked to the entry stage to be reachable; activation reports no_stage or not_entry_stage otherwise.
Require fields before a stage
requiredFields lists the keys of fields a case needs before it can enter the stage. Moving a case there without them answers 422 required_fields_missing, with param naming the missing field. The list replaces the previous one on every update.
Code
Fields
A field holds a piece of data of the case. Create the fields before the agents that fill them.
| Property | Use |
|---|---|
key | Required. Letters, numbers and underscores, starting with a letter, up to 64. Fixed once created |
type | Required. text, email, phone, url, textarea, file or select. Fixed once created |
label | What people see. Defaults to the key |
required | Whether the field must be filled |
source | Who fills it: team, channel, system or agent. Default team |
instruction | What the agent should ask or look for. Up to 1,000 characters |
sensitive | Hides the value from the agent and from webhooks |
options | For select: a list of { value, label }. The label defaults to the value. Up to 80 characters each |
dependsOn | The key of another select. The options of this field then depend on the value of that one |
A select can depend on another select. Give each option whenParent: the values of the parent for which the option is offered.
Expected: 201 with the field. A key that breaks the format answers 400 invalid_request with param: "key".
- Who fills it. The agent fills fields with
source: "agent"through itssave_case_datatool, which refusessensitivefields. Your team or your system fills the others, withPATCH /v2/cases/{id}and{ "fields": { ... } }. - Update.
PATCH /v2/pipelines/{id}/fields/{fieldId}changes the label,required,source,instruction,sensitiveanddependsOn.optionsreplaces the list and cases keep the values they already have. - Delete.
DELETE /v2/pipelines/{id}/fields/{fieldId}answers409 resource_in_usewhile another field depends on it. The field stops being required in the stages that asked for it.
Code
Routing rules
A routing rule sends a case to another pipeline when it reaches a stage. With mode: "create" a new case opens in the other pipeline and the first one stays. With mode: "move" the case itself moves. toStageId is the stage of the destination; leave it out for its entry stage.
GET /v2/pipelines/{id}/routing-rules lists the rules in the order they are evaluated. PATCH changes toStageId, mode and enabled; it answers 422 when the values do not form a valid rule. DELETE removes a rule: cases already routed stay where they are, and new cases stop matching it.
Pipeline settings
settings goes in POST /v2/pipelines and PATCH /v2/pipelines/{id}, and every read of a pipeline returns it with all its keys, null when a setting is not set. Only the settings you send change. autoClose, businessHours and waitThresholds replace their whole block, and null removes a setting.
| Setting | Use |
|---|---|
timezone | IANA time zone of the scheduled work of the pipeline, such as America/Santiago |
askFor | How the agent asks for the fields it fills: one_at_a_time as the conversation goes (the default) or in_order following the order of the fields |
autoClose | Closes the cases that go silent: enabled, inactivityMinutes (5 to 525,600), an optional farewell message, closeStageId, includeTaken to close also the chats a person took over, and reopenWindowHours (0 to 720, 24 by default) |
businessHours | When the pipeline attends: an optional timezone, days with mon to sun (each a list of { from, to } as HH:MM, where 24:00 ends the day) and holidays, each a date with optional ranges that replace the ones of that weekday |
waitThresholds | The minutes without an answer after which the wait of a case turns yellow (yellowMinutes, 10 by default) and red (redMinutes, 30 by default) |
stageOnTakeControl | The stage a case moves to when a person takes control |
stageOnHandoff | The stage a case moves to when the agent hands it to a person |
Without businessHours the pipeline attends all day and the minutes are counted by the clock. With them, the wait colors, autoClose and the warning before it count only the minutes inside the hours. A day that is not in days is closed; without days the pipeline attends 24/7. The ranges of a day cannot overlap.
A farewell message needs inactivityMinutes under 1,440, because after 24 hours the WhatsApp window is closed and the message cannot go out.
closeStageId, stageOnTakeControl and stageOnHandoff take the id of a stage of the pipeline, so they cannot be set on creation: create the pipeline with its stages and then send them with PATCH. Deleting a stage that a setting names sets that setting to null. The dashboard shows and changes the same settings.
A setting that breaks a rule answers 400 invalid_request, and param names it, for example settings.autoClose or settings.stageOnHandoff.
Quick replies
quickReplies is a list of { shortcut, text } that your team sends from the inbox. The shortcut is 1 to 30 lowercase letters, numbers, hyphens or underscores, without a slash; the text has up to 4,096 characters. PATCH /v2/pipelines/{id} with quickReplies replaces the whole list, so send every reply you want to keep.
Code
Move and close cases
POST /v2/cases/{id}/move with a stageId moves a case. A stage of another pipeline moves the case to that pipeline. The move answers 422 required_fields_missing when the stage requires a field the case lacks, and 409 conflict for a closed case.
POST /v2/cases/{id}/close ends the case. The contact can then have a new case in the pipeline. POST /v2/cases/{id}/reopen brings it back, and answers 409 conflict when the case is open or when the contact already opened another case in the pipeline.
Entering a stage fires case.stage_entered. A contact has one open case per pipeline: opening a second one answers 409 conflict.
Update and delete a pipeline
PATCH /v2/pipelines/{id} changes the name, the description, the status (draft, active, paused or archived), the quickReplies and the settings. Stages, fields and routing rules have their own routes.
DELETE /v2/pipelines/{id} answers 204. It answers 409 resource_in_use while the pipeline has open cases: close or move them first. The agents linked to its stages are not deleted.
Code
Errors you will meet
| Error | Cause |
|---|---|
400 invalid_request | A key, shortcut, setting, length or target stage breaks a rule; param names it |
400 unknown_field | A key of config or settings that the API does not have |
403 scope_missing | The key lacks pipelines:write or cases:write |
404 not_found | The pipeline, stage, field or rule does not exist in this mode |
409 conflict | The entry stage, a closed case, a second open case for the contact, or a WhatsApp number that already feeds another pipeline |
409 resource_in_use | Delete with cases in a stage, open cases in a pipeline, or a field that others depend on |
422 required_fields_missing | A move into a stage whose required fields the case lacks |
All codes are in the error catalog.
Good practice
- Name stages after what the customer did, not after your tool: "Quote sent", not "Stage 2".
- Keep the entry stage for the agent and put the human work in later stages.
- Create fields before the agent that fills them, and write an
instructionfor each field withsource: "agent". - Use
sensitivefor anything the agent should never see, such as a national ID. - Put the fields a stage needs in
requiredFields, so a case cannot reach "Visit booked" without an address. - Close cases with an
outcome; it is what your reports group by.
What's next
- Agents to build the agent that works a stage.
- Build your own inbox to show cases and let your team answer.
- Measure how long cases take once cases move.