# Verify

Source: https://joinsimplesms.com/docs/verify
Index: https://joinsimplesms.com/llms.txt

Phone verification in two calls. SimpleSMS generates the code, sends it, enforces
expiry and attempt limits, and defends against SMS pumping. You never store a
code, and you don't need to own a phone number.

## The whole integration

Two calls, no dependency required:

```js
const send = (to) => fetch('https://api.joinsimplesms.com/v1/verify', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.SIMPLESMS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ to }),
}).then((r) => r.json());

const check = (to, code) => fetch('https://api.joinsimplesms.com/v1/verify/check', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.SIMPLESMS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ to, code }),
}).then((r) => r.json());
```

Or from the terminal: the CLI ships inside the SDK package, so `npx` needs
no install at all:

```bash
SIMPLESMS_API_KEY=ssms_sk_test_... npx joinsimplesms verify +14155550132
npx joinsimplesms verify +14155550132 482193   # check the code
```

Or with the official SDK, which adds retries, typed errors and automatic
idempotency keys:

```bash
npm install joinsimplesms
```

```js
import { SimpleSMS } from 'joinsimplesms';
const sms = new SimpleSMS(process.env.SIMPLESMS_API_KEY);

await sms.verify.send({ to: '+14155550132' });
const { verified } = await sms.verify.check({ to: '+14155550132', code });
```

In Python (`pip install joinsimplesms`, no dependencies):

```python
from joinsimplesms import SimpleSMS
client = SimpleSMS()  # reads SIMPLESMS_API_KEY

client.verify.send(to="+14155550132")
verified = client.verify.check(to="+14155550132", code=code)["verified"]
```

## Send a code

```bash
curl -X POST https://api.joinsimplesms.com/v1/verify \
  -H "Authorization: Bearer ssms_sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+14155550132" }'
```

```json
{
  "id": "ver_a1B2c3D4e5F6g7H8",
  "object": "verification",
  "phone": "+14155550132",
  "status": "pending",
  "attempts": 0,
  "charged": false,
  "expires_at": "2026-08-07T12:10:00.000Z"
}
```

Send an `Idempotency-Key` header so that a retried request, after a timeout
for example, returns the first verification instead of texting a second code.
It works the same way as [on messages](/docs/messages#idempotency).

Optional `app_name` (24 chars max) puts your product's name in the message.
The body is a fixed SimpleSMS template; you can't set the text, which is what
keeps verification traffic out of spam filtering.

## You don't need a phone number

Verify sends from SimpleSMS's own verification numbers, registered under our 10DLC
campaign. You don't buy a number, you don't provision anything, and you pay no
monthly number fee; verification is the whole product.

The same recipient always gets codes from the same sender, so a second code
lands in the thread they already have.

If you'd rather codes came from a number you own, pass it as `from`:

```json
{ "phone": "+14155550132", "from": "+16155550184" }
```

Platforms verifying for their own customers can pass `customer_id`; it is
echoed on the verification and its events and counted in that
[customer's usage](/docs/customers).

## Check the code

```bash
curl -X POST https://api.joinsimplesms.com/v1/verify/check \
  -H "Authorization: Bearer ssms_sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+14155550132", "code": "482193" }'
```

```json
{ "verified": true, "status": "approved", "attempts": 1, "charged": true }
```

That's the whole integration.

## You only pay when it works

A verification is billed **only** when a code is actually verified. Wrong
codes, expiries, abandoned flows and anything Shield blocks are free;
every response tells you plainly with `charged`.

## Rules SimpleSMS enforces for you

| | |
| --- | --- |
| Code lifetime | 10 minutes |
| Check attempts | 5, then the code is dead |
| Resend cooldown | 60 seconds per number |
| Per number | 5 an hour, 10 a day |

Statuses: `pending`, `approved`, `expired`, `max_attempts`, `blocked`.

## Shield

SMS pumping is fraud where someone farms revenue-share by triggering
verification codes to numbers they control. SimpleSMS blocks it before you're
charged:

- **US and Canada only.** Country code +1 also covers Jamaica, the Dominican
  Republic and the Bahamas, the classic pumping destinations. We allowlist
  real US and Canadian area codes and reject the rest.
- **Velocity limits** per destination, per account and per source.
- **No VoIP.** Disposable VoIP numbers are rejected.

Blocked attempts return `403` with code `verification_blocked`, a `reason`,
and `charged: false`.

## Sandbox

Test keys never send a real message. Any code you send returns a verification
whose code is `111111`, and these codes are deterministic:

| Code | Result |
| --- | --- |
| `111111` | approved |
| `000000` | invalid |
| `222222` | expired |
| `333333` | max attempts |

Sandbox has **no resend cooldown**, so you can iterate on a "resend code"
button freely. Live enforces 60 seconds per number. To test that path
deterministically, send to `+15005550003`; it always returns the cooldown
`429` with `retry_after` in the body.

## Retrieve a verification

`GET /v1/verify/{id}` returns the object with its current status, attempt
count, and whether it was charged.
