V1.0 Sunset Extended to December 30, 2026
The V1.0 REST API sunset has been extended to December 30, 2026 based on community feedback. Existing integrations will continue to work until then. New integrations should use V1.2 (recommended REST API) or V2.0 Widget SDK for the maximum security tier.
V1.2 REST API — Recommended Replacement
V1.2 is our recommended REST API. Same V1 simplicity, with configurable Proof-of-Work and Cloudflare Turnstile when enabled for the pipeline, plus pipeline-level rate limiting and circuit breaking — no iframe required. Smooth upgrade path from V1.0. View V1.2 docs
V1.0 REST API Authentication
Step 1: Create an Authentication Transaction
After setting up your pipeline, initiate an authentication transaction when a user needs to be authenticated. This generates a unique transaction ID that will be used throughout the authentication process.
Looking for a simpler integration? Check out our official SDKs for Node.js and Flutter that handle this entire flow with just a few method calls.
Request Parameters
If you include the email field in verificationAddress, ensure you've enabled email as a verification method in your pipeline settings at https://app.akedly.io/pipeline/${pipelineID}
- Name
APIKey- Type
- string
- Description
Your API key from Company Profile → View API Key in the dashboard
- Name
pipelineID- Type
- string
- Description
The unique identifier of your pipeline, found in the pipeline's basic details
- Name
verificationAddress- Type
- JSON
- Description
JSON object containing the user's contact information for OTP delivery. Include
phoneNumberwith country code and/oremail. For country codes, reference https://countrycode.org/
- Name
otp- Type
- string
- Description
Optional: Bring your own 4, 5 or 6 digit OTP code (numbers only). When provided, billing switches to pay-per-message upon activation instead of pay-per-successful-authentication.
- Name
digits- Type
- number
- Description
Optional: Specifies the length of the OTP to be generated. Accepts
4,5or6. Defaults to6if not provided.
- Name
waitForChannels- Type
- boolean
- Description
Optional: When set to
true, the API polls up to 5 seconds for webhook confirmations from delivery channels before responding. Channels are reported as "confirmed" or "failed" instead of just "attempted". Adds ~1 second overhead. Useful when you need accurate delivery status for analytics or user display.
Response Format
- Name
status- Type
- string
- Description
The status of your transaction request ("success" or "error")
- Name
data- Type
- JSON
- Description
Contains the critical
transactionID- your authorization token to proceed with OTP delivery. Save this transactionID as you'll need it for the next step.
- Name
message- Type
- string
- Description
Human-readable description of the transaction status
Best Practice: Store the transactionID in your database alongside the user's current session or operation. This allows you to track which user corresponds to which authentication attempt when they return to your application after verification.
For example, if you're implementing user registration with email verification, save the transactionID with the new user's record to mark them as verified once authentication completes.
Request Body & URL
{
"APIKey": "6e1d6585bbe17f6abc80cf10a1********ad3fe197775b19fde2ebb5464d",
"pipelineID": "6748*******f948b29ef",
"verificationAddress": {
"phoneNumber": "+20155****2491",
"email": "testmail@akedly.io"
},
"digits": 6
}
Response
{
"status": "success",
"data": {
"transactionID": "a77549536888557729a0e4cd454d28371f658426135af11ce6cba7f6123aafff"
},
"message": "Main transaction created successfully"
}
Step 2: Activate Transaction and Send OTP
With your transactionID from Step 1, activate the transaction to trigger OTP delivery to the user's specified contact information.
Request Body & URL
{
//empty body
}
How OTP Delivery Works
Upon successful activation, Akedly delivers the OTP using our smart multi-channel approach:
Multi-Channel Delivery:
- Email: Always sent when email address is provided
- Phone: Delivered through the most reliable available channel:
- WhatsApp (if available on the number)
- Telegram (if available and WhatsApp is not)
- Local SMS (for Egyptian numbers without WhatsApp/Telegram)
- International SMS (for international numbers without WhatsApp/Telegram)
Key Benefits:
- Guaranteed delivery through multiple channels increases success rates
- Flexible billing options based on your OTP preference
Billing Model Differences:
Standard Authentication (Akedly-generated OTP):
- Pay-per-successful-authentication - Only charged when user completes Step 3 verification
- No charge if user fails to verify or abandons the process
- Traditional pay-per-success pricing model
Custom OTP Authentication (Bring Your Own):
- Pay-per-message - Charged immediately upon Step 2 activation
- Billing occurs regardless of verification outcome
- Since the OTP is predetermined, Step 3 verification becomes optional for authentication integrity
Important: When using custom OTP, we strongly recommend completing Step 3 verification to maintain accurate dashboard analytics, usage tracking, and cost analysis in your Akedly app dashboard.
Next Steps:
- Extract the
_idfrom the responsedata.transactionReqobject - Save this
_id- you'll need it for verification in Step 3 - Display your OTP input interface to the user
- Prepare to collect and verify the user's OTP input
Response
{
"status": "success",
"message": "OTP sent successfully",
"channels": ["whatsapp", "email", "sms"],
"email": true,
"WhatsApp": true,
"telegram": false,
"sms": true,
"data": {
"_id": "66726b726cdd6713",
"status": "Pending",
"mainTransactionID": "a77549536888557729a0e4cd454d28371f658426135af11ce6cba7f6123aafff",
"sentVerification": true,
"uid": "0aefbca6-0ef9-490f-a68b-f5f334ad35e5",
"creationDate": "2025-08-31T19:26:32.300Z",
"expirationDate": "2025-08-31T19:29:32.300Z",
"__v": 0,
"sentVerificationDate": "2025-08-31T19:26:33.582Z",
"sentVerificationMethod": "674dff8ae3444b3662d33026"
}
}
Response
{
"status": "error",
"message": "Please wait 47 seconds before requesting another OTP."
}
Channel Delivery Confirmation
Error Handling: - 403 status codes: Indicate OTP delivery failures - 429 status codes: Rate limit exceeded - Akedly enforces a 60-second cooldown per PHONE NUMBER, not per account, so two accounts sending to the same number share it and one account's numbers do not compete. Wait for the time named in the message before retrying. Handle these error cases gracefully in your application flow.
Step 3: Verify User's OTP Input
Submit the user's OTP input for verification using the _id from Step 2:
- Name
otp- Type
- string
- Description
The OTP code entered by the user (must be sent as a string)
Successful Authentication Response
- Name
status- Type
- string
- Description
Authentication result status ("success")
- Name
data- Type
- JSON
- Description
Complete transaction data including
mainTransactionandtransactionReqobjects
- Name
message- Type
- string
- Description
Human-readable success message
- Name
mainTransaction- Type
- JSON
- Description
The original transaction object from Step 1, containing pipeline and user details
- Name
transactionReq- Type
- JSON
- Description
Verification attempt details including timestamps for OTP sending, user input, and verification completion
Note: The response data is provided for logging and analytics purposes. You can also view all verification attempts in your Akedly dashboard at https://app.akedly.io/admin/verifications.
Failed Authentication Response
- Name
data- Type
- JSON
- Description
Contains
frontendCallbackURLfor redirect handling (only relevant when using Akedly's hosted authentication interface)
- Name
message- Type
- string
- Description
Error description explaining why authentication failed
All error responses follow this format, with the message field providing specific details about what went wrong.
Request Body & URL
{
"otp": "123456"
}
Response
{
"message": "OTP verified successfully",
"status": "success",
"data": {
"frontendCallbackURL": "https://yourapp.com/auth/callback?transactionID=ae2eacaebe3ed78b105498d5d0cfe54f11b4130a0847d67b36927752148679e5&status=Successful",
"mainTransaction": {
"verificationAddress": {
"phoneNumber": "+201556452491"
},
"_id": "68b4a1e2d686446a498008b0",
"userID": "6821bb199f9cfd6b2754e6ef",
"pipelineID": "6821bba09f9cfd6b2754e752",
"status": "Successful",
"optinActivated": false,
"payPerMessage": true,
"billedInActivation": true,
"OTP": "123456",
"creationDate": "2025-08-31T19:26:26.689Z",
"expirationDate": "2025-08-31T19:29:26.689Z",
"updateDate": "2025-08-31T19:27:27.440Z",
"transactionID": "ae2eacaebe3ed78b105498d5d0cfe54f11b4130a0847d67b36927752148679e5",
"__v": 0
},
"transactionReq": {
"_id": "68b4a1e8d686446a498008bd",
"status": "Successful",
"mainTransactionID": "ae2eacaebe3ed78b105498d5d0cfe54f11b4130a0847d67b36927752148679e5",
"sentVerification": true,
"uid": "0aefbca6-0ef9-490f-a68b-f5f334ad35e5",
"creationDate": "2025-08-31T19:26:32.300Z",
"expirationDate": "2025-08-31T19:29:32.300Z",
"__v": 0,
"sentVerificationDate": "2025-08-31T19:26:33.582Z",
"sentVerificationMethod": "674dff8ae3444b3662d33026",
"inputOTP": "123456",
"verificationDate": "2025-08-31T19:27:27.440Z"
}
}
}
Response
{
"message": "Invalid OTP",
"data": {
"frontendCallbackURL": "https://your-app.example.com/auth/callback"
}
}
Error Reference
Common errors you may encounter when using the V1.0 REST API, grouped by category.
Authentication Errors
| Status | Error | Cause & Solution |
|---|---|---|
| 401 | Account not active | Your Akedly account is inactive. Contact support to reactivate. |
| 401 | Account not verified | Complete email verification for your Akedly account. |
| 403 | Activation window exceeded | Step 2 (activate) must be called within 2 minutes of Step 1 (create). That window is shorter than the 3-minute OTP validity which starts once the OTP is sent — do not queue activation for later. Create a new transaction instead. |
Configuration Errors
| Status | Error | Cause & Solution |
|---|---|---|
| 400 | No billing plan | Subscribe to a billing plan in your dashboard before creating transactions. |
| 400 | Subscription expired | Your billing subscription has expired. Renew in the dashboard. |
| 400 | Pipeline not found | The pipelineID does not exist. Verify the ID in your dashboard. |
| 400 | Pipeline inactive | The pipeline is disabled. Activate it in Dashboard > Pipelines. |
| 400 | Pipeline misconfigured | The pipeline is missing required settings (e.g., no verification methods enabled). Check pipeline configuration. |
Rate Limiting & Abuse Prevention
| Status | Error | Cause & Solution |
|---|---|---|
| 429 | Phone number banned | The phone number has been temporarily banned due to excessive failed attempts. Wait for the ban to expire. |
| 429 | Cooldown period | A 60-second cooldown is enforced between OTP requests to the same number. Wait and retry. |
| 429 | Throttle exceeded | Too many requests in a short period. Implement exponential backoff in your retry logic. |
Billing Errors
| Status | Error | Cause & Solution |
|---|---|---|
| 402 | Insufficient quota | Your message quota is exhausted. Top up your balance or upgrade your plan. |
Verification Errors
| Status | Error | Cause & Solution |
|---|---|---|
| 403 | Invalid OTP | The OTP entered by the user does not match. Prompt them to re-enter. |
| 403 | Transaction expired | The transaction has expired (3-minute TTL). Create a new transaction and resend the OTP. The body is {"message": "Transaction expired, please re-try from the beginning and request a new one"}. |
| 429 | MAX_ATTEMPTS_EXCEEDED | The 10th wrong OTP force-expires the transaction. Create a new one; further attempts return the 403 expired response above. |
| 403 | Transaction already verified | This transaction was already completed successfully. Each transaction can be verified once — create a new one for a new authentication. |
| 404 | Missing OTP or transaction request ID | The request omitted the otp field or the transaction request ID in the path. Both are required. |
Each transaction allows 10 OTP attempts, the same limit V1.2 and V2 enforce. Never retry more than that against one transaction — create a new one instead.
Telling the failures apart. The three 403s above share a status code, so branch on the message
field: an invalid OTP means "prompt the user to re-enter", while an expired or already-verified
transaction means "start a new transaction".
MAX_ATTEMPTS_EXCEEDED is the exception, and the one difference from V1.2 worth knowing when you
migrate. It is the only V1 verification error whose body carries a machine-readable code field,
so you can branch on it directly:
{
"status": "error",
"code": "MAX_ATTEMPTS_EXCEEDED",
"message": "Maximum OTP attempts exceeded...",
"data": { "frontendCallbackURL": "https://your-app.example.com/auth/callback" }
}
V1.2 returns a code on every error, so error handling ported from V1.2 to V1 must fall back to
message for everything except this one.
Server Errors
| Status | Error | Cause & Solution |
|---|---|---|
| 500 | Failed to send OTP | Internal error during OTP delivery. Retry the request. If persistent, contact support. |
