PortSMS si trova su portsms.com. Dopo l'accesso, il tuo indirizzo personale di ricarica è pronto nella pagina Saldo.

PortSMS

Riferimento API SMS

JSON su HTTPS. Crea una chiave nella pagina della chiave API dopo la tua prima ricarica, poi inviala come token Bearer.

Indirizzo di base e chiavi

Tutte le chiamate vanno all'indirizzo qui sotto. Invia la tua chiave nell'header Authorization. Chiunque abbia la chiave può spendere il tuo saldo, quindi conservala solo sul tuo server.

https://portsms.com/api/v1
Authorization: Bearer psm_your_api_key
Content-Type: application/json

Limiti: 300 chiamate al minuto per account, 1.000 destinatari per richiesta, 50,000 messaggi per account nelle 24 ore.

Inviare un messaggio

Invia un testo a uno o più numeri. I numeri devono essere in formato internazionale (+ e prefisso del paese). L'intero costo viene addebitato sul saldo quando la richiesta viene accettata; i numeri che non possiamo servire vengono restituiti in rejected e non sono addebitati.

POST/api/v1/messages

tostringa o arrayNumeri dei destinatari, fino a 1.000. Ogni elemento può anche essere un oggetto { "to": "+1415...", "vars": ["Alice"] } i cui vars compilano {{1}}, {{2}} nel testo
textstringaTesto del messaggio, fino a 10 parti per destinatario
namestringa, facoltativoUn nome per l'invio, mostrato nella tua cronologia (80 caratteri)
schedule_atstringa, facoltativoOrario di inizio ISO 8601, da 5 minuti a 29 giorni nel futuro. Addebitato subito; annulla prima che inizi per un rimborso completo
opt_outbooleano, facoltativotrue aggiunge "Reply STOP to opt out" su una nuova riga alla fine
refstringa, facoltativoUn tuo riferimento, restituito nei webhook (64 caratteri)
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())

Stima prima dell'invio

Stesso corpo dell'invio. Restituisce codifica, parti, costo e i numeri che verrebbero rifiutati, senza inviare nulla.

POST/api/v1/estimate

{ "encoding": "gsm7", "characters": 19, "parts": 1, "recipients": 2,
  "cost": "0.0440", "balance": "120.00",
  "rejected": [ { "to": "4155550123", "reason": "format" } ] }

Controllare un messaggio

Stato di un singolo destinatario, tramite l'id restituito al momento dell'invio.

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", ... } }

Campagne

Ogni richiesta di invio è una campagna con stato scheduled, sending, done o canceled. Elenca le ultime 100, aprine una per vedere i conteggi per stato e ogni destinatario (500 per pagina, ?page=2 per gli altri), oppure annulla una campagna programmata con POST /api/v1/campaigns/{id}/cancel per un rimborso completo.

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 }

Saldo e prezzi

Il tuo saldo in dollari USA e il prezzo per parte di ogni paese in vendita.

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" }, ... ] }

Stati dei messaggi

queuedAccettato e addebitato, in attesa di essere passato alla rotta dell'operatore
sentPassato alla rotta dell'operatore, in attesa del rapporto di consegna
deliveredL'operatore ha confermato la consegna al telefono
failedRifiutato o non recapitabile. Il prezzo è stato rimborsato sul tuo saldo

Webhook di consegna

Imposta un indirizzo https nella pagina della chiave API. Quando un messaggio diventa delivered o failed, lo inviamo in POST come JSON. Riproviamo fino a 8 volte in circa due ore, finché il tuo server non risponde con uno stato 2xx.

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", ... } }

Verifica l'header X-PortSMS-Signature: t è un orario Unix e v1 è l'HMAC-SHA256 di "t.body" con il tuo segreto di firma. Rifiuta le richieste più vecchie di cinque minuti.

// 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]));
}

Errori

Gli errori vengono restituiti come JSON con un codice error e uno stato HTTP.

HTTP/1.1 402 Payment Required
{ "error": "insufficient_balance", "cost": "44.0000" }
bad_key401La chiave manca o è errata
no_deposit403L'API si attiva dopo l'accredito della prima ricarica
insufficient_balance402Il saldo è inferiore al costo (il costo è incluso)
no_recipients400Nessun numero è stato accettato (rejected indica il motivo)
too_many_recipients413Più di 1.000 numeri in una sola richiesta
empty_text400Il testo è vuoto
text_too_long400Il testo richiede più di 10 parti
bad_schedule400schedule_at non è un orario compreso tra 5 minuti e 29 giorni nel futuro
daily_limit429È stato raggiunto il limite di messaggi nelle 24 ore per l'account
rate_limited429Più di 300 chiamate in un minuto
not_found404Messaggio, campagna o indirizzo inesistente
not_scheduled409Si può annullare solo una campagna programmata che non è ancora iniziata

Perché un numero viene rifiutato

formatNon è in formato internazionale, oppure è troppo corto o troppo lungo
countryUn paese che non serviamo
priceUn paese che al momento non è in vendita
duplicateLo stesso numero compare due volte nella stessa richiesta
too_longCon i suoi vars compilati, il testo supera le 10 parti