Send your first SMS
Send an SMS from the shared sandbox sender PSandbox to the Simulator, the pretend phone in the Ponitor app, then check that it was delivered. Everything here happens in the sandbox: nothing reaches a real phone and nothing is charged.
You need a Ponitor team, and a role in it that can manage developer settings: Owner, Admin, or a custom role with Manage API Keys and Webhooks. Other roles don't see the API keys page.
Create your team
Sign up at app.ponitor.com and create your team. It starts in Test mode, with a sandbox and a $1,000 test balance. You can see the mode in the top bar of the app.
Create a sandbox key
- In the app, make sure the top bar says Test.
- Open Settings › Developers › API Keys.
- Under Subscribed APIs, turn on Messaging API.
- Choose Generate API Key and copy the key. It's shown once.
A key made in Test mode is a sandbox key. It works only at the sandbox base URL. Store both on your server:
export PONITOR_BASE_URL="https://sandbox-api.ponitor.com"
export PONITOR_API_KEY="your_sandbox_key"
Send the SMS
Send it from PSandbox to the Simulator's number, +231880000100. The externalId is your own reference for the message, such as an order number. Pick your language: the examples read the base URL and the key from the environment variables above, and the choice follows you to every page.
- curl
- Node.js
- Python
- PHP
curl -X POST "$PONITOR_BASE_URL/messaging/v1/messages" \
-H "apiKey: $PONITOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sender": "PSandbox",
"channel": "SMS",
"recipient": "+231880000100",
"externalId": "welcome-0001",
"text": "Hello from Acme. Your account is ready."
}'
const response = await fetch(`${process.env.PONITOR_BASE_URL}/messaging/v1/messages`, {
method: 'POST',
headers: {
apiKey: process.env.PONITOR_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
sender: 'PSandbox',
channel: 'SMS',
recipient: '+231880000100',
externalId: 'welcome-0001',
text: 'Hello from Acme. Your account is ready.',
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.post(
f"{os.environ['PONITOR_BASE_URL']}/messaging/v1/messages",
headers={"apiKey": os.environ["PONITOR_API_KEY"]},
json={
"sender": "PSandbox",
"channel": "SMS",
"recipient": "+231880000100",
"externalId": "welcome-0001",
"text": "Hello from Acme. Your account is ready.",
},
timeout=30,
)
print(response.status_code, response.json())
<?php
$ch = curl_init(getenv('PONITOR_BASE_URL') . '/messaging/v1/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'apiKey: ' . getenv('PONITOR_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'sender' => 'PSandbox',
'channel' => 'SMS',
'recipient' => '+231880000100',
'externalId' => 'welcome-0001',
'text' => 'Hello from Acme. Your account is ready.',
]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status, ' ', $body, PHP_EOL;
Ponitor answers 201 Created with the message. Its status is QUEUED, and cost is what it took from your test balance.
{
"message": {
"id": "e2c38865-dc0a-4299-baef-c07a9bfa9c33",
"channel": "SMS",
"sender": "PSandbox",
"recipient": "+231880000100",
"status": "QUEUED",
"cost": 0.05,
"externalId": "welcome-0001"
}
}
The full response has more fields. See Send a message.
If the request is refused:
| Status | Why | Fix |
|---|---|---|
400 | A field is missing or wrong (E01001), or the sender isn't PSandbox or 1234 (E10001). | Read error.message, and error.details for each field. |
401 | The key is wrong (E02002), or it's a sandbox key sent to the live base URL (E02003). | Check PONITOR_API_KEY, and that PONITOR_BASE_URL is the sandbox one. |
403 | The Messaging API isn't turned on for your team. | Turn it on under Subscribed APIs. |
429 | More than 120 requests in a minute from your team's sandbox keys (E01011). | Wait, then try again. See Rate limits. |
A refused request answers with an error object. Branch on error.code, and look the code up in Errors:
{
"error": {
"type": "bad_request_error",
"code": "E10001",
"name": "SenderNotFound",
"message": "In the sandbox every team sends SMS from PSandbox, or the two-way short code 1234. \"ACME\" is a live sender: send from it with a live key at https://api.ponitor.com.",
"doc_url": "https://docs.ponitor.com/api/errors#E10001",
"request_id": "c1f0b7a2-5e1d-4c8e-9a0b-7d6e5f4c3b2a"
}
}
See it arrive
Open Simulator in the app's menu. Its number is 0880 000 100. The SMS arrives in its Messages app, from PSandbox, a few seconds after you send it.
Check that it was delivered
Fetch the message by its id:
- curl
- Node.js
- Python
- PHP
curl "$PONITOR_BASE_URL/messaging/v1/messages/e2c38865-dc0a-4299-baef-c07a9bfa9c33" \
-H "apiKey: $PONITOR_API_KEY"
const response = await fetch(`${process.env.PONITOR_BASE_URL}/messaging/v1/messages/e2c38865-dc0a-4299-baef-c07a9bfa9c33`, {
headers: { apiKey: process.env.PONITOR_API_KEY },
});
console.log(response.status, await response.json());
import os
import requests
response = requests.get(
f"{os.environ['PONITOR_BASE_URL']}/messaging/v1/messages/e2c38865-dc0a-4299-baef-c07a9bfa9c33",
headers={"apiKey": os.environ["PONITOR_API_KEY"]},
timeout=30,
)
print(response.status_code, response.json())
<?php
$ch = curl_init(getenv('PONITOR_BASE_URL') . '/messaging/v1/messages/e2c38865-dc0a-4299-baef-c07a9bfa9c33');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['apiKey: ' . getenv('PONITOR_API_KEY')],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status, ' ', $body, PHP_EOL;
Its status is now DELIVERED. In your own code, use a webhook instead: Ponitor tells you each time the status changes.
Next steps
- Try a failure: send to
+231880000001, and the message ends asUNDELIVERED. See The sandbox. - Get replies on the shared Short Code
1234. See Receiving replies. - Send real messages from your own Sender ID. See Going live.
- Before you send in a loop, read Rate limits: 120 requests a minute in the sandbox.
- Retry safely after a timeout with the same
externalId. See Retries and externalId.