For the complete documentation index, see llms.txt. This page is also available as Markdown.

Channel Manager

Use Channel Manager with your own application. Find setup requirements, credentials, messaging and voice APIs, and the differences between channels.

Channel Manager connects your application to phone calls, SMS/MMS, email, iMessage, and WhatsApp. With the standalone API, your team configures and runs the agent. Channel Manager provides the provisioned senders, channel APIs, and delivery records.

Your application owns the conversation, consent decisions, scheduling, and business records. You can use Channel Manager without a Concurrence workspace, agent, service, or Developer Console. Start with Integrate Channel Manager Through the API.

Before API integration, request a channel, complete the required approvals, and receive the provisioned setup from Concurrence. The customer application owns its agent, workflow, consent decisions, and records. Channel Manager connects it to the recipient through the approved channel.
Required approvals and Concurrence provisioning come before API integration. Test the integration before launch. The boxes show responsibilities, not deployment locations.

If you use a Concurrence workspace service, follow the separate service-binding path.

Webchat is separate from the standalone Channel Manager API. Your application can provide its own webchat; to embed a Concurrence Platform agent, see Serve an Agent From a Web App.

Supported Channels

Choose a channel for the intended workflow, then confirm that its sender and use case are approved. Access to an API does not establish permission to run a particular outreach program.

Channel
Before the first test
What differs

SMS/MMS

Assigned number, required sender registration, and recipient consent

SMS replies do not identify a specific outbound message. MMS also needs verified content access.

Email

Verified sending domain, sender, inbound_mode (receiving modes also need verified inbound MX), and unsubscribe configuration

Replies can carry a parent message reference; bodies and attachments need authenticated retrieval.

iMessage

Assigned sending lines and an approved recipient

Delivery may fall back to SMS. Check the reported service and downgrade result.

WhatsApp

Provisioned business number and a recipient who has contacted the use case

The current send API accepts free-form replies within 23 hours of an inbound message. It does not send templates.

Inbound/outbound voice

Assigned number, voice event receiver, and a compatible audio endpoint

REST controls the call and participants. Audio uses a separate connection to your runtime.

Provision a Channel

Request the channel through your Concurrence implementation contact. Describe the intended sender, recipients, purpose, and sample content. Your contact will confirm which sender registrations and channel-provider approvals are required.

Before using the route, get confirmation that the required approvals are complete and the sender and use case are ready. Submitting a request or obtaining API credentials does not approve the channel or provision it automatically.

Ask your Concurrence implementation contact for the setup ID, use-case ID, assigned sender, and setup-scoped client credentials. A setup is the access boundary for channel resources. A use case identifies the provisioned purpose, channel, and routing configuration within it.

For messaging, provide an HTTPS receiver and obtain its signing secret. For voice, agree the call-event and audio connection contracts as well. Confirm the API host, processing region, retention, and support responsibilities before handling customer data. See Program Readiness.

Standalone API

Use Channel Manager-issued IDs to identify resources in your application, API requests, and support records. Save message IDs alongside your own workflow ID; do not depend on provider identifiers.

The current public base URL is https://channels.api-us.concurrence.com. Its OpenAPI schema and full interactive reference are separate from the workspace Platform API. Use the host assigned during onboarding. Using this public endpoint does not mean Channel Manager runs in your cloud account; that deployment requires its own agreement and acceptance.

Keep the host in configuration. Change it only when you receive an explicit API migration notice; a website or email-domain change does not change API URLs or callback destinations.

Authenticate

Exchange the provisioned client ID and secret for a bearer token. Tokens last one hour; there is no refresh token. Request the scopes needed for your channel and check the returned scope: ungranted scopes are omitted. Access is restricted by both operation scope and setup ID.

Keep client secrets on your server. Use separate credentials for runtime traffic and administration. Rotating a client secret prevents new token exchanges with that secret; issued tokens can remain valid until expiry.

Get Token

post

Exchange client credentials for a 1-hour access token (client_credentials grant).

Authenticate via HTTP Basic or client_id/client_secret in the form body; the requested scopes are intersected down to the client's grant. No refresh token.

Authorizations
AuthorizationstringRequired
Body
grant_typeconst: client_credentialsRequired
scopestringRequired

Space-delimited requested scopes (RFC 6749 §3.3); down-scoped to the grant.

client_idstring · nullableOptional
client_secretstring · nullableOptional
Responses
200

Successful Response

application/json
access_tokenstringRequired
token_typeconst: bearerRequired
expires_inintegerRequired
scopestringRequired
post/v1/oauth/token
POST /v1/oauth/token HTTP/1.1
Authorization: Basic username:password
Content-Type: application/x-www-form-urlencoded
Accept: */*
Content-Length: 78

"grant_type='text'&scope='text'&client_id='text'&client_secret='text'"
{
  "access_token": "text",
  "token_type": "text",
  "expires_in": 1,
  "scope": "text"
}

Inspect Provisioned Use Cases

List the use cases in your assigned setup, then read the selected use case. Check its channel, sender configuration, and receiver URL against the provisioning handoff. For phone-based channels, use the detailed read to inspect assigned numbers; the collection response can leave phone_numbers empty.

List Use Cases

get

List a setup's use cases, optionally filtered by entity, channel, or deletion state.

Requires the use-case:read OAuth2 scope on the setup given by the setup_id query parameter.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Query parameters
setup_idstring · uuidRequired

Filter to use cases bound to this setup.

entity_namestring · max: 256 · nullableOptional

Filter to use cases belonging to this entity.

channelstring · enum · nullableOptional

Filter by channel.

Possible values:
include_deletedbooleanOptional

Include soft-deleted use cases (audit trail). Defaults to live only.

Default: false
Responses
200

Successful Response

application/json
get/v1/use-case
GET /v1/use-case?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "items": [
    {
      "id": "text",
      "entity_name": "text",
      "name": "text",
      "description": "text",
      "environment": "staging",
      "region": "us-east-1",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z",
      "deleted_at": "2026-01-01T00:00:00.000Z",
      "channel": "text",
      "twilio_setup_id": "text",
      "inbound_voice_url": "text",
      "webhook_url": "text",
      "allowed_forwarding_numbers": [
        "text"
      ],
      "phone_numbers": [
        "text"
      ]
    }
  ]
}

Get Use Case

get

Fetch one use case with its channel binding. Tombstoned rows return 404.

Requires the use-case:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
use_case_idstring · uuidRequired
Responses
200

Successful Response

application/json
or
or
or
or
or
get/v1/use-case/{use_case_id}
GET /v1/use-case/{use_case_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "id": "text",
  "entity_name": "text",
  "name": "text",
  "description": "text",
  "environment": "staging",
  "region": "us-east-1",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "deleted_at": "2026-01-01T00:00:00.000Z",
  "channel": "text",
  "twilio_setup_id": "text",
  "inbound_voice_url": "text",
  "webhook_url": "text",
  "allowed_forwarding_numbers": [
    "text"
  ],
  "phone_numbers": [
    "text"
  ]
}

Send and Reconcile SMS

Save the Channel Manager message id with your workflow ID. Use it for message and attachment reads. A successful send response means the provider accepted the message. Read the message again for later delivery or error information.

The send operation has no client-supplied idempotency key. After a timeout, reconcile the attempt before resending. When listing messages, supply setup_id and at least one of use_case_id or peer_phone_number.

Send Sms Message

post

Send an SMS or MMS from one of the use case's numbers.

A2P and toll-free senders only reach recipients who've opted in, and never anyone who's opted out.

Requires the sms:send OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
use_case_idstring · uuidRequired
to_phone_numberstring · phoneRequired
messagestring · min: 1 · max: 1600 · nullableOptional
media_attachmentsstring[] · max: 10OptionalDefault: []
Responses
201

Successful Response

application/json
idstring · uuidRequired
use_case_idstring · uuidRequired
from_numberstring · nullableRequired

NULL at INSERT — when sending via messaging_service_sid Twilio doesn't pick a sender until queueing; the queued callback backfills it.

to_numberstringRequired
bodystring · nullableRequired
num_segmentsinteger · nullableRequired

NULL until sent callback — Twilio computes segments at carrier handoff.

statusstring · enumRequiredPossible values:
attachment_namesstring[]Required
handed_off_atstring · date-time · nullableRequired
delivered_atstring · date-time · nullableRequired
errored_atstring · date-time · nullableRequired
error_codeinteger · nullableRequired
created_atstring · date-timeRequired
updated_atstring · date-timeRequired
post/v1/sms/
POST /v1/sms/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 125

{
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "to_phone_number": "text",
  "message": "text",
  "media_attachments": [
    "text"
  ]
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "from_number": "text",
  "to_number": "text",
  "body": "text",
  "num_segments": 1,
  "status": "pending",
  "attachment_names": [
    "text"
  ],
  "handed_off_at": "2026-01-01T00:00:00.000Z",
  "delivered_at": "2026-01-01T00:00:00.000Z",
  "errored_at": "2026-01-01T00:00:00.000Z",
  "error_code": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Get Sms Message

get

Fetch one SMS or MMS by id, inbound or outbound.

Requires the sms:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
message_idstring · uuidRequired
Responses
200

Successful Response

application/json
or
get/v1/sms/{message_id}
GET /v1/sms/{message_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "direction": "outbound",
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "from_number": "text",
  "to_number": "text",
  "body": "text",
  "num_segments": 1,
  "status": "pending",
  "attachment_names": [
    "text"
  ],
  "handed_off_at": "2026-01-01T00:00:00.000Z",
  "delivered_at": "2026-01-01T00:00:00.000Z",
  "errored_at": "2026-01-01T00:00:00.000Z",
  "error_code": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

List SMS messages

get

List inbound + outbound SMS messages, interleaved chronologically. At least one of use_case_id or peer_phone_number is required so the query stays on a leading index per leg. Optional direction=inbound|outbound prunes one leg. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: created_at). Default: -created_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Query parameters
setup_idstring · uuidRequired
use_case_idstring · uuid · nullableOptional
directionstring · enum · nullableOptionalPossible values:
peer_phone_numberstring · phone · nullableOptional
is_mmsboolean · nullableOptional
created_beforestring · date-time · nullableOptional
created_afterstring · date-time · nullableOptional
sort_bystring[]Optional

Sort axes, applied left-to-right. +field ascending, -field descending. Defaults to [("created_at", -1)] (newest first).

Default: []
limitinteger · max: 200Optional

The maximum number of SMS messages to return.

Default: 50
continuation_tokenanyOptional

Opaque continuation token from a prior response. Treat as a black box — the server decodes it.

Responses
200

Successful Response

application/json
has_morebooleanRequired
continuation_tokenanyRequired
get/v1/sms
GET /v1/sms?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "items": [
    {
      "direction": "outbound",
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
      "from_number": "text",
      "to_number": "text",
      "body": "text",
      "num_segments": 1,
      "status": "pending",
      "num_attachments": 1,
      "handed_off_at": "2026-01-01T00:00:00.000Z",
      "delivered_at": "2026-01-01T00:00:00.000Z",
      "errored_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "has_more": true,
  "continuation_token": null
}

MMS uses the same send operation with attachments. Test retrieval from your application before enabling inbound media.

Get Sms Message Attachment

get

Redirect to a presigned URL for one MMS attachment, the retained private copy.

The name must be listed on the row, so a caller can't sign a URL for an arbitrary key. 404 if the message or name is unknown.

Requires the sms:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
sms_idstring · uuidRequired
attachment_namestringRequired
Responses
200

Successful Response

application/json
anyOptional
get/v1/sms/{sms_id}/attachment/{attachment_name}
GET /v1/sms/{sms_id}/attachment/{attachment_name} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

SMS Recipient Opt-In and Opt-Out

Recipient controls use separate setup-scoped permissions: sms:consent:read to read opt-outs and sms:consent:write to send an opt-in message or clear a setup-wide opt-out. A workspace API key or an sms:send grant alone does not authorize these operations. Supply recipient phone numbers in E.164 format.

Send the registered opt-in message. Supply use_case_id and to_phone_number. Channel Manager selects the confirmation copy and sender from the use case's verified campaign or approved US/Canada toll-free registration. This is required before ordinary sends on the supported US long-code and US/Canada toll-free paths. Your application must already hold the appropriate consent evidence.

The operation returns 201 without a message resource ID. Once the recipient has an opt-in record for that use case, repeating it is a no-op. It returns 409 for an active setup-wide opt-out; sending an opt-in message cannot reverse a STOP. The record establishes acceptance of the confirmation send, not receipt or proof of consent.

Send Sms Opt In Message

post

Send the registered consent message and record the recipient's opt-in.

Copy and sending line come from the use case's verified A2P campaign or approved toll-free verification; 422 if neither backs it. Idempotent per recipient. 409 when the recipient has a live opt-out — an opt-in can't reverse a STOP.

Requires the sms:consent:write OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
use_case_idstring · uuidRequired
to_phone_numberstring · phoneRequired
Responses
201

Successful Response

application/json
anyOptional
post/v1/sms/opt-in
POST /v1/sms/opt-in HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 79

{
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "to_phone_number": "text"
}

No content

Read opt-outs. Supply setup_id, with optional user_phone_number to check one recipient. Results contain active setup-wide suppressions by default; include_deleted=true includes cleared records and their reversal reasons. Use the returned CM record and message IDs for correlation. An empty result is not proof of consent or of clearance on every sending line.

List SMS setup opt-outs

get

List setup-wide SMS opt-outs (STOP records) for the setup given by the required setup_id query parameter. Live rows only by default; pass include_deleted=true for the cleared/audit history. Optional user_phone_number exact-match filter. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: created_at). Default: -created_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Query parameters
setup_idstring · uuidRequired
user_phone_numberstring · phone · nullableOptional

Optional exact-match filter — the opted-out recipient's E.164 number.

include_deletedbooleanOptional

Include cleared (soft-deleted) opt-outs. Default False → live rows only.

Default: false
sort_bystring[]Optional

Sort axes, applied left-to-right. +field ascending, -field descending. Defaults to [("created_at", -1)] (newest first).

Default: []
limitinteger · max: 200Optional

The maximum number of opt-outs to return.

Default: 50
continuation_tokenanyOptional

Opaque continuation token from a prior response. Treat as a black box — the server decodes it.

Responses
200

Successful Response

application/json
has_morebooleanRequired
continuation_tokenanyRequired
get/v1/sms/opt-out
GET /v1/sms/opt-out?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "setup_id": "123e4567-e89b-12d3-a456-426614174000",
      "phone_number": "text",
      "opted_out_use_case_id": "123e4567-e89b-12d3-a456-426614174000",
      "opted_out_message_id": "123e4567-e89b-12d3-a456-426614174000",
      "created_at": "2026-01-01T00:00:00.000Z",
      "deleted_at": "2026-01-01T00:00:00.000Z",
      "delete_reason": "text"
    }
  ],
  "has_more": true,
  "continuation_token": null
}

Clear a setup-wide opt-out. An authorized caller supplies setup_id, user_phone_number, and a non-empty reason after reviewing renewed consent. Success returns 204 and retains the cleared record. The operation returns 404 if no active setup-wide opt-out exists, or 409 while a toll-free line under the setup still has an active opt-out for that recipient. The recipient must text START or UNSTOP to the affected toll-free line first.

Remove Sms Setup Opt Out

post

Operator-clear a setup-wide SMS opt-out, recording the reason for audit.

The only way to re-enable sending after a STOP. 409 while a live toll-free opt-out for the recipient remains: route them through START/UNSTOP first.

Requires the sms:consent:write OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
setup_idstring · uuidRequired
user_phone_numberstring · phoneRequired
reasonstring · min: 1 · max: 2000Required

Required, non-empty: this is the audit record of why the STOP was reversed.

Responses
204

Opt-out cleared; the recipient can be sent to again on this setup.

No content

post/v1/sms/remove-setup-opt-out
POST /v1/sms/remove-setup-opt-out HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 94

{
  "setup_id": "123e4567-e89b-12d3-a456-426614174000",
  "user_phone_number": "text",
  "reason": "text"
}

No content

Recipients opt out through the supported SMS keywords. The current public API exposes opt-out reads and reversal; it does not expose an operation to create an opt-out directly.

Send and Reconcile Email

Send inline HTML or use a stored template. For an unsubscribable use case, inline HTML must reference both unsubscribe_link and unsubscribe_from_all_link; Channel Manager supplies the URLs. For a non-unsubscribable use case, inline HTML must contain no template variables. Stored templates have their own declared parameters.

Save the returned Channel Manager email id. Use it for email reads and as email_id when reading events; the event query also requires setup_id. These reads expose delivery, bounce, and complaint information. Treat opens and clicks as tracking signals, not proof of an outcome.

When replying to an inbound email, supply its Channel Manager ID as reply_to_inbound_email_id. It must belong to the same setup and use case. The send path has no client-supplied idempotency key, so reconcile uncertain sends before retrying.

To correlate an inbound reply, read the email using the callback's inbound_message_ref. The returned in_reply_to_email_id identifies the outbound CM email it replies to, or is null for an unthreaded message. Use these CM IDs for threading.

Send Email

post

Send one email through SES, from an inline HTML body or a stored template.

raw_html mode needs subject and body_html; template mode needs template_id and template_parameters covering every declared variable. Unsubscribe links are injected by the send path, never the caller. The use case's setup must be DNS-verified.

Requires the email:send OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
use_case_idstring · uuidRequired
to_addressstring · emailRequired
modestring · enumRequiredPossible values:
template_parametersstring · nullableOptional
template_idstring · uuid · nullableOptional
subjectstring · min: 1 · max: 998 · nullableOptional
body_htmlstring · min: 1 · max: 1000000 · nullableOptional
attachmentsstring[]OptionalDefault: []
reply_to_inbound_email_idstring · uuid · nullableOptional
Responses
201

Successful Response

application/json
idstring · uuidRequired

Our row id (also passed to SES as EmailTag.amigo_email_id).

created_atstring · date-timeRequired

When SendEmail returned and the row landed.

attachment_filenamesstring[]Required

Filenames in send-time order, matching each attachment's S3 key suffix.

replied_to_inbound_email_idstring · uuid · nullableRequired

The inbound row's id when this send replied to one of our inbound emails; NULL otherwise.

post/v1/email/
POST /v1/email/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 310

{
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "to_address": "name@gmail.com",
  "mode": "template",
  "template_parameters": "text",
  "template_id": "123e4567-e89b-12d3-a456-426614174000",
  "subject": "text",
  "body_html": "text",
  "attachments": [
    "text"
  ],
  "reply_to_inbound_email_id": "123e4567-e89b-12d3-a456-426614174000"
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "created_at": "2026-01-01T00:00:00.000Z",
  "attachment_filenames": [
    "text"
  ],
  "replied_to_inbound_email_id": "123e4567-e89b-12d3-a456-426614174000"
}

Get Email

get

Fetch one email by id, inbound or outbound.

Requires the email:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
email_idstring · uuidRequired
Responses
200

Successful Response

application/json
or
get/v1/email/{email_id}
GET /v1/email/{email_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "direction": "text",
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "ses_use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "setup_id": "123e4567-e89b-12d3-a456-426614174000",
  "from_address": "text",
  "to_address": "text",
  "subject": "text",
  "received_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "in_reply_to_email_id": "123e4567-e89b-12d3-a456-426614174000",
  "attachment_filenames": [
    "text"
  ],
  "body_kind": "html"
}

List email events

get

List SES events matching the request filters. At least one of use_case_id or setup_id is required so the underlying emails join lands on a leading index. Other filters (email_id, event_type, event_at_after, event_at_before) narrow within that scope. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: event_at). Default: -event_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Query parameters
use_case_idstring · uuid · nullableOptional

Filter to events for emails sent through this use case.

setup_idstring · uuidRequired

Filter to events for emails sent from this SES setup.

email_idstring · uuid[]Optional

Narrow to one or more emails' event timelines (set semantics: any match).

Default: []
event_at_afterstring · date-time · nullableOptional

Inclusive lower bound on event_at.

event_at_beforestring · date-time · nullableOptional

Exclusive upper bound on event_at.

sort_bystring[]Optional

Sort axes applied left-to-right; +field/-field. Default -event_at (newest first).

Default: []
limitinteger · max: 200Optional

The maximum number of events to return.

Default: 50
continuation_tokenanyOptional

Opaque continuation token from a prior response; treat as a black box.

Responses
200

Successful Response

application/json
has_morebooleanRequired

Whether there are more events to retrieve.

continuation_tokenanyOptional

Opaque token to pass back on the next page request; null when no more results.

get/v1/email/event
GET /v1/email/event?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "email_id": "123e4567-e89b-12d3-a456-426614174000",
      "event_type": "Reject",
      "event_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "has_more": true,
  "continuation_token": null
}

Sending requires the email setup's sending DNS records (DKIM, MAIL FROM, and DMARC) to be verified; otherwise the send returns 409. The inbound MX record is not needed to send. Whether a use case receives email at all depends on its inbound_mode (send_only, replies_only, or all_inbound), shown on the use-case read; a send_only use case drops inbound email, including replies. Changing a send_only use case to a receiving mode returns 409 until inbound MX is verified. See Email for the modes.

Inbound bodies and attachments need a separate read. Confirm that the configured content URLs are accessible from your deployment before relying on email replies.

Get Email Body

get

Stream one email's body from S3, inbound or outbound.

Content-Type follows the stored body kind (HTML or plain text).

Requires the email:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
email_idstring · uuidRequired
Responses
200

Successful Response

application/json
anyOptional
get/v1/email/{email_id}/body
GET /v1/email/{email_id}/body HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

Get Email Attachment

get

Redirect to a presigned URL for one email attachment.

The filename must be listed on the row, so a caller can't sign a URL for an arbitrary key under the email's S3 prefix. 404 if the email or filename is unknown.

Requires the email:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
email_idstring · uuidRequired
filenamestringRequired
Responses
200

Successful Response

application/json
anyOptional
get/v1/email/{email_id}/attachment/{filename}
GET /v1/email/{email_id}/attachment/{filename} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

Send and Reconcile iMessage

Send text, one media file, or both within the operation’s limits. Before the recipient replies to the selected line, sends are text-only within the limits below. The service prefers a pool line the contact has replied to, then the most recently used line, then another available line. Delivery can fall back to SMS; do not use this route when your program requires iMessage-only delivery. Read the message's service, was_downgraded, and delivery/error fields after sending.

Replies do not carry a parent outbound reference. Use your own session mapping, as described in the receiver walkthrough. Reconcile an uncertain send before retrying; this endpoint has no client-supplied idempotency key.

Send Imessage

post

Send one iMessage from the use case's pool, sticky per contact.

At least one of content or media is required, one media file at most. 422 when the pool is empty, a pre-reply limit applies, or SendBlue rejects the send.

Requires the imessage:send OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
use_case_idstring · uuidRequired
to_numberstring · phoneRequired
contentstring · max: 18996OptionalDefault: ""
mediastring · nullableOptional
Responses
201

Successful Response

application/json

An outbound row projection, plus terminal-delivery columns.

idstring · uuidRequired

Row id; also the S3 key prefix imessage/{id}/{filename} for media.

use_case_idstring · uuidRequired

Top-level channel.use_cases.id, not the SendBlue-side id.

from_numberstringRequired

E.164 sender (outbound: our line; inbound: the foreign sender).

to_numberstringRequired

E.164 recipient (outbound: the foreign recipient; inbound: our line).

contentstringRequired

Text content. Empty string for media-only messages.

has_mediabooleanRequired

True when media is stored; fetch via GET /v1/imessage/{id}/media.

servicestring · enum · nullableRequired

SendBlue's service classification; NULL on outbound until the terminal-delivery webhook lands.

Possible values:
occurred_atstring · date-timeRequired

Send time (outbound) or receive time (inbound); list orders by this DESC. Not a carrier delivery time.

directionconst: outboundOptionalDefault: outbound
delivered_atstring · date-time · nullableRequired

Set on the first terminal DELIVERED / SENT webhook. NULL until then.

errored_atstring · date-time · nullableRequired

Set on the first terminal ERROR / DECLINED webhook; mutually exclusive with delivered_at.

error_messagestring · nullableRequired

Free-text SendBlue failure description. NULL when not errored.

was_downgradedboolean · nullableRequired

True when SendBlue downgraded iMessage → SMS; NULL until the terminal-delivery webhook lands.

sent_atstring · date-time · nullableRequired

When SendBlue accepted and sent the message.

post/v1/imessage/
POST /v1/imessage/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 101

{
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "to_number": "text",
  "content": "",
  "media": "text"
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "from_number": "text",
  "to_number": "text",
  "content": "text",
  "has_media": true,
  "service": "iMessage",
  "occurred_at": "2026-01-01T00:00:00.000Z",
  "direction": "outbound",
  "delivered_at": "2026-01-01T00:00:00.000Z",
  "errored_at": "2026-01-01T00:00:00.000Z",
  "error_message": "text",
  "was_downgraded": true,
  "sent_at": "2026-01-01T00:00:00.000Z"
}

Get Message

get

Fetch one iMessage by id, inbound or outbound.

Requires the imessage:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
message_idstring · uuidRequired
Responses
200

Successful Response

application/json
or
get/v1/imessage/{message_id}
GET /v1/imessage/{message_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "from_number": "text",
  "to_number": "text",
  "content": "text",
  "has_media": true,
  "service": "iMessage",
  "occurred_at": "2026-01-01T00:00:00.000Z",
  "direction": "inbound"
}

Get Message Media

get

Redirect to a presigned URL for an iMessage's media. 404 if the message carries none.

Requires the imessage:read OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
message_idstring · uuidRequired
Responses
200

Successful Response

application/json
anyOptional
get/v1/imessage/{message_id}/media
GET /v1/imessage/{message_id}/media HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

Before a contact’s first reply to the selected line, messages are limited to 300 characters, cannot include media, links, phone numbers, or email addresses, and stop after six recorded sent/delivered messages. A pre-reply restriction returns 422; a content-check failure does not silently authorize the send. Setup-wide iMessage opt-outs also block sends with 422; the recipient can text START to opt back in. These controls are separate from SMS recipient controls.

Reply Through WhatsApp

Use the business-scoped recipient ID (BSUID) from the inbound event's caller_ref, not a phone number. The recipient must have sent a message to this use case within the last 23 hours. The current send operation supports free-form messages; the presence of a template-creation API does not provide template sending or cold outreach through this route.

Save the returned message_id, which is a Channel Manager-issued message ID. The current public schema has no WhatsApp message-read or delivery-event-list operation. Before launch, ask your channel operator how to investigate delivery and uncertain sends. The messaging receiver does not provide a delivery-event feed.

Send Whatsapp Message

post

Send a WhatsApp message from a use case's number to a BSUID-addressed recipient.

Free-form only (no templates): the recipient must have messaged this use case within 23 hours (one hour inside Meta's 24, so an accepted send can't die at the window edge). Requires the whatsapp:send OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
or
or
or
or
or
or
Responses
201

Successful Response

application/json
message_idstring · uuidRequired
post/v1/whatsapp/
POST /v1/whatsapp/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/x-www-form-urlencoded
Accept: */*
Content-Length: 170

"use_case_id='123e4567-e89b-12d3-a456-426614174000'&to_bsuid='text'&message_type='text'&message='text'&target_message_id='123e4567-e89b-12d3-a456-426614174000'"
{
  "message_id": "123e4567-e89b-12d3-a456-426614174000"
}

Control Voice Calls

Voice separates call control from audio. Creating a call reserves a call reference and sender number; it does not dial anyone. Add a media participant to connect your runtime's wss:// audio endpoint, and a dial participant to call the recipient. The audio endpoint must implement the provisioned media protocol; a generic agent WebSocket is not sufficient.

Participant creation returns before the person answers or joins. Read the call and participants to track those transitions. Keep call_ref and each participant_ref with your application's session ID. Ending a connected call requests teardown; read it again to confirm ended_at.

These APIs also support human participants and participant redirection. Design and test the handoff with your voice integration owner before using it with customers. Confirm recording, retention, and disclosure requirements during provisioning.

Deleting a participant removes that leg, including canceling a still-ringing destination. A leg already recorded as having left returns 204; an unknown call or participant remains 404. Until the conference reference is available, deletion returns 409, so retry after checking call state rather than assuming removal.

Delete Voice Call Participant

delete

Remove a participant from the conference. voice:call on the setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
call_refstring · uuidRequired
participant_refstring · uuidRequired
Responses
200

Successful Response

application/json
anyOptional
delete/v1/voice-call/{call_ref}/participant/{participant_ref}
DELETE /v1/voice-call/{call_ref}/participant/{participant_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

Create Voice Call

post

Create a call resource and select its From number. voice:call on the setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body
use_case_idstring · uuidRequired

Outbound-voice use case whose sender pool the call is placed from.

idempotency_keystring · min: 1 · max: 255 · nullableOptional

Retry-dedup key; a repeat returns the existing call rather than a second one.

Responses
200

Successful Response

application/json
call_refstring · uuidRequired
phone_numberstringRequired

The From number selected from the use case's pool.

post/v1/voice-call/
POST /v1/voice-call/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 79

{
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "idempotency_key": "text"
}
{
  "call_ref": "123e4567-e89b-12d3-a456-426614174000",
  "phone_number": "text"
}

Add Voice Call Participant

post

Dial or media-stream a leg into the call's conference. voice:call on the setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
call_refstring · uuidRequired
Body
or
Responses
200

Successful Response

application/json
participant_refstring · uuidRequired
rolestring · enumRequiredPossible values:
provider_call_sidstringRequired
post/v1/voice-call/{call_ref}/participant
POST /v1/voice-call/{call_ref}/participant HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 103

{
  "kind": "media",
  "stream_url": "https://example.com",
  "stream_context": {
    "ANY_ADDITIONAL_PROPERTY": "text"
  }
}
{
  "participant_ref": "123e4567-e89b-12d3-a456-426614174000",
  "role": "caller",
  "provider_call_sid": "text"
}

Get Voice Call

get

Return one call and its participants. voice:read on the call's setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
call_refstring · uuidRequired
Responses
200

Successful Response

application/json
call_refstring · uuidRequired
use_case_idstring · uuidRequired
setup_idstring · uuidRequired
directionstring · enumRequiredPossible values:
phone_numberstringRequired
peer_numberstring · nullableRequired
recording_availablebooleanRequired
created_atstring · date-timeRequired
started_atstring · date-time · nullableRequired
ended_atstring · date-time · nullableRequired
get/v1/voice-call/{call_ref}
GET /v1/voice-call/{call_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "call_ref": "123e4567-e89b-12d3-a456-426614174000",
  "use_case_id": "123e4567-e89b-12d3-a456-426614174000",
  "setup_id": "123e4567-e89b-12d3-a456-426614174000",
  "direction": "inbound",
  "phone_number": "text",
  "peer_number": "text",
  "recording_available": true,
  "created_at": "2026-01-01T00:00:00.000Z",
  "started_at": "2026-01-01T00:00:00.000Z",
  "ended_at": "2026-01-01T00:00:00.000Z",
  "participants": [
    {
      "participant_ref": "123e4567-e89b-12d3-a456-426614174000",
      "kind": "inbound",
      "role": "caller",
      "to_phone_number": "text",
      "dial_status": "answered",
      "answered_by": "human",
      "created_at": "2026-01-01T00:00:00.000Z",
      "answered_at": "2026-01-01T00:00:00.000Z",
      "joined_at": "2026-01-01T00:00:00.000Z",
      "left_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Delete Voice Call

delete

End the call's conference and stamp ended_at. voice:call on the setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
call_refstring · uuidRequired
Responses
200

Successful Response

application/json
anyOptional
delete/v1/voice-call/{call_ref}
DELETE /v1/voice-call/{call_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

The participant media-stream operation below forks that participant's incoming audio in one direction. It is separate from the media participant used to connect the agent's audio. Start it only after the participant has joined and before it leaves; either an unjoined or departed participant returns 409, as does a repeated start. If the participant leg has already ended by the time the fork is attempted - for example when the leave notification lags the recorded departure - the operation also returns 409 rather than a server error, and the once-only start guard is not consumed, so you can reconcile and retry. Supply per-fork string metadata in stream_context, which is echoed in the stream connection parameters; do not rely on URL query parameters for that context.

Start Participant Media Stream

post

Fork a participant's inbound audio to a wss:// URL. voice:call on the setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
call_refstring · uuidRequired
participant_refstring · uuidRequired
Body
stream_urlstring · uri · min: 1Required

The wss:// endpoint Twilio forks this participant's inbound audio to.

Responses
200

Successful Response

application/json
anyOptional
post/v1/voice-call/{call_ref}/participant/{participant_ref}/media-stream
POST /v1/voice-call/{call_ref}/participant/{participant_ref}/media-stream HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 88

{
  "stream_url": "https://example.com",
  "stream_context": {
    "ANY_ADDITIONAL_PROPERTY": "text"
  }
}

No content

Forwarding destinations. Voice use cases require approved forwarding destinations in allowed_forwarding_numbers, set when the use case is created. The list applies to dialed operator and transfer participants and to dial redirects. An empty list denies them, and a denied target returns 422 with forwarding_number_not_allowed. The ordinary outbound callee and media participants are not gated. Have the provisioning owner configure the list, then verify each transfer destination. A configured service forwarding number alone does not establish channel approval.

For inbound calls, have Concurrence configure the number's routing and your call-event receiver. The receiver starts or resumes your agent and attaches its media participant to the received call. Voice events are separate from messaging inbound-turn callbacks; the public schema does not currently define their payloads. Obtain and test the event contract during voice onboarding.

Messaging Webhook Secrets

Messaging callbacks use a per-use-case signing secret. Explicit rotation returns the new secret once and an old_secret_lasts_until boundary. During the overlap, a callback can carry signatures from both secrets. Changing the receiver URL also rotates the secret, without the same overlap. Coordinate either change with the receiver owner.

See the receiver walkthrough for signature checks, duplicate handling, and session mapping.

Rotate Webhook Secret

post

Rotate a messaging use case's inbound-turn HMAC signing secret.

Returns the new secret once plus the cutoff after which the old secret stops verifying (30-minute dual-sign grace). 400 for voice use cases, which have none.

Requires the use-case:write OAuth2 scope on the target setup.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
use_case_idstring · uuidRequired
Responses
200

Successful Response

application/json
webhook_secretstringRequired

The freshly-minted signing secret. Returned ONCE — never re-readable via the API; the caller stores it on its receiver to verify future signatures.

old_secret_lasts_untilstring · date-timeRequired

When the previous secret stops being accepted; until then the webhooks dual-sign both, so a receiver reconfigured before this still verifies.

post/v1/use-case/{use_case_id}/webhook-secret
POST /v1/use-case/{use_case_id}/webhook-secret HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "webhook_secret": "text",
  "old_secret_lasts_until": "2026-01-01T00:00:00.000Z"
}

Service Binding

A service binding is needed when a Concurrence workspace service handles the conversation. It links the provisioned use case to that workspace and service. Rebinding can select another service in the same workspace; it cannot claim a use case already bound elsewhere.

A customer-owned application using the standalone API does not need this binding. See Use Cases & Bindings for the workspace API operations.

Last updated

Was this helpful?