V1.2 REST API
V1.2 is the REST API for teams that want full control of the UI. You call three endpoints (get a challenge, send the OTP, verify) and Akedly Shield adds proof-of-work and Cloudflare Turnstile on the client when the pipeline has them on. No iframe is required.
When to choose V1.2
- You build your own screens and want full UI control.
- You cannot embed an iframe or open a system browser: a pure REST backend, or a native app that cannot hand off to a system browser.
- You want the Shield SDKs for Web, iOS, Android, Flutter or React Native.
Choose V2.0 if you would rather have Akedly run the verification screen.
Page map
- Set up the pipeline: credentials and where they come from.
- Get a challenge and send the OTP: the two requests and the OTP message language.
- Solve the challenge: proof-of-work, Turnstile and the Shield SDKs.
- Verify and callbacks: the verify request and the webhook your backend receives.
- Channels and security: channel choice, adaptive difficulty, challenge expiry and Turnstile.
- Errors and troubleshooting: error codes by category.
- Migrate from V1.0: how the three V1.0 server calls map to the V1.2 flow.
Next: after a verified OTP, turn on passkeys so returning users skip the code. OTP once, Passkey forever.
What is Akedly Shield?
V1.2 wraps the familiar V1 REST API with Akedly Shield — a client-side security layer that can enforce Proof-of-Work and Cloudflare Turnstile according to the pipeline configuration. Same REST model, full UI control.
Traditional OTP services are plain REST APIs — any bot can call them, draining your SMS budget with fake verification requests. Akedly Shield adds a client-side proof layer when the corresponding pipeline controls are enabled: before your user receives an OTP, the client may need to:
- Solve a Proof-of-Work challenge — computational cost deters automated abuse
- Pass Cloudflare Turnstile verification — bot detection without user friction
This "shield" sits between attackers and your OTP pipeline. Unlike other services where you bolt on captcha yourself, Shield is built into the API flow. No iframe required (unlike V2 widgets) -- you keep full UI control.
| Feature | V1.0 | V1.2 (Shield) | V2.0 |
|---|---|---|---|
| Integration Style | REST API | REST API + Shield SDK | Iframe Widget |
| UI Control | Full (you build it) | Full (you build it) | Managed by Akedly |
| Proof-of-Work | None | Configurable per pipeline (Shield SDK-supported) | N/A (widget-managed) |
| Captcha Protection | Implement your own | Configurable per pipeline | Built-in (widget) |
| Rate Limiting | Implement your own | Pipeline-level | Widget-level |
| Circuit Breaker | Implement your own | Pipeline-level | Widget-level |
| Device Fingerprinting | Implement your own | Not provided by this REST route | Widget-level |
| PPSA Pricing | No | No | Eligible |
| Security Coverage | DIY | ~80% of V2 | Full |
Authentication Flow
V1.2 uses a 4-step flow. The challenge response tells the client whether PoW is required; Turnstile is likewise conditional. The client then makes two server calls to complete the OTP lifecycle.
Never ship your API key to the client
APIKey and pipelineID are credentials. Keep them on your backend and expose thin proxy endpoints (e.g. /auth/akedly/challenge, /auth/akedly/send, /auth/akedly/verify) that your frontend calls. The backend tab below shows the minimal Node.js proxy; the frontend tab shows the matching client code. All SDK pages follow the same split.
Every send must carry the end user's IP
Every POST /api/v1.2/transactions/send must carry the real end user's public IP in the x-end-user-ip request header, as it reached your backend: not your server's, load balancer's or proxy's IP, and never read on the user's device. It is a header, not a body field. Per-IP rate limits, Turnstile and Cluster Gate AI Protection use it, so a send without it leaves them without the user's real address. V2.0 Widgets need nothing here; Akedly resolves the IP itself. The V1.2 API reference shows extraction patterns for Node/Express, Next.js, Flask and PHP.
Send every code your user enters to the verify call
Send every code your user enters to POST /api/v1.2/transactions/verify when they submit it, right or wrong, and even if your server also checks the code. That includes the case where you pass your own code in the otp field of POST /api/v1.2/transactions/send: your own check does not replace this call. In the V1.2 REST flow only this call records that a code was entered correctly, and Akedly's defenses, such as Cluster Gate AI Protection, rely on that record. An identity that keeps receiving codes with none recorded can be blocked from new sends with IDENTITY_TEMPORARILY_BLOCKED. V2.0 Widgets need nothing here, and a passkey sign-in has no code to submit. The V1.2 verify reference shows the request and the responses.
- Name
- 1. Get Challenge
- Type
- GET
- Description
Request the challenge and Turnstile configuration from the server. PoW may be disabled for the pipeline.
- Name
- 2. Solve Challenge
- Type
- client-side
- Description
If
challengeRequiredistrue, use a Shield SDK to solve the PoW challenge. Obtain a Turnstile token only whenturnstile.requiredistrue.
- Name
- 3. Send OTP
- Type
- POST
- Description
Submit the applicable proofs along with the user's phone number or email address, and the real end user's IP in the
x-end-user-ipheader. The server validates enabled proofs and sends the OTP.
- Name
- 4. Verify OTP
- Type
- POST
- Description
Submit the user's OTP input for verification. Identical to V1.0 Step 3.
Stay Updated
Questions?
Reach out to the team at support@akedly.io or contact the co-founders directly at muhad@akedly.io or hana@akedly.io.
