@virtualsmslabs/js-sdk
v1.1.0
Published
JavaScript SDK for the VirtualSMS Consumer API
Maintainers
Readme
@virtualsmslabs/js-sdk
JavaScript/TypeScript SDK for the VirtualSMS Consumer API. Compatible with Node.js 18+ and Bun 1.0+. Zero external dependencies — uses the native fetch API.
Requirements
- Node.js 18+ or Bun 1.0+
- TypeScript 5.5+ (for TypeScript users; optional for JavaScript users)
Installation
npm install @virtualsmslabs/js-sdk
# or
bun add @virtualsmslabs/js-sdk
# or
yarn add @virtualsmslabs/js-sdkQuick Start
import { VirtualSMSClient, ActivationStatus } from "@virtualsmslabs/js-sdk";
const client = new VirtualSMSClient("YOUR_API_KEY");
// Check balance
const balance = await client.getBalance();
console.log(`Balance: $${balance.balance}`);
// Order a number
const number = await client.getNumber("wa", 73, { maxPrice: 0.50 });
console.log(`Number: ${number.phoneNumber} (ID: ${number.activationId})`);
// Mark as ready for SMS
await client.setStatus(number.activationId, ActivationStatus.READY);
// Poll for SMS code
const status = await client.getStatus(number.activationId);
if (status.code) {
console.log(`SMS code: ${status.code}`);
await client.setStatus(number.activationId, ActivationStatus.COMPLETE);
}API Reference
Constructor
new VirtualSMSClient(apiKey: string, baseUrl?: string, transport?: Transport)| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| apiKey | string | Yes | — | Your VirtualSMS API key |
| baseUrl | string | No | https://api.virtualsms.de | API base URL |
| transport | Transport | No | FetchTransport | Custom HTTP transport |
Account
getBalance(): Promise<BalanceResponse>
Returns the current account balance.
const { balance } = await client.getBalance();Information & Pricing
getCountries(poolProvider?): Promise<Record<string, any>>
Returns all available countries.
getServicesList(country?, lang?): Promise<Record<string, any>>
Returns available services, optionally filtered by country.
getOperators(country, poolProvider?): Promise<string[]>
Returns available mobile operators for a country.
getPrices(service?, country?, poolProvider?): Promise<Record<string, any>>
Returns pricing information.
getPricesExtended(service?, country?, freePrice?, poolProvider?): Promise<Record<string, any>>
Returns extended pricing with price tiers.
getPricesVerification(service?, poolProvider?): Promise<Record<string, any>>
Returns prices in inverted structure (service → country).
getNumbersStatus(country, operator?, poolProvider?): Promise<Record<string, any>>
Returns available phone number counts per service.
getTopCountriesByService(service): Promise<Record<string, any>[]>
Returns top 10 countries ranked by purchase share and success rate.
Ordering Numbers
getNumber(service, country, options?): Promise<NumberResponse>
Purchases a virtual phone number. Returns ACCESS_NUMBER text response as { activationId, phoneNumber }.
| Option | Type | Description |
|--------|------|-------------|
| maxPrice | number | Maximum price willing to pay |
| operator | string | Mobile operator filter |
| forward | boolean | Enable call forwarding (serialized as "1"/"0") |
| phoneException | string | Comma-separated prefixes to exclude |
| activationType | number | Activation type (0=SMS, 1=number, 2=voice) |
| language | string | Language for voice activation |
| ref | string | Referral code |
| useCashBack | boolean | Use cashback balance (serialized as "true"/"false") |
| userId | string | End-user ID for stats |
| poolProvider | string | Pool provider alias |
getNumberV2(service, country, options?): Promise<Record<string, any>>
Same as getNumber but returns a JSON object with additional fields. Supports orderId for idempotency.
Activation Management
setStatus(id, status): Promise<string>
Changes the status of an activation. Returns the status confirmation string.
getStatus(id): Promise<StatusResponse>
Returns the activation status. StatusResponse includes status, code (nullable), and smsText (nullable).
getStatusV2(id): Promise<Record<string, any>>
Returns detailed activation status as JSON (includes SMS and call verification details).
getActiveActivations(): Promise<Record<string, any>>
Returns all currently active activations.
checkExtraActivation(id): Promise<Record<string, any>>
Checks if a number can be reused for an extra activation.
getExtraActivation(id): Promise<NumberResponse>
Creates an extra activation on a previously used number.
Notifications
getNotifications(): Promise<Record<string, any>>
Returns user notifications and unread count.
Constants
ActivationStatus
| Constant | Value | Description |
|----------|-------|-------------|
| ActivationStatus.READY | 1 | SMS sent, waiting for code |
| ActivationStatus.RETRY | 3 | Request another SMS code |
| ActivationStatus.COMPLETE | 6 | Finish activation |
| ActivationStatus.CANCEL | 8 | Cancel activation |
PoolProvider
| Constant | Value |
|----------|-------|
| PoolProvider.ALPHA | "alpha" |
| PoolProvider.PRIME | "prime" |
| PoolProvider.GAMMA | "gamma" |
| PoolProvider.ZETA | "zeta" |
Error Handling
All SDK exceptions extend VirtualSMSException. Use instanceof to catch specific error types.
import {
VirtualSMSClient,
AuthenticationException,
InsufficientBalanceException,
NoNumbersException,
ValidationException,
ActivationException,
RateLimitException,
ServerException,
} from "@virtualsmslabs/js-sdk";
try {
const number = await client.getNumber("wa", 73);
} catch (e) {
if (e instanceof AuthenticationException) {
console.log("Invalid API key or banned");
} else if (e instanceof InsufficientBalanceException) {
console.log("Not enough balance");
} else if (e instanceof NoNumbersException) {
console.log("No numbers available");
} else if (e instanceof ValidationException) {
console.log("Invalid parameters:", e.errorCode);
} else if (e instanceof ActivationException) {
console.log("Activation error:", e.errorCode);
} else if (e instanceof RateLimitException) {
console.log(`Rate limited. Retry after ${e.retryAfter}s`);
console.log(`Limit: ${e.rateLimitLimit}, Remaining: ${e.rateLimitRemaining}`);
} else if (e instanceof ServerException) {
console.log("Server error:", e.message);
}
}Error Code Reference
| Error Code | Exception Class | HTTP Status |
|------------|-----------------|-------------|
| BAD_KEY | AuthenticationException | 401 |
| BANNED | RateLimitException | 429 |
| BANNED | AuthenticationException | 403 |
| PURCHASE_RESTRICTED | AuthenticationException | 403 |
| SERVICE_RESTRICTED | AuthenticationException | 403 |
| NO_BALANCE | InsufficientBalanceException | 402 |
| NO_NUMBERS | NoNumbersException | 404 |
| WRONG_SERVICE | ValidationException | 400 |
| WRONG_COUNTRY | ValidationException | 400 |
| BAD_ACTION | ValidationException | 400 |
| BAD_STATUS | ValidationException | 400 |
| NO_PRICES | ValidationException | 400 |
| INVALID_PROVIDER | ValidationException | 400 |
| WRONG_EXCEPTION_PHONE | ValidationException | 400 |
| WRONG_SECURITY | ValidationException | 400 |
| NO_ACTIVATION | ActivationException | 404 |
| WRONG_ACTIVATION_ID | ActivationException | 404 |
| EARLY_CANCEL_DENIED | ActivationException | 400 |
| RENEW_ACTIVATION_NOT_AVAILABLE | ActivationException | 400 |
| NEW_ACTIVATION_IMPOSSIBLE | ActivationException | 400 |
| SIM_OFFLINE | ActivationException | 400 |
| CONCURRENT_LIMIT | RateLimitException | 429 |
| ERROR_SQL | ServerException | 500 |
Each exception instance has:
errorCode— the API error code stringmessage— human-readable descriptionhttpStatus— the HTTP status code from the responseerrorMessage— the error message from JSON error responses (if available)retryAfter— (only onRateLimitException) seconds to wait before retryingrateLimitLimit— (only onRateLimitException) the rate limit ceiling fromX-RateLimit-Limit(nullable)rateLimitRemaining— (only onRateLimitException) remaining requests fromX-RateLimit-Remaining(nullable)
Rate Limiting
The API returns rate limit headers on every response. The SDK captures these automatically.
// After any API call, check the current rate limit status
const balance = await client.getBalance();
const info = client.getRateLimitInfo();
if (info) {
console.log(`Limit: ${info.limit}, Remaining: ${info.remaining}`);
}getRateLimitInfo() returns RateLimitInfo | null:
nullbefore any request has been made, or if the response lacked rate limit headers{ limit: number, remaining: number }after a successful or failed request
When a request hits the rate limit (HTTP 429), a RateLimitException is thrown with the rate limit details embedded:
try {
await client.getNumber("wa", 73);
} catch (e) {
if (e instanceof RateLimitException) {
console.log(`Retry after ${e.retryAfter}s`);
console.log(`Limit: ${e.rateLimitLimit}, Remaining: ${e.rateLimitRemaining}`);
}
}Note: An HTTP 429 with body BANNED means you are rate-limited (temporary). An HTTP 403 with body BANNED means your account or IP is actually banned (permanent). The SDK distinguishes these automatically.
Tracking Headers
The SDK sends anonymized tracking headers with every request for analytics and debugging:
| Header | Description |
|--------|-------------|
| X-SDK-Version | SDK version (e.g., 1.1.0) |
| X-SDK-Language | Always javascript |
| X-SDK-Machine-Id | SHA-256 hash of runtime + platform, truncated to 32 chars. Irreversible — cannot identify the host machine. |
| X-SDK-Timestamp | ISO 8601 UTC timestamp |
Custom Transport
You can provide a custom transport implementation for testing or to use a different HTTP library:
import type { Transport, TransportResponse } from "@virtualsmslabs/js-sdk";
class MockTransport implements Transport {
async send(url: string, headers: Record<string, string>): Promise<TransportResponse> {
return {
statusCode: 200,
body: "ACCESS_BALANCE:100.00",
headers: {},
};
}
}
const client = new VirtualSMSClient("YOUR_API_KEY", "https://api.virtualsms.de", new MockTransport());Testing
bun test157 tests covering the parser, exception mapping, rate limit handling, and all 18 client methods.
License
MIT
