Skip to content

Script + Outcome

This group covers Call Scripts and Disposition Codes — two core building blocks the agent uses on every call: the script tells them what to say; the disposition classifies the result when the call ends.

5. Call Scripts

EndpointPurpose
GET /api/telesales/scriptsList the customer's scripts
GET /api/telesales/scripts/{id}Detail for one script (with detected placeholders)
POST /api/telesales/scriptsCreate a script
PUT /api/telesales/scripts/{id}Update a script
DELETE /api/telesales/scripts/{id}Delete a script
POST /api/telesales/scripts/{id}/previewRender a preview using a real lead + agent

Scripts use Markdown syntax. variables are auto-detected from the content — no separate declaration needed. When the agent opens a lead, each variable is replaced with the live value from the lead's custom_fields plus the agent's profile. Industry-specific placeholders (contract number, account ID, order code, ...) are declared as custom_fields per customer.

GET /api/telesales/scripts — list

Query parameters

FieldTypeRequiredDescriptionValid values
qstringoptionalSearch by name (LIKE %keyword%)
campaign_idintegeroptionalFilter scripts in use by a given campaign
pageintegeroptionalPage number≥ 1 (default 1)
per_pageintegeroptionalRecords per page1-200 (default 50)

Response fields (each item in data[])

FieldTypeDescriptionValid values
idintegerScript ID
namestringScript name (unique per account)max 150 chars
content_previewstringMarkdown content — first 200 chars
placeholders_detected[]array<string> variables detected in the content
used_by_campaigns[]array<integer>Campaign IDs currently using this script
created_atdatetimeCreation timestamp (ISO 8601 UTC)
updated_atdatetimeLast update timestamp (ISO 8601 UTC)

Response 200:

json
{
  "data": [
    {
      "id": 12,
      "name": "Default Outbound Script",
      "content_preview": "## Opening\nHello {{customer_name}}...",
      "placeholders_detected": ["customer_name", "agent_name", "brand_name"],
      "used_by_campaigns": [36, 42],
      "created_at": "2026-06-06T06:00:00Z",
      "updated_at": "2026-06-06T06:00:00Z"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 3 }
}

content_preview is truncated to the first 200 chars. Use GET /api/telesales/scripts/{id} to fetch the full content.

GET /api/telesales/scripts/{id} — detail

Path parameter

FieldTypeDescription
idintegerScript ID

Response fields

FieldTypeDescriptionValid values
idintegerScript ID
namestringScript namemax 150 chars
contentstringFull Markdown contentmax 32 KB
placeholders_detected[]array<string> variables parsed from content
used_by_campaigns[]array<object>Array of {id, name} for campaigns using this script
created_atdatetimeCreation timestamp (ISO 8601 UTC)
updated_atdatetimeLast update timestamp (ISO 8601 UTC)

Response 200:

json
{
  "data": {
    "id": 12,
    "name": "Default Outbound Script",
    "content": "## Opening\nHello {{customer_name}}, this is {{agent_name}} from {{brand_name}}...\n\n## Body\n... talk track ...\n\n## Closing\nThank you.",
    "placeholders_detected": ["customer_name", "agent_name", "brand_name"],
    "used_by_campaigns": [
      { "id": 36, "name": "Summer Promo" },
      { "id": 42, "name": "Retention Q3" }
    ],
    "created_at": "2026-06-06T06:00:00Z",
    "updated_at": "2026-06-06T06:00:00Z"
  }
}

Response 404: { "message": "Script not found." }

POST /api/telesales/scripts — create

Request body fields

FieldTypeRequiredDescriptionValid values
namestringScript name — unique per accountmax 150 chars
contentstringMarkdown content; variables are auto-detectedmax 32 KB

Body:

json
{
  "name": "Default Outbound Script",
  "content": "## Opening\nHello {{customer_name}}, this is {{agent_name}} from {{brand_name}}...\n\n## Body\n... talk track for the campaign ...\n\n## Closing\nThank you for your time."
}

Response fields

FieldTypeDescriptionValid values
idintegerNewly created script ID
namestringScript name
contentstringMarkdown content
placeholders_detected[]array<string> variables parsed from content
created_atdatetimeCreation timestamp (ISO 8601 UTC)

Response 201:

json
{
  "data": {
    "id": 12,
    "name": "Default Outbound Script",
    "content": "## Opening\nHello {{customer_name}}...",
    "placeholders_detected": ["customer_name", "agent_name", "brand_name"],
    "created_at": "2026-06-06T06:00:00Z"
  }
}

Response 422 — validation cases:

json
// Duplicate script name
{
  "message": "The given data was invalid.",
  "errors": { "name": ["The name has already been taken."] }
}

// Content too long
{
  "message": "The given data was invalid.",
  "errors": { "content": ["The content may not be greater than 32768 characters."] }
}

PUT /api/telesales/scripts/{id} — update

Body: same as POST.

Response 200: the updated script object, with placeholders_detected re-computed from the new content.

Response 422 — conflict:

json
// Script is in use by active campaigns — update is allowed with a warning
{
  "data": { "id": 12, ... },
  "warnings": ["Script is currently used by 2 active campaigns. Agents will see new content on next call."]
}

DELETE /api/telesales/scripts/{id}

Response 204 No Content (success).

Response 422 — script is in use by an active campaign:

json
{
  "message": "Cannot delete a script that is in use by a campaign.",
  "errors": { "id": ["Script 12 is bound to campaign(s): 36, 42. Detach from campaign first."] }
}

POST /api/telesales/scripts/{id}/preview — render preview

Path parameter

FieldTypeDescription
idintegerScript ID to render

Request body fields

FieldTypeRequiredDescriptionValid values
lead_idintegerLead ID — supplies from custom_fieldsMust exist
agent_idintegerAgent user ID — supplies from profileMust exist

Body:

json
{
  "lead_id":   78912,
  "agent_id":  25
}

Renders the content with each replaced by the live value from lead.custom_fields + the agent's profile + the customer's brand.

Response fields

FieldTypeDescriptionValid values
idintegerScript ID
rendered_contentstringMarkdown content with placeholders substituted
missing_placeholders[]array<string>Variables that could not be resolved from custom_fields / profile — the UI can warn the agent

Response 200:

json
{
  "data": {
    "id": 12,
    "rendered_content": "## Opening\nHello Anh Hung, this is Nguyen Van A from Zorio...\n\n## Body\n...",
    "missing_placeholders": ["policy_number"]
  }
}

6. Call Outcomes (Disposition Codes)

6.1 Disposition catalog management

EndpointPurpose
GET /api/telesales/dispositionsList the customer's dispositions
GET /api/telesales/dispositions/{id}Detail for one disposition
POST /api/telesales/dispositionsCreate a disposition (admin)
PUT /api/telesales/dispositions/{id}Update a disposition (admin)
DELETE /api/telesales/dispositions/{id}Delete a disposition (admin)

GET /api/telesales/dispositions — list

Query parameters
FieldTypeRequiredDescriptionValid values
categorystringoptionalFilter by outcome groupcontact / no_contact / callback / remove
is_activebooloptionalWhether to include hidden dispositionstrue (default) / false
Response fields (each item in data[])
FieldTypeDescriptionValid values
idintegerDisposition ID
codestringImmutable code — agents / API use this to tag calls[A-Za-z0-9_-], max 32 chars
labelstringDisplay labelmax 100 chars
descriptionstring|nullDetailed descriptionmax 500 chars
categorystringOutcome groupcontact / no_contact / callback / remove
is_finalboolIs this a final outcome or still retryabletrue / false
trigger_callbackboolAuto-create a callback when the agent picks this codetrue / false (true only when category=callback)
trigger_dncboolAuto-add the number to DNC when pickedtrue / false
default_retry_delay_minutesinteger|nullMinutes to wait before retrying the lead if not final1-43200 (minutes)
colorstringHex color for UI badge#RRGGBB
is_activeboolWhether it appears in the agent dropdowntrue / false
usage_count_30dintegerCalls that used this code in the last 30 days≥ 0
created_atdatetimeCreation timestamp (ISO 8601 UTC)

Response 200:

json
{
  "data": [
    {
      "id": 5,
      "code": "SALE-OK",
      "label": "Sale Success",
      "description": "Customer agreed and committed",
      "category": "contact",
      "is_final": true,
      "trigger_callback": false,
      "trigger_dnc": false,
      "default_retry_delay_minutes": null,
      "color": "#10b981",
      "is_active": true,
      "usage_count_30d": 78,
      "created_at": "2026-06-01T06:00:00Z"
    }
  ]
}

GET /api/telesales/dispositions/{id} — detail

Path parameter
FieldTypeDescription
idintegerDisposition ID

Response 200: disposition object (same shape as one element of the list).

Response 404: { "message": "Disposition not found." }

POST /api/telesales/dispositions — create (admin)

Request body fields
FieldTypeRequiredDescriptionValid values
codestringImmutable code — unique per account[A-Za-z0-9_-], max 32 chars
labelstringDisplay labelmax 100 chars
descriptionstringoptionalDetailed descriptionmax 500 chars
categorystringOutcome groupcontact / no_contact / callback / remove
is_finalbooloptionalFinal outcome (no retry)true / false (default false)
trigger_callbackbooloptionalAuto-create callback — requires category=callbacktrue / false (default false)
trigger_dncbooloptionalAuto-add number to DNCtrue / false (default false)
default_retry_delay_minutesintegeroptionalMinutes before retrying the lead1-43200
colorstringoptionalUI color#RRGGBB

Body:

json
{
  "code": "SALE-OK",
  "label": "Sale Success",
  "description": "Customer agreed and committed",
  "category": "contact",
  "is_final": true,
  "trigger_callback": false,
  "trigger_dnc": false,
  "default_retry_delay_minutes": null,
  "color": "#10b981"
}
Response fields
FieldTypeDescriptionValid values
idintegerNewly created disposition ID
codestringImmutable code
labelstringDisplay label
categorystringOutcome groupcontact / no_contact / callback / remove
is_finalboolFinal outcome
trigger_callbackboolAuto-create callback
trigger_dncboolAuto-add DNC
colorstringColor#RRGGBB
is_activeboolVisible in the agent dropdown (default true)
created_atdatetimeCreation timestamp (ISO 8601 UTC)

Response 201:

json
{
  "data": {
    "id": 5,
    "code": "SALE-OK",
    "label": "Sale Success",
    "category": "contact",
    "is_final": true,
    "trigger_callback": false,
    "trigger_dnc": false,
    "color": "#10b981",
    "is_active": true,
    "created_at": "2026-06-06T06:00:00Z"
  }
}

Response 422 — validation cases:

json
// Duplicate code
{
  "message": "The given data was invalid.",
  "errors": { "code": ["The code has already been taken."] }
}

// Invalid category
{
  "message": "The given data was invalid.",
  "errors": { "category": ["The selected category is invalid."] }
}

// trigger_callback=true but category is not 'callback'
{
  "message": "The given data was invalid.",
  "errors": { "trigger_callback": ["trigger_callback only applies when category='callback'."] }
}

Response 403 — caller is not an admin:

json
{ "message": "You are not allowed to create dispositions. The admin role is required." }

PUT /api/telesales/dispositions/{id} — update (admin)

Path parameter
FieldTypeDescription
idintegerDisposition ID
Request body fields

Same as POST but every field is optional — send only what you want to change. code cannot be changed after creation (immutable identifier); sending a new code returns 422.

FieldTypeDescriptionValid values
labelstringDisplay labelmax 100 chars
descriptionstring|nullDetailed descriptionmax 500 chars
categorystringOutcome groupcontact / no_contact / callback / remove
is_finalboolFinal outcome
trigger_callbackboolAuto-create callback (true only when category=callback)
trigger_dncboolAuto-add DNC
default_retry_delay_minutesinteger|nullRetry delay in minutes1-43200
colorstringColor#RRGGBB
is_activeboolShow/hide in dropdowntrue / false

Response 200: the updated object.

Response 422:

json
// Attempt to change code
{
  "message": "The given data was invalid.",
  "errors": { "code": ["Code is immutable. Create a new disposition if a different code is needed."] }
}

DELETE /api/telesales/dispositions/{id} — delete (admin)

Path parameter
FieldTypeDescription
idintegerDisposition ID to delete

Response 204 No Content (success — soft delete; sets is_active=false).

Response 422 — disposition is in use:

json
{
  "message": "Cannot delete a disposition with associated call history.",
  "errors": { "id": ["Disposition 5 has 78 calls in last 30 days. Hide instead of deleting (set is_active=false)."] }
}

Hide vs Delete

To preserve reporting history, delete defaults to soft delete (set is_active=false). Old dispositions still appear in reports but are hidden from the agent dropdown. Hard delete is only allowed when usage_count = 0.

6.2 Suggested seed catalog (8 common codes)

Each new account is seeded with a generic catalog that is not tied to any industry. Customers can extend or replace it through the API or the admin UI.

CodeLabelCategoryis_finaltrigger_callbacktrigger_dnc
SUCCESSSuccessful Contactcontactx
CALLBACK_REQCustomer Asked for Callbackcallbackx
REFUSEDCustomer Refusedcontactx
NO_ANSWERNo Answerno_contact
BUSYBusyno_contact
VOICEMAILVoicemail Leftno_contact
WRONG_NUMBERWrong Numberremovex
DNC_REQUESTDNC Requestremovexx

For industry-specific catalogs (customer care, service appointments, surveys, medical appointments, ...), see the separate per-customer configuration package — it will pre-seed an extended code set tailored to the business workflow.

Cấp phép theo điều khoản sử dụng của Zorio.