Skip to content
Akedly
V1.2 REST API

Channels and security

Let users choose their delivery channel, and see how proof-of-work difficulty, challenge expiry and Turnstile work.

Letting Users Choose Their Channel

By default a pipeline decides where a code goes. You can instead let the person receiving the code choose, either by passing channel yourself or by letting the hosted V2 widget ask them.

Phone channels only. whatsapp, telegram and sms can be chosen. Email cannot be picked — if your verificationAddress includes an email, it is still sent alongside the phone channel when that phone channel succeeds; if the chosen phone channel fails, the request ends there and no email goes out. Passing channel without a phoneNumber is rejected, and so is passing it as null.

What has to be switched on

Two switches are yours to think about, and they are controlled by different people:

  • Your account is entitled — held by Akedly staff. If your calls are refused and your pipeline switch is on, this is the one to ask us about.
  • The pipeline's channel-choice switch is on — yours, in Dashboard > Pipelines.

Both must be on. Beyond those, the feature is also unavailable on legacy (non-granular) pricing and on accounts not billed per message; all of these refuse with the same channel_choice_not_enabled code, so treat that code as "not available for this account and pipeline" rather than as a statement about any one switch.

A utility pipeline is a different case: it is rejected earlier, by the endpoint itself, with 403 UTILITY_PIPELINE_ON_OTP_ENDPOINT — you never reach channel validation at all.

What happens when a chosen channel fails

There are two different failures here, and they behave differently.

The send is rejected immediately. The request fails there and then, and returns 422 with pre_send_failure when we knew before sending that the channel could not be used, or 500 with ACTIVATION_FAILED otherwise. A channel you asked for explicitly never falls back, whatever the pipeline's enforcement setting says — that is the point of naming a channel.

The send is accepted and later reported undelivered. Only this case is governed by the per-pipeline enforcement setting:

  • Fallback (default) — the pipeline's normal fallback order takes over, so the person still gets their code, just not on the channel they asked for.
  • Hard pin — no fallback is sent; the attempt is left to fail rather than arriving somewhere the person did not choose. Choose this when "it must be this channel or not at all" matters more than delivery.

Billing

Each channel bills at its own rate, exactly as sending does today — choosing SMS costs what SMS costs. Nothing about this feature adds a charge of its own. Your own per-country rates are in Dashboard > Cost.


Security

Difficulty is adaptive

Difficulty is not a static number you set once. On a V1.2 pipeline the Enable Proof-of-Work toggle is rendered locked on, so you cannot switch PoW off from the dashboard, and Adaptive Difficulty is enabled by default, so the ramp is always running.

That is a dashboard constraint rather than a promise that a challenge is always required: challengeRequired still comes back false when dev mode bypasses PoW. Branch on the field — never assume it is always true.

The server scales difficulty linearly from the pipeline's base difficulty up to a maximum, driven by how much of that pipeline's own rate-cap the current traffic is consuming. A quiet pipeline sits at its base; a pipeline under a flood climbs automatically, and eases back down as traffic subsides. Pipelines that also enforce Turnstile start ramping later, since Turnstile already filters headless scripts.

The default base difficulty is 3. Read difficulty from the challenge response on every request — never hardcode a value or cache one across requests.

  • Name
    Enable Proof-of-Work
    Type
    boolean
    Description

    Shown locked on for V1.2 pipelines — the dashboard offers no way to turn it off.

  • Name
    Adaptive Difficulty (Recommended)
    Type
    boolean
    Description

    On by default. The system automatically adjusts the difficulty based on traffic patterns.

Challenge expiry scales with difficulty

A challenge does not live for a flat five minutes. Its TTL is derived from the difficulty the server issued, so a harder challenge is given proportionally more time to solve:

DifficultyChallenge TTL
290 seconds
3 (default)90 seconds
4120 seconds
5240 seconds
6300 seconds

Turnstile is opt-in

Unlike Proof-of-Work, Turnstile is off by default. Enable it per pipeline with the Enable Turnstile toggle; switching it on provisions a Cloudflare widget for that pipeline. While it is on, Step 1 returns turnstile.required: true with a siteKey, and Step 2 rejects requests without a token. While it is off, siteKey is null and you omit turnstileToken entirely.

Cluster Gate AI Protection

Cluster Gate AI Protection is coming soon to the V1.2 REST API: an AI-powered layer designed to stop coordinated attacks such as SMS pumping before they run up your bill. A refused request returns the standard 429 rate-limit response, and availability may vary by region. Learn more