Skip to main content
POST
/
api
/
v1
/
verify
Verify Code
curl --request POST \
  --url https://app.easyotp.dev/api/v1/verify \
  --header 'Authorization: <authorization>' \
  --header 'Content-Type: <content-type>' \
  --data '
{
  "verification_id": "<string>",
  "code": "<string>"
}
'
import requests

url = "https://app.easyotp.dev/api/v1/verify"

payload = {
"verification_id": "<string>",
"code": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({verification_id: '<string>', code: '<string>'})
};

fetch('https://app.easyotp.dev/api/v1/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://app.easyotp.dev/api/v1/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'verification_id' => '<string>',
'code' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "https://app.easyotp.dev/api/v1/verify"

payload := strings.NewReader("{\n \"verification_id\": \"<string>\",\n \"code\": \"<string>\"\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://app.easyotp.dev/api/v1/verify")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"verification_id\": \"<string>\",\n \"code\": \"<string>\"\n}")
.asString();
require 'uri'
require 'net/http'

url = URI("https://app.easyotp.dev/api/v1/verify")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"verification_id\": \"<string>\",\n \"code\": \"<string>\"\n}"

response = http.request(request)
puts response.read_body
{
  "success": true,
  "verified": true,
  "message": "Code verified successfully",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Invalid code",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Code expired",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Code already used",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}

Request

Verify a code that was sent using the send endpoint. Codes can only be verified once and must not be expired.

Headers

Authorization
string
required
Bearer token with your API key: Bearer YOUR_API_KEY
Content-Type
string
required
Must be application/json

Body Parameters

verification_id
string
required
The verification ID returned from the send endpointExample: "11f951d5-32d1-4b49-bdda-7da248e2615c"
code
string
required
The verification code to check. Must be a numeric string between 4-10 digits.Example: "123456"

Response

success
boolean
Always true for successful requests (even if the code is invalid)
verified
boolean
true if the code was correct and not expired, false otherwise
message
string
Human-readable result message. Possible values:
  • "Code verified successfully"
  • "Invalid code"
  • "Code expired"
  • "Code already used"
request_id
string
Unique request identifier for debuggingExample: "7b4d6022-7260-4568-b6b7-29c366c47bbc"

Examples

curl -X POST https://app.easyotp.dev/api/v1/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "verification_id": "11f951d5-32d1-4b49-bdda-7da248e2615c",
    "code": "123456"
  }'
const response = await fetch('https://app.easyotp.dev/api/v1/verify', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    verification_id: '11f951d5-32d1-4b49-bdda-7da248e2615c',
    code: '123456'
  })
});

const data = await response.json();

if (data.verified) {
  console.log('Code verified successfully!');
} else {
  console.log('Verification failed:', data.message);
}
import requests

response = requests.post(
    'https://app.easyotp.dev/api/v1/verify',
    headers={
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json',
    },
    json={
        'verification_id': '11f951d5-32d1-4b49-bdda-7da248e2615c',
        'code': '123456'
    }
)

data = response.json()

if data['verified']:
    print('Code verified successfully!')
else:
    print(f"Verification failed: {data['message']}")
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type VerifyRequest struct {
    VerificationID string `json:"verification_id"`
    Code          string `json:"code"`
}

type VerifyResponse struct {
    Success    bool   `json:"success"`
    Verified   bool   `json:"verified"`
    Message    string `json:"message"`
    RequestID  string `json:"request_id"`
}

func main() {
    payload := VerifyRequest{
        VerificationID: "11f951d5-32d1-4b49-bdda-7da248e2615c",
        Code:          "123456",
    }
    
    jsonData, _ := json.Marshal(payload)
    
    req, _ := http.NewRequest(
        "POST",
        "https://app.easyotp.dev/api/v1/verify",
        bytes.NewBuffer(jsonData),
    )
    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()
    
    body, _ := io.ReadAll(resp.Body)
    
    var result VerifyResponse
    json.Unmarshal(body, &result)
    
    if result.Verified {
        fmt.Println("Code verified successfully!")
    } else {
        fmt.Printf("Verification failed: %s\n", result.Message)
    }
}

Response Examples

{
  "success": true,
  "verified": true,
  "message": "Code verified successfully",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Invalid code",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Code expired",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Code already used",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}

Error Responses

{
  "error": "verification_id is required",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "error": "code is required",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "error": "code must be a numeric string between 4-10 digits",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "error": "API key is required. Provide it via Authorization: Bearer <token> or x-api-key header",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "error": "API key is disabled",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "error": "Verification not found",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}
{
  "success": true,
  "verified": false,
  "message": "Too many failed attempts. Please try again in 15 minutes or request a new code.",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc",
  "retry_after": 900
}
{
  "error": "Internal server error",
  "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
}

Rate Limiting

To prevent brute-force attacks, this endpoint limits failed verification attempts:
  • 5 failed attempts per verification code
  • 15-minute lockout after exceeding the limit
  • Automatic reset on successful verification
When locked out, you’ll receive a 429 response. Users should either:
  1. Wait for the lockout period to expire
  2. Request a new verification code
In addition to verification attempt limits, API keys are also rate limited to 120 requests per minute across all endpoints. See the Rate Limits section for more details.
Implement client-side retry limits (3-5 attempts) to provide better UX before hitting the server limit.

Understanding Verification States

A verification code can be in one of several states:
1

Valid

Code has been sent, not yet used, and not expired. Can be verified successfully.
2

Used

Code has been verified successfully once. Cannot be used again (prevents replay attacks).
3

Expired

Code has exceeded its expiration time. Cannot be verified.
4

Invalid

The provided code doesn’t match the sent code.

Best Practices

Limit verification attempts: Implement client-side limits on how many times a user can attempt to verify a code (e.g., 3-5 attempts) to prevent brute force attacks.
Store verification IDs securely: Keep verification IDs server-side, associated with user sessions. Never expose them in URLs or client-side JavaScript.
Check the verified field: Always check the verified field in the response, not just the HTTP status code. A 200 response with verified: false means verification failed.
No credit consumed: Verification attempts do not consume credits, only sending codes does.

Security Considerations

Single Use Codes

Once a code is verified successfully, it cannot be used again. This prevents replay attacks where an attacker might intercept a code and try to use it themselves.

Automatic Expiration

All codes automatically expire based on the expires_in parameter from the send request. This limits the window of opportunity for attackers.

Rate Limiting

Both send and verify endpoints are rate-limited to prevent abuse. If you need higher limits, contact our support team.

Integration Example

Here’s a complete example of integrating verification into a user registration flow:
const express = require('express');
const app = express();

const sessions = new Map();

app.post('/auth/request-code', async (req, res) => {
  const { phoneNumber } = req.body;
  
  const response = await fetch('https://app.easyotp.dev/api/v1/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.EASYOTP_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      channel: 'sms',
      recipient: phoneNumber,
      message: 'Your MyApp verification code is: {code}',
      expires_in: 300
    })
  });
  
  const data = await response.json();
  
  const sessionId = generateSessionId();
  sessions.set(sessionId, {
    phoneNumber,
    verificationId: data.verification_id,
    verified: false
  });
  
  res.json({ sessionId });
});

app.post('/auth/verify-code', async (req, res) => {
  const { sessionId, code } = req.body;
  
  const session = sessions.get(sessionId);
  if (!session) {
    return res.status(400).json({ error: 'Invalid session' });
  }
  
  const response = await fetch('https://app.easyotp.dev/api/v1/verify', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.EASYOTP_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      verification_id: session.verificationId,
      code: code
    })
  });
  
  const data = await response.json();
  
  if (data.verified) {
    session.verified = true;
    res.json({ success: true });
  } else {
    res.json({ success: false, message: data.message });
  }
});

Troubleshooting

Make sure you’re using the exact verification_id returned from the send endpoint. Verification IDs are case-sensitive UUIDs.
Check that your server time is synchronized. If the server time is incorrect, codes may appear expired immediately. Also verify the expires_in parameter when sending.
This usually means the verification_id doesn’t exist or was created with a different API key. Each API key has its own isolated verification space.