SMSHubSpot API Integration Guide
This guide explains how to integrate with the SMSHubSpot API: authenticate, browse the catalog, place orders, receive SMS codes, and manage orders.
Base URL: https://smshubspot.net/api/v1
API Reference (schemas and Try It Out): /docs
Endpoint index
| Method | Path | Purpose |
|---|---|---|
GET |
/me |
Account profile, USD balance, and remaining credits |
GET |
/providers |
List providers and their capabilities |
GET |
/providers/{provider}/countries |
List countries for a provider |
GET |
/providers/{provider}/countries/{country}/services |
List services for a country |
GET |
/providers/{provider}/countries/{country}/services/{service}/pricing |
List carriers and prices |
POST |
/orders |
Create an order |
GET |
/orders/{id} |
Read order status, SMS code, and message |
POST |
/orders/{id}/finish |
Mark the number as used |
POST |
/orders/{id}/cancel |
Cancel an active order |
POST |
/orders/{id}/ban |
Ban the number |
There is no list-orders endpoint. Store each id returned by POST /orders in your own database.
Typical integration flow
1. GET /me check balance and credits
2. GET /providers pick global or premium
3. GET /providers/{p}/countries
4. GET /providers/{p}/countries/{c}/services
5. GET /providers/{p}/countries/{c}/services/{s}/pricing
6. POST /orders save the returned id
7. GET /orders/{id} poll until code or message arrives
8. POST /orders/{id}/finish optional, when you are done
Both providers (global and premium) use the same request and response shapes. Only the catalog values differ.
Part 1: Foundations
Authentication
Every request requires a Bearer API key.
Authorization: Bearer SHS_your_api_key
Content-Type: application/json
Accept: application/json
Generate a key from your dashboard under API Key. The full key is shown once when you create or regenerate it. Copy it immediately.
Missing or invalid keys return 401. Suspended accounts return 403.
Response format
Success responses wrap the payload:
{
"msg": "Providers",
"data": [ ... ]
}
Error responses use the same envelope with an errors field:
{
"msg": "Unprocessable entity",
"errors": {
"provider": ["The provider field is required."]
}
}
Validation failures return 422. Business failures (insufficient funds, number unavailable) return 400.
Action endpoints (finish, cancel, ban) return "data": [] on success.
Rate limits
Limits apply per API key:
| Activity | Default limit |
|---|---|
Catalog and read endpoints (GET) |
100 requests / minute |
Order creation (POST /orders) |
30 requests / minute |
When exceeded, the API returns 429 Too Many Requests.
Billing and credits
Orders are paid in this order:
- Active credit package. If you have an active package with enough credits, the order uses credits. Your USD wallet is not charged.
- USD wallet balance. If credits do not cover the order, the USD amount is debited from your wallet.
| Field | Meaning |
|---|---|
Catalog price |
List price in USD before credits are applied |
Order cost |
USD actually charged. 0 means paid with credits. A positive value means debited from your USD balance |
GET /me → balance |
Spendable USD wallet balance |
GET /me → credits |
Remaining credits on your active package (0 if none) |
Credit packages are purchased from the Credits page in your dashboard, not via the API.
If an order cannot be covered, you receive 400 with a message like "Insufficient funds. Please fund your wallet."
Refunds:
- USD-charged orders refund to your USD wallet when cancelled before the SMS arrives.
- Credit-paid orders (
cost: 0) do not refund USD. Credits become available again when the order reachescancelledorexpiredstatus. - Once the SMS code or message has been received, refunds and credit returns are not issued.
Part 2: Account
GET /me
Returns the authenticated user's profile, spendable USD balance, and remaining credits.
Request
GET /api/v1/me
Authorization: Bearer SHS_your_api_key
Response 200
{
"msg": "Profile",
"data": {
"id": "usr_abc123",
"name": "Jane Doe",
"username": "janedoe",
"email": "jane@example.com",
"balance": 12.5,
"credits": 250,
"created_at": "2026-08-07T14:20:00+00:00"
}
}
| Field | Type | Description |
|---|---|---|
id |
string | Opaque user identifier |
name |
string | Display name |
username |
string | Account username |
email |
string | Account email |
balance |
number | Spendable USD wallet balance |
credits |
integer | Remaining credits on the active package (0 if none) |
created_at |
string | ISO 8601 account creation timestamp |
Call this before placing orders to confirm you have enough balance or credits.
Part 3: Catalog
The catalog is a tree. Walk it top to bottom and copy values exactly as returned. Do not guess or reformat codes.
GET /providers/{provider}/countries
GET /providers/{provider}/countries/{country}/services
GET /providers/{provider}/countries/{country}/services/{service}/pricing
Unknown or disabled branches return "data": [], not 404. An empty list means nothing is available at that branch right now.
GET /providers
Lists the available providers and their capabilities.
Response 200
{
"msg": "Providers",
"data": [
{
"id": "global",
"name": "Global",
"active": true,
"cancellable": true,
"bannable": true
},
{
"id": "premium",
"name": "Premium",
"active": true,
"cancellable": true,
"bannable": false
}
]
}
| Field | Description |
|---|---|
id |
Value to send as provider on orders (global or premium) |
name |
Display name |
active |
Whether the provider is accepting new orders |
cancellable |
Whether POST /orders/{id}/cancel is supported |
bannable |
Whether POST /orders/{id}/ban is supported |
| Provider | Coverage |
|---|---|
global |
Worldwide numbers |
premium |
US numbers only |
GET /providers/{provider}/countries
Lists countries available for the given provider.
Example
GET /api/v1/providers/global/countries
Response 200
{
"msg": "Countries",
"data": [
{ "code": "unitedstates", "iso": "us", "name": "United States" },
{ "code": "nigeria", "iso": "ng", "name": "Nigeria" }
]
}
| Field | Description |
|---|---|
code |
Send this value as country when placing an order. Treat it as opaque. Copy it exactly |
iso |
ISO 3166 alpha-2 code. For display or mapping only |
name |
Human-readable country name |
Premium: only one country is returned:
{ "code": "usa", "iso": "us", "name": "United States" }
Always send "country": "usa" for premium orders.
GET /providers/{provider}/countries/{country}/services
Lists services (apps or platforms) available for verification SMS.
Example
GET /api/v1/providers/global/countries/nigeria/services
Response 200
{
"msg": "Services",
"data": [
{ "code": "telegram", "name": "Telegram" },
{ "code": "whatsapp", "name": "WhatsApp" }
]
}
| Field | Description |
|---|---|
code |
Send this value as service when placing an order |
name |
Display name |
GET /providers/{provider}/countries/{country}/services/{service}/pricing
Returns carriers and list prices in USD. Unavailable carriers are omitted from the response.
Example (global)
GET /api/v1/providers/global/countries/unitedstates/services/telegram/pricing
Response 200
{
"msg": "Pricing",
"data": [
{ "carrier": "AT&T", "price": 1.5 },
{ "carrier": "Virtual One", "price": 1.2 }
]
}
| Field | Description |
|---|---|
carrier |
Send this value as carrier when placing an order |
price |
List price in USD. You may pay cost: 0 if credits cover the order |
Availability is confirmed at order time. Re-fetch pricing and retry on 400.
Example (premium)
GET /api/v1/providers/premium/countries/usa/services/CashApp/pricing
{
"msg": "Pricing",
"data": [{ "carrier": "SMSHubSpot", "price": 2.5 }]
}
Premium pricing returns a single row. Send the carrier value from that row (typically "SMSHubSpot").
Part 4: Orders
Order object
Every order endpoint returns the same object shape inside data:
| Field | Type | Description |
|---|---|---|
id |
string | Opaque order id. Store this locally. Use it for all follow-up calls |
number |
string | Virtual phone number assigned to the order (E.164 when available) |
status |
string | One of: active, completed, cancelled, expired |
code |
string or null | Extracted OTP code from the SMS. null until the SMS arrives |
message |
string or null | Full SMS body text. null until the SMS arrives |
expires_at |
string | ISO 8601 timestamp. The number is valid until this time |
cost |
number | USD charged (0 if paid with credits) |
provider |
string | Provider id (global or premium) |
About code and message:
- Both fields come from the same inbound SMS.
codeis the parsed verification code when the provider extracts one.messageis the raw SMS text. Use it when you need the full body or whencodeis null but text is present.- Poll
GET /orders/{id}untilcodeis non-null, or untilmessagecontains what you need, or untilstatusis no longeractive. - The server syncs SMS data from the provider in the background. You only need to poll this endpoint; do not call the provider directly.
POST /orders
Creates a new order. All four body fields are required.
Request
POST /api/v1/orders
Content-Type: application/json
Body (global example)
{
"provider": "global",
"country": "unitedstates",
"service": "telegram",
"carrier": "AT&T"
}
Body (premium example)
{
"provider": "premium",
"country": "usa",
"service": "CashApp",
"carrier": "SMSHubSpot"
}
| Field | Required | Description |
|---|---|---|
provider |
yes | global or premium |
country |
yes | Country code from the catalog |
service |
yes | Service code from the catalog |
carrier |
yes | Carrier name from the pricing response |
Response 201
{
"msg": "Order created",
"data": {
"id": "123456789",
"number": "+12025550100",
"status": "active",
"code": null,
"message": null,
"expires_at": "2026-08-07T14:20:00+00:00",
"cost": 1.5,
"provider": "global"
}
}
Save data.id immediately. There is no way to list orders later.
Common errors
| Status | Meaning |
|---|---|
400 |
Insufficient funds, number unavailable, or provider error |
422 |
Missing or invalid body fields |
Prices and stock are re-checked at purchase time. A valid payload can still return 400 if availability changed.
GET /orders/{id}
Returns the current order state. Poll this endpoint to receive the SMS.
Request
GET /api/v1/orders/{id}
Replace {id} with the opaque id from POST /orders.
Response 200 (waiting for SMS)
{
"msg": "Order",
"data": {
"id": "123456789",
"number": "+12025550100",
"status": "active",
"code": null,
"message": null,
"expires_at": "2026-08-07T14:20:00+00:00",
"cost": 1.5,
"provider": "global"
}
}
Response 200 (SMS received)
{
"msg": "Order",
"data": {
"id": "123456789",
"number": "+12025550100",
"status": "active",
"code": "123456",
"message": "Your verification code is 123456",
"expires_at": "2026-08-07T14:20:00+00:00",
"cost": 1.5,
"provider": "global"
}
}
Polling guidance
- Poll every 3 to 5 seconds while
statusisactiveand bothcodeandmessageare null. - Stop polling when
codeis set, whenmessagecontains the text you need, whenstatuschanges, or whenexpires_athas passed.
POST /orders/{id}/finish
Marks the number as used after you have received the SMS.
Request
POST /api/v1/orders/{id}/finish
Response 200
{
"msg": "Order finished",
"data": []
}
Only works while the order is active. Returns 400 if the order is no longer active.
POST /orders/{id}/cancel
Cancels an active order and applies a refund when eligible.
Request
POST /api/v1/orders/{id}/cancel
Response 200
{
"msg": "Order cancelled",
"data": []
}
Check cancellable on the provider from GET /providers before offering cancel in your UI. Returns 400 if the order cannot be cancelled.
POST /orders/{id}/ban
Bans the number so it cannot be reused. Available only when the provider supports it.
Request
POST /api/v1/orders/{id}/ban
Response 200
{
"msg": "Order banned",
"data": []
}
Check bannable on the provider from GET /providers. Returns 400 if the order cannot be banned.
Order statuses
| Status | Meaning |
|---|---|
active |
Number is live, waiting for SMS |
completed |
SMS received and order finished |
cancelled |
Order cancelled or banned. Refund applied when eligible |
expired |
Validity window elapsed before SMS arrived. Refund applied when eligible |
Part 5: Errors and compatibility
HTTP status codes
| Status | Meaning |
|---|---|
200 |
Success |
201 |
Order created |
400 |
Request could not be completed (funds, availability, invalid action) |
401 |
Missing or invalid API key |
403 |
Account suspended |
404 |
Order not found |
422 |
Invalid request payload |
429 |
Rate limit exceeded |
Integration tips
Recommended client behavior based on how the API is built:
- Cache catalog data locally.
countriesandservicesare stable. Cache them for a few minutes in your app. Re-fetchpricingwhen the user is about to order. - Poll orders, not the provider.
GET /orders/{id}reads from our database. SMS is synced from the provider in the background. Poll every 3 to 5 seconds untilcodeormessageis set. - Persist order ids. Store every
idfromPOST /orders. There is no list endpoint. - Re-fetch pricing on failure. A valid order payload can return
400if availability changed. Re-fetch pricing and retry once. - Check balance before batches. Call
GET /mebefore a run of orders, not on every catalog click. - Handle both
codeandmessage. Some SMS deliveries populate onlymessage. Parse either field. - Backoff on
429. Catalog caching and sensible poll intervals keep you under the per-key rate limits.
Integration rules
- Copy catalog values exactly. Send back the same
provider,country,service, andcarrierstrings the catalog returned. - Store order ids. They are opaque strings. Do not parse or assume a format.
- Additive changes. New fields may appear in responses. Existing fields keep their meaning.
- No provider names in your integration. Use
globalandpremiumonly. Internal provider names are not part of the public API.
Questions or feedback? Contact us and we will help.