API docs
The Spam I Am API lets your code or AI agent check whether a number shows a spam label on AT&T, Verizon and T-Mobile. You start a check, your calling platform calls each carrier's test line from the number being checked, and you read the result: the label, the caller name and a screenshot from a real phone on each carrier.
Last updated: 2026-10-24
- Base address:
https://ismynumberspam.com/v1 - Format: JSON in, JSON out
- Keyless first check: the first check on any number works without a key
- Keys: make one on your Balance page after signing up. Send it as
Authorization: Bearer sia_... - Machine-readable spec: openapi.json
The API can't place calls. The test calls must come from the number you're checking, and we never spoof numbers, so your platform (Twilio, Retell, Vapi, Bland, a dialer or a phone) places them.
Quickstart
1. Start a check
curl -X POST https://ismynumberspam.com/v1/checks \
-H "Content-Type: application/json" \
-d '{"number": "2145550100"}'
Without a key this uses the number's free check, which covers every carrier that's online. The answer includes a token. Keep it: it's the only way to read this check's result.
{
"id": "9f1c...",
"number": "+12145550100",
"status": "waiting_for_calls",
"token": "ct_...",
"calls": [
{"carrier": "AT&T", "dial": "+14695550123", "status": "waiting", "seconds_left": 239},
{"carrier": "Verizon", "dial": "+1...", "status": "waiting", "seconds_left": 239},
{"carrier": "T-Mobile", "dial": "+1...", "status": "waiting", "seconds_left": 239}
],
"instructions": "From +12145550100, with caller ID showing, call each 'dial' number..."
}
2. Call each test line
From the number being checked, with caller ID showing, call each dial number, one after another. Our phone reads the screen, then sends the call to voicemail, so you can hang up after a few seconds. You have 2 minutes for one carrier, plus 1 minute for each additional carrier.
3. Read the result
Ask for the check every 5 seconds until status is complete:
curl "https://ismynumberspam.com/v1/checks/9f1c...?token=ct_..."
{
"id": "9f1c...",
"status": "complete",
"flagged": true,
"flagged_on": ["AT&T"],
"calls": [
{"carrier": "AT&T", "status": "complete", "flagged": true, "label": "Spam Risk",
"caller_name": "Dallas, TX", "checked_at": "2026-10-24T18:02:11.000Z",
"screenshot_url": "https://ismynumberspam.com/v1/checks/9f1c.../screenshots/....jpg?token=ct_..."},
{"carrier": "Verizon", "status": "complete", "flagged": false, "label": null,
"caller_name": null},
{"carrier": "T-Mobile", "status": "incomplete", "reason": "no_call"}
],
"cost": {"free": true, "held_cents": 0, "charged_cents": 0}
}
With a key, send Authorization: Bearer sia_... instead of the token.
Endpoints
POST /v1/checks
Starts a check. Body:
number(required): the US number you'll call from, in any format, such as"2145550100","+1 214-555-0100"or"(214) 555-0100"carriers(optional): a list such as["AT&T", "Verizon"]. Leave it out to check every carrier that's online.webhook_url(optional): an https address to send the result to when the check finishes. See Webhooks.
Answers 201 with the check. Without a key, the number must still have its free check.
With a key, the number's free check is used if it hasn't been. After that, the price is held from your balance while the check runs. You're charged only for carriers that return a result.
GET /v1/checks/{id}
The check and each carrier's result. Add ?token=ct_... for a keyless check. While any carrier is still waiting, the answer has a Retry-After: 5 header.
Each entry in calls:
| Field | Meaning |
|---|---|
carrier | AT&T, Verizon or T-Mobile |
dial | The test line to call for this carrier |
status | waiting, complete or incomplete |
seconds_left | While waiting: time left to place the call |
flagged | When complete: true if the label is a spam warning |
label | When complete: the label shown, such as "Spam Risk", or null |
caller_name | When complete: the caller name shown, or null |
reason | When incomplete: no_call (no call arrived in time) or unreadable_screen |
screenshot_url | The incoming-call screen, as a JPEG |
checked_at | When the call rang |
At the top level, flagged is true if any carrier shows a spam label, false if none do, and null if no carrier returned a result. flagged_on lists the carriers that do.
**Incomplete with no_call, even though you dialed from the right number with caller ID on?** The carrier may have blocked the call before it rang. Treat that as more urgent than a label, and check your logs for SIP 603+. What 603+ means
GET /v1/checks/{id}/screenshots/{result}.jpg
The screenshot. Use screenshot_url from the check; it already carries the token for a keyless check.
GET /v1/balance
Needs a key. Your balance, the amount held for checks in progress, and what's available, in cents. Also this key's spending limit (key_spend_limit_cents, null for none) and what it has spent (key_spent_cents).
POST /v1/handoffs and GET /v1/handoffs/{id}
Ask your human to approve spending. See below.
GET /v1/coverage
No key needed. Which carriers are online now, prices in cents by number of carriers, whether the free first check is on, and the dialing window.
Webhooks
Pass webhook_url when you start a check, and we POST the finished check to it: once every carrier has a result or its dialing window has ended.
{"type": "check.completed", "check": { ...the same JSON as GET /v1/checks/{id}... }}
- Secret: the answer to
POST /v1/checkshaswebhook.secret(startingwhsec_). It's shown only there. - Signature: each POST has a
Spamiam-Signature: t=1761321731,v1=5f2b...header. Compute HMAC-SHA256 of<t>.<raw body>with the secret, and compare it withv1. Reject oldtvalues to stop replays. - Answer with any 2xx status. Otherwise we try again after 30 seconds, 2 minutes, 10 minutes and 1 hour, then stop.
GET /v1/checks/{id}showswebhook.status(pending,deliveredorfailed) and the last error. - Addresses must be https and reachable on the public internet.
Ask your human to approve spending
After a number's free check, checks cost money. An agent shouldn't pay or handle card details. Instead it asks its human:
curl -X POST https://ismynumberspam.com/v1/handoffs \
-H "Content-Type: application/json" \
-d '{"reason": "Check our sales line before Monday's campaign", "amount_cents": 500}'
{
"id": "4c7e...",
"status": "pending",
"approve_url": "https://ismynumberspam.com/approve/4c7e...",
"token": "ht_...",
"expires_at": "2026-10-25T18:00:00.000Z"
}
- **Show
approve_urlto your human.** They sign in or create an account, add funds if needed, set a spending limit and approve (or decline). - Ask for the request every 5 seconds:
GET /v1/handoffs/{id}?token=ht_.... Itsstatusbecomesapproved,declinedorexpired(after 24 hours). - **When it's approved, the answer has
api_key,** shown once. Store it and send it asAuthorization: Bearer sia_.... It can spend up to the limit your human set.
All fields are optional: reason (up to 300 characters, shown to your human), number (the number you want to check) and amount_cents (the amount you're asking for; suggested as the limit).
Already have a key that hit its limit? Send the request with your key. Approving it sets your key's new limit; no new key is made. Read it with the same key.
Errors
Errors use one format:
{"error": {"code": "too_soon", "message": "(214) 555-0100 was checked recently..."}}
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_number | Send a 10-digit US number |
| 400 | invalid_carriers | Use AT&T, Verizon or T-Mobile |
| 400 | invalid_json | Send a JSON object |
| 400 | test_line | That's one of our test lines. Send the number you'll call from |
| 401 | api_key_required | Send a key |
| 401 | invalid_api_key | The key is wrong or revoked. Make a new one |
| 400 | invalid_webhook_url | Use a public https address |
| 402 | insufficient_funds | Ask your human to add funds (approval request). The error has price_cents and available_cents |
| 402 | key_limit | This key reached its spending limit. Ask your human to approve more. The error has limit_cents and spent_cents |
| 404 | not_found | Wrong id, or the check belongs to another key or token |
| 409 | free_check_used | This number had its free check. Use a key |
| 429 | too_soon | Each number can be checked once an hour. Wait for Retry-After seconds |
| 429 | too_many_open | Finish or let your open checks expire first |
| 429 | daily_limit | Daily check limit reached |
| 429 | free_limit | Too many keyless checks from your network today. Use a key |
| 429 | handoff_limit | Too many approval requests from your network today |
| 503 | no_lines | No test line is online for those carriers. Try again later |
Rules
- Check only numbers you control. The test calls must come from the number.
- Ask before spending. Checks after the first cost money. An agent shouldn't add funds itself. It asks its human with an approval request.
- Once an hour per number. A check with no calls doesn't count.
- Don't auto-rotate numbers when one is flagged. Pause it, tell a human and request a review. Why numbers get flagged
Pricing
The first check on any number is free. After that, $0.25 for one carrier or $0.40 for two or three, from the same balance you use on the website. Incomplete carriers are free. Pricing
MCP
Agents that use Model Context Protocol can connect to https://ismynumberspam.com/mcp instead. MCP server
FAQ
Do I need a key?
Not for the first check on a number. After that, sign up, add funds and make a key on the Balance page. An agent can also ask its human for a key with an approval request.
Can I cap what an agent spends?
Yes. Every key can have a spending limit. Set it when you make the key on the Balance page, or when you approve an agent's request.
Can the API place the test calls for me?
No. The calls must come from the number you're checking, so your own platform places them. We never spoof numbers.
How do I get the result?
Ask for the check every 5 seconds until its status is complete, or pass a webhook_url and we'll send it to you. Results usually arrive about 5 seconds after each call rings.
What does an API check cost?
The same as on the website: $0.25 for one carrier or $0.40 for two or three. Carriers that return no result are free.
See what your number shows
Call our real AT&T, Verizon and T-Mobile phones and see the label, caller name and a screenshot. The first check of any number is free.