Bitcall logoBitcall API
ProductsOTP

OTP Overview

Rent numbers and receive one-time passcodes through the gateway.

The OTP product lets you rent a phone number for a specific service and country, then poll for the one‑time passcode that arrives on it.

All OTP endpoints are /v1/otp/* requests authenticated with your key id and secret — see Authentication.

Every endpoint, with its full request and response schema, is in the OTP API Reference.

A typical flow

Find the service and country

Browse either way round — services, or countries first:

await api('GET', '/v1/otp/services', { query: { q: 'whatsapp' } });
await api('GET', '/v1/otp/countries');

Take the slug of the service you want (whatsapp) and the country_code of the country (US_1).

Pick an offer, and take its serviceId

The catalogue is one collection, filtered — there is no per-country sub-resource:

const { data } = await api('GET', '/v1/otp/services', {
  query: { q: 'whatsapp', country_code: 'US_1' },
});

// each row's `pools` is one entry per pool that sells this service here:
// [{ serviceId: 'CHARLIE-US-whatsapp', price: 0.42, availableCount: 12, successRate: 87 }, …]
const offer = data[0].pools[0];

Each entry in pools is one offer — the same service and country, from a different pool, at its own price, availability and success rate. Choose one, and keep its serviceId: that single value is what you buy.

serviceId is opaque. It happens to read as pool‑country‑service, but do not assemble one yourself — the segments are spelled the way the selling side spells them, and a value you build will not resolve. Send back exactly what the catalog gave you.

Request a number

const { data: activation } = await api('POST', '/v1/otp/activations', {
  body: { serviceId: offer.serviceId },
});

Each call to this endpoint rents a number and charges for it. If it times out or fails without a clear answer, do not resend it — read your orders back to see whether one was created, and only then decide whether to try again.

The response is the activation: activationId, the rented phoneNumber, the price charged, and status: "waiting". serviceId comes back on it too, so a later order tells you which offer it was placed against — and you can re-request the same one.

Poll for the code

const { data } = await api('GET', `/v1/otp/activations/${activation.activationId}`);

Poll until the activation reaches received, then read its code. An id that is unknown — or not yours — answers 404, never an empty result.

This addresses one order by path and returns it as an object in data.

Reviewing several orders is a different endpoint: GET /v1/otp/activations, with no id in the path. It takes status, page and pageSize, and returns data as an array with a meta block, newest first — useful for a dashboard. It carries no activationId filter, so a single rental is always polled by path, as above.

Cancel if unused

await api('POST', `/v1/otp/activations/${activation.activationId}/cancel`);

The three identifiers

Nothing else in the OTP flow needs to be constructed — you only ever pass back a value you were given.

IdentifierWhere you get itWhere it goes
serviceIdpools[].serviceId on any catalog offerPOST /v1/otp/activations
activationIdThe activation returned by POST /v1/otp/activationsThe {activationId} in the path of GET /v1/otp/activations/{activationId} and of cancel, resend and reactivate
poolGET /v1/otp/poolsThe pool filter on GET /v1/otp/services. A pool is a grouping of numbers, not something you rent directly — offers are what you rent

Every endpoint, with its parameters, response schema and error codes, is in the OTP API Reference.

Order statuses

An order is always in exactly one of these. They are the whole vocabulary — anything we cannot map appears as unknown rather than as an internal name.

StatusMeaningWhat to do
waitingThe number is rented and waiting for a code.Poll.
activatingThe number is still being assigned.Poll; treat as waiting.
receivedA code has arrived and is in code.Read it.
completedThe rental window closed with a code received.Nothing.
expiredThe window closed without a code.Request a new number, or reactivate.
cancelledYou cancelled it.Nothing.
refundedThe charge was returned.Nothing.
unknownWe could not determine the state.Poll; contact support with the requestId if it persists.

An order reaches refunded on its own — cancelling a number before its code arrives returns the rental. There is no refund endpoint to call.

Use actions on the order rather than inferring from the status. The two flags are not equally strong, and it matters:

canReactivate is a hint, not a guarantee. The two actions flags carry different weight:

  • canResend tracks the resend cooldown, which is local and time-based — when it says true, a resend is available.
  • canReactivate reflects the provider's reactivation window (~10 minutes) as it stood when the flag was computed. That window can close between the read and your click, so a reactivation can still be refused with 409 REACTIVATION_UNAVAILABLE even though the flag said true. Treat true as "worth trying", not "will succeed" — attempting it is the only real test, and you were not charged when it is refused.

When things go wrong

You seeMeaningDo
400 VALIDATION_ERRORA field is wrong; fields says which.Fix it. Retrying unchanged fails identically.
409 PRODUCT_UNAVAILABLENo number is available for that service and country right now.Try another pool or country, or retry later.
409 INSUFFICIENT_BALANCENot enough balance for the rental.Top up, then send the request again. Nothing was charged, so no number was rented.
409 CANCELLATION_NOT_AVAILABLE_YETYou asked to cancel too soon — the provider enforces a short cooldown before a fresh number can be cancelled.Wait, then cancel again. The block lifts on its own; nothing was charged.
409 REACTIVATION_UNAVAILABLEThe number cannot be reactivated — its reactivation window has closed, whatever canReactivate said.Do not retry. Request a new number instead. You were not charged.
401 INVALID_API_KEYThe key is not accepted for this call.Check the secret first; if it is definitely right, confirm your key can make this call with whoever issued it.
404 NOT_FOUNDNo such order — or not yours.Check the id. The two cases are indistinguishable by design.

Full request and response schemas are in the OTP API Reference; the shared rules are in Conventions and Errors.

On this page