# Rentals, Unlimited and cancellation

Browse `GET /services?type=rent&countryCode=US`, then request `/services/{serviceId}/durations?countryCode=US&kind=rent`. Use the returned IDs and exact price. **For `kind=rent`, each term’s `duration` is in days. `POST /rentals.duration` and returned rental-order `duration` are in milliseconds. Multiply the quoted days by `86400000`: a 30-day quote with `duration: 30` must be purchased with `duration: 2592000000`.** OTP term queries (`kind=otp`) already return milliseconds. Available services and terms depend on stock and the account's store catalog. A plan is available when `count > 0` or it explicitly has `onDemand: true`. An on-demand plan can have `count: 0`: the number is procured after purchase, so no inventory count is promised. Always request the current exact term before buying.

**Unlimited** is a catalog service for a general rental, rather than a duration or a special API flag. Find its service ID in the rental catalog and quote it like any other service. The number can receive supported SMS during its active rental term. Service availability is not a guarantee that every external app will accept a number.

Buy with `POST /rentals`, a required `Idempotency-Key`, and `serviceId`, `countryCode`, `duration` and `maxPrice`. Optional fields are `providerId` and `description`. Do not request `alwaysOn` or `autoRenew` purchase options: these are currently rejected, although the OpenAPI DTO lists their names.

## Additional-country per-service rentals

Some countries offer service-specific 7-day and 30-day rentals. Use the returned catalog and exact term: a per-service rental receives supported SMS for the selected service only. Do not interpret an “other service” or multi-service offer as Unlimited. Unlimited is available only when the catalog explicitly returns that product.

Plans can return `requiresActivation: true` and `automaticCancellation: false`. Activation can take 10–15 minutes or longer. The customer wallet is charged only after the number is confirmed active. A pending or uncertain purchase must be reconciled using its saved purchase attempt and the original `Idempotency-Key`; never submit another key to obtain the same intended rental. A delayed accepted allocation can complete later under the original price ceiling and current wallet/permission checks.

These plans have no automatic cancellation, refund or wallet auto-renew. Read the order's eligibility fields before presenting any such action. The usual cancellation window below applies only to eligible rentals. A successful authenticated inbox read does not establish that a particular external service will deliver or accept an SMS.

## Add another service to the same number

Eligible active rentals expose `canAddService: true`. Read `GET /rentals/{rentalId}/services` to obtain the available service IDs, names, additional USD totals and `durationDays`. Adding a service uses an already rented number; new-number stock does not establish whether that specific phone can accept the service. Availability is checked when adding.

Call `POST /rentals/{rentalId}/services` with a required saved `Idempotency-Key` and JSON `{ "serviceId": "<quoted ID>", "maxPrice": "1.68" }`, using the exact quoted price as your ceiling. The API key binds the account; do not send `accountId`. The dashboard uses its authenticated account instead. Each addition buys a separate **seven-day** rental for the selected service on the **same phone number**. The response returns the new rental ID and its actual expiry. Its term starts when added and does not extend or change the original service. Read messages using the new rental ID; each service has its own paid SMS history.

If the response is uncertain, inspect the purchase attempt and reuse the same key and identical request. An accepted addition can remain processing until the number is active; the wallet is charged after confirmation. The dashboard saves the request across closure and reload. Do not issue another key to bypass a pending result. A confirmed failed attempt has no wallet charge; a different service can then use a new intended-purchase key. Added services have no automatic cancellation, refund or wallet auto-renew. They remain separate paid services, rather than Unlimited access.


## One-hour SMS rentals

Where the current catalog offers a one-hour SMS rental, buy it through `POST /rentals` with `duration: 3600000`. It is a full 60-minute rental and appears in the rental history, separate from a 20-minute verification. A rental term quote may express one hour as `duration: 0.041666666666666664` days; do not round it to a whole day. Multiply by `86400000` to obtain the exact purchase duration. The one-hour price is at least USD0.20 above the matching 20-minute OTP price under the same account pricing; use the server quote rather than deriving your own charge. Availability is service-specific, and only explicitly supported renewal actions may be offered.

## Cancellation

`POST /rentals/{id}/cancel` cancels a rental only when the server considers it eligible. The cancellation window is **5 through 120 minutes after purchase, inclusive, provided no SMS has been received**. Read the order's `isCancelable` before offering cancellation; the server checks again when the request arrives. Pending transfers or renewals and provider restrictions can also prevent it.

A successful cancellation returns HTTP 200 with an empty body. Refund accounting follows confirmed supplier cancellation and the original payer. An ambiguous supplier outcome needs reconciliation; do not promise a refund before confirmation. After a timeout, read the order before retrying.

OTP verifications use `/verifications/{id}/cancel`; eligibility is also server-controlled through `isCancelable`. The rental's 5–120-minute rule must not be assumed to apply to OTP orders.

## Auto-renew

Eligible active rentals expose `canAutoRenew` and `isAutoRenewEnabled`. TextVerified’s dashboard lets the customer set auto-renew; it is off by default. Eligible TextVerified rentals use wallet-funded renewal for the same term, with the original purchase price as ceiling. The renewal worker checks each minute and starts renewal 15 minutes before expiry. Insufficient funds or a higher price disables renewal.

The public developer API supports one-time renewal of eligible active numbers using the quote and renewal endpoints below. Auto-renew consent remains dashboard-only; reactivation is unavailable. Legacy manual-renewal or reactivation routes in the broader dashboard Swagger are separate from these API-key endpoints. Proxy renewal is described in the [proxy guide](https://textverified.org/en/store-docs/proxies).

[Documentation index](https://textverified.org/en/store-docs/index)

## Unlimited carrier selection

US Unlimited rentals accept an optional `carrier` value: `att` for AT&T or `tmobile` for T-Mobile. Pass the same value to `GET /services/{serviceId}/durations` and `POST /rentals`. Use only a returned duration and price; each choice has its own availability. Omission preserves the default routing. An explicit choice never falls back to the other carrier option. The selector is unavailable for other services or countries. Carrier labels follow the configured number supply; a delivered number’s physical carrier has not been independently verified.

Catalog prices refresh sitewide every five minutes. Browsing uses the shared cached snapshot; a purchase checks the current exact price and stock again. Save the selected carrier with the purchase Idempotency-Key: changing it under the same key returns a conflict.

## Optional US area code

US Unlimited SMS rentals on T-Mobile accept an optional three-digit `areaCode`, such as `212`. The ordinary route adds **USD1.00 per rented number once at purchase**, with no added area-code fee on renewals. Account-specific Any-area-code agreements can use a different route and do not necessarily apply after selecting an area code. Quote the exact selected carrier and area code again; do not calculate its total by adding USD1 to an account-specific Any-area-code price. Availability is term-specific and this option does not apply to AT&T, app-specific rentals, voice or other countries.

Pass `carrier=tmobile&areaCode=212` to the service duration query, then pass the same `carrier` and `areaCode` to `POST /rentals`. The returned `price` and your `maxPrice` must include the one-time fee. Changing the code under a reused `Idempotency-Key` returns a conflict. Omit `areaCode` for an unrestricted number.

The cached rental plans expose an optional `areaCodePrice` (the full selected-area-code total, not a surcharge); the cached catalog includes a state-grouped area-code list when available. Code metadata is refreshed in the shared five-minute catalog job. If that list is unavailable, the dashboard accepts manual three-digit entry and validates availability when purchasing. The server checks exact stock, current cost and the delivered number's prefix; it never silently substitutes another area code. Unsupported selection or a higher current price is rejected before billing. Confirmed eligible cancellation refunds the original paid amount, including the area-code fee.

## Renew a number with an API key

Base URL: `https://textverified.org/api/v1/developer`. Use the same store hostname and API key as the original integration. Every operation is restricted to the account bound to the key; do not send `accountId`.

1. Read `GET /rentals/{id}/renewal` for the current renewal price, existing term in milliseconds, and current `expiresAt`.
2. Save a new `Idempotency-Key` for this intended renewal. Call `POST /rentals/{id}/renew` with JSON `{"maxPrice":"<quote price>","expectedExpiresAt":"<quote expiresAt>"}`. The key must contain 8–128 letters, digits, underscores or hyphens.
3. Read `GET /rentals/{id}/renewals/{renewalId}` to check the saved result. This read makes no renewal request or wallet charge.

Renewal adds the rental's **existing term** to the verified current expiry. It keeps the same number and does not change the dashboard auto-renew preference. Only eligible active SMS rentals can be renewed. Canceled or expired numbers cannot be reactivated, and a pending transfer must be canceled first. Eligibility depends on the original rental's supported renewal capability; an available new-number offer does not prove that an existing rental can be renewed.

The quote is read-only. Renewal checks eligibility, ownership, current price and wallet balance again. `maxPrice` is the maximum total USD charge, including applicable reseller pricing. `expectedExpiresAt` prevents an old quote from extending the number again after another renewal. The original one-time area-code selection fee is not charged again. Read and accept a fresh quote for each later renewal.

Example, using values read from the quote:

```sh
BASE=https://textverified.org/api/v1/developer
# Set API_KEY, RENTAL_ID and a unique RENEWAL_KEY in your server environment.
QUOTE=$(curl --fail-with-body -sS "$BASE/rentals/$RENTAL_ID/renewal" \
  -H "X-API-Key: $API_KEY")
BODY=$(printf '%s' "$QUOTE" | jq '{maxPrice: .price, expectedExpiresAt: .expiresAt}')
# Persist RENEWAL_KEY and BODY before sending. Retries must reuse both.
curl --fail-with-body -sS "$BASE/rentals/$RENTAL_ID/renew" \
  -H "X-API-Key: $API_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $RENEWAL_KEY" -d "$BODY"
```

The renewal response includes `rentalId`, `renewalId`, `status`, `currency`, `price`, `duration`, `previousExpiresAt`, `expiresAt` and `refunded`.

- `completed`: the new expiry is confirmed and returned in `expiresAt`.
- `pending`: the price is reserved from the wallet while confirmation is checked; `expiresAt` is null. Retain the original key and body. A timeout does not prove failure. Retry only that same request, or poll the status endpoint. **Never use a new key to bypass a pending result.** The worker checks confirmation without submitting the renewal again. Contact support if it remains pending.
- `failed`: the renewal was not confirmed as successful and the reserved amount was returned; `refunded` is true and `expiresAt` is null. Replaying the key returns this same saved result.

Missing or invalid keys return 401. An unowned or missing rental/renewal returns 404. Invalid input, an unsupported rental, an insufficient balance or a price over `maxPrice` returns 400. HTTP 409 means a changed expiry, conflicting idempotency key, pending transfer or pending renewal. A quote that cannot be verified returns 503 before any charge. Account deposit restrictions can return 403. Read the response `code` and message for details.

API-key renewal does not enable automatic renewal. The dashboard remains the place to change auto-renew consent. Proxy renewal uses its separate endpoint and contract.
