Developer docs

Build with our API

REST API for virtual numbers and SMS verification. Bearer auth, JSON responses, one catalog flow for global and premium.

Base URL

https://smshubspot.net/api/v1
REST JSON Bearer auth OpenAPI
API Reference OpenAPI spec, Try It Out, and request schemas. Create Account Sign up free, fund your wallet, get an API key.
Typical flow

providers → countries → services → pricing → POST /orders → poll GET /orders/{id}

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:

  1. Active credit package. If you have an active package with enough credits, the order uses credits. Your USD wallet is not charged.
  2. 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 /mebalance Spendable USD wallet balance
GET /mecredits 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 reaches cancelled or expired status.
  • 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.
  • code is the parsed verification code when the provider extracts one.
  • message is the raw SMS text. Use it when you need the full body or when code is null but text is present.
  • Poll GET /orders/{id} until code is non-null, or until message contains what you need, or until status is no longer active.
  • 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 status is active and both code and message are null.
  • Stop polling when code is set, when message contains the text you need, when status changes, or when expires_at has 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. countries and services are stable. Cache them for a few minutes in your app. Re-fetch pricing when 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 until code or message is set.
  • Persist order ids. Store every id from POST /orders. There is no list endpoint.
  • Re-fetch pricing on failure. A valid order payload can return 400 if availability changed. Re-fetch pricing and retry once.
  • Check balance before batches. Call GET /me before a run of orders, not on every catalog click.
  • Handle both code and message. Some SMS deliveries populate only message. 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, and carrier strings 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 global and premium only. Internal provider names are not part of the public API.

Questions or feedback? Contact us and we will help.