Create a Campaign

SMS V3 APIs

Schedule a message to a list of phone numbers, or to one of your phonebook groups, at a time you choose.

POSThttps://telcomw.com/api/v3/campaigns

Send a JSON body with the header Content-Type: application/json. Your API key goes in the X-API-Key header.

Parameters

Parameter Sent in Required Description
X-API-Key header YES Your API key, sent as a header. You can also send it as Authorization: Bearer YOUR_API_KEY.
Idempotency-Key header NO A unique value you make for this request (8 to 64 letters, digits, dots, dashes or underscores). If you send the same request again with the same key, for example after a timeout, you get the first answer back and are not charged twice. Keys are kept for 24 hours.
name body YES A name for the campaign, up to 150 characters. You see it in the dashboard under Campaigns.
message body YES The text of the message.
sender_id body YES The sender ID the message shows as, for example WGIT. It must be approved on your account, see Sender IDs.
scheduled_at body YES When the campaign runs: a date and time with its time zone, like 2026-11-20T09:00:00+02:00. At least one minute from now and not more than a year ahead.
to body NO The phone numbers, as a list of text. Send this or group_id.
group_id body NO The number of one of your phonebook groups: the campaign goes to every number of the group when you create it. Send this or to.
skip_invalid body NO true to leave out the phone numbers that are not valid and send to the rest. By default one invalid number rejects the whole request, and nothing is sent or charged.
notify_before body NO true (the default) to get an email a few minutes before the campaign runs, with its cost and whether your balance covers it.

Example request

curl --location --request POST 'https://telcomw.com/api/v3/campaigns' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Idempotency-Key: campaign-nov-sale-001' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "November sale",
  "message": "Our sale starts today. Visit us!",
  "sender_id": "WGIT",
  "to": [
    "0999123456",
    "0888123456"
  ],
  "scheduled_at": "2026-11-20T09:00:00+02:00"
}'
curl --location --request POST "https://telcomw.com/api/v3/campaigns" ^
  --header "X-API-Key: YOUR_API_KEY" ^
  --header "Idempotency-Key: campaign-nov-sale-001" ^
  --header "Content-Type: application/json" ^
  --data "{\"name\":\"November sale\",\"message\":\"Our sale starts today. Visit us!\",\"sender_id\":\"WGIT\",\"to\":[\"0999123456\",\"0888123456\"],\"scheduled_at\":\"2026-11-20T09:00:00+02:00\"}"
<?php

$headers = [
    'X-API-Key: YOUR_API_KEY',
    'Idempotency-Key: campaign-nov-sale-001',
    'Content-Type: application/json',
];

$data = [
    'name' => 'November sale',
    'message' => 'Our sale starts today. Visit us!',
    'sender_id' => 'WGIT',
    'to' => [
        '0999123456',
        '0888123456'
    ],
    'scheduled_at' => '2026-11-20T09:00:00+02:00'
];

$ch = curl_init('https://telcomw.com/api/v3/campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

print_r(json_decode($response, true));
const axios = require('axios');

axios.request({
  method: 'post',
  url: 'https://telcomw.com/api/v3/campaigns',
  headers: {
    'X-API-Key': 'YOUR_API_KEY',
    'Idempotency-Key': 'campaign-nov-sale-001',
    'Content-Type': 'application/json'
  },
  data: {
    "name": "November sale",
    "message": "Our sale starts today. Visit us!",
    "sender_id": "WGIT",
    "to": [
      "0999123456",
      "0888123456"
    ],
    "scheduled_at": "2026-11-20T09:00:00+02:00"
  }
}).then((res) => console.log(res.data))
  .catch((err) => console.error(err.response ? err.response.data : err.message));
import requests

headers = {
    'X-API-Key': 'YOUR_API_KEY',
    'Idempotency-Key': 'campaign-nov-sale-001',
    'Content-Type': 'application/json'
}
payload = {
    'name': 'November sale',
    'message': 'Our sale starts today. Visit us!',
    'sender_id': 'WGIT',
    'to': [
        '0999123456',
        '0888123456'
    ],
    'scheduled_at': '2026-11-20T09:00:00+02:00'
}

response = requests.request('POST', 'https://telcomw.com/api/v3/campaigns', headers=headers, json=payload)
print(response.json())

Response

The response is JSON.

Created (201)

{
  "success": true,
  "request_id": "req_5f2c9a1d0e7b4a63",
  "data": {
    "campaign_id": 42,
    "name": "November sale",
    "sender_id": "WGIT",
    "message": "Our sale starts today. Visit us!",
    "status": "scheduled",
    "scheduled_at": "2026-11-20T07:00:00.000Z",
    "recipients_source": "numbers",
    "group_id": null,
    "recipients": 2,
    "queued": 0,
    "failed": 0,
    "cost": 0,
    "currency": "MWK",
    "notify_before": true,
    "batch_ids": [],
    "error": null,
    "started_at": null,
    "completed_at": null,
    "created_at": "2026-11-05T09:12:44.000Z",
    "estimate": {
      "pages": 2,
      "cost": 40,
      "balance": 4250.5,
      "enough": true,
      "currency": "MWK"
    }
  }
}

Time in the past (400)

{
  "success": false,
  "request_id": "req_5f2c9a1d0e7b4a63",
  "error": {
    "code": "INVALID_SCHEDULE",
    "message": "Choose a time at least one minute from now",
    "details": {
      "field": "scheduled_at"
    }
  }
}

Fields explained

FieldDescription
successtrue when the request worked.
request_idThe number of this request. Quote it if you contact us.
data.campaign_idThe id of the campaign. Use it in the other campaign calls.
data.statusscheduled, running, completed, failed or cancelled.
data.scheduled_atWhen the campaign runs (UTC).
data.recipients_sourcenumbers (a list you sent) or group (a phonebook group).
data.recipientsHow many numbers the campaign has.
data.queuedAfter it ran: how many messages were queued for delivery.
data.failedAfter it ran: how many could not be queued.
data.costAfter it ran: what it cost, in MWK. 0 before.
data.errorWhy a failed campaign failed, otherwise null.
data.estimateWhat the campaign would cost today: pages, cost, your balance, and enough (false when your balance would not cover it; short_by says how much is missing).
data.rejectedWith skip_invalid: how many invalid numbers (invalid_count) and repeated numbers (duplicates) were left out.

Good to know

  • Nothing is charged when you create a campaign. The messages are priced and charged when the campaign runs, from your balance at that moment, exactly like a message sent with Quick Message. Keep enough balance for the time it runs, or the campaign fails and sends nothing.
  • A campaign made here shows in the dashboard under Campaigns, and the other way round.
  • A campaign can be created at least one minute ahead. It can have up to 50,000 numbers. A number is used once.
  • A campaign is private to your account, administrators included.
  • Send an Idempotency-Key so that a retry after a timeout does not make a second campaign.