SMS-API-Referenz
JSON über HTTPS. Erstellen Sie nach Ihrer ersten Aufladung einen Schlüssel auf der Seite API-Schlüssel und senden Sie ihn als Bearer-Token mit.
Basisadresse und Schlüssel
Alle Aufrufe gehen an die unten stehende Adresse. Senden Sie Ihren Schlüssel im Authorization-Header. Wer den Schlüssel besitzt, kann Ihr Guthaben ausgeben, bewahren Sie ihn daher nur auf Ihrem Server auf.
https://portsms.com/api/v1Authorization: Bearer psm_your_api_key Content-Type: application/json
Limits: 300 Aufrufe pro Minute und Konto, 1.000 Empfänger pro Anfrage, 50,000 Nachrichten pro Konto innerhalb von 24 Stunden.
Nachricht senden
Sendet einen Text an eine oder mehrere Nummern. Die Nummern müssen im internationalen Format vorliegen (+ und Ländervorwahl). Die gesamten Kosten werden bei Annahme der Anfrage von Ihrem Guthaben abgebucht; Nummern, die wir nicht bedienen können, werden in rejected zurückgegeben und nicht berechnet.
POST/api/v1/messages
| to | String oder Array | Empfängernummern, bis zu 1.000. Jeder Eintrag kann auch ein Objekt { "to": "+1415...", "vars": ["Alice"] } sein, dessen vars {{1}}, {{2}} im Text füllen |
| text | String | Nachrichtentext, bis zu 10 Teile pro Empfänger |
| name | String, optional | Ein Name für den Versand, der in Ihrem Verlauf angezeigt wird (80 Zeichen) |
| schedule_at | String, optional | Startzeit im Format ISO 8601, 5 Minuten bis 29 Tage im Voraus. Wird sofort berechnet; stornieren Sie vor dem Start, um den vollen Betrag erstattet zu bekommen |
| opt_out | Boolean, optional | true fügt am Ende in einer neuen Zeile "Reply STOP to opt out" hinzu |
| ref | String, optional | Ihre eigene Referenz, wird in Webhooks zurückgegeben (64 Zeichen) |
curl https://portsms.com/api/v1/messages \
-H "Authorization: Bearer $PORTSMS_KEY" \
-H "Content-Type: application/json" \
-d '{"to": ["+14155550123", "+447700900123"],
"text": "Your code is 482913",
"name": "Login codes",
"ref": "order-1042"}'// One text, filled in per recipient, sent tomorrow at 09:00 UTC with an opt-out line
{"to": [{"to": "+14155550123", "vars": ["Alice", "A-1042"]},
{"to": "+447700900123", "vars": ["Ben", "A-1043"]}],
"text": "Hi {{1}}, your order {{2}} has shipped.",
"schedule_at": "2026-10-14T09:00:00Z",
"opt_out": true}HTTP/1.1 201 Created
{
"campaign": {
"id": "c_7kq2m9xd4tpa", "name": "Login codes", "source": "api",
"text": "Your code is 482913", "encoding": "gsm7", "parts": 1,
"recipients": 2, "cost": "0.0440", "refunded": "0.0000",
"status": "sending", "ref": "order-1042", "scheduled_at": null,
"created_at": "2026-10-13T09:30:00.000Z", "done_at": null
},
"messages": [
{ "id": "m_3hx8q2vkd7mrtc", "campaign_id": "c_7kq2m9xd4tpa", "to": "+14155550123",
"country": "US", "status": "queued", "error": null, "parts": 1, "price": "0.0090",
"created_at": "2026-10-13T09:30:00.000Z", "sent_at": null, "done_at": null }
],
"rejected": []
}// Node.js 18+
const res = await fetch("https://portsms.com/api/v1/messages", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.PORTSMS_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ to: ["+14155550123"], text: "Your code is 482913" }),
});
const data = await res.json();# Python 3 with requests
import os, requests
r = requests.post("https://portsms.com/api/v1/messages",
headers={"Authorization": "Bearer " + os.environ["PORTSMS_KEY"]},
json={"to": ["+14155550123"], "text": "Your code is 482913"})
print(r.status_code, r.json())Kosten vor dem Senden berechnen
Gleicher Body wie beim Senden. Liefert Kodierung, Teile, Kosten und die Nummern, die abgelehnt würden, und sendet nichts.
POST/api/v1/estimate
{ "encoding": "gsm7", "characters": 19, "parts": 1, "recipients": 2,
"cost": "0.0440", "balance": "120.00",
"rejected": [ { "to": "4155550123", "reason": "format" } ] }Einzelne Nachricht prüfen
Status eines Empfängers anhand der id, die beim Senden zurückgegeben wurde.
GET/api/v1/messages/{id}
{ "message": { "id": "m_3hx8q2vkd7mrtc", "to": "+14155550123", "status": "delivered",
"error": null, "price": "0.0090", "done_at": "2026-10-13T09:30:06.000Z", ... } }Kampagnen
Jede Sendeanfrage ist eine Kampagne mit dem Status scheduled, sending, done oder canceled. Listen Sie Ihre letzten 100 auf, öffnen Sie eine, um die Anzahl pro Status und jeden Empfänger zu sehen (500 pro Seite, ?page=2 für weitere), oder stornieren Sie eine geplante mit POST /api/v1/campaigns/{id}/cancel und erhalten Sie den vollen Betrag zurück.
GET/api/v1/campaigns
GET/api/v1/campaigns/{id}?page=1
POST/api/v1/campaigns/{id}/cancel
{ "campaign": { "id": "c_7kq2m9xd4tpa", "status": "done", "recipients": 2,
"counts": { "queued": 0, "sent": 0, "delivered": 1, "failed": 1 }, ... },
"messages": [ ... ], "page": 1, "page_size": 500 }Guthaben und Preise
Ihr Guthaben in US-Dollar und der Preis pro Teil für jedes Land im Angebot.
GET/api/v1/balance
GET/api/v1/prices
{ "balance": "120.00", "currency": "USD", "plan": "basic", "discount_percent": 3 }
{ "prices": [ { "country": "US", "name": "United States", "dial_code": "+1", "price_per_part": "0.0090" }, ... ] }Nachrichtenstatus
| queued | Angenommen und berechnet, wartet auf die Übergabe an die Carrier-Route |
| sent | An die Carrier-Route übergeben, wartet auf einen Zustellbericht |
| delivered | Der Netzbetreiber hat die Zustellung an das Telefon gemeldet |
| failed | Abgelehnt oder nicht zustellbar. Der Preis wurde Ihrem Guthaben erstattet |
Zustell-Webhooks
Hinterlegen Sie auf der Seite API-Schlüssel eine https-Adresse. Sobald eine Nachricht delivered oder failed ist, senden wir sie per POST als JSON. Wir versuchen es bis zu 8 Mal über etwa zwei Stunden erneut, bis Ihr Server mit einem 2xx-Status antwortet.
POST https://your-server.example/portsms
X-PortSMS-Signature: t=1791883806,v1=5f2c...e91a
Content-Type: application/json
{ "event": "sms.status",
"data": { "id": "m_3hx8q2vkd7mrtc", "campaign_id": "c_7kq2m9xd4tpa", "to": "+14155550123",
"country": "US", "status": "delivered", "error": null, "parts": 1, "price": "0.0090",
"ref": "order-1042", "done_at": "2026-10-13T09:30:06.000Z", ... } }Prüfen Sie den X-PortSMS-Signature-Header: t ist eine Unix-Zeit und v1 ist der HMAC-SHA256 von "t.body" mit Ihrem Signaturgeheimnis. Lehnen Sie Anfragen ab, die älter als fünf Minuten sind.
// Node.js: verify before trusting the body
import crypto from "node:crypto";
function verify(raw, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const want = crypto.createHmac("sha256", secret).update(m[1] + "." + raw).digest("hex");
return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}Fehler
Fehler werden als JSON mit einem Fehlercode und einem HTTP-Status zurückgegeben.
HTTP/1.1 402 Payment Required
{ "error": "insufficient_balance", "cost": "44.0000" }| bad_key | 401 | Der Schlüssel fehlt oder ist falsch |
| no_deposit | 403 | Die API wird freigeschaltet, sobald Ihre erste Aufladung gutgeschrieben ist |
| insufficient_balance | 402 | Das Guthaben ist niedriger als die Kosten (cost ist enthalten) |
| no_recipients | 400 | Keine Nummer konnte angenommen werden (rejected nennt die Gründe) |
| too_many_recipients | 413 | Mehr als 1.000 Nummern in einer Anfrage |
| empty_text | 400 | Der Text ist leer |
| text_too_long | 400 | Der Text benötigt mehr als 10 Teile |
| bad_schedule | 400 | schedule_at ist keine Zeit zwischen 5 Minuten und 29 Tagen im Voraus |
| daily_limit | 429 | Das 24-Stunden-Nachrichtenlimit des Kontos ist erreicht |
| rate_limited | 429 | Mehr als 300 Aufrufe in einer Minute |
| not_found | 404 | Diese Nachricht, Kampagne oder Adresse existiert nicht |
| not_scheduled | 409 | Nur eine geplante Kampagne, die noch nicht begonnen hat, kann storniert werden |
Warum eine Nummer abgelehnt wird
| format | Nicht im internationalen Format, zu kurz oder zu lang |
| country | Ein Land, das wir nicht bedienen |
| price | Ein Land, das derzeit nicht im Angebot ist |
| duplicate | Dieselbe Nummer kommt in einer Anfrage zweimal vor |
| too_long | Mit eingesetzten vars ist der Text länger als 10 Teile |