# API keys
Source: https://docs.persistence.dev/accounts/api-keys
How API keys authenticate your Persistence REST API requests, and how to store and rotate them safely.
API keys authenticate your requests to the Persistence REST API.
An API key belongs to a workspace and uses the scopes you select when you create it. A workspace can have one API key at a time.
## Create a key
In **Settings**, open **API Keys** and select **Create API key**.
Enter a key name and description, then select at least one scope. Grant only the scopes the integration needs.
Create the key, then copy or download it immediately. Persistence shows the full key only once.
## Authentication
Include your API key in the request headers:
```http theme={null}
x-api-key: YOUR_API_KEY
```
## Storing keys safely
Keep your API keys secure. Never commit them to public repositories or expose
them in client-side code.
Store keys in environment variables or a secrets manager.
## Replace a key
Revoke the existing key before you create a replacement. Revocation takes effect immediately, so integrations that still use the old key will fail.
Plan the change, revoke the key, create and store the replacement, then update every integration that authenticates to the workspace.
# Billing
Source: https://docs.persistence.dev/accounts/billing
Manage payment details and review workspace transactions.
Use **Billing** to manage the workspace payment method and billing address, and to review transaction history, usage, and invoices.
Some paid operations require workspace credit or a valid payment method. This can include buying a phone number, running a campaign, or starting a web or telephony preview.
Keep the billing address and payment method current. Use the transaction list to investigate charges associated with workspace activity.
Billing analytics can summarize usage and break it down by category or agent. Download an invoice when you need an approved record for reconciliation.
Available billing controls depend on your workspace access. Ask a workspace administrator if you cannot view or update them.
# Personal preferences
Source: https://docs.persistence.dev/accounts/preferences
Configure appearance, regional formats, navigation, profile, and notifications.
Personal settings control how Persistence appears and behaves for your account without changing the workspace configuration for other members.
Use **General** to select your appearance and regional preferences. Use **Profile** to maintain your display name, job details, time zone, email, and sign-in methods.
Changes to a personal preference apply to your account or current device as indicated by the setting. Workspace identity, defaults, access, and security policies remain under the workspace administration pages.
# Privacy and PII controls
Source: https://docs.persistence.dev/accounts/privacy
Configure workspace and agent controls for personally identifiable information.
Workspace privacy settings define the default PII behavior. Agent security settings can override that behavior for a specific workflow.
## Configure protection
Open **Settings → Privacy**, enable PII detection, and choose the protection behavior:
* **Anonymisation** replaces detected values before they are used downstream.
* **Redaction** removes detected values from stored output. Redaction is unavailable when anonymisation is enabled.
Choose the PII categories relevant to the workflow. Supported categories include identity, contact, government identifier, and financial information such as names, dates of birth, addresses, email addresses, phone numbers, IP addresses, SSNs, passports, driver's licenses, credit cards, and IBANs.
## Override an agent
Open the agent's **Security** settings to apply agent-specific PII controls. Test the exact categories and formats your callers may provide.
Detection can miss unexpected formats and context. Do not use product controls as a substitute for consent, access control, retention policy, or legal review.
# Secrets
Source: https://docs.persistence.dev/accounts/secrets
Store credentials for actions and integrations without placing them in prompts.
Use **Settings → Secrets** to store encrypted values that actions, MCP servers, and integrations need.
## Create and use a secret
Create a name that describes the credential, such as `CRM_API_TOKEN`. Avoid names that reveal the secret value.
Enter the credential in Secret Manager. Do not place the same value in an agent prompt, test variable, or documentation.
In a supported credential, header, or parameter field, insert the secret using the `{{secrets.NAME}}` form shown by the editor.
Run a simulated call that invokes the action or integration and confirm both success and failure behavior.
## Rotate a secret
Update the stored value, test each dependent workflow, and revoke the old credential at its provider. Deleting a secret can immediately break every tool or integration that references it.
# Account and workspace security
Source: https://docs.persistence.dev/accounts/security
Manage sessions, sign-in methods, and workspace security policy.
Security settings separate controls for your own account from policies that apply across a workspace.
## Personal security
Review active devices and sign out sessions you do not recognize. Keep sign-in methods current, enable the strongest supported multi-factor option, and remove a passkey or device when it is no longer controlled by you.
## Workspace security
Workspace owners can apply shared policies such as session duration, credential hygiene, release channel, and API-key controls. Evaluate the effect on integrations and automation before tightening a policy.
Treat an unfamiliar session, unexpected credential change, or unexplained role change as a security event. Revoke access first, then review activity and affected integrations.
# Sign in and onboarding
Source: https://docs.persistence.dev/accounts/sign-in
Sign in, verify your email, and create or join a workspace.
Persistence supports email passcodes and Google sign-in.
## Sign in with email
Enter your email address and select **Send passcode**.
Enter the six-digit passcode sent to your email. You can request another passcode after the resend timer finishes.
If you are new to Persistence, enter your name, organization name, and workspace name. If you have pending invitations, you can review them before creating a workspace.
You can also choose **Login with Google** and complete the Google authorization flow.
## Fix an expired sign-in flow
Start again from the Persistence dashboard when a login page or passcode has expired. Avoid reusing a bookmarked verification page because its login session may no longer be valid.
# Team access
Source: https://docs.persistence.dev/accounts/team-access
Invite users and control workspace permissions with roles.
Use **Users**, **Roles**, and **Invites** to control who can access a workspace and what they can do.
## Add a user
Create an invitation with the user's details and select a role. The recipient can accept or decline the workspace invitation before it expires. You can resend or revoke a pending invitation.
## Manage roles
A role groups permissions. Create a role with a display name, description, and the permissions required for that job. Assign roles to users through their access settings.
Follow least privilege: grant only the permissions a user needs, and review access when responsibilities change.
## Remove access
Suspending a user temporarily removes access. Deleting a user or removing all access has a broader effect. Review every workspace and role assignment before confirming either action.
# Workspaces
Source: https://docs.persistence.dev/accounts/workspaces
Organize agents, phone resources, integrations, and users in a workspace.
A workspace contains the resources your team uses together, including agents, phone numbers, integrations, contact groups, campaigns, and API access.
Use the workspace switcher to move between workspaces you can access. Check the active workspace before you create, edit, or delete a resource.
## Keep environments separate
When your organization uses separate workspaces for development and production, connect the correct phone resources and integrations in each workspace. Do not copy production credentials into prompts or test calls.
## Before deleting a workspace
Review its agents, numbers, campaigns, integrations, API access, and team membership first. Workspace deletion affects the resources contained in that workspace.
# API reference
Source: https://docs.persistence.dev/api-reference
Public API information for Persistence.
Use the Persistence API to manage agents, calls, workflows, workspaces, campaign audiences, campaigns, billing transactions, phone numbers, number pools, and knowledge bases. Each endpoint page documents its request and response contract.
All public API routes use the `https://api.persistence.dev/v1` prefix and authenticate with `X-API-Key`. The API key determines the workspace for resources whose path does not contain a workspace ID.
Create, save, and publish AI voice agents programmatically.
Start outbound batches and end active calls.
Start, inspect, cancel, and wake published event-to-call workflow runs.
Manage workspaces and create the API keys your integrations use.
Manage the contact lists that define who a campaign calls.
Create and run outbound calling campaigns.
Manage the phone numbers your agents call from and answer on.
Group outbound phone numbers for campaign caller-ID rotation.
Create and manage reusable agent knowledge.
# Create API key
Source: https://docs.persistence.dev/api-reference/accounts/create-api-key
api-reference/accounts/openapi.json POST /v1/{workspaceId}/developer/api-keys
Issue a scoped API key for a workspace.
# Create workspace
Source: https://docs.persistence.dev/api-reference/accounts/create-workspace
api-reference/accounts/openapi.json POST /v1/workspaces
Create a workspace to hold agents, phone numbers, and API keys.
# Delete workspace
Source: https://docs.persistence.dev/api-reference/accounts/delete-workspace
api-reference/accounts/openapi.json DELETE /v1/workspaces/{workspaceId}
Delete a workspace.
# List workspaces
Source: https://docs.persistence.dev/api-reference/accounts/list-workspaces
api-reference/accounts/openapi.json GET /v1/workspaces
Page through the workspaces your API key can access.
# Accounts and workspaces
Source: https://docs.persistence.dev/api-reference/accounts/overview
Manage Persistence workspaces and create the API keys your integrations use.
A workspace is the container for your Persistence resources. Agents, phone numbers, contact lists, and API keys all belong to a workspace. Use these endpoints to manage workspaces and to create the API keys that authenticate the rest of the API.
## Base URL
```
https://api.persistence.dev
```
These endpoints are served under the public `/v1` prefix.
## Authentication
Every request requires an existing workspace API key in the `X-API-Key` header.
```bash theme={null}
curl https://api.persistence.dev/v1/workspaces \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
Treat API keys as secrets. Send them only from server-side code and never commit them to source control.
## Endpoints
`GET /v1/workspaces` — page through the workspaces the key can access.
`POST /v1/workspaces` — create a workspace.
`PUT /v1/workspaces/{workspaceId}` — change a workspace name or scope.
`DELETE /v1/workspaces/{workspaceId}` — delete a workspace.
`POST /v1/{workspaceId}/developer/api-keys` — issue a scoped API key.
## Workspace fields
`POST /v1/workspaces` and `PUT /v1/workspaces/{workspaceId}` accept the same body.
| Field | Type | Description |
| - | - | - |
| `name` | string | Workspace name. |
| `scope` | string | Workspace scope. |
A workspace returned by the API includes these fields.
| Field | Type | Description |
| - | - | - |
| `id` | string | Workspace ID, used as the `workspaceId` path parameter. |
| `name` | string | Workspace name. |
| `scope` | string | Workspace scope. |
| `path` | string | Workspace path. |
| `meta` | object | Additional workspace metadata. |
`PUT /v1/workspaces/{workspaceId}` replaces the workspace fields rather than patching individual values. Send the complete set of fields you want the workspace to have.
## Pagination and sorting
`GET /v1/workspaces` accepts these query parameters.
| Parameter | Type | Description |
| - | - | - |
| `page` | integer | Page number. |
| `perPage` | integer | Items per page. |
| `column` | string | Sort column. Only `name` is supported. |
| `order` | string | Sort order, `asc` or `desc`. |
| `searchText` | string | Filter workspaces by text. |
The response wraps the results in a paged object containing `items`, `page`, `perPage`, `totalCount`, `column`, `order`, and `searchText`.
## API key fields
`POST /v1/{workspaceId}/developer/api-keys` accepts these fields.
| Field | Type | Description |
| - | - | - |
| `name` | string | Required. Name for the key. |
| `scopes` | array of string | Required. Permission scopes granted to the key. |
| `description` | string | Description of the key. |
| `expiresAt` | string | Expiry timestamp for the key. |
| `revokeExisting` | boolean | Revoke existing keys when creating this one. |
The created key is returned with `id`, `name`, `description`, `key`, `scopes`, `status`, `spaceName`, `createdBy`, `createdAt`, `updatedAt`, `expiresAt`, and `lastUsedAt`. The `status` field is either `active` or `revoked`.
The `key` value is returned only in the creation response. Store it securely at that point, because it cannot be retrieved again.
## Errors
Errors return a `code` and an `error` message.
```json theme={null}
{
"code": "not_found",
"error": "workspace not found"
}
```
| Status | Meaning |
| - | - |
| `400` | The request body or a path parameter is invalid. |
| `401` | The `X-API-Key` header is missing or invalid. |
| `403` | The key is not permitted to perform the operation. |
| `404` | The workspace does not exist. |
| `409` | The workspace or API key conflicts with an existing one. |
# Update workspace
Source: https://docs.persistence.dev/api-reference/accounts/update-workspace
api-reference/accounts/openapi.json PUT /v1/workspaces/{workspaceId}
Change the name or scope of an existing workspace.
# Create agent
Source: https://docs.persistence.dev/api-reference/agents/create-agent
api-reference/agents/openapi.json POST /v1/agents
Create an AI voice agent from an agent template.
# Agents API
Source: https://docs.persistence.dev/api-reference/agents/overview
Create, configure, and publish AI voice agents with the Persistence Agents API.
Use the Agents API to build an AI voice agent programmatically instead of through the dashboard. The API covers the build stage of the lifecycle: create the agent, save its configuration as a draft, then publish the configuration you want live calls to use.
## Base URL
```
https://api.persistence.dev
```
## Authentication
Every request requires your workspace API key in the `X-API-Key` header.
```bash theme={null}
curl https://api.persistence.dev/v1/agents \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
Treat the API key as a secret. Send it only from server-side code and never commit it to source control.
## Endpoints
`POST /v1/agents` — create an agent from an agent template.
`PUT /v1/agents/{agentId}/save` — store configuration without affecting live calls.
`POST /v1/agents/{agentId}/publish` — release the configuration used for live calls.
## Build an agent
Send a `name` and a `templateId` to `POST /v1/agents`. The response returns the agent `id`, which is the `agentId` path parameter for the remaining calls.
Send the configuration to `PUT /v1/agents/{agentId}/save`. A draft lets you iterate without changing the behavior of live calls.
Send the configuration to `POST /v1/agents/{agentId}/publish`. The published version is the one used for live calls.
The save and publish requests take the full agent configuration, not a partial patch. Both require `systemPrompt`, `pipelineType`, `ambienceId`, and `triggers`.
## Configuration reference
The save and publish request bodies share the same fields. The tables below group them by purpose. See the individual endpoint pages for every field, type, and constraint.
### Core
| Field | Type | Description |
| - | - | - |
| `systemPrompt` | string | Required. Instructions that define the agent behavior. |
| `pipelineType` | string | Required. `cascaded` or `realtime_native`. |
| `triggers` | array | Required. Trigger definitions evaluated during the call. |
| `name` | string | Agent name, up to 64 characters. |
| `description` | string | Agent description, up to 512 characters. |
| `languageCode` | string | Language code, up to 10 characters. |
| `knowledgeBaseIds` | array of string | Public IDs of the knowledge bases to attach. |
| `tools` | array | Actions the agent can call during a conversation. |
| `dynamicVariables` | object | Variable keys and default values available to the prompt. |
### Language model
| Field | Type | Description |
| - | - | - |
| `llmProvider` | string | One of `openai`, `anthropic`, `google`, `groq`, `together`, `azure`, or `cerebras`. |
| `llmModel` | string | Model identifier for the selected provider. |
| `llmTemperature` | number | Between `0` and `2`. |
| `llmMaxTokens` | integer | Minimum `1`. |
### Voice and audio
| Field | Type | Description |
| - | - | - |
| `ttsVoiceModelId` | integer | Voice model used for speech synthesis. |
| `ttsSpeed` | number | Between `0.5` and `2`. |
| `voiceVolume` | number | Between `0` and `2`. |
| `ambienceId` | string | Required. Ambient sound applied to the call. |
| `ambienceVolume` | number | Between `0` and `2`. |
| `noiseCancellationConfig` | object | `mode` of `noise_only`, `noise_and_speech`, or `none`. |
| `digitPronunciation` | boolean | Read digits individually. |
| `vocabularySpecialization` | string | `general` or `medical`. |
### Conversation behavior
| Field | Type | Description |
| - | - | - |
| `greetingConfig` | object | `mode` of `agent_first` or `user_first`, plus the message to use. |
| `interruptionSensitivity` | number | How readily the caller can interrupt the agent. |
| `endpointingPatience` | number | How long the agent waits before deciding the caller finished. |
| `responseEagerness` | number | How quickly the agent starts responding. |
| `minConsecutiveSpeechDelay` | number | Minimum `0`. |
| `silenceTimeoutSec` | integer | Minimum `1`. |
| `maxCallDurationSec` | integer | Minimum `1`. |
| `presenceDetectionConfig` | object | `checkIntervalSec` and `maxAwayChecks`. |
### Privacy
| Field | Type | Description |
| - | - | - |
| `piiAnonymisation` | boolean | Anonymise detected personal information. |
| `piiRedaction` | boolean | Redact detected personal information. |
| `piiCategories` | array of string | Categories to detect. |
## Errors
Errors return a `code` and an `error` message.
```json theme={null}
{
"code": "bad_request",
"error": "systemPrompt is required"
}
```
| Status | Meaning |
| - | - |
| `400` | The request body or a path parameter is invalid. |
| `401` | The `X-API-Key` header is missing or invalid. |
| `403` | The key is not permitted to perform the operation. |
| `404` | The agent does not exist. |
| `409` | The agent conflicts with an existing one. |
# Publish agent
Source: https://docs.persistence.dev/api-reference/agents/publish-agent
api-reference/agents/openapi.json POST /v1/agents/{agentId}/publish
Publish the agent configuration used for live calls.
# Save agent draft
Source: https://docs.persistence.dev/api-reference/agents/save-agent-draft
api-reference/agents/openapi.json PUT /v1/agents/{agentId}/save
Save an agent configuration as a draft without affecting live calls.
# List billing transactions
Source: https://docs.persistence.dev/api-reference/billing/list-billing-transactions
api-reference/billing/openapi.json GET /v1/billing/transactions
Page through billing transactions and sort the results.
# End active call
Source: https://docs.persistence.dev/api-reference/calls/end-call
api-reference/calls/openapi.json POST /v1/calls/{roomId}/end
End an active call by its session ID.
# Calls API
Source: https://docs.persistence.dev/api-reference/calls/overview
Start outbound call batches and end active calls.
Use the Calls API to start a batch of outbound calls and to end an active call by its session ID.
Every request requires a workspace API key in the `X-API-Key` header. The API key determines the workspace, so these paths do not include a workspace ID.
`POST /v1/calls/batch` — accept up to 1,000 recipients for immediate or scheduled calling.
`POST /v1/calls/{roomId}/end` — end the active session identified by `roomId`.
Recipient `metadata` carries string values into the call. Check `acceptedRecipients` and `rejectedRecipients` in the batch response before treating a submission as fully accepted.
# Start outbound batch
Source: https://docs.persistence.dev/api-reference/calls/start-batch
api-reference/calls/openapi.json POST /v1/calls/batch
Start or schedule a batch of outbound calls from a caller ID or a number pool.
# Confirm upload
Source: https://docs.persistence.dev/api-reference/campaign-audience/confirm-upload
api-reference/campaign-audience/openapi.json POST /v1/calls/contact-lists/{id}/uploads/{uploadID}/confirm
Import a validated upload into the contact list.
# Create contact list
Source: https://docs.persistence.dev/api-reference/campaign-audience/create-contact-list
api-reference/campaign-audience/openapi.json POST /v1/calls/contact-lists
Create a contact list and get a presigned URL for uploading its CSV file.
# Create upload URL
Source: https://docs.persistence.dev/api-reference/campaign-audience/create-upload-url
api-reference/campaign-audience/openapi.json POST /v1/calls/contact-lists/{id}/get-presign-url
Get a presigned URL for uploading a new CSV file to a contact list.
# Delete contact list
Source: https://docs.persistence.dev/api-reference/campaign-audience/delete-contact-list
api-reference/campaign-audience/openapi.json DELETE /v1/calls/contact-lists/{id}
Delete a contact list.
# Get contact list
Source: https://docs.persistence.dev/api-reference/campaign-audience/get-contact-list
api-reference/campaign-audience/openapi.json GET /v1/calls/contact-lists/{id}
Retrieve a contact list and the status of its most recent upload.
# List contact list items
Source: https://docs.persistence.dev/api-reference/campaign-audience/list-contact-list-items
api-reference/campaign-audience/openapi.json GET /v1/calls/contact-lists/{id}/items
Page through the phone numbers in a contact list.
# List contact lists
Source: https://docs.persistence.dev/api-reference/campaign-audience/list-contact-lists
api-reference/campaign-audience/openapi.json GET /v1/calls/contact-lists
Page through the contact lists in your workspace.
# Campaign audience
Source: https://docs.persistence.dev/api-reference/campaign-audience/overview
Manage the contact lists that define who a campaign calls.
A contact list holds the phone numbers a campaign calls. You build a list by uploading a CSV file: create the list to get a presigned upload URL, upload the file, wait for validation, then confirm the upload to import the contacts.
## Base URL
```
https://api.persistence.dev
```
These endpoints are served under the public `/v1` prefix.
## Authentication
Every request requires a workspace API key in the `X-API-Key` header.
```bash theme={null}
curl https://api.persistence.dev/v1/calls/contact-lists \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
## Endpoints
`GET /v1/calls/contact-lists` — page through contact lists.
`POST /v1/calls/contact-lists` — create a list and get a presigned upload URL.
`GET /v1/calls/contact-lists/{id}` — retrieve a list and its upload status.
`PATCH /v1/calls/contact-lists/{id}` — change a list name or type.
`DELETE /v1/calls/contact-lists/{id}` — delete a list.
`GET /v1/calls/contact-lists/{id}/items` — page through the phone numbers in a list.
`POST /v1/calls/contact-lists/{id}/get-presign-url` — get a presigned URL for a new file.
`POST /v1/calls/contact-lists/{id}/uploads/{uploadID}/confirm` — import a validated upload.
`GET /v1/calls/contact-lists/{id}/uploads/{uploadID}/events` — follow upload progress over SSE.
## Upload a contact list
Send `POST /v1/calls/contact-lists` with `name`, `type`, and the `fileName` of the CSV you intend to upload. The response returns the new list along with `uploadUrl`, `uploadHeaders`, and `s3Key`, plus an `uploadDetails` object whose `id` is the `uploadID` used by the remaining steps.
```bash theme={null}
curl --request POST https://api.persistence.dev/v1/calls/contact-lists \
--header "X-API-Key: $PERSISTENCE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "Spring outreach",
"type": "contacts",
"fileName": "contacts.csv"
}'
```
Send the CSV file directly to `uploadUrl`, including every header returned in `uploadHeaders`. This request goes to the storage provider, so do not send your API key with it.
```bash theme={null}
curl --request PUT "$UPLOAD_URL" \
--header "Content-Type: text/csv" \
--upload-file contacts.csv
```
After the file lands, the upload moves through the statuses below. Follow the progress with `GET /v1/calls/contact-lists/{id}/uploads/{uploadID}/events`, or poll `GET /v1/calls/contact-lists/{id}` and read `uploadDetails.status`.
Once the upload reaches `validated`, call `POST /v1/calls/contact-lists/{id}/uploads/{uploadID}/confirm` to import the rows into the list. Confirming before validation completes returns a `409`.
```bash theme={null}
curl --request POST \
"https://api.persistence.dev/v1/calls/contact-lists/$LIST_ID/uploads/$UPLOAD_ID/confirm" \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
To replace or extend the contents of an existing list, start at step 2 by requesting a fresh URL from `POST /v1/calls/contact-lists/{id}/get-presign-url`.
## Contact list fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Contact list ID. |
| `name` | string | Contact list name. |
| `type` | string | List type: `contacts`, `dnd`, or `batch`. |
| `status` | string | List status: `draft`, `ready`, or `archived`. |
| `totalEntries` | integer | Number of entries in the list. |
| `uploadDetails` | object | The most recent upload for the list. |
| `createdAt` | string | Creation timestamp. |
| `updatedAt` | string | Last update timestamp. |
Use `contacts` for the audience a campaign calls and `dnd` for numbers a campaign must skip.
## Upload fields
`uploadDetails` describes the state of a file upload.
| Field | Type | Description |
| - | - | - |
| `id` | string | Upload ID, used as the `uploadID` path parameter. |
| `status` | string | Current upload status. |
| `s3Key` | string | Storage key for the uploaded file. |
| `metadata` | object | Row counts for the upload. |
| `errors` | array | Errors recorded during validation. |
| `errorReportS3Key` | string | Storage key for the error report. |
| `errorReportUrl` | string | Download URL for the error report. |
| `uploadedAt` | string | Time the file was uploaded. |
| `validatedAt` | string | Time validation finished. |
| `processedAt` | string | Time the import finished. |
`metadata` reports `totalRows`, `validRows`, `invalidRows`, `duplicateRows`, and `importedRows`.
### Upload statuses
| Status | Meaning |
| - | - |
| `uploaded` | The file has been received. |
| `validating` | The file is being checked. |
| `validated` | The file passed validation and can be confirmed. |
| `validation_failed` | The file failed validation. |
| `processing` | The confirmed upload is being imported. |
| `processed` | The import finished. |
| `failed` | The upload could not be processed. |
When validation fails, `errorReportUrl` points to a report describing the rejected rows.
## Track upload progress
`GET /v1/calls/contact-lists/{id}/uploads/{uploadID}/events` returns a server-sent event stream. Each event carries an `eventId`, an `eventType`, the current upload in `data`, and `errorReportUrl` when a report is available.
```bash theme={null}
curl --no-buffer \
"https://api.persistence.dev/v1/calls/contact-lists/$LIST_ID/uploads/$UPLOAD_ID/events" \
--header "X-API-Key: $PERSISTENCE_API_KEY" \
--header "Accept: text/event-stream"
```
The interactive playground on this page sends a normal request and shows a single event body. Use a client that supports server-sent events to consume the stream.
## Pagination and filtering
`GET /v1/calls/contact-lists` accepts these query parameters.
| Parameter | Type | Description |
| - | - | - |
| `page` | integer | Page number. |
| `perPage` | integer | Items per page. |
| `type` | string | Filter by list type, `contacts` or `dnd`. |
| `status` | string | Filter by list status. |
| `searchText` | string | Filter lists by text. |
`GET /v1/calls/contact-lists/{id}/items` accepts `page` and `perPage`. Both endpoints return a paged object containing `items`, `page`, `perPage`, `totalCount`, `column`, `order`, and `searchText`.
Each item in a list has a `phoneNumber` and a `metadata` object holding the other columns imported from the CSV file.
## Errors
Errors return a `code` and an `error` message.
```json theme={null}
{
"code": "not_found",
"error": "contact list not found"
}
```
| Status | Meaning |
| - | - |
| `400` | The request body or a path parameter is invalid. |
| `401` | The `X-API-Key` header is missing or invalid. |
| `403` | The key is not permitted to perform the operation. |
| `404` | The contact list or upload does not exist. |
| `409` | The list already exists, or the upload is not ready to be confirmed. |
# Stream upload status
Source: https://docs.persistence.dev/api-reference/campaign-audience/stream-upload-status
api-reference/campaign-audience/openapi.json GET /v1/calls/contact-lists/{id}/uploads/{uploadID}/events
Follow the progress of a contact list upload over server-sent events.
# Update contact list
Source: https://docs.persistence.dev/api-reference/campaign-audience/update-contact-list
api-reference/campaign-audience/openapi.json PATCH /v1/calls/contact-lists/{id}
Change the name or type of a contact list.
# Cancel campaign
Source: https://docs.persistence.dev/api-reference/campaigns/cancel-campaign
api-reference/campaigns/openapi.json POST /v1/calls/campaigns/{id}/cancel
Cancel a campaign.
# Create campaign
Source: https://docs.persistence.dev/api-reference/campaigns/create-campaign
api-reference/campaigns/openapi.json POST /v1/calls/campaigns
Create an outbound calling campaign.
# Delete campaign
Source: https://docs.persistence.dev/api-reference/campaigns/delete-campaign
api-reference/campaigns/openapi.json DELETE /v1/calls/campaigns/{id}
Delete a campaign.
# Get campaign
Source: https://docs.persistence.dev/api-reference/campaigns/get-campaign
api-reference/campaigns/openapi.json GET /v1/calls/campaigns/{id}
Retrieve a campaign and its contact groups.
# List campaign contacts
Source: https://docs.persistence.dev/api-reference/campaigns/list-campaign-contacts
api-reference/campaigns/openapi.json GET /v1/calls/campaigns/{id}/contacts
Page through the contacts in a campaign and their call status.
# List campaign conversations
Source: https://docs.persistence.dev/api-reference/campaigns/list-campaign-events
api-reference/campaigns/events-openapi.json GET /v1/calls/campaigns/{id}/events
Page through conversations produced by a campaign.
# List campaigns
Source: https://docs.persistence.dev/api-reference/campaigns/list-campaigns
api-reference/campaigns/openapi.json GET /v1/calls/campaigns
Page through the campaigns in your workspace.
# Campaigns
Source: https://docs.persistence.dev/api-reference/campaigns/overview
Create and run outbound calling campaigns.
A campaign places outbound calls to a set of contacts. It ties together the agent that speaks on the call, the phone number the call comes from, and the contact lists that define who to call. Once created, you control the campaign with the start, pause, resume, and cancel operations.
## Base URL
```
https://api.persistence.dev
```
These endpoints are served under the public `/v1` prefix.
## Authentication
Every request requires a workspace API key in the `X-API-Key` header.
```bash theme={null}
curl https://api.persistence.dev/v1/calls/campaigns \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
## Endpoints
`GET /v1/calls/campaigns` — page through your campaigns.
`POST /v1/calls/campaigns` — create a campaign.
`GET /v1/calls/campaigns/{id}` — retrieve a campaign and its contact groups.
`PATCH /v1/calls/campaigns/{id}` — change a campaign's configuration.
`DELETE /v1/calls/campaigns/{id}` — delete a campaign.
`GET /v1/calls/campaigns/{id}/contacts` — see per-contact call status.
`GET /v1/calls/campaigns/{id}/events` — page through conversations produced by the campaign.
`POST /v1/calls/campaigns/{id}/start` — begin placing calls.
`POST /v1/calls/campaigns/{id}/pause` — pause a running campaign.
`POST /v1/calls/campaigns/{id}/resume` — resume a paused campaign.
`POST /v1/calls/campaigns/{id}/cancel` — cancel a campaign.
## Before you start
A campaign references resources created through the other APIs. Gather these first.
`agentId` — the agent that speaks on the call.
`fromNumber` — the number the call comes from, or use a number pool.
`contactListIds` and `dndListIds` — who to call and who to skip.
## Create a campaign
`POST /v1/calls/campaigns` requires `name`, `agentId`, `contactListIds`, `maxConcurrency`, and exactly one of `fromNumber` or `numberPoolId`.
| Field | Type | Description |
| - | - | - |
| `name` | string | Required. Campaign name. |
| `agentId` | string | Required. Agent that places the calls. |
| `fromNumber` | string | Caller ID in E.164 format. Mutually exclusive with `numberPoolId`. |
| `numberPoolId` | string | Number pool used for caller ID rotation. Mutually exclusive with `fromNumber`. |
| `contactListIds` | array of string | Required. Contact lists to call. Must contain at least one ID. |
| `maxConcurrency` | integer | Required. Maximum simultaneous calls. Minimum `1`. |
| `dndListIds` | array of string | Contact lists whose numbers the campaign skips. |
| `retryPolicy` | object | Retry configuration for campaign calls. |
| `scheduledAt` | date-time | Required unless `sendNow` is `true`. |
| `scheduleStart` | string | Optional daily calling-window start in zero-padded `HH:MM`. |
| `scheduleEnd` | string | Optional daily calling-window end in zero-padded `HH:MM`. |
| `timezone` | string | IANA time zone used for the schedule. |
| `sendNow` | boolean | Start the campaign immediately instead of scheduling it. |
Pass `contactListIds` for the audience and `dndListIds` for numbers to exclude. Both accept IDs from the [Campaign audience API](/api-reference/campaign-audience/overview); create do-not-disturb lists with `type` set to `dnd`.
```bash theme={null}
curl --request POST https://api.persistence.dev/v1/calls/campaigns \
--header "X-API-Key: $PERSISTENCE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "Spring outreach",
"agentId": "AGENT_ID",
"fromNumber": "+15551234567",
"contactListIds": ["CONTACT_LIST_ID"],
"maxConcurrency": 5
}'
```
A `422` indicates the campaign could not be created with the values supplied.
Set `scheduleStart` and `scheduleEnd` together. Do not send scheduling fields when `sendNow` is `true`.
## Control a running campaign
Four operations move a campaign through its lifecycle. Each takes only the campaign ID and returns the updated campaign.
| Operation | Endpoint |
| - | - |
| Start | `POST /v1/calls/campaigns/{id}/start` |
| Pause | `POST /v1/calls/campaigns/{id}/pause` |
| Resume | `POST /v1/calls/campaigns/{id}/resume` |
| Cancel | `POST /v1/calls/campaigns/{id}/cancel` |
```bash theme={null}
curl --request POST \
"https://api.persistence.dev/v1/calls/campaigns/$CAMPAIGN_ID/start" \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
The campaign records `pausedAt`, `resumedAt`, `stoppedAt`, and `cancelledAt` as these operations are applied, so you can read the current state from the campaign itself.
## Campaign fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Campaign ID. |
| `name` | string | Campaign name. |
| `description` | string | Campaign description. |
| `status` | string | Current campaign status. |
| `direction` | string | Call direction, `inbound` or `outbound`. |
| `agentId` | string | Agent that places the calls. |
| `fromNumber` | string | Number the calls come from. |
| `lists` | array | Contact lists attached to the campaign, each with an `id` and a `role`. |
| `maxConcurrency` | integer | Maximum simultaneous calls. |
| `retryPolicy` | array of integer | Retry schedule for unanswered calls. |
| `totalContacts` | integer | Number of contacts in the campaign. |
| `targetCallCount` | integer | Target number of calls. |
| `targetConversionRate` | number | Target conversion rate. |
| `scheduledAt` | string | When the campaign is scheduled to run. |
| `scheduleStart` | integer | Start of the daily calling window. |
| `scheduleEnd` | integer | End of the daily calling window. |
| `timezone` | string | Timezone the schedule is interpreted in. |
| `startDate` | string | Campaign start date. |
| `endDate` | string | Campaign end date. |
| `pausedAt` | string | Time the campaign was paused. |
| `resumedAt` | string | Time the campaign was resumed. |
| `stoppedAt` | string | Time the campaign stopped. |
| `cancelledAt` | string | Time the campaign was cancelled. |
| `meta` | object | Additional campaign metadata. |
| `createdAt` | string | Creation timestamp. |
| `updatedAt` | string | Last update timestamp. |
When a campaign fails, `meta.failure.reason` describes why.
`GET /v1/calls/campaigns/{id}` returns these fields plus `agentName` and a `contactGroups` array, where each group has an `id`, `name`, and `type`.
## Campaign contacts
`GET /v1/calls/campaigns/{id}/contacts` lists the individual contacts in a campaign.
| Field | Type | Description |
| - | - | - |
| `phoneNumber` | string | The contact's phone number. |
| `status` | string | Call status for this contact. |
| `attempts` | integer | Number of call attempts made. |
| `createdAt` | string | Creation timestamp. |
| `updatedAt` | string | Last update timestamp. |
The endpoint accepts `page`, `perPage`, and a `status` filter.
## Pagination and filtering
`GET /v1/calls/campaigns` accepts these query parameters.
| Parameter | Type | Description |
| - | - | - |
| `page` | integer | Page number. |
| `perPage` | integer | Items per page. |
| `status` | string | Filter by status: `active`, `paused`, or `completed`. |
| `direction` | string | Filter by direction: `inbound` or `outbound`. |
| `name` | string | Filter by campaign name. |
| `searchText` | string | Filter campaigns by text. |
Both list endpoints return a paged object containing `items`, `page`, `perPage`, `totalCount`, `column`, `order`, and `searchText`.
## Errors
Errors return a `code` and an `error` message.
```json theme={null}
{
"code": "not_found",
"error": "campaign not found"
}
```
| Status | Meaning |
| - | - |
| `400` | The request body or a path parameter is invalid. |
| `401` | The `X-API-Key` header is missing or invalid. |
| `403` | The key is not permitted to perform the operation. |
| `404` | The campaign does not exist. |
| `422` | The campaign could not be created with the values supplied. |
# Pause campaign
Source: https://docs.persistence.dev/api-reference/campaigns/pause-campaign
api-reference/campaigns/openapi.json POST /v1/calls/campaigns/{id}/pause
Pause a running campaign.
# Resume campaign
Source: https://docs.persistence.dev/api-reference/campaigns/resume-campaign
api-reference/campaigns/openapi.json POST /v1/calls/campaigns/{id}/resume
Resume a paused campaign.
# Start campaign
Source: https://docs.persistence.dev/api-reference/campaigns/start-campaign
api-reference/campaigns/openapi.json POST /v1/calls/campaigns/{id}/start
Start a campaign so it begins placing calls.
# Update campaign
Source: https://docs.persistence.dev/api-reference/campaigns/update-campaign
api-reference/campaigns/openapi.json PATCH /v1/calls/campaigns/{id}
Change the configuration of a campaign.
# Create knowledge base
Source: https://docs.persistence.dev/api-reference/knowledge-bases/create
api-reference/knowledge-bases/openapi.json POST /v1/knowledge-bases
Create an empty knowledge base.
# Delete knowledge base
Source: https://docs.persistence.dev/api-reference/knowledge-bases/delete
api-reference/knowledge-bases/openapi.json DELETE /v1/knowledge-bases/{kbId}
Delete a knowledge base and its documents.
# Delete knowledge base document
Source: https://docs.persistence.dev/api-reference/knowledge-bases/delete-document
api-reference/knowledge-bases/openapi.json DELETE /v1/knowledge-bases/{kbId}/documents/{docId}
Delete one document from a knowledge base.
# Get knowledge base
Source: https://docs.persistence.dev/api-reference/knowledge-bases/get
api-reference/knowledge-bases/openapi.json GET /v1/knowledge-bases/{kbId}
Get a knowledge base and its documents.
# List knowledge bases
Source: https://docs.persistence.dev/api-reference/knowledge-bases/list
api-reference/knowledge-bases/openapi.json GET /v1/knowledge-bases
List knowledge bases in the API key's workspace.
# Knowledge Bases API
Source: https://docs.persistence.dev/api-reference/knowledge-bases/overview
Create and manage knowledge bases through the public API.
Use the Knowledge Bases API to create, list, inspect, and delete knowledge bases. You can also delete an individual document from a knowledge base.
Every request requires a workspace API key in the `X-API-Key` header.
`GET /v1/knowledge-bases`
`POST /v1/knowledge-bases`
`GET /v1/knowledge-bases/{kbId}`
`DELETE /v1/knowledge-bases/{kbId}`
`DELETE /v1/knowledge-bases/{kbId}/documents/{docId}`
The knowledge base status is derived from its documents: `empty`, `processing`, `ready`, or `failed`. Attach a knowledge base to an agent only after it is `ready`.
# Create number pool
Source: https://docs.persistence.dev/api-reference/number-pools/create
api-reference/number-pools/openapi.json POST /v1/number-pools
Create a number pool with at least one phone number.
# Delete number pool
Source: https://docs.persistence.dev/api-reference/number-pools/delete
api-reference/number-pools/openapi.json DELETE /v1/number-pools/{id}
Delete a number pool.
# Get number pool
Source: https://docs.persistence.dev/api-reference/number-pools/get
api-reference/number-pools/openapi.json GET /v1/number-pools/{id}
Get one number pool.
# List number pools
Source: https://docs.persistence.dev/api-reference/number-pools/list
api-reference/number-pools/openapi.json GET /v1/number-pools
List number pools in the workspace.
# List number pool members
Source: https://docs.persistence.dev/api-reference/number-pools/members
api-reference/number-pools/openapi.json GET /v1/number-pools/{id}/members
List the phone numbers in a pool.
# Move number pool member
Source: https://docs.persistence.dev/api-reference/number-pools/move-member
api-reference/number-pools/openapi.json POST /v1/number-pools/{id}/members/move
Move a phone number from its current pool into the target pool.
# Number Pools API
Source: https://docs.persistence.dev/api-reference/number-pools/overview
Group phone numbers for campaign caller-ID rotation.
A number pool groups phone numbers that a campaign can use instead of one fixed `fromNumber`. A phone number can belong to one pool at a time.
`GET /v1/number-pools`
`POST /v1/number-pools`
`GET /v1/number-pools/{id}`
`PATCH /v1/number-pools/{id}`
`DELETE /v1/number-pools/{id}`
`GET /v1/number-pools/{id}/members`
`POST /v1/number-pools/{id}/members/move`
`GET /v1/number-pools/{id}/validate-trunks`
Validate outbound trunks before assigning a pool to a campaign. The result reports valid and invalid member counts and identifies invalid numbers.
# Update number pool
Source: https://docs.persistence.dev/api-reference/number-pools/update
api-reference/number-pools/openapi.json PATCH /v1/number-pools/{id}
Update pool details or add and remove phone numbers.
# Validate number pool trunks
Source: https://docs.persistence.dev/api-reference/number-pools/validate-trunks
api-reference/number-pools/openapi.json GET /v1/number-pools/{id}/validate-trunks
Check outbound trunk readiness for every pool member.
# Buy phone number
Source: https://docs.persistence.dev/api-reference/telephony/buy-phone-number
api-reference/telephony/openapi.json POST /v1/telephony/phone-numbers/buy
Buy a phone number from a telephony provider.
# Get phone number
Source: https://docs.persistence.dev/api-reference/telephony/get-phone-number
api-reference/telephony/openapi.json GET /v1/telephony/phone-numbers/{id}
Retrieve a phone number and its configuration.
# Import phone numbers
Source: https://docs.persistence.dev/api-reference/telephony/import-phone-numbers
api-reference/telephony/openapi.json POST /v1/telephony/phone-numbers/import/manual
Import phone numbers with your own SIP termination.
# List phone numbers
Source: https://docs.persistence.dev/api-reference/telephony/list-phone-numbers
api-reference/telephony/openapi.json GET /v1/telephony/phone-numbers
Page through the phone numbers in your workspace.
# Telephony
Source: https://docs.persistence.dev/api-reference/telephony/overview
Manage the phone numbers your agents call from and answer on.
A phone number connects your agents to the telephone network. Buy a number, bind it to the agents that handle its inbound and outbound calls, then use it as the `fromNumber` for outbound campaigns.
## Base URL
```
https://api.persistence.dev
```
These endpoints are served under the public `/v1` prefix.
## Authentication
Every request requires a workspace API key in the `X-API-Key` header.
```bash theme={null}
curl https://api.persistence.dev/v1/telephony/phone-numbers \
--header "X-API-Key: $PERSISTENCE_API_KEY"
```
## Endpoints
`GET /v1/telephony/phone-numbers` — page through the numbers in your workspace.
`POST /v1/telephony/phone-numbers/buy` — buy a number from a provider.
`POST /v1/telephony/phone-numbers/import/manual` — import owned numbers with SIP termination.
`GET /v1/telephony/phone-numbers/{id}` — retrieve a single number.
`PATCH /v1/telephony/phone-numbers/{id}` — bind a number to agents.
## Phone number fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Phone number ID. |
| `phoneNumber` | string | The number itself. |
| `friendlyName` | string | Display name for the number. |
| `label` | string | Label applied to the number. |
| `country` | string | Country the number belongs to. |
| `status` | string | Current status of the number. |
| `provider` | string | Telephony provider the number was bought from. |
| `providerMonthlyCost` | number | Monthly cost charged by the provider. |
| `providerCurrency` | string | Currency of the monthly cost. |
| `isCustomerManaged` | boolean | Whether you supplied the number rather than buying it through Persistence. |
| `isInboundReady` | boolean | Whether the number can receive calls. |
| `isOutboundReady` | boolean | Whether the number can place calls. |
| `inboundAgentPublicId` | string | ID of the agent that answers inbound calls. |
| `inboundAgentName` | string | Name of the inbound agent. |
| `inboundWebhookUrl` | string | Webhook called for inbound calls. |
| `outboundAgentPublicId` | string | ID of the agent used for outbound calls. |
| `outboundAgentName` | string | Name of the outbound agent. |
| `allowedInboundCountries` | array of string | Countries permitted to call this number. |
| `allowedOutboundCountries` | array of string | Countries this number is permitted to call. |
| `sipConfig` | object | SIP trunk configuration for the number. |
| `spaceId` | string | Workspace the number belongs to. |
| `createdAt` | string | Creation timestamp. |
| `updatedAt` | string | Last update timestamp. |
### SIP configuration
`sipConfig` describes the SIP trunk settings for the number.
| Field | Type | Description |
| - | - | - |
| `terminationUrl` | string | SIP termination URL. |
| `username` | string | SIP username. |
| `password` | string | SIP password. |
| `transport` | string | SIP transport. |
| `ipAllowlist` | array of string | IP addresses permitted to connect. |
## Buy a phone number
`POST /v1/telephony/phone-numbers/buy` requires both fields below.
| Field | Type | Description |
| - | - | - |
| `phoneNumber` | string | Required. The number to buy. |
| `provider` | string | Required. The telephony provider to buy the number from. |
Contact Persistence support for the provider values available to your workspace.
The response returns the created phone number. A `402` indicates the purchase could not be completed for billing reasons.
```bash theme={null}
curl --request POST https://api.persistence.dev/v1/telephony/phone-numbers/buy \
--header "X-API-Key: $PERSISTENCE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"phoneNumber": "+15551234567",
"provider": "PROVIDER"
}'
```
## Bind a number to agents
`PATCH /v1/telephony/phone-numbers/{id}` updates a number and controls which agents handle its calls.
| Field | Description |
| - | - |
| `friendlyName` | Display name for the number. |
| `label` | Label applied to the number. |
| `inboundAgentId` | Agent that answers inbound calls. |
| `outboundAgentId` | Agent used for outbound calls. |
| `useSameForInbound` | Use the outbound agent for inbound calls as well. |
| `outboundTermination` | Outbound termination setting for the number. |
| `allowedInboundCountries` | Countries permitted to call this number. |
| `allowedOutboundCountries` | Countries this number is permitted to call. |
Agent IDs come from the [Agents API](/api-reference/agents/overview). After binding, `isInboundReady` and `isOutboundReady` on the phone number reflect whether the number is ready to handle calls in each direction.
## Pagination and sorting
`GET /v1/telephony/phone-numbers` accepts these query parameters.
| Parameter | Type | Description |
| - | - | - |
| `page` | integer | Page number. |
| `perPage` | integer | Items per page. |
| `column` | string | Sort column, `phone_number` or `created_at`. |
| `order` | string | Sort order, `asc` or `desc`. |
| `searchText` | string | Filter numbers by text. |
The response wraps the results in a paged object containing `items`, `page`, `perPage`, `totalCount`, `column`, `order`, and `searchText`.
## Errors
Errors return a `code` and an `error` message.
```json theme={null}
{
"code": "not_found",
"error": "phone number not found"
}
```
| Status | Meaning |
| - | - |
| `400` | The request body or a path parameter is invalid. |
| `401` | The `X-API-Key` header is missing or invalid. |
| `402` | The number could not be bought for billing reasons. |
| `403` | The key is not permitted to perform the operation. |
| `404` | The phone number does not exist. |
# Update phone number
Source: https://docs.persistence.dev/api-reference/telephony/update-phone-number
api-reference/telephony/openapi.json PATCH /v1/telephony/phone-numbers/{id}
Update a phone number and bind it to the agents that handle its calls.
# Cancel a workflow run
Source: https://docs.persistence.dev/api-reference/workflows/cancel-run
api-reference/workflows/openapi.json POST /v1/workflows/{workflowId}/runs/{runId}/cancel
Stop one active workflow run.
# Cancel workflow runs
Source: https://docs.persistence.dev/api-reference/workflows/cancel-runs
api-reference/workflows/openapi.json POST /v1/workflows/{workflowId}/runs/cancel
Stop a workflow's active runs by concurrency key, or all of them.
# Get workflow run
Source: https://docs.persistence.dev/api-reference/workflows/get-run
api-reference/workflows/openapi.json GET /v1/workflows/{workflowId}/runs/{runId}
Inspect the status of a workflow run.
# Workflows API
Source: https://docs.persistence.dev/api-reference/workflows/overview
Start, inspect, cancel, and wake published event-to-call workflow runs.
Use the Workflows API after you publish an event-to-call [workflow](/deploy/workflows) in the dashboard. These routes start and control runs. They do not create or edit the graph.
Every request requires a workspace API key in the `X-API-Key` header. Use the `workflow run` scope to start, cancel, or wake a run. Add `workflow read` to check status.
The API key determines the workspace, so these paths do not include a workspace ID.
`POST /v1/workflows/{workflowId}/run` — queue a published workflow with `context`.
`GET /v1/workflows/{workflowId}/runs/{runId}` — inspect status, context, and output.
`POST /v1/workflows/{workflowId}/runs/{runId}/cancel` — stop one active run.
`POST /v1/workflows/{workflowId}/runs/cancel` — stop runs by concurrency key, or all of them.
`POST /v1/workflows/{workflowId}/runs/wake` — continue runs sleeping at a wait step.
Publish the workflow first. Drafts reject start requests.
Reuse `Idempotency-Key` when you retry the same start. Add `Concurrency-Key` when only one active run should exist for a store, ticket, or similar key.
# Start a workflow run
Source: https://docs.persistence.dev/api-reference/workflows/start-run
api-reference/workflows/openapi.json POST /v1/workflows/{workflowId}/run
Queue a published event-to-call workflow with context.
# Wake waiting runs
Source: https://docs.persistence.dev/api-reference/workflows/wake-runs
api-reference/workflows/openapi.json POST /v1/workflows/{workflowId}/runs/wake
Continue runs that are sleeping at a wait step.
# Actions and tools
Source: https://docs.persistence.dev/build/actions
Let an agent act during a conversation.
Actions let an agent do more than speak. Add only the tools required for the workflow, give each one a narrow responsibility, and test its failure behavior before publishing.
## Built-in actions
| Action | Purpose |
| - | - |
| **End call** | End the current conversation when its goal is complete or a stop condition is met. |
| **Transfer call** | Transfer to a phone number, SIP URI, dynamic variable, or dynamically selected route using a cold or warm transfer. |
| **Extract dynamic variable** | Store a text, number, boolean, or fixed-choice value for later use. |
| **Capture input** | Collect keypad input with an optional timeout, digit limit, and termination key. |
| **Calendar** | Check availability, book, reschedule, or cancel through a connected calendar. |
| **Custom function** | Send an HTTP request to your service and use the response during the call. |
## Configure a custom function
Give the function a lowercase snake\_case name and explain exactly when the agent should use it.
Choose the method and URL, then add headers, query parameters, and a JSON object schema for inputs.
Decide which fields should be stored as variables and what the agent should say before, during, or after execution. You can play a typing sound while it runs.
Verify timeout, non-success response, missing field, and malformed-response behavior.
Do not place credentials directly in prompts or tool configuration. Store encrypted values under **Settings → Secrets** and reference them through the supported configuration.
## Configure transfers
Choose a static destination or provide routing instructions for a dynamic destination. A destination can be a phone number, SIP URI, or supported dynamic variable.
* A **cold transfer** connects the destination without an agent debrief.
* A **warm transfer** lets the AI provide a debrief to the next agent.
Choose SIP invite or SIP refer according to your provider support. For SIP invite, configure the displayed caller ID and transfer ring duration. Add custom SIP headers only when the receiving system requires them.
## Capture keypad input
Set the expected digit count, termination key, prompt, timeout, confirmation behavior, and variable that stores the result. Test slow entry, early termination, invalid digits, and timeout.
## MCP servers
An agent can connect to an MCP server and expose selected MCP tools. Configure the server URL, headers, query parameters, and timeout, then choose the tools the agent may call. A workspace-global MCP can be available across agents; an agent-specific MCP stays scoped to that agent.
Treat an MCP server like any other privileged integration: grant the smallest useful tool set and verify its authentication and failure behavior.
## Calendar connections
Calendar actions require a connected Cal.com or Calendly account. Manage connections under **Integrations** before adding calendar actions to an agent.
# Agent settings
Source: https://docs.persistence.dev/build/agent-settings
Configure call behavior, speech, transcription, data, and security.
Open an agent and select **Settings** to configure the behavior around its prompt or conversation flow. Agent settings save automatically while you edit the current draft.
## Call settings
* Maximum call duration
* End call after a period of silence
* Voicemail and answering-machine detection, with hang-up or leave-message behavior
* IVR detection and hang-up behavior
* Call recording, consent, and recording beep controls when available
* Keypad and digit handling
## Speech settings
* Voice and language
* Voice speed
* Interruption sensitivity
* Response eagerness
* Reminder-message frequency
* Background ambience and volume
* Voice volume and digit pronunciation
## Instructions and model behavior
Adjust the LLM temperature when you need more or less variation in responses. Persistence appends default guardrails to agent instructions unless you explicitly remove them.
Removing the default guardrails can weaken context boundaries, instruction compliance, topic restrictions, sensitive-data protection, action boundaries, fallback handling, and behavior consistency. Strengthen and test your custom instructions before removing them.
## Transcription settings
* Denoising
* Endpointing behavior
* Vocabulary specialization for domain-specific terms
Test changes with real examples from your callers. Faster endpointing can reduce latency but may cut off a caller who pauses mid-sentence.
## Dynamic variables
Define variables that personalize a prompt, greeting, tool request, or flow. Provide a default value for preview calls and for production calls where the caller-specific value is unavailable.
Use test values in the preview. Do not use production secrets as test variables.
See [Dynamic variables](/build/dynamic-variables) for supported sources and testing guidance.
## Data extraction
Configure extraction triggers for events such as **On Call Ended** or **On Call Transferred**. An extracted category can be text, yes/no, numeric, or a fixed known value.
## Security
Agent security controls include PII protection, anonymization, and redaction. Workspace-level [privacy settings](/accounts/privacy) can provide defaults, while an agent can apply the behavior appropriate for its workflow.
Configure recording consent, data handling, and PII behavior for the laws and policies that apply to your callers. Product controls do not replace legal review.
# Agent workflow
Source: https://docs.persistence.dev/build/agent-workflow
Configure the full call lifecycle: knowledge, pre-call and post-call integrations, extraction, and webhooks.
Open an agent and select **Workflow**. This canvas is the call lifecycle around the prompt or conversation flow. It is labeled **Agent workflow**.
Use **Agent** to edit instructions or the visual flow. Use **Workflow** to attach what happens before, during, and after the call.
## Lifecycle
The canvas always shows this order:
1. **Call incoming / outgoing**
2. **Pre-call integrations** — prepare context before the conversation.
3. **Call started**
4. **Knowledge bases** — give the agent knowledge to answer questions.
5. **Agent** — in-call integrations and system tools.
6. **Call ended**
7. **Data extraction** — extract structured data from the conversation.
8. **Post-call integrations** — send results to connected services.
Dotted nodes sit beside that path:
| Node | Purpose |
| - | - |
| **Transfer/Transcript** | Fires on transfer started, transfer failed, and transcript updated. |
| **Recording ready** | Fires when the recording is available. |
| **Extraction done** | Fires after post-call extraction finishes. |
| **Webhooks** | POST selected call events to your HTTPS endpoints. |
## Attach knowledge bases
The **Knowledge bases** card lists collections on this agent.
In **Knowledge Base**, add sources and wait until the collection status is **Ready**. An agent can only use a ready knowledge base.
On **Workflow**, select **Attach**. You can attach up to three knowledge bases per agent.
Select **Attach another** until you reach three. **Attach another** is disabled at the limit.
Dimmed collections are still processing, empty, or failed. See [Knowledge bases](/build/knowledge-base).
## Pre-call integrations
**Pre-call integrations** run before the agent speaks. Use them to look up the caller, load account context, or reject work you should not take.
Select **Add integration**. HTTP functions and pre-call APIs can run with execution timing **before the call**. Keep tokens in [secrets](/accounts/secrets).
Empty state: **No integrations added**.
## In-call configuration
Select **Open** on the **Agent** card to return to the builder, or configure tools on the card.
| Tab | What it holds |
| - | - |
| **In-call integrations** | Connected services during the conversation (calendar, CRM, Slack, HTTP with during-call timing, and similar). Copy: **Use connected services during the conversation.** |
| **In-call system tools** | Built-in actions such as end call, transfer, extract, capture input, and custom functions. |
Disabled tools show **Disabled**. Edit or remove a row from the card. See [Actions and tools](/build/actions).
## Data extraction
**Data extraction** stores structured fields after the call. This is separate from an **Extract** node inside a conversation flow.
Select **Add extraction**.
**On call ended** or **On call transferred**.
Name each field and describe what to capture. These values can feed post-call integrations and [event-to-call workflows](/deploy/workflows).
Empty state: **No extraction rules configured**.
## Webhooks
**Webhooks** send call events to your endpoints. Select **Add webhook**.
| Field | Rule |
| - | - |
| Name | Shown in the subscriptions list. |
| Callback URL | HTTPS POST only. |
| Events | Select at least one event. |
Event groups:
| Group | Events |
| - | - |
| **Call lifecycle** | Call started, Call completed |
| **Real-time events** | Transcript updated, Transfer started, Transfer failed |
| **Processing** | Recording ready, Analysis done |
Headers can be a literal (**Const**), a variable, or a secret. Do not generate header values with the model.
Use **Send test event** to verify delivery before you publish.
Empty state: **No webhooks configured**.
## Post-call integrations
**Post-call integrations** run after **Call ended**. Use them to write the outcome to a CRM, notify Slack, or call your own HTTP endpoint.
Select **Add integration** and set execution timing to after the call.
## Save and publish
Workflow changes save with the agent draft. Wait for pending saves, preview, then **Publish**. A superseded version is read-only on this canvas.
# Agents
Source: https://docs.persistence.dev/build/agents
Create, manage, publish, import, and restore AI voice agents.
An agent defines how Persistence handles a conversation. Choose the agent structure, configure its behavior, test the draft, and publish it when it is ready for live calls.
## Choose an agent structure
Use one instruction set for flexible conversations that do not require a fixed sequence.
Build a visual flow with explicit conversation, condition, action, transfer, and end nodes.
After you create either type, the builder switcher has **Agent** and **Workflow**. **Agent** is the prompt or canvas. **Workflow** is the [call lifecycle](/build/agent-workflow). See [Workflows](/build/workflows) if those labels are unclear.
## Create an agent
In the dashboard, open **Agents** and select **Create Agent**.
Select **Single Prompt** or **Conversation Flow**.
Start from a template that matches the conversation you want to automate. Review the proposed configuration before creating the agent.
Use a name that identifies the workflow, describe its main goal, and optionally provide a public website whose content can help personalize the draft. Enable **Chat only** when the workflow should use text without audio processing.
## Drafts and live versions
Changes are made to a draft. Test that draft before you publish it. Publishing promotes the current configuration for live calls while preserving earlier versions in the version timeline.
You can inspect an earlier version and restore it into a new draft when you need to roll back a change. Superseded versions are read-only.
## Import and export
The **Agents** page supports importing a `.pa` agent package up to 2 MB. An agent can also be exported from its actions menu by selecting a version. Use import and export to move a supported agent configuration without recreating it manually.
## Share an agent preview
Save an agent version, select **Share**, and choose an expiry of 1, 3, 7, or 15 days. A generated link can be opened up to 20 times and is copied to your clipboard.
The recipient can try the shared version without receiving workspace access. Treat the link as access to an agent test: do not configure the shared version to expose secrets, production customer data, or privileged actions.
## Discover reusable agents
Use **Marketplace** to browse agents by job, industry, language, and outcome. Review an agent profile, listen to its sample conversation, inspect its capabilities and integrations, and configure a copy for your workspace.
Before deleting an agent, remove it from phone numbers, number pools' routing, event-to-call workflows, and active or scheduled campaigns. Persistence blocks deletion while those assignments exist.
## Next steps
Attach knowledge, pre-call and post-call integrations, extraction, and webhooks.
Start a browser-based voice preview before publishing.
# Build with Composer
Source: https://docs.persistence.dev/build/composer
Create or modify an agent through the AI-assisted workspace.
Composer turns a written goal into an agent configuration. Use it to build a new AI voice agent, modify an existing agent, or explore a workflow before opening the full builder.
## Start a build
State the caller, call direction, goal, information the agent needs, actions it may take, and when it should transfer or end the call.
Attach an existing agent, knowledge base, or document when Composer should use that resource. You can also mention an agent or knowledge base in the message.
Confirm important choices such as structure, voice, and capabilities. Treat generated configuration as a draft that still needs review.
Talk to the generated agent, inspect it in the builder, correct any unsupported assumption, and publish only after simulated calls pass.
Composer keeps chat history so you can return to earlier work. Do not paste credentials or production customer data into a Composer message or attachment.
# Conversation flows
Source: https://docs.persistence.dev/build/conversation-flow
Build structured call logic with a visual flow.
Use a **Conversation Flow** when a call must follow explicit stages, branch on known conditions, or run actions in a controlled order.
If the task fits in one instruction set, start with a [single-prompt agent](/build/single-prompt) instead. Rebuild as a flow when the prompt has grown too long to follow, or when compliance needs a fixed sequence.
Conversation, Condition, Function, MCP, Extract, Transfer call, and End call.
Prompt, Condition (if), Else, and response-timed edges that move the call to the next node.
## Create a conversation flow
Open **Agents** and select **Create Agent**. Choose **Conversation Flow**, pick a template and use case, then name the agent.
The canvas begins at the start node. Choose whether the **Agent** or the **User** starts first.
* **Agent** starts first: the flow waits **Start after** a pause of 0–60 seconds, then speaks.
* **User** starts first: the flow waits until the caller speaks, or until the configured seconds of silence.
Use the canvas toolbar to add **Conversation**, **Condition**, **Function**, **MCP**, **Extract**, **Transfer call**, and **End call**. Rename each node so its responsibility is obvious.
Drag from the start node to the first conversation or action. Every live path must eventually reach **Transfer call** or **End call**.
Open a node to set its prompt or static message, function URL, MCP tool, extract variables, or transfer destination. Store secrets in [workspace secrets](/accounts/secrets), not in node text.
On conversation, condition, function, MCP, and extract nodes, add the edges that leave the node. Always include an **Else** (or **If failed**) path for values you did not anticipate.
Use **Preview agent** to walk the expected path, a failed tool call, a missing variable, and the fallback. Wait for pending saves to finish before you start a test or close the page.
When the draft handles those paths, select **Publish**. Publishing promotes this configuration for live calls and keeps earlier versions in the timeline.
The editor saves flow changes as you work.
## Agent vs Workflow
On a Conversation Flow agent, the switcher has two views:
| View | What you edit |
| - | - |
| **Agent** | The visual call script on this page. |
| **Workflow** | The [call lifecycle](/build/agent-workflow): knowledge, pre-call and post-call integrations, extraction, and webhooks. |
Open **Workflow** after the script is sketched. Do not duplicate lifecycle tools on every conversation node when they belong on the lifecycle canvas.
## Design guidance
* Keep each conversation node focused on one goal.
* Extract a value before a condition depends on it.
* Give functions explicit success and failure transitions. Function **Else** is labeled **If failed to call**.
* Keep transfer and end nodes terminal unless a later node is intentional.
* Test missing, malformed, and unexpected variable values before you publish.
Attach knowledge, webhooks, and pre-call or post-call integrations around this flow.
# Flow nodes
Source: https://docs.persistence.dev/build/conversation-flow/nodes
Configure every node type on a Persistence conversation flow.
The canvas toolbar adds nodes in this order: **Conversation**, **Condition**, **Function**, **MCP**, **Extract**, **Transfer call**, and **End call**. The start node is already on the canvas.
Click a node to expand it. Compact mode keeps unselected nodes collapsed.
## Start
The start node is the entry of every flow. It is not in the toolbar.
| Control | What it does |
| - | - |
| **Agent** starts first | The agent speaks after the pause. |
| **User** starts first | The caller speaks first. If they stay silent, the agent starts after the silence timeout. |
| Pause | 0–60 seconds, in 0.5 second steps. |
Connect start to the first conversation or action.
## Conversation
Use **Conversation** for one stage of the call: greet, qualify, collect a field, or confirm.
| Control | What it does |
| - | - |
| Node name | Shown on the canvas. Name the stage, not the whole agent. |
| **Speak exact message** | Off: the node prompt describes what the agent should say. On: the agent speaks the text as written. |
| Prompt field | Placeholder is **Describe what the agent should say...** or **Enter the exact message to speak...** |
| **Transitions** | Add **Prompt**, **Condition (if)**, **Else**, **Before user response**, or **After user response**. |
Keep one goal per conversation node. Move tool calls to **Function** or **MCP**, and stored values to **Extract**.
## Condition
**Condition** routes silently. It does not speak.
Add one or more rules, then choose **Any** or **All** of the following conditions match.
| Operator | Meaning |
| - | - |
| Equals / Does not equal | Exact comparison |
| Greater than / Less than / Greater than or equal / Less than or equal | Numeric comparison |
| Contains / Does not contain | Text containment |
| Is present / Does not exist | Existence checks |
Use a variable reference in the field. Always add an **Else** edge.
## Function
**Function** runs a configured HTTP function when the call enters the node, then branches on the result.
Open the node to set method, URL, headers, and body. Reference [secrets](/accounts/secrets) instead of pasting tokens.
Default transition labels:
| Transition | When it fires |
| - | - |
| **Prompt transition** | Model decides the next node from the response. |
| **If...** | A rule on the response or a variable matches. |
| **If failed to call** | The HTTP request did not succeed. |
Give every function a failure edge. Do not rely on the model to recover from a timeout.
## MCP
**MCP** runs a tool from a configured MCP server. Configure the server URL, headers, query parameters, timeout, and the tool this node may call.
Treat MCP like any other privileged integration: grant the smallest useful tool set and verify authentication and failure behavior. See [Actions and tools](/build/actions#mcp-servers).
## Extract
**Extract Variables** stores values from the conversation for later nodes, prompts, and post-call work.
Add variables with a name, description, and type:
| Type | Use it for |
| - | - |
| **text** | Names, IDs, free-form answers |
| **number** | Amounts, counts, scores |
| **boolean** | Yes/no flags |
| **date** | Dates the later steps must parse |
Transition labels:
| Transition | When it fires |
| - | - |
| **Prompt transition** | Extraction succeeded and the model routes onward. |
| **If...** | A rule on an extracted value matches. |
| **If failed** | The node could not extract the required values. |
Extract before a condition that depends on the value.
## Transfer call
**Transfer call** hands the live call to a destination. Configure routing in the node. A destination can be a phone number, SIP URI, or supported dynamic variable.
Warm vs cold transfer, SIP invite vs SIP refer, caller ID, ring duration, and custom SIP headers follow the same rules as the [Transfer call action](/build/actions). This node is terminal unless you intentionally continue after a failed transfer.
## End call
**End call** hangs up at a defined terminal point. Use it for successful completion, a hard stop, or a fallback that should not transfer.
Do not leave hanging conversation nodes with no transfer or end edge.
# Transitions
Source: https://docs.persistence.dev/build/conversation-flow/transitions
Control when a conversation flow moves from one node to the next.
Transitions are the edges out of a node. Add them from the node's **Transitions** menu, then drag the handle to the next node.
## Conversation transitions
| Menu label | Default edge label | Use it when |
| - | - | - |
| **Prompt** | Prompt transition | The model should decide that this stage is done. Describe the exit condition in the transition text. |
| **Condition (if)** | If... | A variable rule should route without waiting on the model. |
| **Else** | Else | No other transition matched. One Else per node. |
| **Before user response** | Before user response | Leave the node before the caller speaks. Exclusive with other response-timed edges. |
| **After user response** | After user response | Leave the node after the caller speaks. Exclusive with other response-timed edges. |
**Before user response** and **After user response** replace other transitions on that node. Use them for a short spoken line that must not wait on a prompt condition.
You can reorder **Prompt** and **If...** transitions of the same type. Evaluation follows that order.
## Condition rules
On **Condition (if)** transitions, and on **Condition** nodes, each rule has a field, an operator, and a value.
Combine rules with **Any** or **All** of the following conditions match.
| Operator | Label |
| - | - |
| `=` | Equals |
| `!=` | Does not equal |
| `>` | Greater than |
| `<` | Less than |
| `>=` | Greater than or equal |
| `<=` | Less than or equal |
| `contains` | Contains |
| `not_contains` | Does not contain |
| `exists` | Is present |
| `not_exists` | Does not exist |
Use a variable reference for the field. **Is present** and **Does not exist** do not take a comparison value.
Always provide **Else** (or **If failed** / **If failed to call** on extract and function nodes) for values you did not anticipate.
## Function and extract edges
| Node | Failure label |
| - | - |
| Function | **If failed to call** |
| Extract | **If failed** |
Wire those edges to a recovery conversation, a transfer, or **End call**. Do not leave them unconnected.
## Checklist before you publish
* Start connects to the first speaking or action node.
* Every conversation has at least one way out.
* Every function and extract node has a failure edge.
* Transfer and end nodes are the terminals you intend.
* Prompt transition text describes a real exit, not a restatement of the node prompt.
# Dynamic variables
Source: https://docs.persistence.dev/build/dynamic-variables
Personalize calls and carry structured values through prompts, flows, and actions.
Dynamic variables let one agent use call-specific values such as a caller name, account reference, campaign attribute, or action result.
## Variable sources
A value can come from:
* Defaults configured on the agent
* Test values entered for a preview
* Recipient metadata supplied for an outbound call or campaign
* An inbound webhook response
* An extraction or action result during the conversation
## Use variables safely
Give every required variable a default so the draft can be saved and previews remain predictable. Use a neutral fallback rather than a fake production value.
Reference variables only in fields that support them, such as instructions, greetings, action requests, and flow conditions. Keep credentials in [Secrets](/accounts/secrets), not dynamic variables.
## Test missing and malformed values
Run a preview with the expected value, an empty value, an unexpected value, and characters that need pronunciation. Confirm that the agent does not expose raw placeholders or take an unsafe branch.
# Knowledge bases
Source: https://docs.persistence.dev/build/knowledge-base
Create reusable knowledge collections and attach them to agents.
A knowledge base gives an agent information it can retrieve during a conversation. Use it for product information, policies, support material, or other content that should remain separate from the core prompt.
## Create a knowledge base
From the dashboard, open **Knowledge Base** and select **Add new**. Create a collection with a clear name and description. You can skip the first source and add documents later.
Select **Add source**. Choose one source per save:
| Source | Dashboard label | What it adds |
| - | - | - |
| Files | **Upload files** | PDF only, up to 5 MB, one file per save |
| Website | **Website** | A public HTTPS page, or that page and linked pages on the same domain |
| Import | **Import** | One item from a connected app, saved as a PDF |
Document processing moves through pending, crawling, processing, extracting, chunking, embedding, and **Ready**. Replace a document that reaches **Failed** or **Upload failed**.
Open the agent, select **Workflow**, and **Attach** a ready knowledge base. You can also attach from agent settings. An agent can use at most three knowledge bases.
Ask questions that should be answered, should combine nearby information, and should return a fallback because the answer is absent.
## Website sources
For **Website**, enter a public HTTPS URL (no login). Each site can be added once per knowledge base.
| Scope | What is ingested |
| - | - |
| **This page only** | The URL you entered |
| **This page and linked pages** | Follows links on the same domain |
A site-root URL crawls linked pages on that domain. A path URL ingests that page only.
### Auto-sync
Open the site and use **Auto-sync** to re-crawl on a schedule. Changes require your approval before going live.
* Interval is 7–60 days when auto-sync is on
* **Sync now** starts an on-demand crawl
* Review added, updated, and removed pages, then **Approve** or **Reject**
A sync already running or waiting for review blocks another run.
## Import from connected apps
Connect the app under **Integrations** first. One item from one app per save.
| App | What you select |
| - | - |
| Google Drive | PDFs, or Google Docs and Sheets converted to PDF |
| Microsoft OneDrive | PDFs, or supported Word, Excel, OpenDocument, and RTF files converted to PDF |
| Notion | Shared Notion pages |
| HubSpot | Knowledge-base articles |
| Confluence | Confluence Cloud pages |
HubSpot can **Keep updated** (auto-sync published articles, up to 1000, with review) or **Import once** (up to 20 articles, no ongoing sync).
## Keep retrieval reliable
Use **Retrieval Testing** to select a knowledge base, enter a representative query, adjust supported retrieval settings, and inspect the chunks the agent would receive.
* Keep each knowledge base focused on one domain.
* Remove obsolete or conflicting documents.
* Use descriptive headings and plain text in source documents.
* Put behavior rules in the agent instructions, not only in the knowledge documents.
* Re-test the agent after adding or deleting a document.
An agent can only use a knowledge base that has finished processing. Dimmed collections are still processing, empty, or failed.
Knowledge base names can contain up to 128 characters and descriptions up to 512 characters.
Deleting a knowledge base or a source document removes it from future retrieval. If the collection is attached to agents, the dashboard warns you before you remove a source.
Create, list, inspect, and delete knowledge bases through the public API.
# Models and voice pipelines
Source: https://docs.persistence.dev/build/models-and-pipelines
Choose the language, models, and processing pipeline for an agent.
An agent can use a cascaded voice pipeline or a realtime model, depending on the models available in your workspace.
## Cascaded pipeline
A cascaded pipeline uses separate components for speech-to-text, language generation, and text-to-speech. It gives you direct control over the transcription model, LLM, and voice.
## Realtime pipeline
A realtime pipeline uses a realtime model for the conversation. Some configurations use realtime audio directly, while others pair a realtime model with a selected text-to-speech voice.
## Choose a configuration
Select the conversation language first, then choose compatible models and a voice. Review the model's supported language, context window, and specialization in the builder.
Use a representative simulated call to compare:
* Transcription accuracy for names, numbers, and domain terms
* Voice quality and pronunciation
* Response latency
* Interruption and endpointing behavior
* Action execution and structured output
Change one model or speech setting at a time so you can attribute the result.
# Single-prompt agents
Source: https://docs.persistence.dev/build/single-prompt
Define an agent's behavior with one structured instruction set.
A single-prompt agent uses one instruction set throughout the conversation. It works well when the caller can move between topics and the agent should decide the next response from context.
## Write the instructions
Open a single-prompt agent and edit **Instructions**. A useful prompt separates the agent's role, goal, information, conversation rules, and fallback behavior.
```text theme={null}
Role
You are Acme's returns assistant.
Goal
Answer questions about the returns policy and collect the order number.
Policy
- Unused items can be returned within 30 days of delivery.
- Refunds return to the original payment method.
Conversation rules
- Ask one question at a time.
- Keep responses concise and natural for a phone call.
- Confirm important numbers before continuing.
Fallback
If the prompt or connected knowledge does not contain the answer, say you do not know.
```
## Configure the first message
Choose whether the AI or the caller starts the conversation. For an AI-first call, configure a static greeting or instructions for a generated greeting. You can also control the pause before the greeting and whether the caller may interrupt it.
Use dynamic variables in the greeting or instructions when the value changes per call. Define a default value so previews and calls still behave predictably when a value is missing.
## Connect data and actions
* Attach a [knowledge base](/build/knowledge-base) for information that is too large or changes too often to place in the prompt.
* Add [actions](/build/actions) when the agent needs to end or transfer a call, call an API, capture keypad input, extract values, or use a calendar.
* Configure post-call extraction when you need structured results after the conversation.
## Test the boundaries
Test the intended path and at least these failure paths:
* The caller asks for information that is not available.
* A required dynamic variable is missing.
* A tool returns an error or times out.
* The caller interrupts or stays silent.
* The caller gives an ambiguous date, phone number, or identifier.
Change one prompt section at a time, repeat the same test, and compare the result before publishing.
# Voices
Source: https://docs.persistence.dev/build/voices
Browse, preview, organize, and create voices for agents.
The voice library contains text-to-speech and speech-to-speech voices available to your workspace. Preview a voice with the same kind of sentence your agent will use before assigning it.
## Find a voice
Open **Voices** to search and filter the library. Available filters can include provider, language, accent, gender, age, traits, and voice category.
The library can include voices from Cartesia, ElevenLabs, MiniMax, OpenAI, and custom sources. Availability depends on the current workspace configuration.
## Preview consistently
Enter a specimen script to hear the same text across candidate voices. Test names, dates, amounts, product terms, and phone numbers that occur in the real workflow.
When you select a voice for an agent, also configure its language and speaking speed. Use the browser preview to check pacing and interruption behavior over a complete conversation.
## Voice collections
Use **Collections** to group voices for a team, brand, language, or use case. Adding a voice to a collection does not change the voice itself.
You can copy a voice ID from the library for a supported integration. Deleting a collection unlinks its voices; it does not delete the underlying voices.
## Clone a voice
The **Custom voices** page supports creating a Cartesia voice from a clean recording.
Use WAV, MP3, FLAC, OGG, M4A, or WebM audio smaller than 10 MB. Record one speaker in a quiet environment.
Confirm that you have permission to clone and use the speaker's voice.
Name the voice, upload or record the clip, create the clone, and preview it before using it in an agent.
Only clone a voice when you have the speaker's authorization. Voice consent is a product requirement and may also be a legal requirement in your jurisdiction.
You can edit the local name and description of a cloned voice. Deleting a custom voice permanently removes it, so update any dependent agent first.
# Workflows
Source: https://docs.persistence.dev/build/workflows
Choose the right Persistence workflow: a conversation flow, the agent lifecycle canvas, or an event-to-call workflow.
Persistence uses **workflow** in three related places. Pick the one that matches the job, then keep labels consistent with the dashboard.
Visual call script. Use it when the conversation must follow stages, branch on known conditions, or run tools in a fixed order.
Lifecycle canvas on the agent. Use it to attach knowledge, pre-call and post-call integrations, extraction, and webhooks around the call.
Workspace **Workflows**. Turn CRM, HTTP, or API events into outbound calls, delays, and follow-ups without rebuilding the agent prompt.
## When to use each
| You need to… | Open |
| - | - |
| Control what the agent says, which tool runs next, and when the call ends | **Agents** → create a **Conversation Flow** |
| Configure what happens before, during, and after every call for one agent | That agent → **Workflow** |
| Start calls from new leads, poll an API, or chain retries and follow-ups | Sidebar **Workflows** |
A Conversation Flow agent still has the **Agent** / **Workflow** switcher. The canvas is the call script. The **Workflow** view is the lifecycle around that script.
Event-to-call workflows sit beside campaigns. Use a campaign for a contact list you already have. Use **Workflows** when each call should start from an incoming event, a previous step's result, or a [Run via API](/deploy/workflows/api) request.
## Recommended path
Start with a [single-prompt agent](/build/single-prompt) unless the call must follow a fixed order. Switch to a [conversation flow](/build/conversation-flow) when stages, branches, or tool order matter.
Open **Workflow** on the agent. Attach knowledge bases, pre-call lookups, in-call tools, extraction, webhooks, and post-call integrations.
Preview in the browser, then run a [simulation](/test/simulation) suite before you route a number or campaign.
Assign a [phone number](/deploy/phone-numbers) or [number pool](/deploy/number-pools), or build an event-to-call [workflow](/deploy/workflows) that dials the published agent.
# API-triggered calls
Source: https://docs.persistence.dev/deploy/api-triggered-calls
Start outbound call batches with the Persistence API.
Use the public Calls API when your application needs to start outbound calls without creating a dashboard campaign.
An outbound batch identifies the caller ID, recipients, concurrency, and optional schedule. Recipient metadata becomes call-specific context for the agent. You can use the phone number's assigned outbound agent or provide an agent override supported by the request.
## Before you call the API
* Publish the agent version you intend to use.
* Configure and activate the caller phone number.
* Create an API key with the call permission required by the endpoint.
* Validate every destination and remove recipients covered by DND requirements.
* Choose a safe concurrency value for downstream actions and integrations.
The API accepts up to 1,000 recipients in one batch. Inspect rejected recipients in the response instead of assuming the complete batch was accepted.
Use the end-call endpoint only with the session identifier returned for the active call.
Review the batch-call and end-call contracts.
# Outbound campaigns
Source: https://docs.persistence.dev/deploy/campaigns
Configure, schedule, and control outbound calling campaigns.
Campaigns use an agent, a caller ID, and contact groups to place outbound calls. The caller ID can be one phone number or a [number pool](/deploy/number-pools).
## Create a campaign
Open **Campaigns**, select **New campaign**, and enter a campaign name. For outgoing calls, choose a **Number or pool**.
* A **Phone number** must have the intended outbound agent assigned.
* A **Number pool** rotates caller IDs across members of one connection. Pick a **Number selection strategy**: **Auto**, **Spread evenly**, or **Match recipient area code**.
Choose how many calls the campaign can make concurrently. Use a level your workflow and connected services can support.
Add at least one contact group. Include the DND groups that apply to the campaign.
Start immediately or choose a start date and time. For a scheduled window, set its start, end, and repeat days.
Review the reach analysis, then confirm the agent, caller ID, included and excluded audiences, schedule, and concurrency before you start or schedule the campaign. For a pool, confirm how many members are **Callable** versus **Inactive**.
Use an [event-to-call workflow](/deploy/workflows) instead of a campaign when each call should start from a CRM, HTTP, or API event rather than from a contact list you already uploaded.
## Control a campaign
The campaign list shows its current state. Depending on that state, you can start, pause, resume, or cancel the campaign. A campaign can move through states including draft, scheduled, running, paused, completed, cancelled, failed, or stopped.
Open campaign details to review total contacts and call progress, including pending, queued, dialing, answered, and failed attempts.
Edit a campaign only when its current state allows configuration changes. Pausing stops new campaign work while preserving progress; cancelling ends the campaign lifecycle.
Confirm consent, calling hours, DND groups, and other requirements that apply to your calls before launch.
# Connections
Source: https://docs.persistence.dev/deploy/connections
Connect provider accounts and SIP trunks that supply your phone numbers.
Open **Numbers** → **Connections**. Connections are provider accounts and SIP trunks that supply your numbers.
| Kind | Dashboard label | Who hosts telephony |
| - | - | - |
| Persistence-managed | **Platform-managed** | Persistence buys and bills the number. |
| Provider account | **Customer-managed** | Your Twilio, Telnyx, or other account. |
| SIP trunk | **Custom SIP** | Your own SIP infrastructure. |
## Add a connection
Go to **Numbers** → **Connections** and select **Add connection**.
**Connect provider** authorizes numbers from a provider and imports them in bulk. **Connect with SIP** is for telecom teams that already have trunks.
Use a name your team will recognize, for example `US contact center`.
After the connection exists, import numbers from the provider or add SIP numbers you already own.
Empty state: **No connections yet** / **Connect a provider account or SIP so your agents can use those numbers.**
Supported provider labels include **Twilio**, **Telnyx**, **Plivo**, **Exotel**, **Vobiz**, and **Custom SIP**. The options you see depend on the workspace.
## Connect with SIP
**SIP trunks route numbers you already own — connect one, then add its numbers.**
Configure:
* **Provider** — the telephony provider behind this trunk
* **SIP termination URL** — the destination your phone provider gave you
* Optional SIP username, password, from domain, and transport
* Inbound trunk details if this connection receives calls
Test inbound routing before you assign agents.
## Assign agents
Open a number on the connection to set **Incoming agent** and **Outgoing agent**. You can use the same agent for both.
Deactivating a number keeps the connection and the inventory row. Settings do not take effect until you reactivate it.
## Remove a connection
Deleting a connection removes its numbers from the workspace. Provider data is not deleted. Confirm no campaign, pool, or workflow still depends on those numbers.
Buy, import, and route individual numbers after the connection exists.
# Audiences and contacts
Source: https://docs.persistence.dev/deploy/contact-lists
Manage campaign audiences, individual contacts, and do-not-contact suppression lists.
Audiences define who a campaign may call. Contacts are the individual people or phone numbers in those audiences. Do-not-contact lists, also called DNC or DND groups, define numbers that must not receive calls.
## Create a group
Open **Audiences** or **Contacts**, start an upload, enter a name, and choose whether the file supplies callable contacts or suppression entries.
Download the template when you need the expected file shape. Upload one `.csv`, `.xlsx`, or `.xls` file up to 21 MB, or import contacts from a connected HubSpot or Salesforce account.
Review the valid, invalid, and duplicate number counts. Persistence excludes invalid and duplicate numbers when you save the group.
Save the processed group, then select it when you configure a campaign audience.
Use a DNC or DND suppression list for numbers that must not receive campaign calls. Review suppression coverage before every launch.
You can replace the uploaded list or edit the group name and type. Check campaigns that use the group before you delete it.
Open a group to review entries and their metadata. A saved group can be ready while individual numbers remain active or blocked.
# Inbound webhooks
Source: https://docs.persistence.dev/deploy/inbound-webhooks
Provide call-specific variables or reject an inbound call before the agent starts.
An inbound webhook lets your application make a decision when a configured phone number receives a call. The runtime invokes the endpoint before the agent conversation starts.
Use the webhook to return dynamic variables needed by the greeting, prompt, flow, or actions. Your endpoint can also reject the call when your application decides it should not proceed.
## Configure the endpoint
Open a phone number's inbound configuration, enter an HTTPS webhook URL, and use **Test** to inspect the response before enabling live traffic.
Design the endpoint to:
* Respond within the configured timeout
* Return dynamic variables as string-compatible values
* Avoid returning credentials or unnecessary personal data
* Handle retries without causing duplicate side effects
* Fail safely when the caller or account cannot be resolved
Keep authorization values in secrets or the supported phone configuration. Monitor webhook failures alongside the related conversation or observability session.
# Number pools
Source: https://docs.persistence.dev/deploy/number-pools
Group phone numbers so outbound calls rotate across a connection.
Open **Numbers** → **Number pools**. Pools group numbers for rotating outgoing calls. A campaign or an event-to-call [workflow](/deploy/workflows) can dial from a pool instead of one caller ID.
A phone number can belong to one pool at a time. A pool can only contain numbers from a single [connection](/deploy/connections).
## Create a pool
Go to **Numbers** → **Number pools** and select **Create pool**.
Use a name your team will recognize, for example `collections pool`.
Select the provider account or SIP trunk that owns the numbers. Only that connection's numbers can join the pool.
Choose at least one number. Status **Available · unassigned** means the number is in the space and not already in this pool.
Empty state: **No pools yet** / **Pools group numbers for rotating outgoing calls.**
## Manage members
Open a pool to **Add numbers**, **Remove from pool**, or **Rename pool**.
Removing a number from a pool keeps the number in the workspace. Deleting a pool does the same: numbers stay, they just leave the pool.
You cannot add a number that already belongs to another pool until you move it. The public API can [move a member](/api-reference/number-pools/move-member) from its current pool into the target pool.
## Use a pool on outbound work
| Surface | How you select the pool |
| - | - |
| [Campaigns](/deploy/campaigns) | Choose **Number pool**, then a **Number selection strategy**: **Auto**, **Spread evenly**, or **Match recipient area code**. |
| [Workflows](/deploy/workflows) | On an **Action** node, set **Dial from** to **Number pool**, then pick **Automatic**, **Even distribution**, or **Local area match**. |
| [API-triggered calls](/deploy/api-triggered-calls) | Pass the pool id on the outbound batch instead of a single `from` number. |
Validate outbound trunks before you send production traffic. The API reports valid and invalid member counts. See [Number Pools API](/api-reference/number-pools/overview).
Confirm every member is active, ready, and allowed to call the destination countries before you assign the pool.
# Phone numbers
Source: https://docs.persistence.dev/deploy/phone-numbers
Buy or import a phone number and connect it to an agent.
Phone numbers connect published agents to inbound and outbound calls. The **Numbers** workspace has three tabs: **Numbers**, **Number pools**, and **Connections**.
Rotate outgoing calls across numbers from one connection.
Provider accounts and SIP trunks that supply your numbers.
## Add a number
Open **Numbers** and select **Add number**. Choose the path that matches your phone setup:
Search available inventory and buy a number billed through Persistence.
Connect a provider, then import numbers that stay in that account.
Connect a SIP trunk, then add numbers you already own.
**Buy new number** can be platform-managed or placed on a customer-managed connection. Search by country, local or toll-free, and area code or city.
**Import from provider** uses **Connect provider**. Numbers stay in your Twilio, Telnyx, or other account — Persistence just uses them.
**Add manually** uses **Connect with SIP**. Configure the outbound termination URL, optional credentials, transport, and inbound trunk information supplied by your provider.
## Configure routing
Open a number to assign its inbound and outbound agents. Publish the agent version you want to use before routing live calls to it.
You can also configure supported controls for the number, including:
* **Receive incoming calls** and **Choose an incoming agent**
* **Make outgoing calls** and **Choose an outgoing agent**
* **Same as outgoing agent** when both directions should use one agent
* Allowed inbound countries and allowed outbound countries
* SIP settings for a manually connected number
* An inbound webhook for events that need to reach your application
For custom SIP, test the inbound webhook before saving it.
The **Number pool** column shows **Not in a pool** until you add the number to a pool. A number can belong to one pool at a time.
Verify the selected agent, phone number, and country restrictions before you enable production traffic.
## Remove a number
Unassign agents and confirm that no campaign, pool, or event-to-call workflow depends on the number before you remove it. Removing a number can stop inbound or outbound calls that use it.
You can deactivate a number temporarily and activate it again later. A deactivated number is unavailable for calls. Settings do not take effect until you reactivate it.
# Event-to-call workflows
Source: https://docs.persistence.dev/deploy/workflows
Turn incoming events into outbound calls with a fixed workflow.
Open **Workflows** in the sidebar. This workspace turns incoming events into calls with a simple, fixed workflow. It is separate from the [agent workflow](/build/agent-workflow) canvas and from a [conversation flow](/build/conversation-flow).
The page has two tabs: **Workflows** and **Runs**.
Empty state: **Build your first workflow** / **Choose a trigger, map your data, and decide when your agent should call.**
Start, Action, Data, Mapper, Condition, Delay, Iterator, and End.
Inspect queued, live, blocked, and failed runs after you publish.
Start, inspect, cancel, or wake a published workflow with a workspace API key.
## Create a workflow
In the dashboard, open **Workflows** and select **Create workflow**.
Use a name that identifies the trigger and the call, for example `Call new qualified leads`. The empty editor starts as **Untitled workflow**.
On the start node, choose how this workflow runs:
| Start | When it fires |
| - | - |
| **HTTP polling** | Persistence sends GET requests at the interval you set. New data in the JSON response starts a run. |
| **CRM polling** | New or updated records from a connected CRM start a run. |
| **Manual** | You start a run with input data when execution is available. |
| **After workflow** | This workflow starts when a linked workflow in the series finishes. |
| **Run via API** | After you publish, your system sends a POST to start a run. |
Optional **Meta (optional)** is JSON available when the run starts. Use it for values that are not coming from a poll.
Add an **Action** node. Choose the published **Agent**, then **Dial from** a **Phone number** or a **Number pool**. The recipient comes from the mapped `phone_number` field.
Add a **Mapper** if the trigger payload needs to become agent variables. Type two opening braces to insert fields.
Use **Condition** to branch, **Delay** to wait, **Iterator** to repeat for each item in a list, and further **Action** nodes for retries or a second call.
Select **Save draft**, then **Publish**. Polling and **Run via API** requests are rejected until the workflow is **Live**.
## HTTP polling
**HTTP polling** checks your endpoint with GET at the interval you set. New data in the JSON response starts a run.
On the start node, choose **HTTP polling** and enter **Your endpoint URL**.
Choose how often Persistence should check. The editor summarizes it as **Every N minutes**.
Add a **Mapper** so `phone_number` and any agent variables come from the JSON.
Keep tokens in [workspace secrets](/accounts/secrets). The start node can also send a failure webhook when a run fails or is cancelled.
## CRM polling
**CRM polling** uses new or updated records from a connected CRM as the input for each call.
Connect the account under **Integrations** first. See [CRM](/integrations/crm).
On the start node, set **Connected account**, **Record type**, and **Trigger when**.
**Only changes after activation** skips existing records. **Include existing records** includes them on the first poll, then watches for the selected event.
Supported record labels include **Contact**, **Lead**, **Deal**, **Company**, **Ticket**, **Opportunity**, **Account**, and **Case**. The options you see depend on the connected CRM.
Trigger when: **New** record, record **updated**, or **New or updated**.
## Dial from a number or a pool
On each **Action** node:
| Dial from | Behavior |
| - | - |
| **Phone number** | Every call uses that caller ID. |
| **Number pool** | Calls rotate across members of one connection. |
Pool selection strategy:
| Strategy | Behavior |
| - | - |
| **Automatic** | Choose an available number from the pool. |
| **Even distribution** | Distribute calls evenly across the pool. |
| **Local area match** | Prefer a number matching the recipient's area code. |
Create the pool under **Number pools** first. A pool can only contain numbers from a single connection. See [Number pools](/deploy/number-pools).
## Call hours, skips, retries, and timeouts
Open **Action settings** on a call step to set:
* **Call hours** (for example `09:00-21:00` or a variable such as `store_hours`) and what to do **Outside these hours**: **Skip the call** (`call.skipped = true`) or **Wait and call when the hours open**.
* **Skip if called within (minutes)**, counted from **All workflows in this workspace** or **Only this workflow**. `0` turns this off.
* **Give up waiting for the call after (minutes)**. Counted from dialling, at least 10. `0` waits up to 24 hours. If the call never reports ending, the run fails and the workflow failure alert fires.
* Retries for **Busy / unanswered**, **Declined**, **Failed**, and **Voicemail / automated menu**, with exponential backoff.
* Optional **Before retrying**: refresh a GET **Data** step and only redial while rules still match. Otherwise the call continues as if no retry was set, with `call.retry_skipped = true`.
Set retries to 0 to disable a reason. Up to 3 retries per reason, excluding the initial call.
## Link another workflow
Use **Link another workflow** to run a second workflow after this one in the same series. Give the series a **Series name**. The later workflow's start mode is **After workflow**.
A common series is a first call, then a manager follow-up, then a support follow-up. Each later workflow starts when the previous one finishes, unless an **End** node stops the series.
**End** can **End this workflow** or **End this and all later workflows**.
You can **Reorder workflows** in the series. The first workflow is required.
## Run via API
After the workflow is published, open **Run via API**.
Create a workspace API key with the `workflow run` scope. Add `workflow read` if you will check status.
POST to `/v1/workflows/{workflowId}/run`. Send the key in the `X-API-Key` header and a unique `Idempotency-Key` per run.
Put input in `context`. A `phone_number` field is then available to later steps as `{{phone_number}}`.
Add a `Concurrency-Key` (for example a store id) to allow only one active run per key. Other starts return 409. Optional `Cooldown-Minutes` refuses a new run for that key until this many minutes after the last run ended.
Publish first. The panel states that requests are rejected until the workflow is live.
See [Run via API](/deploy/workflows/api) and the [Workflows API](/api-reference/workflows/overview).
# Run a workflow via API
Source: https://docs.persistence.dev/deploy/workflows/api
Start, inspect, cancel, or wake a published event-to-call workflow.
Open a published [workflow](/deploy/workflows) and expand **Run via API**. The panel shows the endpoint for that workflow.
Requests are rejected until the workflow is **Live**.
## How to connect
Create an API key for this workspace with the `workflow run` scope. Add `workflow read` to check status. See [API keys](/accounts/api-keys).
Send the key in the `X-API-Key` header, with a unique `Idempotency-Key` per run.
Put input in `context`. For example `phone_number` becomes `{{phone_number}}` in later steps.
Runs start in parallel. Add a `Concurrency-Key` (for example a store id) to allow only one active run per key; others return 409.
Optional `Cooldown-Minutes` (0–10080) refuses a new run for that key until this many minutes after its last run ended.
## Start a run
`POST https://api.persistence.dev/v1/workflows/{workflowId}/run`
```bash theme={null}
curl -X POST https://api.persistence.dev/v1/workflows/{workflowId}/run \
-H "X-API-Key: " \
-H "Idempotency-Key: " \
-H "Concurrency-Key: " \
-H "Content-Type: application/json" \
-d '{"context": {"phone_number": "+14155550123"}}'
```
A successful start returns `202` with a `runId` in `data.id`.
## Check status
`GET https://api.persistence.dev/v1/workflows/{workflowId}/runs/{runId}`
```bash theme={null}
curl https://api.persistence.dev/v1/workflows/{workflowId}/runs/{runId} \
-H "X-API-Key: "
```
Requires the `workflow read` scope. Status values match the **Runs** tab: `queued`, `running`, `waiting_call`, `waiting_schedule`, `waiting_automation`, `blocked`, `completed`, `failed`, `cancelled`.
## Cancel
Cancel one run:
`POST /v1/workflows/{workflowId}/runs/{runId}/cancel`
Cancel every active run, or only those that share concurrency keys:
`POST /v1/workflows/{workflowId}/runs/cancel`
```bash theme={null}
curl -X POST https://api.persistence.dev/v1/workflows/{workflowId}/runs/cancel \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"all": true}'
```
Or send `"concurrencyKeys": ["store-10"]` instead of `"all": true`. You can include an optional `reason`. Workflows with **Send the failure webhook when a run is cancelled** notify that webhook for each cancelled run.
## Wake a waiting run
If a run is sleeping at a **Delay** (or another wait step) for a promised time, you can continue it now. Runs pausing between call retries are not woken.
`POST /v1/workflows/{workflowId}/runs/wake`
```bash theme={null}
curl -X POST https://api.persistence.dev/v1/workflows/{workflowId}/runs/wake \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"concurrencyKeys": ["store-10"]}'
```
Send 1–500 concurrency keys. The response lists the run ids that woke.
Request and response contracts for start, status, cancel, and wake.
# Workflow nodes
Source: https://docs.persistence.dev/deploy/workflows/nodes
Nodes you can add to an event-to-call workflow.
Open **Add node** on an event-to-call [workflow](/deploy/workflows). Each item in the menu matches the dashboard label.
| Node | Menu description |
| - | - |
| **Action** | Start a call with an agent. |
| **Data** | Fetch data from an API. |
| **Mapper** | Map data to reusable variables. |
| **Condition** | Branch based on your conditions. |
| **Delay** | Wait for a duration or a scheduled time. |
| **Iterator** | Repeat steps for each item in a list. |
| **End** | End an iteration, workflow, or series. |
The graph also has a **Start** node. You do not add it from the menu.
## Start
**Begin this workflow.** Choose the trigger, optional **Meta (optional)** JSON available when the run starts, and the failure webhook if you need one.
## Action
**Start a call using your selected agent and phone number or pool.**
Configure:
* **Call name**
* **Agent**
* **Dial from**: **Phone number** or **Number pool**, plus a pool strategy when you use a pool
* **Dynamic variables** passed into the agent
* **Call hours**, skip rules, **Give up waiting for the call after (minutes)**, and **Retry settings**
* **Transition** states such as **Fulfilled** and **Not fulfilled** for the next step
A skipped call sets `call.skipped = true`. A retry that did not redial after a refresh sets `call.retry_skipped = true`.
## Data
**Fetch data to use in the next steps.**
The step sends GET to your endpoint when it runs. Enter a valid HTTP URL. Use [secrets](/accounts/secrets) for credentials.
**Response data path** can be empty to use the entire response. Use list matching to pick one item, for example `data[id=record_id]` where `record_id` is a mapped variable.
Enable **Continue with empty data if nothing matches** when a missing record should not fail the run. Map fields in a **Mapper** node after this step.
You can also **Fetch from CRM** when a CRM account is connected. Choose the account and a supported record type.
## Mapper
Map source fields onto names later steps can use. Use unique names (letters, numbers, and underscores) and a source for every mapping. Inside an iterator, refer to the current row as `item` and `iterator.index`.
## Condition
Route to **Fulfilled** or **Not fulfilled**.
Combine rules with **Any** or **All**. Operators include equals, contains, exists, numeric comparisons, **Contains any of**, and **Contains none of**.
**Fulfilled** and **Not fulfilled** must connect to different nodes.
## Delay
**Wait for a duration or until a scheduled time.**
| Mode | What to enter |
| - | - |
| Duration | Seconds, minutes, hours, or days |
| **Date and time** | A timestamp, or **Set dynamically** with a variable |
| Spoken times | Add a time filter on the variable, with a timezone such as Asia/Kolkata |
If the time cannot be read, choose **Fail the run** or **Continue without waiting** (`wait_skipped = true`).
## Iterator
**Run the nodes inside once for each item in a list.**
Set **List to iterate** to a list variable such as `meta.leads`. Iterators can only sit at the workflow level, not nested inside another iterator. Add at least one node inside. Deleting an iterator also removes the nodes inside it.
**End** inside a loop can **End this iteration** without stopping the rest of the list.
## End
**End this workflow** or **End this and all later workflows** in the series.
Connect every branch that should stop. Do not leave **Action** nodes with **Not connected** transitions.
# Workflow runs
Source: https://docs.persistence.dev/deploy/workflows/runs
Inspect and cancel event-to-call workflow runs.
Open **Workflows** → **Runs**. Runs appear here once a published workflow starts.
## Find a run
Search runs, then filter by status and workflow.
| Filter | Label |
| - | - |
| All statuses | **All statuses** |
| All workflows | **All workflows** |
Empty states: **No runs yet** / **Runs appear here once a published workflow starts.** or **No matching runs**.
## Run status
| Status | Meaning |
| - | - |
| Queued | Waiting to start |
| Running | A step is in progress |
| On a call | Waiting on a live call |
| Scheduled | Waiting on a delay or schedule |
| Waiting on linked workflow | Blocked on another workflow in the series |
| Blocked | The outcome of a step is unknown |
| Completed | Finished successfully |
| Failed | A step failed |
| Cancelled | Stopped by you |
A run is **Blocked** when the outcome of a step is unknown, for example a call that may or may not have been placed. **Fail blocked runs after (minutes)** fails those runs and frees the slot. `0` waits until someone cancels the run.
## Inspect steps
Open a run to see each step's input, output, and status.
| Step status | Label |
| - | - |
| Completed | Completed |
| Failed | Failed |
| In progress | In progress |
| Cancelled | Cancelled |
| Blocked | Blocked |
Use **Jump to issue** to open the failing step. **View JSON** shows the recorded payload. Oversized output is truncated in the dashboard.
Condition steps show **Condition true → Fulfilled branch** or **Condition false → Not fulfilled branch**.
## Cancel
* **Cancel run** stops one run.
* **Cancel active runs** stops every active run for that workflow. Press again to confirm.
Enable **Send the failure webhook when a run is cancelled** if the start-node webhook should fire on cancel.
You can also cancel from the [Workflows API](/api-reference/workflows/cancel-runs) by concurrency key or for every active run.
## Common step errors
| Title | What to fix |
| - | - |
| Secret missing | The named workspace secret could not be found. |
| Variable missing | The variable had no value when the step ran. |
| Step failed | Open **Details** for the HTTP status, call attempts, or validation error. |
# Persistence documentation
Source: https://docs.persistence.dev/index
Put phone conversations on autopilot.
## Welcome to Persistence
Persistence is the platform for voice AI agents that handle phone calls the way a person would — and finish the task instead of passing it along.
Take one call workflow from prompt to live phone number.
How build, test, deploy, and performance fit together.
Choose a single prompt or visual conversation flow.
Call script, call lifecycle, or event-to-call sequences.
Connect phone numbers, pools, and outbound campaigns.
## What Persistence does
Define an agent in a single prompt or a visual flow. Connect a knowledge base and define actions. Test with simulated calls before launch, then connect phone calls and review live performance.
Define the agent's behavior, then connect the knowledge, actions, and [call lifecycle](/build/agent-workflow) it needs.
Run simulated calls across expected paths, edge cases, and accents. Use [test plans](/test/simulation) before you publish.
Provision a number, use a SIP trunk, rotate caller IDs with a [number pool](/deploy/number-pools), or trigger calls through the API or an [event-to-call workflow](/deploy/workflows).
Review containment, conversion, sentiment, and latency.
## What you can build
Answer from your knowledge base and escalate when needed.
Call leads in seconds, qualify them, push results onward.
Check availability, book, and reschedule on the call.
Run reminders and follow-ups at scale from a contact list.
# Calendar integrations
Source: https://docs.persistence.dev/integrations/calendars
Connect Cal.com or Calendly and give an agent selected booking actions.
Persistence supports Cal.com and Calendly connections for calendar actions.
## Connect a calendar
For Cal.com, use an API key. For Calendly, use a personal access token. Store reusable credentials in [Secrets](/accounts/secrets).
Open **Integrations**, choose the provider, name the account, and enter or reference its credential.
In the agent's calendar action, select the connection and event type.
Enable at least one of **Check availability**, **Book appointment**, **Cancel booking**, or **Reschedule booking**.
Describe when the agent should use the calendar and what it must confirm before making a change. Test time zones, unavailable slots, ambiguous dates, and provider errors.
# CRM integrations
Source: https://docs.persistence.dev/integrations/crm
Connect HubSpot or Salesforce for CRM tools and contact imports.
Persistence supports HubSpot and Salesforce connections.
Connected CRM accounts can provide agent tools and contact-group imports. Available tools can include lead, contact, case, deal, query, and task operations, depending on the provider.
## Import campaign contacts
Open **Integrations**, choose HubSpot or Salesforce, and complete the provider authorization.
Open **Contacts**, select **New contact group**, and choose the connected CRM as the import source.
Confirm the imported, valid, invalid, and duplicate counts before saving the group.
Grant only the CRM permissions needed by the workflow. Test create or update actions with non-production records before enabling them for live calls.
# Integrations
Source: https://docs.persistence.dev/integrations/overview
Connect calendar, CRM, and messaging services to Persistence.
Integrations let agents use approved external services and let workspaces import campaign contacts.
The catalog can show additional services as they become available to a workspace. Configure only integrations supported by your current workspace and release channel.
## Available integration types
| Integration | Use in Persistence |
| - | - |
| [Calendly](/integrations/calendars) | Check availability and manage bookings |
| [Cal.com](/integrations/calendars) | Check availability and manage bookings |
| [HubSpot](/integrations/crm) | Use CRM tools and import contacts |
| [Salesforce](/integrations/crm) | Use CRM tools and import contacts |
| [Slack](/integrations/slack) | Send messages to selected workspace channels |
## Connect an account
Choose the service you want to connect and start its connection flow.
Complete the provider's authorization or enter the credential requested by the connection form.
Choose only the accounts, calendars, or Slack channels that the workflow needs when those controls are available.
Add the related action to an agent or select the CRM while creating a contact group.
Treat provider credentials as secrets. Store reusable values in the workspace secret controls instead of placing them in prompts or documentation.
Test the connected action in a simulated call before publishing the agent.
Disconnecting an account makes its tools unavailable to agents immediately. Review dependent agents and contact-import workflows first.
# Slack integration
Source: https://docs.persistence.dev/integrations/slack
Connect Slack and restrict which channels an agent may use.
The Slack integration lets an agent send messages to approved channels.
## Connect Slack
Open **Integrations**, choose **Slack**, provide a unique account name, and complete the Bot OAuth connection.
Choose the public or private channels the bot may use. Do not approve channels unrelated to the workflow.
Make the connected **Send message** tool available only to agents that require it.
Run a simulated call and verify the destination, content, failure behavior, and absence of sensitive data.
Disconnecting the account immediately stops agents from using its tools. Review agent dependencies before disconnecting or removing channel access.
# Alerts and activity
Source: https://docs.persistence.dev/monitor/alerts
Monitor incidents and audit important workspace changes.
Alerts identify operational conditions that need attention. Activity records important workspace and system events so a team can understand what changed and who or what initiated it.
## Review an alert
Use severity, status, date, resource type, and saved filters to narrow the incident list. Open an incident to review the affected resource, start time, acknowledgement, resolution, and related activity.
Create alert rules only for conditions that have a clear owner and response. Snooze an alert when investigation is already in progress, and unsnooze it when notifications should resume.
## Audit activity
Use **Activity** to review events such as agent configuration changes, campaign starts or failures, integration changes, role changes, import failures, and usage or cost events. Pair an activity entry with the relevant agent version, campaign, conversation, or integration record when investigating.
An activity record explains that an event occurred. It does not replace provider logs, conversation details, or an incident-response record when deeper evidence is required.
# Analytics and observability
Source: https://docs.persistence.dev/monitor/analytics
Track outcomes, latency, errors, and tool performance across calls.
Analytics summarizes call activity and performance. Observability helps you isolate where time and errors occur inside a session.
## Review analytics
Use **Analytics** to track live calls, total calls, P50 and P90 duration, answer and pickup rates, transfer rate, average cost, P50 and P90 latency, call volume, dispositions, and duration distribution. Apply the available date and agent filters before comparing periods.
Pair aggregate metrics with conversation reviews. A trend shows where to investigate; the transcript and call details show what happened.
## Diagnose with observability
Open **Observability** to review:
* Voice-to-voice latency at the 95th percentile
* Greeting latency at the 95th percentile
* Tool success rate and error rate
* Voice-to-voice latency by component and model
* Session end-reason distribution
* Tool execution count, success rate, and duration
The sessions view lets you filter sessions, open a session, and inspect its greeting, interruptions, tools, errors, and turn waterfall.
## Improve safely
Choose a measurable target, change one agent component, test the draft, and publish a new version. Compare the same filters and time window after enough calls have completed.
# Conversations
Source: https://docs.persistence.dev/monitor/conversations
Inspect completed and live calls to understand agent behavior.
Use **Conversations** to investigate individual calls and find patterns that require an agent change.
## Review a call
Filter conversations by time range, agent, campaign, collection, tag, status, channel, end result, or user sentiment. Open a conversation to review its summary, call details, transcription, activity log, and recording when available.
Focus on:
* Whether the call reached its intended result
* The end reason and call duration
* What the caller said before a wrong or delayed response
* Which actions ran and whether they succeeded
* User sentiment and end-to-end latency
End results distinguish connected hangups, transfers, voicemail, IVR detection, no answer, and failures. Activity logs show events such as participants joining or leaving, transfers, voicemail detection, and call ending.
## Monitor active calls
Open **Live calls** to see calls that are currently in progress. Use live monitoring for operational awareness; use the completed conversation record for a full investigation after the call ends.
## Turn findings into changes
Group similar failures before changing an agent. Update one part of the draft—prompt, flow, knowledge, action, or speech setting—then run simulated calls and publish a new version.
Use [collections and exports](/monitor/exports-and-collections) when you need a repeatable review cohort or a filtered data extract.
# Exports and collections
Source: https://docs.persistence.dev/monitor/exports-and-collections
Organize conversation reviews and export filtered results.
Collections group related conversations for review. Exports let you take a filtered result set into an approved analysis workflow.
## Use collections
Create a collection with a descriptive name, then add or remove conversations from it. Use collections for a bounded review set such as a campaign cohort, escalation sample, or regression test.
Deleting a collection unlinks its conversations; it does not describe a deletion of the conversations themselves.
## Export conversations
Apply filters for time range, agent, campaign, collection, tag, status, channel, end result, or sentiment, then select **Export**. Persistence starts the export using the active filters.
Review exported data under your organization's storage, access, and retention requirements. Apply PII controls before sharing or moving call data.
# Platform concepts
Source: https://docs.persistence.dev/platform
How build, test, deploy, and performance fit together.
Persistence brings agent design, pre-launch simulation, phone connectivity, and live performance insights into one workflow.
## Build
Define the conversation, the knowledge the agent can use, and the actions it can take.
Design a structured conversation as a visual flow.
Define the agent behavior in one prompt.
Attach knowledge, integrations, extraction, and webhooks around the call.
Define what the agent can do while handling a call.
## Test
Challenge the agent before it handles live phone calls.
Run call simulations before launch.
Group test scenarios, run them against a version, and review a run report.
## Deploy
Connect the agent to phone calls through the path that fits your setup.
Provision a number and activate the agent.
Rotate outgoing caller IDs across numbers from one connection.
Start calls from CRM, HTTP, or API events.
Run outbound calling at scale from a contact list.
## Improve performance
Use live call metrics and session details to decide what to refine next.
Track whether calls achieve the intended outcome.
Review caller sentiment and response latency.
Inspect transcripts, recordings, outcomes, and call details.
## API information
Review the API information included in the current public documentation.
# Quickstart
Source: https://docs.persistence.dev/quickstart
Build, test, publish, and improve your first AI voice agent in the Persistence dashboard.
Build a simple returns assistant, test it in your browser, and publish it from the Persistence dashboard. You can adapt the example to your own phone-call workflow.
**Estimated time:** 30 minutes
## Before you begin
* Get access to the [Persistence dashboard](https://dashboard.persistence.dev).
* Use a browser with microphone access for the web preview.
* Choose one call type and one result for your first agent.
This example answers questions about a fictional returns policy. It does not use external knowledge or actions, so you can test the core conversation first.
## Build the agent
In the dashboard, open **Agents** and select **Create Agent**. Choose **Single Prompt**, select a template and use case, then review the agent details.
Enter:
* **Agent name:** `Returns assistant`
* **Description:** `Answers questions about the Acme returns policy`
Complete the creation flow. The agent builder opens and marks the new agent as a draft.
Select the language and voice that fit your callers. Keep the default model for this first test.
Replace the content in **Instructions** with this single prompt:
```text theme={null}
You are Acme's returns assistant.
Goal
Answer callers' questions about the returns policy.
Policy
- Acme accepts unused items within 30 days of delivery.
- The caller needs the order number and the email used for the order.
- Refunds return to the original payment method.
Rules
- Keep each response under 30 words.
- Ask one question at a time.
- Use only the policy in this prompt.
- If the policy does not answer a question, say you do not know.
```
This prompt gives the agent one goal, the information it can use, and a fallback for questions it cannot answer.
Skip this step for the example, or extend your own agent:
* Open **Knowledge base** to add documents the agent can reference.
* Open **Functions** to add the actions your workflow needs.
Add only the knowledge and actions required for the call's intended result.
## Test the conversation
In **Preview agent**, select **Web**, then select **Start talking**. Allow microphone access when your browser asks.
Test the expected path:
> I received an unused item 10 days ago. Can I return it?
The agent should confirm that the item is within the 30-day return window and explain what information the caller needs.
Then test a question outside the supplied policy:
> Can I exchange the item at a retail store?
The agent should say that it does not know. It should not invent an exchange policy.
Run more simulated calls for your real workflow. Include edge cases and a range of accents. Update the prompt, knowledge, or actions when a test exposes a problem, then test again.
## Publish and deploy
When the agent handles the test calls as expected, select **Publish** in the agent builder.
Publishing makes the current agent configuration available for live calls. Connect it through the deployment path approved for your setup:
* Provision a phone number.
* Use a SIP trunk.
* Trigger calls through the API.
The controls available for these paths can differ by workspace. Confirm the destination number and agent before you route production calls.
## Improve the agent
After the agent handles calls, open **Conversations** and filter the results by agent. Review the call status, end result, end-to-end latency, and user sentiment.
Use these results to assess containment and conversion. Change one part of the prompt, knowledge, or actions at a time. Test the draft again before you publish the next version.
## Troubleshooting
### Create is unavailable
Select an agent type and complete both **Agent name** and **Description**.
### The web preview cannot hear you
Allow microphone access in your browser and confirm that the browser uses the correct input device. Restart the preview after you change the permission or device.
### The agent invents an answer
Add a direct fallback instruction, such as `If the provided information does not answer a question, say you do not know.` Add the missing information to the prompt or knowledge base only after you verify it.
### Your deployment path is not visible
Deployment options depend on your phone setup and workspace. Use the supported phone number, SIP trunk, or API-triggered path available to you.
## Next steps
See how build, test, deploy, and improve fit together.
Automate supported Persistence workflows.
# File and workflow limits
Source: https://docs.persistence.dev/reference/limits
Verified limits enforced by the dashboard and public API.
These limits are enforced by the current product interfaces or public API.
| Resource | Limit |
| - | - |
| Agent import package | One `.pa` file up to 2 MB |
| Knowledge bases per agent | Up to 3 ready collections |
| Knowledge base document | PDF only, up to 5 MB per file; one source per save |
| Knowledge base name | Up to 128 characters |
| Knowledge base description | Up to 512 characters |
| Website auto-sync interval | 7–60 days |
| HubSpot keep-updated sync | Up to 1000 published articles |
| HubSpot import once | Up to 20 articles |
| Voice-cloning clip | WAV, MP3, FLAC, OGG, M4A, or WebM under 10 MB |
| Contact-group upload | One `.csv`, `.xlsx`, or `.xls` file up to 21 MB |
| API outbound batch | Up to 1,000 recipients |
| Shared agent link | Expires after 1, 3, 7, or 15 days; up to 20 opens |
| Agent API name | Up to 64 characters |
| Agent API description | Up to 512 characters |
| Workspace API keys | One active key at a time |
| Conversation flow start pause | 0–60 seconds |
| Workflow action retries | Up to 3 per reason, excluding the initial call |
| Workflow graph | Up to 500 nodes and 100 business steps; definition at most 1 MiB |
| Workflow name | 1–200 characters |
| Workflow API cooldown | 0–10080 minutes after a concurrency key's last run |
| Workflow wake or cancel-by-key | 1–500 concurrency keys per request |
| Workflow call wait timeout | At least 10 minutes, or `0` to wait up to 24 hours |
| Number pool membership | One pool per number; one connection per pool |
| Simulation run report | Automatic after a run with at least 5 distinct scenarios |
Limits can differ from provider quotas and workspace permissions. A campaign's concurrency cannot exceed the workspace concurrency available to it.
# Go-live checklist
Source: https://docs.persistence.dev/test/go-live-checklist
Verify an agent, its dependencies, and its deployment before production traffic.
## Agent
* The prompt or every flow branch has a clear goal, fallback, transfer, and ending.
* The intended version is published.
* Required dynamic variables have safe defaults.
* Knowledge bases are ready and contain current, non-conflicting information.
* Actions and MCP tools have the smallest required access and tested failure behavior.
* Secrets are stored in Secret Manager rather than prompts or variables.
## Conversation quality
* Web and phone previews pass the expected path and important failure paths.
* Names, dates, amounts, identifiers, and domain terms are pronounced and transcribed correctly.
* Silence, interruption, background noise, DTMF, voicemail, IVR, and transfers behave as intended.
* Maximum duration and end-call conditions prevent an unbounded call.
## Privacy and operations
* Recording, consent, PII, retention, and calling policies match your requirements.
* Phone numbers are active, ready, assigned to the correct agents, and restricted to intended countries.
* Inbound webhooks and external actions meet timeout and retry expectations.
* Campaign contacts are validated and applicable DND groups are included.
* Payment method, credit, and concurrency are ready for the expected traffic.
## Monitoring
* The team knows which conversations, outcomes, latency, tool errors, and end reasons to monitor.
* A rollback version and owner are identified.
* The first production cohort is small enough to review before traffic expands.
# Test by phone
Source: https://docs.persistence.dev/test/phone-preview
Run a telephony preview before assigning production traffic.
A phone preview lets you test the agent through the telephony path instead of the browser microphone.
## Start a test call
Open the agent preview, select the phone option, enter the destination requested by the preview, and start the call. Answer the incoming test call and exercise the same paths you use in web preview.
Test telephony-specific behavior:
* The configured greeting and opening pause
* Number and keypad pronunciation
* DTMF capture and termination keys
* Voicemail and answering-machine behavior
* IVR detection
* Cold and warm transfers
* Silence and maximum-duration endings
A valid payment method may be required for telephony previews. The preview uses live phone connectivity and can incur usage.
Use test phone numbers and non-sensitive data. Publish only after both the conversation logic and telephony behavior pass.
# Simulation suites
Source: https://docs.persistence.dev/test/simulation
Run repeatable conversation suites and release gates before deployment.
Open **Agent testing**. Test your agents in one shared workspace. Use test plans when a workflow needs more coverage than a single manual preview can provide.
The workspace has these areas:
| Area | Dashboard label | Use it to |
| - | - | - |
| Test plans | **Test plans** | Group test scenarios for one agent and test them against a selected version. |
| Personalities | **Personalities** | How the simulated participant communicates. Reuse presets across agents. |
| Test results | **Test results** | Start runs and review conversations and success criteria. |
| Success criteria | **Success criteria** | Manage how each test scenario is checked. |
## Create a test plan
Select **Create test plan**. Name it, pick the agent, write a test scenario, then choose how it is checked and review.
Cover the expected conversation, missing or malformed information, interruptions, tool failures, transfers, voicemail, silence, and policy boundaries. Add scenarios manually or with AI after the plan exists.
Personality sets tone and style. The scenario sets the participant's role and circumstances.
Select **New run**. Point the run at the agent version you are about to publish. Enable **Real tools** only when the scenario must hit live integrations.
When **Real tools** is off, a successful mock response is generated for each scenario. Edit that mock when you need a specific payload.
## Review results
Use the suite pass rate and failed-scenario count to find regressions. Open a failed run, inspect the transcript and evaluation result, correct the prompt, flow, action, knowledge, or speech settings, and run the plan again.
Mark critical plans as release gates so a failing result is visible before deployment. A passing plan is evidence for the tested scenarios, not a guarantee that every production call will succeed.
### Run report
Select **View report** after a run finishes. Reports are generated automatically for new runs with at least 5 distinct scenarios.
The report includes:
* Scenario pass rate and conversations passed
* **What went wrong** and **What went well**
* **Recommended changes**
* Per-scenario verdicts: **Passed**, **Failed**, **Inconclusive**, **Execution failed**, **Cancelled**
* **Real-tool latency** when **Real tools** was on (mocked calls do not measure tool latency)
Keep plans small enough to diagnose quickly, and separate critical release gates from broader exploratory coverage.
# Test in the web preview
Source: https://docs.persistence.dev/test/web-preview
Run simulated calls before you route live traffic to an agent.
Use the web preview to test a draft agent through your browser microphone or text chat. A simulated call lets you check the conversation without assigning a phone number.
## Start a simulated call
Open **Agents**, select an agent, and make the draft you want to test active in the builder.
In the preview, enter values for any dynamic variables the agent expects. Use test data only.
Select **Start talking** and allow microphone access when your browser asks. For a text-only test, select **Start chatting** instead.
Test the expected path, missing information, interruptions, silence, and requests that should invoke an action.
## What to verify
* The greeting matches the call direction and opening mode.
* The agent asks one clear question at a time.
* Knowledge answers stay grounded in the connected sources.
* Actions receive the required values and handle failures safely.
* Transfers and call endings happen only under the intended conditions.
* The agent responds correctly when a caller interrupts or pauses.
Change the prompt, flow, knowledge, or settings when a test fails. Start a new simulated call after each meaningful change.
Test with several speakers and audio environments. A successful happy-path call does not cover interruptions, background noise, or ambiguous answers.
## Before you publish
Confirm that the draft passes your expected path and important failure paths. Then publish the version and assign it to a deployment path.
# Troubleshooting
Source: https://docs.persistence.dev/troubleshooting/common-issues
Resolve common agent, preview, telephony, integration, and campaign issues.
## An agent will not publish
Check the required system prompt, language and models, voice, greeting, ambience, actions, extraction triggers, and dynamic-variable defaults. Wait for any pending autosave before publishing again.
## A knowledge base cannot be attached
Only a knowledge base in **Ready** state can be attached. Open it to find documents that are pending, processing, or failed. Replace a failed PDF and wait for processing to finish. An agent already at three knowledge bases cannot attach another.
## A preview cannot start
Confirm microphone permission for a web preview. A valid payment method may be required for web and phone previews. Also verify that the draft has compatible language, model, and voice settings. On a conversation flow, wait for pending canvas saves to finish.
## A phone number cannot place calls
Confirm that the number is active and ready, has an outbound agent, allows the destination country, and has valid provider or SIP configuration. Check workspace credit or payment method when a paid operation fails. If you dial from a pool, confirm members are **Callable** and belong to one connection.
## A campaign cannot start
Confirm that it has a name, an active phone number or number pool with an outbound agent, at least one contact group, a valid schedule, concurrency, and a payment method or credit. Review DND groups and excluded invalid contacts.
## A workflow will not run
Confirm the workflow is **Published**. Drafts reject polling and **Run via API** requests. Check the start node trigger, mapped `phone_number`, agent, and dial-from number or pool. Open **Runs** for **Secret missing**, **Variable missing**, or a failed step.
A `409` on start means another run already holds that `Concurrency-Key`, or cooldown has not expired. Wake only applies to runs sleeping at a wait step — not to retries between calls.
## A conversation flow takes the wrong branch
Confirm each node has an **Else** or **If failed** edge. Extract a variable before a condition depends on it. Test the failure path of **Function** nodes (**If failed to call**).
## An action or integration fails
Check the endpoint or provider connection, secret reference, selected permissions, timeout, required parameters, and response shape. For Slack, confirm the channel remains allowed. For calendars, refresh the event type and fields.
## Sign-in loops or expires
Return to the dashboard and start a fresh login. Reused links, old tabs, strict cookie settings, and expired passcodes can invalidate the login session.