Lookup
A lookup tells you which carrier a US or Canada number is on and whether it is a mobile, a landline or a VoIP line. Run one before you text a list to skip landlines, or at signup to reject VoIP numbers.
Look up a number
GET /v1/lookup/{phone}
curl https://api.jointextkit.com/v1/lookup/+14155550132 \
-H "Authorization: Bearer ssms_sk_test_YOUR_KEY"{
"phone_number": "+14155550132",
"valid": true,
"line_type": "mobile",
"carrier": { "name": "Sandbox Wireless", "type": "mobile" },
"caller_name": null
}The number can be written any way you like: +14155550132, 4155550132
and (415) 555-0132 are the same lookup. It must have 10 digits, or 11
starting with 1. The response always carries the E.164 form. A restricted key
needs the lookup scope.
Response fields
| Field | Notes |
|---|---|
phone_number | The number you asked about, normalized to E.164. |
valid | false only when the carrier network reports that the number does not exist (unassigned or vacant). Everything else is true, including a number whose line type is unknown. |
line_type | mobile, landline, voip, or null. null means we could not classify the carrier; it is never a guess. |
carrier.name | The carrier the number is on today. It follows ports, so a number moved to another carrier shows the new one. null when the network returned no name. |
carrier.type | The same value as line_type. |
caller_name | Always null today. The field is reserved, so check for null before using it. |
A successful response also carries an X-Quota-Remaining header: the lookups
left in your account's day.
With the SDKs
import { TextKit } from 'jointextkit';
const sms = new TextKit(process.env.TEXTKIT_API_KEY);
const result = await sms.lookup.phone('+14155550132');
if (result.line_type === 'landline') {
// skip: this number cannot receive texts
}from jointextkit import TextKit
client = TextKit() # reads TEXTKIT_API_KEY
result = client.lookup.phone("+14155550132")
print(result["line_type"], result["carrier"]["name"])Test keys
A test key never contacts a carrier and is never billed. The answer is decided by the number alone, so your tests get the same result every time:
| Number | line_type | carrier.name |
|---|---|---|
| Last digit even | mobile | Sandbox Wireless |
| Last digit odd | landline | Sandbox Telecom |
+15005550010 | landline | Sandbox Telecom |
+15005550010 is the sandbox landline number, so it answers
landline although it ends in 0. valid is always true and
caller_name is always null with a test key.
Live keys
A live key returns real carrier data. Each answer is cached for 24 hours, so a second lookup of the same number inside a day returns the stored answer, and a port can take up to 24 hours to show. A cached answer still counts as a lookup.
On an account with a payment method, each successful live lookup costs $0.008. A lookup that ends in an error is not billed.
Sending a message never runs a lookup. The destination_carrier field on a
message is filled from the same 24-hour cache, so it is
null unless you looked the number up recently.
Limits
Lookups are counted per account, per UTC day. Test and live keys draw from the same counter.
| Account | Lookups per day |
|---|---|
| With a payment method | 5,000 |
| New account, no payment method | 100 |
| No payment method, after live access is approved | 250 |
A lookup that fails with carrier_error still uses one of the day's lookups;
a request rejected with invalid_request does not. The usual limit of 60
requests a minute per key applies too.
Errors
| Status | Code | When |
|---|---|---|
400 | invalid_request | The number is not a valid US or Canada number. param is phone. |
401 | invalid_api_key | The key is missing, malformed or revoked. |
403 | insufficient_scope | A restricted key without the lookup scope. required_scope names it. |
403 | forbidden | A live key on an account with no lookup allowance. Add a payment method in the console. |
429 | quota_exceeded | The day's lookups are used up. The message states the limit. |
429 | rate_limited | Too many requests in a minute. Wait for the Retry-After seconds. |
502 | carrier_error | The carrier network did not answer. Try again shortly. |
{
"error": {
"code": "quota_exceeded",
"message": "Daily lookup quota reached (5000/day).",
"request_id": "req_a1B2c3D4e5F6g7H8i9J0"
}
}Every code is explained in Errors.
Retired: spam scores
GET /v1/lookup/{phone}/spam was retired on 2026-09-30 and answers
410 endpoint_retired. The Node SDK still has sms.lookup.spam(), which
now always throws that error.