Plando
Plando is an Israeli CRM for managing contacts, leads, activities, and documents.
Official Plando API docs (for deeper reference):
- Contact Details API —
getCustomerDetails(/contact_details) - Lead Form API —
newOpportunity(/contacts/lead_form1)
CRM config (crmConfig)
| Field | Required | Default | Use |
|---|---|---|---|
token | Yes | — | API access key (access_key). Required for all operations. |
chat | Yes | — | Plando chat ID. Required for newOpportunity. |
customerId | No | Customer Texter project ID | Allowes overriding the current project ID with something else. Used in openTicket/closeTicket — sets formal_whatsapp_url to {customerId}.texterchat.com. |
defaultEmail | No | — | Fallback agent email for openTicket/closeTicket when no agent is assigned to the chat. |
closeTicketMaxMessagesCount | No | 30 | Max number of messages to include in the transcript pushed by openTicket/closeTicket. |
failOnMultipleContacts | No | false | If true, getCustomerDetails returns on_failure when the phone matches more than one contact. |
statusField | No | id | Plando contact field name whose value is mapped to crmData.status. |
idField | No | id | Plando contact field name whose value is mapped to crmData.id. |
attrFields | No | — | Comma-separated contact attribute field names to request (e.g. "category_1,category_2"). Merged with pullContactByField, idField, statusField into the API's attr param. |
pullContactByField | No | — | { "fieldName": "expectedValue" }. When set, selects the first contact where fieldName === expectedValue. If expectedValue is empty, selects the first contact where fieldName is non-empty. Falls back to the first contact if none match. |
Adapter functions
getCustomerDetails
Looks up a contact in Plando by phone (or other search options).
When it runs: At the start of the flow to identify whether the sender is a known contact before routing, and whenever a chat is opened in the Texter UI.
Basic
plando_lookup:
type: func
func_type: crm
func_id: getCustomerDetails
on_complete: known_customer
on_failure: unknown_customer
All YAML params (except phoneNumber) are forwarded as-is into the API's search_options object.
| Param | Notes |
|---|---|
phoneNumber | Phone to look up. Defaults to the chat's formatted channel phone. |
contact_id | Search by Plando contact ID instead of phone. |
contact_mail | Search by email. |
contact_p_id | Search by personal / national ID. |
contact_name | Search by full name. Per Plando docs: only relevant when contact_mail is also present. |
from_contact_id | Return contacts starting from this Plando contact ID. |
created_at | Search contacts created from this datetime ("YYYY-MM-DD HH:mm"). |
created_at_to | Upper bound for created_at range. |
updated_at | Search contacts updated from this datetime ("YYYY-MM-DD HH:mm"). |
updated_at_to | Upper bound for updated_at range. |
customer_status | Filter by status. Possible values: 0 = Potential customer, 1 = Customer, 2 = Not interested. Multiple values comma-separated. Only relevant with updated_at or created_at. |
extra_params | Object { field_name: value } for filtering by a specific contact field. Only relevant with updated_at or created_at. |
input_id | Set to 1 to return categories and tags by ID instead of name. Default 0. |
extra_objects_ids | Comma-separated type IDs — retrieves extra linked object details. |
| (attr) | Not a YAML param. Always set by the adapter from crmConfig.attrFields, idField, statusField, and pullContactByField. |
Result: On success, crmData is populated with:
crmData field | Source |
|---|---|
| (all contact fields) | All raw Plando contact fields spread directly (e.g. first_name, last_name, category_1, category_30, etc.) |
contact_id | contact.id |
name | first_name + ' ' + last_name |
status | Value of crmConfig.statusField on the contact (default field: id) |
id | Value of crmConfig.idField on the contact (default field: id) |
phone | Phone used for lookup |
deepLink | https://plando.co.il/contacts/show/{contact.id} |
Returns on_failure if: token/chat missing from config, phone is unavailable, API returns an error, no contact is found, or failOnMultipleContacts: true and multiple contacts match.
Advanced — lookup by custom phone
plando_lookup_by_collected_phone:
type: func
func_type: crm
func_id: getCustomerDetails
params:
phoneNumber: "%state:node.collect_phone.text%"
on_complete: known_customer
on_failure: unknown_customer
Use crmConfig.attrFields to specify which Plando category/attribute fields should be fetched (comma-separated). These are then accessible on crmData under their Plando field name.
attrFields: "category_1,category_9,category_17,category_30,invoices_archive,last_open_invoice,signed_form"
Access them in YAML as %chat:crmData.category_30%, %chat:crmData.last_open_invoice%, etc.
newOpportunity
Creates a new contact/lead in Plando. Can also update an existing contact when updateContact is set.
Basic
plando_new_lead:
type: func
func_type: crm
func_id: newOpportunity
on_complete: lead_created
on_failure: lead_failed
The adapter always sends chat (from crmConfig.chat), access_key (from crmConfig.token), and no_redirect: 1. These are not YAML params.
| Param (YAML) | Sent to API as | Required | Code default | Notes |
|---|---|---|---|---|
name | name | Yes* | Chat title | Full name. *Defaults to chat title if omitted. Can use contact_first_name + contact_last_name instead. |
phoneNumber | phone | No | Formatted phone, digits only | Mobile phone. Formatted channel phone (no dashes) used if omitted. |
email | email | No | — | Valid email address. |
legacy_id | legacy_id | No | — | External system ID. |
is_org | is_org | No | 0 | 1 to create an organization instead of a contact. |
updateContact | update_contact | No | — | Set to 1 to update an existing contact. Identification requires id + at least one of each pair: phone/email, name/legacy_id. |
id | id | No* | — | *Required when updateContact is set. Plando contact ID to update. |
approve_mailing | approve_mailing | No | — | 0 to opt the contact out of mailing. |
tags | tags | No | — | Keywords, comma-separated. |
refer_email | refer_email | No | — | Comma-separated email(s) to notify with lead details. Set to 0 to suppress the notification email. |
contact_first_name | contact[first_name] | No | — | First name. Can be used instead of name. |
contact_last_name | contact[last_name] | No | — | Last name. Can be used instead of name. |
contact_remark | contact[remark] | No | — | Note / remark. |
contact_main_city | contact[main_city] | No | — | City. |
contact_main_address | contact[main_address] | No | — | Address. |
contact_customer_cat_id | contact[customer_cat_id] | No | — | Customer category ID. 0 = potential customer. Get IDs from Plando item categories. |
contact_customer_sales_person_id | contact[customer_sales_person_id] | No | — | Plando system ID of the assigned sales person / team member. |
contact_lead_status_cat_id | contact[lead_status_cat_id] | No | — | Numeric ID of the lead process status (from Plando item categories). |
contact_lead_origin_cat_id | contact[lead_origin_cat_id] | No | — | Numeric ID of the lead origin source (from Plando item categories). |
(any contact_*) | contact[*] | No | — | Any custom Plando contact field. E.g. contact_category_9: "123" → contact[category_9]=123. |
| (any other param) | same key | No | — | Any remaining params are passed as-is to the form body. |
Result: { success: true, crmData: { id: contact_id } }. The new/updated contact's Plando ID is accessible as %chat:crmData.id%.
The op returns on_failure if the API responds with err !== "0" or does not return a contact_id.
Advanced — new lead with custom category fields
plando_new_lead_with_fields:
type: func
func_type: crm
func_id: newOpportunity
params:
contact_first_name: "%state:node.ask_name.text%"
contact_lead_origin_cat_id: "724240"
contact_lead_status_cat_id: "323140"
contact_customer_sales_person_id: "4512"
tags: "whatsapp,bot"
on_complete: lead_created
on_failure: lead_failed
Advanced — update existing contact
plando_update_contact:
type: func
func_type: crm
func_id: newOpportunity
params:
updateContact: 1
id: "%chat:crmData.contact_id%"
email: "%state:node.ask_email.text%"
name: "%chat:crmData.name%"
contact_lead_status_cat_id: "601305"
on_complete: contact_updated
on_failure: update_failed
openTicket / closeTicket
Pushes today's conversation transcript to Plando. Both openTicket and closeTicket use the same handler (POST /contacts/parse_whatsapp_session) and behave identically.
When it runs: Most commonly triggered automatically when an agent resolves a chat (if the integration is configured to do so). Can also be called manually from bot YAML to push a transcript mid-flow.
Basic
plando_push_transcript:
type: func
func_type: crm
func_id: closeTicket
on_complete: done
on_failure: done
| Param | Required | Notes |
|---|---|---|
email | No | Override the agent email used as user_email. If omitted, uses the assigned agent's email, then falls back to crmConfig.defaultEmail. |
What is sent:
| Field | Value |
|---|---|
user_email | Resolved agent email (see above) |
formal_whatsapp_url | {customerId}.texterchat.com |
phone | Formatted channel phone (digits only) |
msg | JSON array of today's messages — includes message (text), timestamp, from, to, and media (with 7-day shared download links for files) |
Returns on_failure if: no email can be resolved, customerId is missing from config, or the API returns a non-200 result code.
Out of Adapter Scope
The following endpoints are not covered by the adapter. Use the request func instead.
Create activity record on a contact
Log an activity (e.g. "came from Meta ad", "sent template") on an existing Plando contact using a pre-configured record type.
plando_log_activity:
type: func
func_type: system
func_id: request
params:
url: "https://plando.co.il/contacts/crm_form"
method: post
keepResponse: true
useProxy: true
headers:
Content-Type: "application/x-www-form-urlencoded; charset=utf-8"
data:
phone: "%chat:channelInfo.id%"
record[record_type_id]: YOUR_RECORD_TYPE_ID
access_key: "YOUR_ACCESS_KEY"
no_redirect: 1
on_complete: next_node
on_failure: fallback_node
Contact identification — provide at least one of: phone, contact_id, or email.
| Field | Required | Notes |
|---|---|---|
phone | One of the three | Contact's phone number. |
contact_id | One of the three | Plando contact ID — available as %chat:crmData.contact_id% after getCustomerDetails. |
email | One of the three | Contact's email address. |
record[record_type_id] | Yes | Activity/record type ID (number). Defined in Plando — get from the customer's Plando account. |
access_key | Yes | API token (crmConfig.token). |
no_redirect | Yes | Always 1. |
record[description] | No | Free-text description of the activity record. |
record[actual_date] | No | Date of the activity. |
record[start_time] | No | Activity start time. Format: "dd/mm/yyyy HH:MM". |
record[end_time] | No | Activity end time. Format: "dd/mm/yyyy HH:MM". |
task_details | No | Free text attached to the activity (custom per-record notes). |
send_email | No | Set to 1 to send an email notification for this activity. |
org_custom_value[field_name] | No | Pass a custom org field value. Replace field_name with the Plando field name. |
record_id | No | Existing Plando record ID. Only needed when updating or deleting a record (used with mark_to_delete). |
mark_to_delete | No | Set to 1 to delete the record specified by record_id. |
Response: On success (err: "0"), the API returns contact_id and record_id. Accessible via %state:node.plando_log_activity.*% when keepResponse: true.
Upload a file to a contact
Attach a file to a Plando contact after sharing it with shareFile.
share_file:
type: func
func_type: system
func_id: shareFile
params:
file_node: upload_doc_user
on_complete: plando_upload_file
plando_upload_file:
type: func
func_type: system
func_id: request
params:
url: "https://plando.co.il/api/upload_contact_file"
method: post
keepResponse: true
json: true
useProxy: true
headers:
Content-Type: application/json
X-Access-Key: "YOUR_ACCESS_KEY"
data:
contact_id: "%chat:crmData.contact_id%"
title: "%state:node.upload_doc_user.text%"
url: "%state:node.share_file.link%"
on_complete: upload_done
on_failure: upload_failed
| Field | Notes |
|---|---|
contact_id | Contact's Plando ID — available as %chat:crmData.contact_id% after getCustomerDetails. |
title | Document title shown in Plando. |
url | Shared file URL from the shareFile node — use %state:node.<shareFile_node_name>.link%. |
X-Access-Key | API token (crmConfig.token). |
shareFile generates a temporary download link valid for 7 days. Make sure plando_upload_file runs immediately after shareFile so Plando can fetch the file while the link is still active.
Plando Onboarding (for Texter Support)
Contact Plando support to obtain credentials.
Customer DB — crmConfig fields
| Field | Required | Use |
|---|---|---|
token | Yes | API access key (access_key). Provided by the customer from their Plando account. |
chat | Yes | Plando chat ID. Provided by the customer. |
customerId | For openTicket/closeTicket | Texter tenant subdomain (e.g. mycompany → mycompany.texterchat.com). |
defaultEmail | For openTicket/closeTicket (recommended) | Fallback agent email for transcript push. |
attrFields | Recommended | Comma-separated list of contact attribute fields to fetch in getCustomerDetails. Agree with the customer which Plando category fields they use. |
statusField | No | Contact field to expose as crmData.status in the Texter CRM panel. |
idField | No | Contact field to expose as crmData.id in the Texter CRM panel. |
pullContactByField | No | { "fieldName": "value" } — used when multiple contacts can share the same phone and you need to select by a specific field. |
failOnMultipleContacts | No | Set true to hard-fail on ambiguous phone matches. |
closeTicketMaxMessagesCount | No | Max messages in transcript. Default 30. |