Documentação da API

Acesso programático a números virtuais, pedidos e saldo da conta.

Visao Geral

Todos os campos monetários na API /v1 estão em IDR (Rupia indonésia), como unidades inteiras — por exemplo, "price": 15000 e "balance": 500000 significam Rp 15.000 e Rp 500.000. Para uma projeção nativa em USD do mesmo livro-razão, mude para a API v2 usando o seletor de versão acima.

Autenticação

Todas as requisições da API exigem um Bearer token. Gere um nas Configurações da Conta no painel, e inclua-o em cada requisição:

Authorization: Bearer YOUR_API_TOKEN

Requisições sem um token válido recebem uma resposta 401 UNAUTHORIZED.

URL Base

Todos os caminhos de endpoints abaixo são relativos a:

https://api.smscode.gg/v1

Formato de Resposta

Toda resposta retorna JSON com um envelope consistente. Todas as respostas incluem um cabeçalho x-request-id para depuração.

Sucesso
{
  "success": true,
  "data": { ... }
}
Erro
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Todos os campos monetários na API /v1 estão em IDR (Rupia indonésia), como unidades inteiras — por exemplo, "price": 15000 e "balance": 500000 significam Rp 15.000 e Rp 500.000. Para uma projeção nativa em USD do mesmo livro-razão, mude para a API v2 usando o seletor de versão acima.

GET /catalog/countries

Retorna uma lista de todos os países disponíveis.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v1/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET /catalog/services

Retorna uma lista de serviços (plataformas) disponíveis. Opcionalmente filtre por país.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
country_idintegerNãoFiltrar serviços disponíveis para este país

Exemplo de Requisição

curl -s "https://api.smscode.gg/v1/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET /catalog/products

Retorna uma lista paginada de produtos disponíveis. Filtre por país e/ou plataforma.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
country_idintegerNãoFiltrar por ID do país
platform_idintegerNãoFiltrar por ID da plataforma/serviço
sortstringNãoOrdenação: price_asc (padrão), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNãoResultados por página (1-10.000, padrão 1.000)
pageintegerNãoNúmero da página (mín. 1, padrão 1)

Exemplo de Requisição

curl -s "https://api.smscode.gg/v1/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
GET /catalog/exchange-rate

Retorna a taxa de câmbio USD/IDR atual usada para conversão de moeda.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
pairstringNãoPar de moedas (padrão: USD/IDR)

Exemplo de Requisição

curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET /balance

Retorna o saldo da conta do usuário autenticado.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v1/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
GET /orders

Retorna uma lista dos pedidos do usuário autenticado, ordenados do mais recente. Suporta filtragem por status e paginação via offset.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
limitintegerNãoMáximo de resultados (1-100, padrão 20)
offsetintegerNãoNúmero de resultados a pular (padrão 0)
statusstringNãoFiltrar por status: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (sem distinção de maiúsculas)

Exemplo de Requisição

curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": 15000,
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ]
}
GET /orders/{id}

Retorna um único pedido por ID. Retorna apenas pedidos do usuário autenticado.

Parâmetros de Caminho

NomeTipoObrigatórioDescrição
idintegerSimID do pedido (parâmetro de caminho)

Exemplo de Requisição

curl -s https://api.smscode.gg/v1/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": 15000,
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  }
}
GET /orders/active

Lista todos os pedidos ativos no momento (ACTIVE + OTP_RECEIVED). Use para verificar atualizações de status de OTP.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v1/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}
POST /orders/create

Cria um novo pedido de número virtual. Debita o saldo automaticamente. Suporta um cabeçalho Idempotency-Key opcional para evitar pedidos duplicados em retentativas de rede.

Corpo da Requisição

NomeTipoObrigatórioDescrição
product_idintegerNãoID do produto a ser pedido. Informe APENAS UM: product_id OU catalog_product_id, não ambos.
catalog_product_idintegerNãoID de produto estável — o servidor escolhe a melhor oferta atual (mais barata, considerando a confiabilidade). Informe este ou product_id.
max_priceinteger · stringNãoLimite de preço opcional. v1: inteiro em IDR. v2: string decimal em USD (ex.: "0.50").
prefer_providerstringNãoCódigo de provedor opcional a preferir quando as ofertas empatam.
policystringNãoPolítica de roteamento opcional, válida apenas com catalog_product_id. Valores: cheapest (padrão) escolhe a oferta saudável mais barata; best_success ordena as ofertas pelo sucesso de entrega recente primeiro. O best_success pontua cada provedor pela proporção de pedidos que receberam OTP nos últimos 30 dias completos, em faixas de 10%, e só conta um provedor depois que ele tem pelo menos 20 pedidos nesse período — provedores abaixo desse limite ou sem histórico são tratados como neutros, de modo que ofertas novas nunca ficam sem chance (opcional; o sinal começa neutro). Quando prefer_provider também é informado, o provedor preferido ainda fica em primeiro lugar.
quantityintegerNãoQuantidade de itens (1-100, padrão 1)

Envie um cabeçalho Idempotency-Key para repetir solicitações com segurança sem criar pedidos duplicados. A chave pode conter letras, dígitos, hífen e sublinhado (A-Z a-z 0-9 _ -), até 128 caracteres; uma chave inválida é rejeitada com 422 VALIDATION_ERROR. Repetir com a mesma chave e o mesmo corpo reproduz o resultado original (incluindo o failed_count de um sucesso parcial). Uma retentativa que chega ao provedor mas falha é registrada e reproduz esse mesmo erro — use uma chave NOVA para tentar de novo. Falhas sem efeitos colaterais (saldo insuficiente, nenhuma oferta disponível) liberam a chave, então você pode adicionar saldo e repetir com a mesma chave. Reutilizar uma chave com um corpo diferente retorna 422 IDEMPOTENCY_KEY_REUSED, e uma solicitação ainda em andamento com essa chave retorna 409 REQUEST_IN_PROGRESS. O campo failed_reason nas respostas de create é sempre null — ele só é preenchido na consulta/listagem de pedidos.

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"product_id":142,"quantity":1}'

# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-124" \
  -d '{"catalog_product_id":87,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v1/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  }
}
POST /orders/cancel

Cancela um pedido ativo. O custo do aluguel é reembolsado ao saldo da sua conta.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido a cancelar

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
POST /orders/finish

Marca um pedido como concluído após receber o OTP. Isso libera o número imediatamente em vez de aguardar a expiração.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido a finalizar

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v1/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST /orders/resend

Solicita que a plataforma reenvie o SMS para o número alugado. Nem todas as plataformas suportam reenvio — verifique o campo resent na resposta.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido para reenviar SMS

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v1/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
GET /webhook

Retorna a configuração atual de notificação por webhook.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH /webhook

Atualiza a URL e/ou o segredo do webhook. Um segredo é gerado automaticamente quando você define uma URL pela primeira vez. Envie uma string vazia para limpar. A URL deve usar HTTPS.

Corpo da Requisição

NomeTipoObrigatórioDescrição
webhook_urlstringNãoURL HTTPS para receber eventos de webhook (string vazia para limpar)
webhook_secretstringNãoSegredo compartilhado para assinatura HMAC-SHA256 (gerado automaticamente se omitido na primeira configuração)

Pelo menos um campo é obrigatório.

Exemplo de Requisição

curl -s -X PATCH https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v1/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST /webhook/test

Envia um evento de teste para a URL de webhook configurada. Retorna o código de status HTTP do seu servidor. Útil para verificar se o seu endpoint está funcionando antes de começar a usar.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v1/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Notificações por Webhook

Configure uma URL de webhook para receber notificações push em tempo real sobre eventos de pedidos em vez de fazer polling. Esta é a abordagem recomendada para scripts de bot.

Eventos

EventoGatilho
order.otp_receivedCódigo OTP entregue ao número alugado
order.completedPedido marcado como concluído (manualmente ou por expiração)
order.expiredPedido expirado sem OTP (saldo reembolsado)
order.canceledPedido cancelado pelo usuário (saldo reembolsado)

Payload

Corpo do POST do Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Verificação de Assinatura

Cada requisição de webhook inclui um cabeçalho X-Webhook-Signature com uma assinatura HMAC-SHA256 do corpo da requisição, usando seu webhook_secret como chave:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

Verifique esta assinatura no seu servidor para garantir que a requisição é autêntica. A entrega é do tipo disparar e esquecer, com timeout de 3 segundos e sem retentativas.

Rate Limits

As requisições da API possuem limites por grupo de endpoint. Exceder o limite retorna 429 Too Many Requests com um cabeçalho Retry-After indicando quantos segundos aguardar.

Grupo de EndpointsLimiteJanela
Catálogo (countries, services, products, exchange-rate)5.000 requisições60 segundos
Saldo600 requisições60 segundos
Consulta de pedidos (list, get, active)5.000 requisições60 segundos
Criação de pedido3.000 requisições60 segundos
Cancelamento de pedido1.000 requisições60 segundos
Ações de pedido (finish, resend)1.000 requisições60 segundos
Config de webhook (get, update)600 requisições60 segundos
Teste de webhook10 requisições60 segundos

Códigos de Erro

Respostas de erro incluem um destes códigos em error.code:

CódigoHTTPDescrição
UNAUTHORIZED401Token de API ausente ou inválido
FORBIDDEN403Acesso negado
NOT_FOUND404Recurso não encontrado (pedido, taxa de câmbio, etc.)
CONFLICT409Requisição duplicada ou conflito de recurso
INSUFFICIENT_BALANCE409Saldo insuficiente para criar o pedido
VALIDATION_ERROR422Os parâmetros da requisição falharam na validação
RATE_LIMIT_EXCEEDED429Muitas requisições (verifique o cabeçalho Retry-After)
INTERNAL_ERROR500Erro interno do servidor
PROVIDER_ERROR422O provedor de SMS upstream rejeitou a requisição. Em falhas de criação de pedido, o erro pode incluir <code>details</code>: <code>cause_counts</code> (pedidos com <code>product_id</code> legado — uma contagem agrupada por causa) ou <code>attempts</code> (pedidos com <code>catalog_product_id</code> — resultados por tentativa), usando os valores <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>.
NO_OFFER_AVAILABLE422Nenhuma oferta ativa corresponde ao produto e à política solicitados (limite de preço, disponibilidade).
CANCEL_TOO_EARLY409Pedido muito recente para cancelar — aguarde 2 minutos
REQUEST_IN_PROGRESS409Uma solicitação de criação com esta chave de idempotência ainda está em andamento
IDEMPOTENCY_KEY_REUSED422Esta chave de idempotência já foi usada com um corpo de solicitação diferente
SERVICE_UNAVAILABLE503Serviço temporariamente indisponível (manutenção)

Visao Geral

Todos os campos monetários na API /v2 estão em USD, retornados como um objeto monetário — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount é uma string decimal; canonical_amount é o valor exato em IDR do livro-razão (use-o para reconciliação). A rate USD/IDR aplicada é divulgada uma vez por resposta em meta.fx. A v2 é uma projeção em USD em tempo de renderização sobre o mesmo livro-razão em IDR da v1 — ela nunca armazena nem transaciona USD.

Migrando da v1 para a v2

Autenticação

Todas as requisições da API exigem um Bearer token. Gere um nas Configurações da Conta no painel, e inclua-o em cada requisição:

Authorization: Bearer YOUR_API_TOKEN

Requisições sem um token válido recebem uma resposta 401 UNAUTHORIZED.

URL Base

Todos os caminhos de endpoints abaixo são relativos a:

https://api.smscode.gg/v2

Formato de Resposta

Toda resposta retorna JSON com um envelope consistente. Todas as respostas incluem um cabeçalho x-request-id para depuração.

Sucesso
{
  "success": true,
  "data": { ... }
}
Erro
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Erro v2: nenhuma taxa de câmbio utilizável (503)

503 Service Unavailable · Retry-After: 60
{
  "success": false,
  "error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
}

Todos os campos monetários na API /v2 estão em USD, retornados como um objeto monetário — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount é uma string decimal; canonical_amount é o valor exato em IDR do livro-razão (use-o para reconciliação). A rate USD/IDR aplicada é divulgada uma vez por resposta em meta.fx. A v2 é uma projeção em USD em tempo de renderização sobre o mesmo livro-razão em IDR da v1 — ela nunca armazena nem transaciona USD.

GET /catalog/countries

Retorna uma lista de todos os países disponíveis.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v2/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

GET /catalog/services

Retorna uma lista de serviços (plataformas) disponíveis. Opcionalmente filtre por país.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
country_idintegerNãoFiltrar serviços disponíveis para este país

Exemplo de Requisição

curl -s "https://api.smscode.gg/v2/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

GET /catalog/products

Retorna uma lista paginada de produtos disponíveis. Filtre por país e/ou plataforma.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
country_idintegerNãoFiltrar por ID do país
platform_idintegerNãoFiltrar por ID da plataforma/serviço
sortstringNãoOrdenação: price_asc (padrão), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNãoResultados por página (1-10.000, padrão 1.000)
pageintegerNãoNúmero da página (mín. 1, padrão 1)

Exemplo de Requisição

curl -s "https://api.smscode.gg/v2/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

GET /catalog/exchange-rate

Retorna a taxa de câmbio USD/IDR atual usada para conversão de moeda.

Parâmetros

Nenhum — a v2 sempre retorna USD/IDR; o parâmetro ?pair da v1 é ignorado.

Exemplo de Requisição

curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
}

v2: retorna { pair, rate, rate_as_of } (sem base_currency/quote_currency, sem o invólucro meta — a taxa é o dado). ?pair é ignorado — a v2 sempre retorna USD/IDR (a v1 respeita ?pair). Retorna 503 FX_RATE_UNAVAILABLE se não existir uma taxa utilizável.

GET /balance

Retorna o saldo da conta do usuário autenticado.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v2/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

GET /orders

Retorna uma lista dos pedidos do usuário autenticado, ordenados do mais recente. Suporta filtragem por status e paginação via offset.

Parâmetros de Consulta

NomeTipoObrigatórioDescrição
limitintegerNãoMáximo de resultados (1-100, padrão 20)
offsetintegerNãoNúmero de resultados a pular (padrão 0)
statusstringNãoFiltrar por status: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (sem distinção de maiúsculas)

Exemplo de Requisição

curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ],
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

GET /orders/{id}

Retorna um único pedido por ID. Retorna apenas pedidos do usuário autenticado.

Parâmetros de Caminho

NomeTipoObrigatórioDescrição
idintegerSimID do pedido (parâmetro de caminho)

Exemplo de Requisição

curl -s https://api.smscode.gg/v2/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

GET /orders/active

Lista todos os pedidos ativos no momento (ACTIVE + OTP_RECEIVED). Use para verificar atualizações de status de OTP.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v2/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}

v2: este endpoint não envolve valores monetários — ele não retorna amount nem meta.fx (mesma estrutura da v1, sob /v2).

POST /orders/create

Cria um novo pedido de número virtual. Debita o saldo automaticamente. Suporta um cabeçalho Idempotency-Key opcional para evitar pedidos duplicados em retentativas de rede.

Corpo da Requisição

NomeTipoObrigatórioDescrição
product_idintegerNãoID do produto a ser pedido. Informe APENAS UM: product_id OU catalog_product_id, não ambos.
catalog_product_idintegerNãoID de produto estável — o servidor escolhe a melhor oferta atual (mais barata, considerando a confiabilidade). Informe este ou product_id.
max_priceinteger · stringNãoLimite de preço opcional. v1: inteiro em IDR. v2: string decimal em USD (ex.: "0.50").
prefer_providerstringNãoCódigo de provedor opcional a preferir quando as ofertas empatam.
policystringNãoPolítica de roteamento opcional, válida apenas com catalog_product_id. Valores: cheapest (padrão) escolhe a oferta saudável mais barata; best_success ordena as ofertas pelo sucesso de entrega recente primeiro. O best_success pontua cada provedor pela proporção de pedidos que receberam OTP nos últimos 30 dias completos, em faixas de 10%, e só conta um provedor depois que ele tem pelo menos 20 pedidos nesse período — provedores abaixo desse limite ou sem histórico são tratados como neutros, de modo que ofertas novas nunca ficam sem chance (opcional; o sinal começa neutro). Quando prefer_provider também é informado, o provedor preferido ainda fica em primeiro lugar.
quantityintegerNãoQuantidade de itens (1-100, padrão 1)

Envie um cabeçalho Idempotency-Key para repetir solicitações com segurança sem criar pedidos duplicados. A chave pode conter letras, dígitos, hífen e sublinhado (A-Z a-z 0-9 _ -), até 128 caracteres; uma chave inválida é rejeitada com 422 VALIDATION_ERROR. Repetir com a mesma chave e o mesmo corpo reproduz o resultado original (incluindo o failed_count de um sucesso parcial). Uma retentativa que chega ao provedor mas falha é registrada e reproduz esse mesmo erro — use uma chave NOVA para tentar de novo. Falhas sem efeitos colaterais (saldo insuficiente, nenhuma oferta disponível) liberam a chave, então você pode adicionar saldo e repetir com a mesma chave. Reutilizar uma chave com um corpo diferente retorna 422 IDEMPOTENCY_KEY_REUSED, e uma solicitação ainda em andamento com essa chave retorna 409 REQUEST_IN_PROGRESS. O campo failed_reason nas respostas de create é sempre null — ele só é preenchido na consulta/listagem de pedidos.

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v2/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"product_id":142,"quantity":1}'

# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v2/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-124" \
  -d '{"catalog_product_id":87,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v2/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

POST /orders/cancel

Cancela um pedido ativo. O custo do aluguel é reembolsado ao saldo da sua conta.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido a cancelar

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: os campos monetários são objetos monetários em USD e a resposta carrega um único meta.fx { pair, rate, rate_as_of }. rate é o valor inteiro em IDR por 1 USD, portanto USD = canonical_amount / rate. Os totais usam 2 casas decimais; preços/reembolsos por item usam 4. Um valor estritamente positivo nunca é arredondado para 0.00. rate_as_of é o timestamp RFC3339 da taxa (no formato +00:00) ou null quando nenhum timestamp é registrado.

Apenas v2: se não existir uma taxa USD/IDR utilizável, os endpoints monetários retornam 503 FX_RATE_UNAVAILABLE com um cabeçalho Retry-After em vez de um corpo monetário. A v1 nunca retorna isso.

POST /orders/finish

Marca um pedido como concluído após receber o OTP. Isso libera o número imediatamente em vez de aguardar a expiração.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido a finalizar

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v2/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

POST /orders/resend

Solicita que a plataforma reenvie o SMS para o número alugado. Nem todas as plataformas suportam reenvio — verifique o campo resent na resposta.

Corpo da Requisição

NomeTipoObrigatórioDescrição
idintegerSimID do pedido para reenviar SMS

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v2/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

GET /webhook

Retorna a configuração atual de notificação por webhook.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

PATCH /webhook

Atualiza a URL e/ou o segredo do webhook. Um segredo é gerado automaticamente quando você define uma URL pela primeira vez. Envie uma string vazia para limpar. A URL deve usar HTTPS.

Corpo da Requisição

NomeTipoObrigatórioDescrição
webhook_urlstringNãoURL HTTPS para receber eventos de webhook (string vazia para limpar)
webhook_secretstringNãoSegredo compartilhado para assinatura HMAC-SHA256 (gerado automaticamente se omitido na primeira configuração)

Pelo menos um campo é obrigatório.

Exemplo de Requisição

curl -s -X PATCH https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v2/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

POST /webhook/test

Envia um evento de teste para a URL de webhook configurada. Retorna o código de status HTTP do seu servidor. Útil para verificar se o seu endpoint está funcionando antes de começar a usar.

Parâmetros

Nenhum

Exemplo de Requisição

curl -s -X POST https://api.smscode.gg/v2/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Exemplo de Resposta

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Idêntico à v1 — apenas o caminho base muda (/v1/v2).

Notificações por Webhook

Configure uma URL de webhook para receber notificações push em tempo real sobre eventos de pedidos em vez de fazer polling. Esta é a abordagem recomendada para scripts de bot.

Eventos

EventoGatilho
order.otp_receivedCódigo OTP entregue ao número alugado
order.completedPedido marcado como concluído (manualmente ou por expiração)
order.expiredPedido expirado sem OTP (saldo reembolsado)
order.canceledPedido cancelado pelo usuário (saldo reembolsado)

Payload

Corpo do POST do Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Verificação de Assinatura

Cada requisição de webhook inclui um cabeçalho X-Webhook-Signature com uma assinatura HMAC-SHA256 do corpo da requisição, usando seu webhook_secret como chave:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

Verifique esta assinatura no seu servidor para garantir que a requisição é autêntica. A entrega é do tipo disparar e esquecer, com timeout de 3 segundos e sem retentativas.

Rate Limits

As requisições da API possuem limites por grupo de endpoint. Exceder o limite retorna 429 Too Many Requests com um cabeçalho Retry-After indicando quantos segundos aguardar.

Grupo de EndpointsLimiteJanela
Catálogo (countries, services, products, exchange-rate)5.000 requisições60 segundos
Saldo600 requisições60 segundos
Consulta de pedidos (list, get, active)5.000 requisições60 segundos
Criação de pedido3.000 requisições60 segundos
Cancelamento de pedido1.000 requisições60 segundos
Ações de pedido (finish, resend)1.000 requisições60 segundos
Config de webhook (get, update)600 requisições60 segundos
Teste de webhook10 requisições60 segundos

Códigos de Erro

Respostas de erro incluem um destes códigos em error.code:

CódigoHTTPDescrição
UNAUTHORIZED401Token de API ausente ou inválido
FORBIDDEN403Acesso negado
NOT_FOUND404Recurso não encontrado (pedido, taxa de câmbio, etc.)
CONFLICT409Requisição duplicada ou conflito de recurso
INSUFFICIENT_BALANCE409Saldo insuficiente para criar o pedido
VALIDATION_ERROR422Os parâmetros da requisição falharam na validação
RATE_LIMIT_EXCEEDED429Muitas requisições (verifique o cabeçalho Retry-After)
INTERNAL_ERROR500Erro interno do servidor
PROVIDER_ERROR422O provedor de SMS upstream rejeitou a requisição. Em falhas de criação de pedido, o erro pode incluir <code>details</code>: <code>cause_counts</code> (pedidos com <code>product_id</code> legado — uma contagem agrupada por causa) ou <code>attempts</code> (pedidos com <code>catalog_product_id</code> — resultados por tentativa), usando os valores <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>.
NO_OFFER_AVAILABLE422Nenhuma oferta ativa corresponde ao produto e à política solicitados (limite de preço, disponibilidade).
CANCEL_TOO_EARLY409Pedido muito recente para cancelar — aguarde 2 minutos
REQUEST_IN_PROGRESS409Uma solicitação de criação com esta chave de idempotência ainda está em andamento
IDEMPOTENCY_KEY_REUSED422Esta chave de idempotência já foi usada com um corpo de solicitação diferente
SERVICE_UNAVAILABLE503Serviço temporariamente indisponível (manutenção)
FX_RATE_UNAVAILABLE503Taxa de câmbio USD/IDR indisponível (endpoints monetários da v2) — retorna 503 com um cabeçalho Retry-After.
v1 → v2

Migrando da v1 para a v2

A v1 serve IDR; a v2 serve USD. Ambas as versões coexistem permanentemente — não há descontinuação. Escolha uma versão por integração; não misture caminhos base. A v2 é idêntica à v1, exceto na forma como o dinheiro é representado.

Aspectov1 · IDRv2 · USD
Campos monetáriosIDR inteiro, ex. 15000Objeto monetário { amount, currency, canonical_amount, canonical_currency }
meta.fxAusenteObrigatório em toda resposta que contém valores monetários
MoedaIDRUSD (fixo no código)
FX_RATE_UNAVAILABLENovo 503 + Retry-After quando não há taxa utilizável
PrecisãoTotais 2 casas, preços/reembolsos 4 casas, arredondamento para cima em positivos
GET /catalog/exchange-rate{pair, base_currency, quote_currency, rate}; respeita ?pair{pair, rate, rate_as_of}; ?pair ignorado (apenas USD/IDR)

Exemplos lado a lado

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}
GET/catalog/products
v1
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

catalog_product_id é a identidade ESTÁVEL do produto (uma por país + plataforma) — pode ser armazenada com segurança nos seus sistemas E também serve para pedir diretamente: faça o pedido com catalog_product_id (mais os opcionais max_price e prefer_provider) e o servidor resolve a oferta ativa para você. O caminho pelo product_id por oferta continua suportado e retrocompatível — esse id é volátil e muda sempre que uma faixa de preço do provedor se altera, então a recomendação de buscar o catálogo novamente antes de pedir vale apenas para esse caminho.

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

Checklist de migração

  1. Troque o caminho base /v1/v2.
  2. Faça o parse dos campos monetários como objetos — leia amount como uma string decimal; currency é "USD".
  3. Para reconciliação do livro-razão use canonical_amount (IDR exato); o amount em USD é uma projeção em tempo de renderização e a rate é divulgada uma vez em meta.fx.
  4. Trate o novo FX_RATE_UNAVAILABLE (503) — tente novamente após o Retry-After. A v1 nunca retorna isso.