The Chat API service (
chat-api.com) may no longer be maintained. Check its status before you integrate.Prerequisites
- A Chat API account with an instance
- Your Chat API instance ID and token
Setup
1
Get your Chat API credentials
In Chat API, open your instance dashboard and copy the instance ID and token.
2
Configure in Courier
Open the in Courier, enter your instance ID and token, then save.
Profile requirements
Chat API addresses the recipient by phone number or chat ID, so the profile you send to needs achat_api object. Store either one once with , which merges into the profile and creates it if it does not exist:
- Phone number
- Chat id
The number in the recipient’s WhatsApp account.
const profile = await client.profiles.create('user_123', {
profile: {
chat_api: {
phone_number: '12345678',
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"chat_api": {
"phone_number": "12345678",
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"chat_api": {
"phone_number": "12345678"
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
chat_api: {
phone_number: "12345678"
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"chat_api": map[string]any{
"phone_number": "12345678",
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("chat_api", JsonValue.from(java.util.Map.of(
"phone_number", "12345678")))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'chat_api' => [
'phone_number' => '12345678',
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"chat_api": {
"phone_number": "12345678"
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"chat_api":{"phone_number":"12345678"}}'
With Courier MCP, save the Chat API phone number 12345678 on user_123.
The id of an existing conversation.
const profile = await client.profiles.create('user_123', {
profile: {
chat_api: {
chat_id: 'recipient-chat-id',
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"chat_api": {
"chat_id": "recipient-chat-id",
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"chat_api": {
"chat_id": "recipient-chat-id"
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
chat_api: {
chat_id: "recipient-chat-id"
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"chat_api": map[string]any{
"chat_id": "recipient-chat-id",
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("chat_api", JsonValue.from(java.util.Map.of(
"chat_id", "recipient-chat-id")))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'chat_api' => [
'chat_id' => 'recipient-chat-id',
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"chat_api": {
"chat_id": "recipient-chat-id"
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"chat_api":{"chat_id":"recipient-chat-id"}}'
With Courier MCP, save the Chat API chat id recipient-chat-id on user_123.
user_id and Courier resolves the address.
For a one-off with no stored profile, pass it inline instead: "to": { "chat_api": { "phone_number": "12345678" } }.
Send by user id
The call in every language, and the rest of the profile object.
Send to a recipient
- Send to user id
- Send to
chat_api
Courier reads
chat_api off the saved profile, so preferences apply and the value can change without touching this code.const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"user_id": "user_123",
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"user_id": "user_123"
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
user_id: "user_123"
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
UserID: courier.String("user_123"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().userId("user_123").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'user_id' => 'user_123',
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { UserID = "user_123" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"user_id": "user_123"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to user_123 by email.
Pass it inline instead and nothing is stored. Swap this
to object into the call on the other tab.const { requestId } = await courier.send.message({
message: {
// A provider recipient is a profile field, so it sits outside the typed union.
to: {
chat_api: {
phone_number: "12345678",
},
} as any,
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"chat_api": {
"phone_number": "12345678",
},
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"chat_api": {
"phone_number": "12345678"
}
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
chat_api: {
phone_number: "12345678"
}
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
// A provider recipient is a profile field, so it sits outside the typed union.
to := param.Override[shared.UserRecipientParam](
json.RawMessage(`{"chat_api": {"phone_number": "12345678"}}`),
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.builder()
.putAdditionalProperty("chat_api", JsonValue.from(Map.of(
"phone_number", "12345678")))
.build();
client.send().message(SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(to)
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build());
$response = $client->send->message(
message: [
'to' => [
'chat_api' => [
'phone_number' => '12345678',
],
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""{"chat_api": {"phone_number": "12345678"}}"""
)
);
var response = await client.Send.Message(new() { Message = new() { To = to, Template = "nt_01kx4h2jdafq8bk9aftxak4b40" } });
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"chat_api": {"phone_number": "12345678"}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to the chat API number 12345678.
Template
In the notification’s integration settings, set a quoted message ID and mentioned phone numbers.Overrides
covers the two levels and which one wins. A provider override changes the request body or swaps credentials at send time.body takes any field Chat API’s /sendMessage endpoint supports. config swaps the instance ID, token, or API URL.
This example swaps the credentials:
const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
chat_api: {
phone_number: "12345678",
},
},
data: {
name: "Sarah Bennett",
},
providers: {
"chat-api": {
override: {
config: {
instanceId: "RUNTIME_INSTANCE_ID",
token: "RUNTIME_TOKEN",
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"chat_api": {
"phone_number": "12345678",
},
},
"data": {
"name": "Sarah Bennett",
},
"providers": {
"chat-api": {
"override": {
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"token": "RUNTIME_TOKEN",
},
},
},
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"chat_api": {
"phone_number": "12345678"
}
},
"data": {
"name": "Sarah Bennett"
},
"providers": {
"chat-api": {
"override": {
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"token": "RUNTIME_TOKEN"
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
chat_api: {
phone_number: "12345678"
}
},
data: {
name: "Sarah Bennett"
},
providers: {
"chat-api": {
override: {
config: {
instanceId: "RUNTIME_INSTANCE_ID",
token: "RUNTIME_TOKEN"
}
}
}
}
}
)
// This provider addresses the recipient with fields outside the typed
// UserRecipient model, so pass the recipient as raw JSON.
to := param.Override[shared.UserRecipientParam](json.RawMessage(`{
"chat_api": {
"phone_number": "12345678"
}
}`))
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Data: map[string]any{
"name": "Sarah Bennett",
},
Providers: shared.MessageProvidersParam{
"chat-api": shared.MessageProvidersTypeParam{
Override: map[string]any{
"config": map[string]any{
"instanceId": "RUNTIME_INSTANCE_ID",
"token": "RUNTIME_TOKEN",
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so pass them as additional properties.
.to(UserRecipient.builder()
.putAdditionalProperty("chat_api", JsonValue.from(java.util.Map.of(
"phone_number", "12345678"
)))
.build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.data(JsonValue.from(java.util.Map.of(
"name", "Sarah Bennett"
)))
.providers(MessageProviders.builder()
.putAdditionalProperty("chat-api", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"config", java.util.Map.of(
"instanceId", "RUNTIME_INSTANCE_ID",
"token", "RUNTIME_TOKEN"
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'chat_api' => [
'phone_number' => '12345678',
],
],
'data' => [
'name' => 'Sarah Bennett',
],
'providers' => [
'chat-api' => [
'override' => [
'config' => [
'instanceId' => 'RUNTIME_INSTANCE_ID',
'token' => 'RUNTIME_TOKEN',
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so build it from raw JSON.
To = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"chat_api": {
"phone_number": "12345678"
}
}
"""
)
),
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Data = new Dictionary<string, JsonElement>()
{
{ "name", JsonSerializer.SerializeToElement("Sarah Bennett") },
},
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"chat-api",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"token": "RUNTIME_TOKEN"
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"chat_api": {"phone_number": "12345678"}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.data '{"name": "Sarah Bennett"}' \
--message.providers '{"chat-api": {"override": {"config": {"instanceId": "RUNTIME_INSTANCE_ID", "token": "RUNTIME_TOKEN"}}}}'
With Courier MCP, send my template to this Chat API number, swapping the instance id and token.
Provider details
chat-api
routing.channels instead is supported, and sends through just this provider.
Send to a specific provider
When that is worth doing, and what you give up: failover, channel priority, and providers you add later.