# sms4sats L402 API Skill

**Version:** 2.3.0 (2026-05-08) | **Status:** Production | **Maintained by:** sms4sats core team

## What this skill does

This skill helps AI agents use the sms4sats L402 API to request one-time verification numbers or rent numbers for SMS delivery, then retrieve incoming SMS messages and verification codes by polling order status with Lightning payment auth.

## Base URLs

- L402 API: `https://api.sms4sats.com/v2/l402`
- Health check: `https://api.sms4sats.com/health`
- Public skill document: `https://api.sms4sats.com/skill.md`
- 402index verification: `https://api.sms4sats.com/.well-known/402index-verify.txt`
- L402 auto-discovery: `https://api.sms4sats.com/.well-known/l402-services`
- Existing API docs: https://docs.sms4sats.com

## Available endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/health` | Server health check |
| `GET` | `/.well-known/402index-verify.txt` | 402index.io verification token |
| `GET` | `/.well-known/l402-services` | L402 auto-discovery document (402index) |
| `GET` | `/v2/l402` | API discovery & documentation |
| `GET` | `/v2/l402/services` | List available services with codes |
| `GET` | `/v2/l402/catalog` | SMS/rent offers by country & service |
| `POST` | `/v2/l402/order` | Create L402-protected order |
| `GET` | `/v2/l402/order/:id` | Poll order status (requires L402 auth) |
| `POST` | `/v2/l402/order/:id/cancel` | Cancel paid receive-sms order & refund (requires L402 auth) |
| `POST` | `/v2/l402/webhooks/rent` | Receive SMS delivery (internal/webhook) |

## Supported order types

- `receive-sms` — one-time verification SMS
- `receive-sms-reactivate` — reactivate number after first code received
- `rent-number` — temporary number rental for incoming SMS

## L402 authentication flow

1. `POST /v2/l402/order` returns `402 Payment Required`
2. Response includes:
   - `invoice` — BOLT11 Lightning invoice to pay
   - `macaroon` — serialized authorization credential
   - `priceSats` — total order cost
3. Client pays the invoice
4. Client retrieves preimage from wallet/node
5. Poll `GET /v2/l402/order/{orderId}` with header:  
   ```
   Authorization: L402 {macaroon}:{preimage}
   ```

## Country support

- **Numeric ID** — HeroSMS country id (e.g., `187` for USA)
- **ISO alpha-2** — Standard code (e.g., `US`, `GB`, `DE`)
- **Country name** — Full English name (e.g., `United States`, `England`)
- **Auto selection** — `country: "auto"` lets provider pick best availability

## Service codes

Use `GET /v2/l402/services` to list available codes. Examples:

- `tg` — Telegram
- `go` — Google
- `fb` — Facebook
- `wa` — WhatsApp
- `ds` — Discord
- `ot` — Any other (catch-all for unlisted services)

Or use user-facing alias: `other` → `ot`

Service names are normalized automatically (example: `telegram` -> `tg`).

## Pricing

- Provider quote currency defaults to USD.
- USD is converted to sats using CoinGecko BTC/USD rate.
- Cached FX rate is stored in Firebase: `sms4sats/fxrates/current`.
- Final charged amount is `providerCostSats * PRICE_MULTIPLIER`.

## Example: Get services

```bash
curl -sS https://api.sms4sats.com/v2/l402/services
```

Response:
```json
{
  "services": [
    { "code": "tg", "name": "Telegram" },
    { "code": "go", "name": "Google,youtube,Gmail" },
    { "code": "fb", "name": "facebook" }
  ]
}
```

## Example: Create receive-sms order

```json
{
  "type": "receive-sms",
  "country": "US",
  "service": "tg",
  "refundInvoice": "lnbc..."
}
```

## Example: Rent number order

```json
{
  "type": "rent-number",
  "country": "US",
  "service": "tg",
  "durationMinutes": 120
}
```

## Refund policy

- **Refundable:** `receive-sms` orders only
- **Non-refundable:** `rent-number` and `receive-sms-reactivate`
- **Refund trigger:** No SMS arrives within timeout, or number allocation fails after payment
- **Refund mechanism:** Requires a no-amount Lightning invoice (`refundInvoice` parameter)
- **`refundInvoice` expiry:** The refund invoice must have **at least 30 minutes** of validity remaining at order creation time. Invoices expiring sooner are rejected with `REFUND_INVOICE_EXPIRES_TOO_SOON`.

## Manual cancellation

- **Endpoint:** `POST /v2/l402/order/:id/cancel` (requires same L402 auth as polling)
- **Allowed:** `receive-sms` orders only, after **3 minutes** from payment time, while **no SMS has been received**
- **Flow:** API cancels provider activation first, then refunds the user's `refundInvoice`
- **Timeout policy:** Orders auto-cancel after **20 minutes** from payment if no SMS arrives — same safety sequence (verify no SMS, cancel at provider, then refund)
- **Error codes:**
  - `CANCEL_TOO_SOON` — less than 3 minutes since payment
  - `CANCEL_SMS_RECEIVED` — SMS already arrived; cannot cancel

## Order status flow

```
pending_payment
  → (pay invoice)
paid
  → allocating_number
  → waiting_for_sms / rent_active
  → code_received / rent_active (polling)
  → completed (or refunded/failed/expired)
```

Status values:
- `pending_payment` — Waiting for payment
- `paid` — Invoice paid, processing order
- `allocating_number` — Requesting number from provider
- `waiting_for_sms` — Number assigned, awaiting SMS
- `code_received` — SMS arrived
- `rent_active` — Rental period ongoing
- `completed` — Order successful
- `refund_pending` — Refund initiated
- `refunded` — Refund completed
- `refund_failed` — Refund failed
- `failed` — Order failed (number unavailable, etc.)
- `expired` — Order timed out

## Polling guidance

- **Endpoint:** `GET /v2/l402/order/{orderId}`
- **Interval:** 5–10 seconds
- **Stop when:** Status is `completed`, `refunded`, `failed`, or `expired`
- **Timeout:** Recommended 20 minutes; check `expiresAt`

## Error responses

| Status | Code | Meaning |
|--------|------|---------|
| 400 | `VALIDATION_ERROR` | Invalid request payload |
| 400 | `INVALID_COUNTRY` | Country not recognized |
| 400 | `INVALID_REFUND_INVOICE` | refundInvoice is not a valid no-amount BOLT11 invoice |
| 400 | `REFUND_INVOICE_EXPIRES_TOO_SOON` | refundInvoice expires in less than 30 minutes |
| 402 | `PAYMENT_REQUIRED` | Invoice + macaroon in response |
| 403 | `INVALID_MACAROON` | L402 auth verification failed |
| 403 | `EXPIRED_MACAROON` | Macaroon has expired (re-create the order) |
| 404 | `NOT_FOUND` | Order not found |
| 409 | `CANCEL_TOO_SOON` | Cancel attempted less than 3 min after payment |
| 409 | `CANCEL_SMS_RECEIVED` | SMS already received; cannot cancel |
| 409 | `UNAVAILABLE` | No numbers available for country/service |
| 429 | (rate limit) | Too many requests |
| 500/503 | (server error) | Temporary service issue |

## Agent best practices

1. **Country selection:** Use `country: "auto"` unless user specifies otherwise
2. **Service selection:** Check `/v2/l402/services` if unsure of codes
3. **Secure storage:** Keep `macaroon` and `preimage` secret; never expose to clients
4. **Polling:** Don't spam; respect 5–10 second intervals
5. **Idempotency:** Don't retry order creation; poll the existing order instead
6. **Refunds:** Always include valid `refundInvoice` for `receive-sms` — must be a no-amount invoice valid for **at least 30 more minutes**
7. **Pricing:** Use `priceSats` from response; don't calculate locally
8. **Timeouts:** Respect `expiresAt` field in order response

## Privacy & compliance

- **Prohibited:** Abuse, spam, fraud, phishing, or illegal activity
- **Account security:** Don't use for account recovery without user consent
- **Data:** Incoming SMS codes are deleted after order completion
- **Retention:** Minimal logging; no code/phone storage in user-accessible logs
- **GDPR:** Comply with EU data residency if applicable

## Testing

For development/testing:

```bash
# Health check
curl https://api.sms4sats.com/health

# API docs
curl https://api.sms4sats.com/v2/l402

# Services catalog
curl https://api.sms4sats.com/v2/l402/services

# Full SMS/rent offers
curl https://api.sms4sats.com/v2/l402/catalog
```

## Maintenance notes

- **Ownership:** User runs/maintains server (NOT the agent)
- **Build:** `npm install`
- **Test:** `npm test` (18 tests, all passing)
- **Refresh catalog:** `npm run refresh:catalog` (loads HeroSMS data)
- **Deploy:** PM2 process manager (see `ecosystem.config.js`)
- **Logs:** Human-readable via pino-pretty
- **Data files:** `data/countriesList.json` and `data/servicesList.json` (local lookup)
