API v2 to v3 Migration
Migrate your integration from the legacy v2 API to the current v3 API.
Key Differences
| Feature | v2 | v3 |
|---|---|---|
| Base URL | api.sent.dm/v2 | api.sent.dm/v3 |
| Auth Header | x-sender-id + x-api-key | x-api-key only |
| Request Body | phoneNumber or contactId, templateId, templateVariables | to, template (object with id/name/parameters), channel |
| Response Format | Flat data | Envelope with success, data, error, meta |
| Rate Limits | Varies | 200 req/min (standard), 10 req/min (sensitive) |
| Sandbox Mode | Not available | sandbox: true |
| Templates | Simple text | Structured components |
| Contacts | Basic | Channel intelligence |
Authentication Changes
v2 (Legacy)
GET /v2/messages/{id}
x-sender-id: your-sender-id
x-api-key: your-api-keyv3 (Current)
GET /v3/messages/{id}
x-api-key: your-api-keyRequest Format Changes
Sending Messages
v2 (Legacy)
v2 sends to one recipient per request through separate endpoints. POST /v2/messages/phone is shown here; POST /v2/messages/contact takes contactId instead of phoneNumber:
{
"phoneNumber": "+1234567890",
"templateId": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"templateVariables": {
"name": "John",
"order_id": "12345"
}
}v3 (Current)
{
"to": ["+1234567890"],
"template": {
"id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"name": "order_confirmation",
"parameters": {
"name": "John",
"order_id": "12345"
}
},
"channel": ["sms"],
"sandbox": false
}Key Changes:
- Separate
/v2/messages/phoneand/v2/messages/contactendpoints → singlePOST /v3/messages phoneNumber(single string) →to(array of recipients)templateId→template.id(inside thetemplateobject)templateVariables→template.parameters- Added
template.name(optional) - Added
channel: explicit per-request channel selection - Added
sandbox: sandbox mode is new in v3; v2 has no equivalent
Response Format Changes
v2 (Legacy)
POST /v2/messages/phone returns 202 Accepted with a bare message ID:
{
"messageId": "8ba7b830-9dad-11d1-80b4-00c04fd430c8"
}v3 (Current)
{
"success": true,
"data": {
"status": "QUEUED",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"recipients": [
{
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"to": "+1234567890",
"channel": "sms"
}
]
},
"error": null,
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-03-04T11:28:25.2096416+00:00",
"version": "v3"
}
}Key Changes:
- Response is now wrapped in an envelope with
success,data,error,meta - Bare
messageId→ one entry per recipient and channel indata.recipients[], each with its ownmessage_id - Added
meta.request_idto quote in support tickets
Error Format Changes
v2 (Legacy)
v2 returns RFC 9110 problem details with human-readable text only:
{
"type": "https://www.rfc-editor.org/rfc/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"PhoneNumber": ["Phone number is required"]
}
}v3 (Current)
{
"success": false,
"data": null,
"error": {
"code": "BUSINESS_003",
"message": "Account balance is insufficient to send this message",
"details": null,
"doc_url": "https://docs.sent.dm/errors/BUSINESS_003"
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-03-04T11:28:25.2096416+00:00",
"version": "v3"
}
}Key Changes:
- Errors now use the same envelope as success responses (
success: false,error,meta) instead of RFC 9110 problem details - Added machine-readable
error.codevalues prefixed by category (for example,BUSINESS_003,AUTH_001,VALIDATION_002); match on the code instead of parsing message text - Added
doc_urlwith link to error documentation
Webhook Event Changes
v2 (Legacy)
Legacy webhooks delivered a flat body identified by a type field with the plural value messages:
{
"type": "messages",
"timestamp": "2025-01-15T08:30:15Z",
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"recipient_phone_number": "+1234567890",
"message_status": "DELIVERED",
"channel": "sms"
}v3 (Current)
{
"field": "message",
"event": "message.delivered",
"timestamp": "2025-01-15T08:30:15Z",
"payload": {
"updated_at": "2025-01-15T08:30:15Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"outbound_number": "+1234567890",
"message_status": "DELIVERED",
"channel": "sms"
}
}Key Changes:
typerenamed tofield; its value changed frommessages(plural) tomessage(singular)- Added
event: granular sub-type (such asmessage.delivered,message.failed) - Added
payloadwrapper: event data is now nested underpayload message_id,template_id,message_status, andchannelmoved intopayloadrecipient_phone_number→payload.outbound_number- Added
payload.updated_at,payload.account_id, andpayload.template_name - Webhooks still subscribed to the legacy
messagesevent type keep receiving allmessage.*events; see the Events Reference for the full catalog
Endpoint Mapping
| v2 Endpoint | v3 Endpoint | Changes |
|---|---|---|
POST /v2/messages/contact | POST /v3/messages | Single send endpoint; new request/response format |
POST /v2/messages/phone | POST /v3/messages | Single send endpoint; new request/response format |
GET /v2/messages/{id} | GET /v3/messages/{id} | Response uses envelope format |
GET /v2/contacts | GET /v3/contacts | Response uses envelope format |
| Not available in v2 | POST /v3/contacts | New in v3: create contacts through the API |
POST /v2/templates | POST /v3/templates | New template structure |
Idempotency Changes
v2 (Legacy)
v2 has no idempotency support: retrying a timed-out request can send the same message twice.
v3 (Current)
curl -X POST "https://api.sent.dm/v3/messages" \
-H "x-api-key: $SENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_123_456" \
-d '{
"to": ["+1234567890"],
"template": {"id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8"}
}'Key Changes:
- Idempotency is new in v3: send an
Idempotency-Keyheader on write requests to make retries safe - Keys are cached for 24 hours
Migration Steps
- Update Base URL - Change from
/v2to/v3 - Update Authentication - Remove
x-sender-idheader, keep onlyx-api-key - Update Request Format - Replace
phoneNumber/contactIdwith thetoarray,templateIdwithtemplate.id, andtemplateVariableswithtemplate.parameters - Update Response Handling - Handle the new envelope format with
success,data,error,meta - Update Error Handling - Match on the new
error.codevalues (for example,BUSINESS_003) instead of parsing error text - Update Webhook Handlers - Adjust for new nested event structure (
payloadwrapper added; usepayload.message_idandpayload.message_status) - Add Idempotency - Send an
Idempotency-Keyheader on write requests (new in v3) - Test in Sandbox Mode - Validate all features with
sandbox: true - Deploy Gradually - Use feature flags for rollout
The v2 API is deprecated for new integrations but remains operational, and no sunset date has been announced. New capabilities such as sandbox mode, idempotency keys, and contact creation ship in v3 only, so plan your migration now rather than against a deadline. Watch the API changelog for version and deprecation announcements.
Compliance & Regulations
The compliance controls Sent enforces automatically at send time, what remains your responsibility, and per-region regulations with authoritative sources.
Multi-tenant architectures on Sent: profiles vs. accounts
Choose a tenant model for your messaging platform: Sender Profiles with per-tenant provisioning, one shared Sent account, or separate accounts per tenant.