# Mobile proxy API

Base path: `https://sms.red/api/v1/developer`. Use the matching store origin for a reseller key. Authenticate with `X-API-Key` (or `Authorization: Bearer`). Every order, wallet debit, credential and request lookup belongs to the account bound to that key. Credential responses use `Cache-Control: no-store`; do not log credentials.

## Buy and recover

1. GET `/proxy-products` for currently purchasable plans, USD prices, `offerKey`, `renewalSupported` and `fingerprintSupported`. Optional initial locations use GET `/proxy-locations?pricingCode=PLAN_CODE`; select IDs from that plan’s location catalog.
2. Save a new `Idempotency-Key` of 8–128 letters, digits, dots, underscores, colons or hyphens for one intended purchase.
3. POST `/proxies` with `{"pricingCode":"day_1","offerKey":"CURRENT_CATALOG_OFFER_KEY"}`. Copy the exact current offer key from the catalog. A changed offer returns `409` before a wallet charge; refresh and review the current plan. Optional `locationId` selects an initial location; `random` is supported. The wallet charge, proxy and request record commit together. The response is `201` with the proxy in `paid` or a later state.
4. Poll GET `/proxies/{proxyId}` until `active`, `disconnected` or `failed`. Preparation can take several minutes. An uncertain supplier result remains pending and must not be repurchased.
5. If the HTTP response is lost, repeat the same body and key, or GET `/proxy-requests/{key}`. The original proxy is returned without another charge. Reusing a key for a different request returns `409`.

GET `/proxies` lists this key account's proxies. Optional `status` accepts `pending`, `paid`, `provisioning`, `active`, `disconnected`, `expired` or `failed`. Invalid statuses return `400`.

## Manage a ready proxy

Read `capabilities` on the selected proxy. `renew`, `disconnect`, `fingerprint`, `relocate`, `rotate`, `fastReset` and `hardReset` describe its available actions; do not infer support from another proxy. A plan without renewal ends at expiry. A new purchase creates a new connection and is not an extension of the old license. The explicit three-hour plan uses `pricingCode: "hour_3"`; it never substitutes for a one-hour purchase.

| Method and path | Body | Effect |
| --- | --- | --- |
| GET `/proxies/{id}/locations` | None | Currently available locations |
| POST `/proxies/{id}/connect` | `{"locationId":"random"}` or a listed location ID | Connect and return updated credentials |
| POST `/proxies/{id}/disconnect` | None | Disconnect and clear saved connection credentials |
| POST `/proxies/{id}/reset` | `{"mode":"fast"}` or `{"mode":"hard"}` | Request an IP/modem reset; inspect returned credentials |
| POST `/proxies/{id}/fingerprint` | `{"fingerprint":"none"}`; also `linux`, `macos`, `windows` | Change TCP fingerprint |
| POST `/proxies/{id}/renew` | None; **Idempotency-Key required** | Charge the current plan price and queue one renewal |

Connect, reset and fingerprint operations require a ready, unexpired license. Expired or failed licenses cannot be operated. Copy the current IP, port, username and password after connection changes. HTTP uses `port`; SOCKS5 uses the explicit `socks5Port`. The connection host in `ip` may be a DNS hostname. Do not calculate one protocol’s port from the other; URI-format IPv6 hosts with brackets. URI-encode the username and password when constructing a URL.

Persist an `Idempotency-Key` for connect and reset, including free actions. After an uncertain response, retain the same body and key. A reset acknowledgement does not guarantee immediate traffic readiness; wait for the returned state and refreshed credentials. Available reset modes depend on the selected proxy. HTTP `422` with `code: "PROXY_ACTION_REJECTED"` is a definite control rejection; a later deliberate attempt may use a new key. An uncertain control remains pending for review and is never dispatched again merely because the client retries.

A renewal returns the proxy with `renewalStatus: "pending"`. Poll its details; `completed` means the expiry was extended, and `failed` means the renewal was rejected and refunded. A pending renewal blocks another key from charging the same license. Repeating the original key is safe. A disconnected license remains disconnected after renewal. Check `renewalPriceUsd` before initiating a paid renewal. Renewal is available before expiry only and requires `capabilities.renew: true`. An unsupported renewal is rejected before charging.

## Failure and recovery

A definitive rejected purchase or renewal refunds exactly once. A timeout does **not** prove rejection: the request may already have reached the supplier. Those operations retain their charge and durable pending state while reconciling. Renewals can be recovered through an authoritative change in supplier expiry. An unconfirmed purchase without a billing receipt or with ambiguous inventory requires support review; it is never blindly purchased again or assigned an arbitrary proxy. Keep the proxy ID and request key when contacting Support. Never include passwords or API keys.

`400`: invalid input or insufficient funds. `401/403`: invalid key or store. `404`: missing, unavailable or foreign proxy. `409`: conflicting request key or renewal already pending. `410`: expired. `429`: rate limited; wait before another connection action. `422`: a control was definitively rejected or input is invalid. `503`: the selected plan or proxy network is unavailable; a new purchase is not charged when its availability cannot be established. For money operations, preserve the original key through every retry.

## Lifecycle webhooks

Configure a webhook in Developers. New webhooks include proxy lifecycle events alongside SMS events. Existing webhooks keep their subscriptions; opt in with the authenticated dashboard API `PATCH /api/v1/webhooks/{id}` and an `eventTypes` array. Supported proxy events:

- `proxy.ready`: connection management is ready, including a disconnected license.
- `proxy.updated`: connection, disconnection, reset or fingerprint changed.
- `proxy.renewed`: renewal confirmed and expiry extended.
- `proxy.renewal_failed`: renewal definitively rejected and refunded.
- `proxy.failed`: purchase definitively rejected and refunded.
- `proxy.expired`: license expired.

The JSON payload contains `type`, `createdAt` and `proxy` with `id`, `status`, `pricingCode`, `expiresAt`, `locationId` and `fingerprint`. It contains no passwords or connection strings. Retrieve credentials through the authenticated API.

Events and deliveries are persisted with the state transition. Delivery uses `X-SMSRed-Event-Id` and `X-SMSRed-Signature: sha256=<hex HMAC-SHA256 of the raw JSON body>`. Verify the signature with the webhook secret, deduplicate by event ID and return HTTP 2xx. Retries reuse the same event ID and payload, with exponential backoff and up to eight attempts. Failed deliveries can be retried in the existing webhook delivery API. See [webhooks](https://sms.red/docs/ai/webhooks.md) for verification and delivery handling.

The HTTP API supports these proxy operations. The existing MCP tools currently cover numbers and carrier lookup; use HTTP for proxies.

## Coverage and location display

Each current pricing option may include `coverage`: `locationCount`, `cityCount`, `stateCodes` and `carriers`. Coverage belongs to that option's current integration. `cityCount` counts distinct labeled city/state areas; two cities with the same name in different states remain separate. `coverage: null` means metadata is unavailable and must not be presented as zero coverage. Listings can change; a listed location is not a reserved connection.

Pass `pricingCode` to `/proxy-locations` to browse the selected plan. For an existing proxy, use `/proxies/{proxyId}/locations`, which keeps its original integration. Locations include optional `city`, `stateCode` and `carrier` display fields. Keep `id` opaque and submit it unchanged; an ID can name a different city than the display label. Conflicting carrier labels remain unclassified. Missing inventory counts remain `available: null`.

The dashboard supports city/state/carrier filters and shorter result lists. Checkout previews coverage before paying; a location is chosen in the dashboard after activation. These browsing controls make no purchase or renewal.
