> ## Documentation Index
> Fetch the complete documentation index at: https://docs.easyotp.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify Code

> Verify a verification code that was previously sent

## Request

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

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token with your API key: `Bearer YOUR_API_KEY`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

### Body Parameters

<ParamField body="verification_id" type="string" required>
  The verification ID returned from the send endpoint

  Example: `"11f951d5-32d1-4b49-bdda-7da248e2615c"`
</ParamField>

<ParamField body="code" type="string" required>
  The verification code to check. Must be a numeric string between 4-10 digits.

  Example: `"123456"`
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  Always `true` for successful requests (even if the code is invalid)
</ResponseField>

<ResponseField name="verified" type="boolean">
  `true` if the code was correct and not expired, `false` otherwise
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable result message. Possible values:

  * `"Code verified successfully"`
  * `"Invalid code"`
  * `"Code expired"`
  * `"Code already used"`
</ResponseField>

<ResponseField name="request_id" type="string">
  Unique request identifier for debugging

  Example: `"7b4d6022-7260-4568-b6b7-29c366c47bbc"`
</ResponseField>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node.js theme={null}
  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);
  }
  ```

  ```python Python theme={null}
  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']}")
  ```

  ```go Go theme={null}
  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)
      }
  }
  ```
</CodeGroup>

## Response Examples

<ResponseExample>
  ```json Code Verified Successfully theme={null}
  {
    "success": true,
    "verified": true,
    "message": "Code verified successfully",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json Invalid Code theme={null}
  {
    "success": true,
    "verified": false,
    "message": "Invalid code",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json Code Expired theme={null}
  {
    "success": true,
    "verified": false,
    "message": "Code expired",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json Code Already Used theme={null}
  {
    "success": true,
    "verified": false,
    "message": "Code already used",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```
</ResponseExample>

## Error Responses

<ResponseExample>
  ```json 400 - Missing verification_id theme={null}
  {
    "error": "verification_id is required",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 400 - Missing code theme={null}
  {
    "error": "code is required",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 400 - Invalid code format theme={null}
  {
    "error": "code must be a numeric string between 4-10 digits",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 401 - Authentication Failed theme={null}
  {
    "error": "API key is required. Provide it via Authorization: Bearer <token> or x-api-key header",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 403 - API Key Disabled theme={null}
  {
    "error": "API key is disabled",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 404 - Verification Not Found theme={null}
  {
    "error": "Verification not found",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```

  ```json 429 - Too Many Failed Attempts theme={null}
  {
    "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
  }
  ```

  ```json 500 - Internal Server Error theme={null}
  {
    "error": "Internal server error",
    "request_id": "7b4d6022-7260-4568-b6b7-29c366c47bbc"
  }
  ```
</ResponseExample>

## 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

<Note>
  In addition to verification attempt limits, API keys are also rate limited to **120 requests per minute** across all endpoints. See the [Rate Limits](/api-reference/introduction#rate-limits) section for more details.
</Note>

<Warning>
  Implement client-side retry limits (3-5 attempts) to provide better UX before hitting the server limit.
</Warning>

## Understanding Verification States

A verification code can be in one of several states:

<Steps>
  <Step title="Valid">
    Code has been sent, not yet used, and not expired. Can be verified successfully.
  </Step>

  <Step title="Used">
    Code has been verified successfully once. Cannot be used again (prevents replay attacks).
  </Step>

  <Step title="Expired">
    Code has exceeded its expiration time. Cannot be verified.
  </Step>

  <Step title="Invalid">
    The provided code doesn't match the sent code.
  </Step>
</Steps>

## Best Practices

<Tip>
  **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.
</Tip>

<Tip>
  **Store verification IDs securely**: Keep verification IDs server-side, associated with user sessions. Never expose them in URLs or client-side JavaScript.
</Tip>

<Warning>
  **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.
</Warning>

<Note>
  **No credit consumed**: Verification attempts do not consume credits, only sending codes does.
</Note>

## 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:

<CodeGroup>
  ```javascript Express.js theme={null}
  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 });
    }
  });
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Always getting 'Invalid code' even with the correct code">
    Make sure you're using the exact `verification_id` returned from the send endpoint. Verification IDs are case-sensitive UUIDs.
  </Accordion>

  <Accordion title="Code expired immediately">
    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.
  </Accordion>

  <Accordion title="Getting 404 - Verification not found">
    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.
  </Accordion>
</AccordionGroup>
