Skip to content

Public documentation

Calling Lists API

Create or update contacts from CSV files, control list membership through import templates, and verify each import. Use an ownership-only template when you need to reassign existing contacts.

Start here

Connect with an authorized team-admin key

This API handles contact imports and list membership for Trellus Calling. For call records, transcripts and reporting, use the Call Data API. To ask an AI assistant about your calls, use the MCP guide.

Reading the docs does not require a login. Running these examples requires access to Trellus Calling and a team-admin API key. The examples are copyable text; this page never reads a key, uploads a file or sends an API request.

Use a server-side environment with Bash, curl and jq. Set your key privately and run the examples in the same Bash session so the returned connection, template and ingest variables stay available. Keep keys and returned secrets out of browser code, logs and chat.

Environment setup
export VCC_BASE_URL='https://vcc.trellus.ai'
export CSV_FILE='contacts.csv'

# Set TRELLUS_API_KEY privately in this server-side environment.
# Use a key belonging to an authorized team admin.

Resolve the team connection

Send Authorization: Bearer $TRELLUS_API_KEY to https://vcc.trellus.ai/resolve-path. The response provides path and secret for subsequent requests. An optional x-team-id header selects a team you are already authorized to administer.

Resolve an authorized team
set -euo pipefail
: "${VCC_BASE_URL:?Set VCC_BASE_URL first}"
: "${TRELLUS_API_KEY:?Set TRELLUS_API_KEY privately first}"

WORKER_JSON=$(curl --fail-with-body --silent --show-error \
  --request POST "$VCC_BASE_URL/resolve-path" \
  --header "Authorization: Bearer $TRELLUS_API_KEY")

VCC_WORKER_URL=$(jq -er '.path | strings | select(length > 0)' <<<"$WORKER_JSON")
VCC_WORKER_SECRET=$(jq -er '.secret | strings | select(length > 0)' <<<"$WORKER_JSON")

# For an authorized team override, add this header to the resolver request:
# --header "x-team-id: $TEAM_ID"
# Keep the returned secret private.

At the returned URL, send the raw API key as Authorization: $TRELLUS_API_KEY and the returned secret as x-worker-secret. Keep that connection paired with the team for which it was resolved.

Endpoint reference

Template and ingest endpoints require team-admin access. Append their paths to the resolved URL. Use the exact returned presigned URL only for the CSV upload.

CSV import endpoints
Method and pathDestinationResult
POST/resolve-pathResolverGet the authorized team’s request URL and secret.
GET/upload-templatesResolved URLRead existing import templates.
POST/upload-templatesResolved URLCreate a template. The response is its template ID as plain text.
POST/ready-ingest-uploadResolved URLRequest an ingest ID and a presigned CSV upload URL. No request body.
PUT<returned presigned URL>Returned upload URLUpload raw CSV bytes with the returned headers. Do not send Trellus credentials.
POST/ingest-uploadedResolved URLQueue the uploaded CSV with its ingest ID, type and template ID. HTTP 200 means queued.
GET/ingests?limit=50&offset=0Resolved URLRead recent imports and find the exact ingest ID to inspect its results.

Map your CSV to contact fields

Choose an existing upload template or create one for the data you plan to send. CSV headers are yours: column_map_str maps each CSV header to one contact field. Its value is a JSON-encoded string inside the template JSON.

Read existing templates
curl --fail-with-body --silent --show-error \
  "$VCC_WORKER_URL/upload-templates" \
  --header "Authorization: $TRELLUS_API_KEY" \
  --header "x-worker-secret: $VCC_WORKER_SECRET"

# To reuse a template, set TEMPLATE_ID to its template_id.

Example: create or update by external ID

Save this sample as contacts.csv and replace it with your intended contact data before uploading. Save the template as template.json. The sample matches on xref_contact_id and updates only a single matching existing contact.

contacts.csv
external_id,first_name,last_name,phone,email
EXAMPLE-001,Ada,Lovelace,+12025550123,ada@example.invalid
Contact upsert template JSON
{
  "label": "Contact upsert — external ID",
  "conflict_on": "xref_contact_id",
  "column_map_str": "{\"external_id\":\"xref_contact_id\",\"first_name\":\"first_name\",\"last_name\":\"last_name\",\"phone\":\"number_1\",\"email\":\"email\"}",
  "include_incomplete_parse": false,
  "can_insert": true,
  "can_update": true,
  "update_resolver": "SINGLES",
  "add_to_lists_str": null,
  "remove_from_lists_str": null,
  "list_resolver": null,
  "add_resets_lists": false
}
  • Allowed match fields are contact_id, number_1, xref_contact_id and email. Include the chosen match field in the map.
  • can_insert and can_update control whether the import can create contacts or update existing ones. SINGLES updates only when a match identifies one contact; FIRST updates the first match and ALL updates every matching contact.
  • Confirm custom field keys in Trellus Calling’s Contact fields. Remove unused optional columns instead of relying on blank cells.
  • Campaign membership comes through Lists. Add or remove list IDs only when that membership change is intended. Keep both list fields null to leave memberships unchanged.

For list changes, add_to_lists_str and remove_from_lists_str contain JSON-encoded arrays of list IDs. A nonempty list change requires list_resolver. Existing-contact list changes currently follow update_resolver; setting list_resolver does not independently override that matching behavior.

List replacement changes the whole target list. add_resets_lists: true clears entire lists named in add_to_lists_str before processing rows. It is not a reset for only the contacts in your CSV. The examples keep it false.
Create a template and retain its ID
: "${VCC_WORKER_URL:?Resolve the team connection first}"
: "${VCC_WORKER_SECRET:?Resolve the team connection first}"
: "${TRELLUS_API_KEY:?Set TRELLUS_API_KEY privately first}"

# Save the template JSON above as template.json.
# The successful response is plain text, not a JSON object.
TEMPLATE_ID=$(curl --fail-with-body --silent --show-error \
  --request POST "$VCC_WORKER_URL/upload-templates" \
  --header "Authorization: $TRELLUS_API_KEY" \
  --header "x-worker-secret: $VCC_WORKER_SECRET" \
  --header 'Content-Type: application/json' \
  --data-binary @template.json)

: "${TEMPLATE_ID:?The template response was empty}"

The successful template response is an ID as plain text. If you reuse a template, set TEMPLATE_ID to its existing template_id.

Request an upload, send the CSV, then queue it

The ready response contains an ingest_id, ingest_type, upload url, method and headers. Use those returned values together. The current CSV upload uses PUT and Content-Type: text/csv; its presigned URL expires after five minutes.

Send raw file bytes to the upload URL without a Trellus authorization header or worker secret. Confirm only after that upload succeeds.

Upload and queue the CSV
set -euo pipefail
: "${TRELLUS_API_KEY:?Set TRELLUS_API_KEY privately first}"
: "${VCC_WORKER_URL:?Resolve the team connection first}"
: "${VCC_WORKER_SECRET:?Resolve the team connection first}"
: "${TEMPLATE_ID:?Set TEMPLATE_ID first}"
: "${CSV_FILE:?Set CSV_FILE first}"

READY_JSON=$(curl --fail-with-body --silent --show-error \
  --request POST "$VCC_WORKER_URL/ready-ingest-upload" \
  --header "Authorization: $TRELLUS_API_KEY" \
  --header "x-worker-secret: $VCC_WORKER_SECRET")

INGEST_ID=$(jq -er '.ingest_id | strings | select(length > 0)' <<<"$READY_JSON")
INGEST_TYPE=$(jq -er '.ingest_type | select(. == "UPLOADED_CSV")' <<<"$READY_JSON")
UPLOAD_URL=$(jq -er '.url | strings | select(length > 0)' <<<"$READY_JSON")
UPLOAD_METHOD=$(jq -er '.method | strings | select(length > 0)' <<<"$READY_JSON")
UPLOAD_CONTENT_TYPE=$(jq -er '.headers["Content-Type"] | strings | select(length > 0)' <<<"$READY_JSON")

# This request goes to the returned presigned URL.
# Do not send a Trellus API key or worker secret.
curl --fail-with-body --silent --show-error \
  --request "$UPLOAD_METHOD" "$UPLOAD_URL" \
  --header "Content-Type: $UPLOAD_CONTENT_TYPE" \
  --data-binary "@$CSV_FILE"

# Confirm only after the upload succeeds.
jq -n \
  --arg ingest_id "$INGEST_ID" \
  --arg ingest_type "$INGEST_TYPE" \
  --arg template_id "$TEMPLATE_ID" \
  '{ingest_id: $ingest_id, ingest_type: $ingest_type, template_id: $template_id}' |
curl --fail-with-body --silent --show-error \
  --request POST "$VCC_WORKER_URL/ingest-uploaded" \
  --header "Authorization: $TRELLUS_API_KEY" \
  --header "x-worker-secret: $VCC_WORKER_SECRET" \
  --header 'Content-Type: application/json' \
  --data-binary @-
HTTP 200 means queued. The confirmation does not mean that every row has been processed or that every contact changed. Save the returned ingest ID and inspect the results.

Verify the result for the exact ingest ID

Poll at most once every five seconds. Find the ingest_id returned by ready-upload. If it is not in the newest 50 results, increase OFFSET by 50 until you find it or reach the end of the history.

Find this import’s result
OFFSET=0
curl --fail-with-body --silent --show-error \
  "$VCC_WORKER_URL/ingests?limit=50&offset=$OFFSET" \
  --header "Authorization: $TRELLUS_API_KEY" \
  --header "x-worker-secret: $VCC_WORKER_SECRET" |
jq --arg ingest_id "$INGEST_ID" '.[] | select(.ingest_id == $ingest_id)'

# Wait at least five seconds before polling again.
# If the ingest is not in this page, increase OFFSET by 50.

For a CSV import, wait for row_count, error_count and the other result statistics to be populated; zero is a valid value. processed_at is written before CSV processing starts. Read inserted, updated, skipped, list and error counts together.

Import result fields
FieldHow to read it
ingest_idThe identifier returned by ready-ingest-upload. Match this exact value.
row_countRows seen in the uploaded data. A populated value is part of the completion check; zero is a valid count.
contact_insert_count / contact_update_countContacts inserted and updated. Read these alongside row and skip counts.
skip_countRows skipped, including duplicate-key rows. A skip is not necessarily a parsing error.
list_add_count / list_remove_countList membership changes made by the template.
error_count / error_sample_strError count and a JSON-encoded array of up to five truncated examples.
created_at / processed_atUnix microseconds. For CSV imports, processed_at records the processing start, not completion. Convert with new Date(value / 1000).

Partial imports can change some rows while others fail or are skipped. Zero errors does not prove every row changed. error_sample_str contains a JSON-encoded array of up to five truncated examples to help identify mapping or parsing problems.

Reassign existing contact owners

Use a separate ownership-only template when you want to update existing owners without inserting contacts or replacing list memberships. Prepare the intended contact-to-agent assignments in advance; the CSV is the reassignment plan.

Ownership-only template JSON
{
  "label": "Ownership updates — existing contacts only",
  "conflict_on": "contact_id",
  "column_map_str": "{\"contact_id\":\"contact_id\",\"owner_user_id\":\"owner_user_id\"}",
  "include_incomplete_parse": false,
  "can_insert": false,
  "can_update": true,
  "update_resolver": "SINGLES",
  "add_to_lists_str": null,
  "remove_from_lists_str": null,
  "list_resolver": null,
  "add_resets_lists": false
}
ownership.csv
contact_id,owner_user_id
CONTACT_UUID,AGENT_UUID
  1. Replace each placeholder with an existing contact UUID and the full Trellus Agent ID, available from Users in Trellus.
  2. Create this template through /upload-templates, then use its ID as TEMPLATE_ID.
  3. Set CSV_FILE=ownership.csv and follow the same upload, confirmation and result checks above.
A mapped blank owner clears ownership. Validate nonblank owner_user_id values before upload when your intent is reassignment. Ownership belongs to the contact across campaigns, so the change can affect every campaign using that contact’s lists.

This template does not clear DNC status, final outcomes, retry state, callback timestamps, list memberships or agent skill grants. Exclude DNC, final-outcome and manual-calendar-callback rows from your reassignment file when those records should retain their owners. An included callback keeps its callback time but receives the new owner.

Troubleshooting

400 or rejected template

Read the response body. Check that column_map_str is a JSON-encoded CSV-header-to-contact-field map, that it includes conflict_on, and that every mapped destination is valid and unique. Confirm that the template is active.

401 or 403

Check the key’s team-admin access and the selected team. Use Bearer authentication for resolve-path; the resolved URL takes the raw key in Authorization plus x-worker-secret. Selecting a team does not grant access to it.

429

Honor Retry-After before trying again. Do not continuously poll import history.

Upload URL expired

The presigned upload URL lasts five minutes. Request a fresh ready-ingest-upload response and use its new ingest ID, URL, method and headers together.

HTTP 200, but the contacts are not updated yet

The confirmation only queues the import. Find its ingest ID and wait for row_count, error_count and the other result statistics to be populated. processed_at alone is not completion.

Zero errors, fewer changes than expected

Compare inserted, updated and skipped counts. Duplicate match values and the selected conflict resolver can prevent a row from changing a contact.

If you need help, send Trellus support the endpoint, HTTP status, response text and relevant ingest or template ID. Keep API keys, worker secrets and contact details out of the message.