Contacts
The workspace Contacts page — your contact lists, a search for one person across every list, and the do-not-call register — and the API behind it.
The Contacts page holds the people your campaigns call. It sits under Tools & Operations, next to Campaigns, and has three tabs:
- Lists — every contact list, and each list's contacts.
- All contacts — find one person by number or name across every list.
- Do-not-call — the workspace's do-not-call register.
The routes for editing, exporting and searching contacts are listed under The API below.
Lists
The Lists tab shows every contact list with:
- its number of contacts;
- the custom value names its contacts carry, read from the list's first 25 contacts (a longer list says so);
- which campaigns use it;
- when it last changed.
New list creates an empty list you can add contacts to one at a time or by CSV.
Import CSV is the same import as the campaign wizard: a CSV or an Excel workbook is read in your browser, you map the columns, and every row is checked before the list is created. Every column you do not map becomes a custom value.
A list's contacts
Open a list to see its contacts in import order, 50 per page. Search by name,
number or reference, and filter with Show: All contacts, Not called
yet, Reached or On do-not-call. Reached means a campaign call
reached a person, with a connected_*
disposition.
Each row shows the phone number, name, first name, language, time zone, custom values and Last call, the latest campaign outcome for that contact. The first name is the contact's own field, never taken from the name; you can set it when you add or edit a contact, or map a first-name column on import.
Under the name, a row shows the contact's consent basis: why you may call
them, Existing customer (existing_customer) or Explicit consent
(explicit_consent). The selected contact and the All contacts view show
it too. Set it when you add or edit a contact, or map a consent column on
import (in the Contacts import and in the campaign wizard). A cell may say
either value in any case, with spaces or hyphens ("Existing customer"); a blank
cell leaves it unset, and any other value is reported as a row problem rather
than imported.
From the list you can:
- Append rows from a CSV. Rows go to the end of the list; numbers already in the list are skipped.
- Export the list as a CSV download. Import CSV reads an exported file back as the same contacts, custom values and consent basis included.
- Add contact one at a time.
- Edit or Delete the selected contact.
- Rename the list. Campaigns that use it keep using it under the new name.
- Delete list, after a confirmation on the page. See Deleting a list.
A contact that a campaign run has already taken can still be edited, but its number cannot change and it cannot be deleted. Deleting a contact keeps the other contacts' import order.
Deleting a list
A list can't be deleted while a campaign that uses it can still call from it: a campaign that is launching, scheduled, running or paused. The page names those campaigns. Cancel them, or wait until they finish, then delete the list. A list is never taken away from a campaign.
When a list is deleted:
- it no longer appears on the Contacts page, in searches, in the campaign wizard's list picker or in the API, and it can't be imported into or exported;
- contacts that no campaign has called from are removed;
- contacts a campaign already took from the list stay in that campaign's records, so its contacts, results export and reviews read exactly as before. They are no longer found as contacts;
- a draft campaign that used the list needs another list before it can launch.
Preview
When a campaign uses the list, the page previews what that campaign's AI agent says to the selected contact. The preview is rendered with this contact's values by the same code a call uses, so what you see is what the contact hears. Call variables explains how an AI agent declares and uses values.
What it shows depends on the AI agent:
- An AI agent hosted on Skysay is rendered with the contact's values: the
greeting, the script's first line and every script line that uses a value,
and its text message templates. A value the contact doesn't have takes the
fallback wording written in the template, for example
Hi {{#contact.first_name}}{{contact.first_name}}{{/contact.first_name}}{{^contact.first_name}}there{{/contact.first_name}}, and the preview names the values it fell back for. Words the AI agent writes itself in a text message are shown in brackets. If a script reads a value this contact doesn't have, the call takes the script's "case unavailable" route, and the preview says so and shows only the opening. If the contact's values would stop the call being placed, for example a required value is missing, has the wrong type, or makes a line empty or too long, the preview says so and names the value, never its contents. - An AI agent whose greeting is generated when the call starts has no greeting to preview.
- An AI agent on your own endpoint decides what it says, so there is nothing to preview.
- An AI agent that speaks several languages needs the contact or the campaign to name one. If neither does, the call would not be placed, and the preview says so. The same goes for a language the AI agent doesn't speak.
- An AI agent that hasn't been published isn't ready for calls, and the preview says so.
If no campaign uses the list, there is no preview.
All contacts
All contacts finds a person by number or name across every list in the workspace:
- a search that starts with
+matches the start of the number; - other digits match anywhere in the number;
- text matches the name, the first name or your reference.
Choose a result to see which lists that number is in, whether it is on the do-not-call register, and its most recent calls and SMS conversations, with links to Calls and SMS.
Do-not-call
The Do-not-call tab is the workspace's do-not-call register. Numbers on it are never called from the workspace: outbound admission refuses them, and queued calls to a number are cancelled when it is added. The register applies to calls only.
- Add number by typing numbers, or import them from a CSV (the first column is read), with an optional reason.
- Remove takes a number off the register, after a confirmation.
- Search the register by number.
Each entry shows when it was added, its reason, and its source:
| Source | Added by |
|---|---|
| API | POST /v1/do-not-call |
| CSV import | A CSV import |
| MCP | An MCP client |
| AI agent's call outcome | An AI agent's Record the call outcome tool, before it stopped adding numbers |
| Script's cease contact step | A script's Cease contact step, before it stopped adding numbers |
| Skysay support | Skysay support |
| Contacts page | You, on this page |
Numbers are added by you: on this page, by CSV, or through the API or MCP. Skysay support can also add a number to your list. Nothing is added automatically from what a caller says: an AI agent's Record the call outcome tool and a script's Cease contact step record that the caller asked not to be contacted, on the call, for you to review, and never add the number. Entries they added before that change keep their source.
The API
The generated API reference lists each route's request
and response shape. All of them need the calls:read or calls:write scope.
PATCH /v1/contact-lists/{contact_list_id}renames a list. Sendnameonly: required, at most 120 characters. Two lists may share a name.DELETE /v1/contact-lists/{contact_list_id}deletes a list, as described in Deleting a list. It returns409withreason: contact_list_in_useand up to five of the campaigns that hold the list (id,name,status, withtotalandhas_more). Otherwise it returnsremoved_contactsandkept_for_campaign_history.PATCH /v1/contact-lists/{contact_list_id}/contacts/{contact_id}edits one contact, including itsfirst_nameandconsent_basis(an empty value clears it). It is validated like an import row. It returns409if the number would duplicate another contact, or if a campaign run holds the contact and the number would change.DELETE /v1/contact-lists/{contact_list_id}/contacts/{contact_id}deletes one contact. It returns409if a campaign run holds the contact.GET /v1/contact-lists/{contact_list_id}/exportreturns the list as CSV with the columnsphone_e164,name,first_name,language,time_zone,country,external_reference,consent_basis, then onevariables.<name>column per custom value for reading (at most 200), then avariablescolumn with every custom value as JSON, types included. Text that looks like a spreadsheet formula, or that starts with an apostrophe, is prefixed with one apostrophe. The dashboard's Import CSV reads thevariablescolumn and removes that apostrophe, so an export imports back exactly. The file streams in import order, 1,000 contacts at a time, so it has noContent-Length. If it fails partway, the download fails rather than ending early. It holds at most 50,000 contacts, the list limit. The row count when the download started and whether the export was truncated are in theX-Skysay-Row-CountandX-Skysay-Truncatedheaders.GET /v1/contacts?search=finds contacts across every list. It returns a bounded page (limitup to 100) in a stable order, withhas_more. An empty search returns nothing.POST /v1/do-not-callaccepts an optionalsource:api(the default),csv,mcpordashboard.
Campaigns
Call a list of your own customers with a hosted agent inside the hours you choose, with per-outcome retries and a do-not-call register.
Multilingual outbound calls
Configure several languages on one hosted agent, choose its opening language, and optionally follow the caller within the published language set.