getotp API

Buy numbers, read verification codes and check your balance from your own code. All endpoints return JSON, use HTTPS, and need an API key created in API & Developer.

Authentication

Send your key as a bearer token. Keys are scoped — a request without the needed scope gets 403. Limit: 120 requests per minute per key (429 with Retry-After).

Authorization: Bearer gotp_live_…

Endpoints

GET/api/v1/catalog?country=USscope: catalog:read

Services and prices. yourPriceMinor includes your plan discount.

GET/api/v1/balancescope: balance:read

Wallet balance in minor units (cents).

POST/api/v1/ordersscope: orders:write

Buy a number. Requires an Idempotency-Key header.

GET/api/v1/ordersscope: orders:read

Your recent orders (?limit=1–100).

GET/api/v1/orders/{id}scope: orders:read

One order with its messages and verification codes. Poll this, or use a webhook.

DELETE/api/v1/orders/{id}scope: orders:write

Cancel an order with no SMS yet and get an automatic refund.

Buying a number

Always send a unique Idempotency-Key (16–128 characters). If your request times out, retry with the same key — you will never be charged twice. A 202 response means the provider outcome is being confirmed; do not buy again, poll the order instead. Amounts are in minor units (cents, USD).

curl -X POST https://your-domain.com/api/v1/orders \
  -H "Authorization: Bearer gotp_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"serviceId":"<id from the catalog>","countryCode":"US","maxPriceMinor":50}'

Webhooks

Set an https URL in API & Developer. For every new SMS we POST this JSON, signed with your endpoint secret in the GetOTP-Signature header. Delivery is best effort with a 4 second timeout and no retries — always keep polling as a fallback. Webhook URLs must be public https hosts.

{
  "type": "sms.received",
  "orderId": "…",
  "serviceName": "WhatsApp",
  "countryCode": "US",
  "phoneNumber": "+15672345678",
  "message": { "id": "…", "sender": "WhatsApp", "body": "Your code is 123-456", "code": "123456", "receivedAt": "2026-01-01T12:00:00.000Z" }
}

Verify the signature against the raw request body:

import { createHmac, timingSafeEqual } from 'node:crypto';

// header looks like: t=1700000000,v1=<hex>
export function verify(secret, rawBody, header) {
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;       // reject replays
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Errors

Errors look like { "error": "message" }. 400 bad input · 401 bad key · 402 insufficient balance · 403 missing scope · 409 price or availability changed · 429 rate or daily-order limit · 503 temporarily unavailable.