The Capabilities API lets you check whether a phone number supports iMessage before you send a message. This is useful when you want to customize your messaging logic based on the recipient’s capabilities — for example, sending rich iMessage content to Apple users and a plain SMS fallback to everyone else.
How Detection Works
When you query a phone number’s capabilities, senderZ checks the number against Apple’s iMessage registration system through the iMessage engine on the dedicated Apple hardware. The result tells you whether the number is registered for iMessage.
Results are cached for 90 days. Subsequent queries for the same number within that window return the cached result instantly without hitting the detection backend.
senderZ exposes two endpoints: GET /v1/capabilities/:number for a single phone, and POST /v1/capabilities for batch lookups of up to 100 numbers in one request.
Check a Number
/v1/capabilities/:number Check iMessage capability for a phone number.
number string
Required
Phone number in E.164 format (e.g. +15551234567). Passed as a URL path parameter.
curl https://api.senderz.com/v1/capabilities/+15551234567 \
-H "Authorization: Bearer sz_live_YOUR_KEY" const number = '+15551234567'
const res = await fetch(
`https://api.senderz.com/v1/capabilities/${encodeURIComponent(number)}`,
{
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
},
}
)
const data = await res.json()
if (data.data.imessage) {
// Send rich iMessage content
} else {
// Fall back to SMS
} Response: iMessage Available
{
"data": {
"number": "+15551234567",
"imessage": true,
"sms": true,
"cached": false,
"checked_at": "2026-04-15T10:30:00Z",
"cache_expires_at": "2026-04-16T10:30:00Z"
}
} Response: SMS Only
{
"data": {
"number": "+15559876543",
"imessage": false,
"sms": true,
"cached": false,
"checked_at": "2026-04-15T10:31:00Z",
"cache_expires_at": "2026-04-16T10:31:00Z"
}
} GET Response Fields
| Field | Type | Description |
|---|---|---|
number | string | The phone number that was checked |
imessage | boolean | true if the number is registered for iMessage |
sms | boolean | true if the number can receive SMS (almost always true for valid numbers) |
cached | boolean | true if this result came from the 90-day cache |
checked_at | string | ISO 8601 timestamp of when the check was performed |
cache_expires_at | string | ISO 8601 timestamp of when the cached result expires |
Batch Lookup
Use the batch endpoint when you need to check many numbers at once — for example, when enriching a contact list before a campaign or seeding a routing decision table.
/v1/capabilities Batch capability lookup for up to 100 phone numbers.
phones string[]
Required
Array of phone numbers in E.164 format. Minimum 1, maximum 100 entries per request. Duplicates within the array are deduplicated; the response preserves the first-seen order.
curl https://api.senderz.com/v1/capabilities \
-X POST \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"phones":["+15551234567","+15559876543"]}' const res = await fetch('https://api.senderz.com/v1/capabilities', {
method: 'POST',
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
phones: ['+15551234567', '+15559876543'],
}),
})
const { results } = await res.json()
for (const r of results) {
if (r.channel === 'imessage') {
// Route to iMessage
} else {
// Fall back to SMS
}
} Batch Response
{
"results": [
{
"phone": "+15551234567",
"channel": "imessage",
"confidence": "medium",
"checked_at": 1715000000,
"ttl_sec": 7776000
},
{
"phone": "+15559876543",
"channel": "sms",
"confidence": "low",
"checked_at": 1715000000,
"ttl_sec": 7776000
}
]
} Batch Response Fields
| Field | Type | Description |
|---|---|---|
phone | string | The phone number from the request, in E.164 format |
channel | string | One of imessage, rcs, sms, unknown — the recommended channel for this number |
confidence | string | One of high, medium, low — how reliable the recommendation is (see below) |
checked_at | number | Unix epoch seconds for when the result was produced or last refreshed |
ttl_sec | number | Seconds the entry remains valid in the cache (currently 7 776 000 = 90 days) |
Confidence Levels
| Level | Meaning |
|---|---|
high | Reserved for results verified by an actual prior delivery on this tenant. Not currently emitted by the heuristic path. |
medium | Cached result less than 30 days old. |
low | Cached result older than 30 days, or a fresh heuristic guess (US numbers default to imessage, all others to sms). |
Batch Limits
- 100 phones per request. Larger lists must be chunked client-side.
- Sequential calls at 1 request per second comfortably fit the default 60 req/min rate limit. Higher throughput requires a tenant-level rate-limit increase.
- Capability lookups do not count against your monthly message quota and have no per-call charge.
Caching Behavior
Detection results are stored in the recipient_capabilities table and cached for 90 days. The cache is shared across both the single-number and batch endpoints — a result populated by a GET is served from cache by a subsequent POST, and vice versa.
- First query: senderZ checks the number via the iMessage engine (or the heuristic path for batch requests), stores the result, and returns
cached: false(orconfidence: lowfor batch). - Subsequent queries within 90 days: senderZ returns the stored result. No additional detection call is made.
- After 90 days: The cache entry expires. The next query performs a fresh check.
The 90-day window balances accuracy against performance. A user who switches from Android to iPhone (or vice versa) may not be re-detected for up to three months — but in practice, when an iMessage send fails, senderZ automatically falls back to SMS, so a stale imessage cache entry does not block delivery.
Relationship to channel: “auto”
When you send a message with channel: "auto", senderZ runs the same iMessage detection internally. The key difference:
| Approach | When to Use |
|---|---|
GET /v1/capabilities/:number | Single-phone routing decision before sending |
POST /v1/capabilities | Batch enrichment of a contact list (up to 100 phones per request) |
channel: "auto" on /v1/messages | You want senderZ to handle routing automatically at send time |
All three approaches share the same recipient_capabilities cache and the same 90-day TTL. If you call either Capabilities endpoint and then send with channel: "auto", the send operation will use the cached result from your earlier check.
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request body — bad E.164, empty array, more than 100 phones, or malformed JSON |
| 401 | INVALID_API_KEY | Bad or missing API key |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
| 500 | SERVER_ERROR | Capability lookup failed during cache read — retry with backoff |
| 503 | DETECTION_UNAVAILABLE | iMessage detection backend is temporarily unreachable (single-number GET only) |