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

# Python SDK

> Official Python SDK for EasyOTP

## Installation

Install the EasyOTP Python SDK using pip:

<CodeGroup>
  ```bash pip theme={null}
  pip install easyotp
  ```

  ```bash pip3 theme={null}
  pip3 install easyotp
  ```

  ```bash poetry theme={null}
  poetry add easyotp
  ```
</CodeGroup>

## Quick Start

```python theme={null}
from easyotp import EasyOTP

client = EasyOTP(api_key='YOUR_API_KEY')

result = client.send(
    channel='sms',
    recipient='+1234567890',
    message='Your verification code is: {code}'
)

print('Verification ID:', result['verification_id'])
```

## Initialization

Create a new EasyOTP client instance with your API key:

```python theme={null}
from easyotp import EasyOTP

client = EasyOTP(
    api_key='YOUR_API_KEY',
    base_url='https://app.easyotp.dev/api/v1'
)
```

### Configuration Options

<ParamField body="api_key" type="string" required>
  Your EasyOTP API key. Get it from your [dashboard](https://app.easyotp.dev/api-keys).
</ParamField>

<ParamField body="base_url" type="string">
  Base URL for the API. Defaults to `https://app.easyotp.dev/api/v1`.
</ParamField>

<Warning>
  Never expose your API key in client-side code. Always use the SDK from your backend server.
</Warning>

## Methods

### send()

Send a verification code via SMS, Email, or Voice.

```python theme={null}
result = client.send(
    channel='sms',
    recipient='+1234567890',
    message='Your verification code is: {code}',
    expires_in=300
)
```

#### Parameters

<ParamField body="channel" type="string" required>
  Communication channel. Must be one of: `sms`, `email`, or `voice`
</ParamField>

<ParamField body="recipient" type="string" required>
  Recipient address:

  * For SMS/Voice: E.164 formatted phone number (e.g., `+1234567890`)
  * For Email: Valid email address (e.g., `user@example.com`)
</ParamField>

<ParamField body="message" type="string">
  Custom message template. Use `{code}` as a placeholder for the verification code.

  Default: `"Your verification code is: {code}"`
</ParamField>

<ParamField body="subject" type="string">
  Email subject line (only used when `channel` is `email`)

  Default: `"Your Verification Code"`
</ParamField>

<ParamField body="expires_in" type="int">
  Code expiration time in seconds. Must be between 60 and 3600.

  Default: `300` (5 minutes)
</ParamField>

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

  If not provided, a code will be automatically generated.
</ParamField>

#### Returns

<ResponseField name="success" type="bool">
  Always `True` for successful requests
</ResponseField>

<ResponseField name="verification_id" type="str">
  Unique identifier for this verification. Use this when calling `verify()`.
</ResponseField>

<ResponseField name="expires_at" type="str">
  ISO 8601 timestamp when the code expires
</ResponseField>

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

### verify()

Verify a code that was previously sent.

```python theme={null}
result = client.verify(
    verification_id='11f951d5-32d1-4b49-bdda-7da248e2615c',
    code='123456'
)
```

#### Parameters

<ParamField body="verification_id" type="string" required>
  The verification ID returned from the `send()` method
</ParamField>

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

#### Returns

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

<ResponseField name="verified" type="bool">
  `True` if the code was correct and not expired, `False` otherwise
</ResponseField>

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

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

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

## Examples

### Complete Verification Flow

```python theme={null}
from easyotp import EasyOTP
import os

client = EasyOTP(api_key=os.environ.get('EASYOTP_API_KEY'))

def verify_phone_number(phone_number):
    try:
        result = client.send(
            channel='sms',
            recipient=phone_number,
            message='Your verification code is: {code}',
            expires_in=300
        )
        
        print('Code sent! Verification ID:', result['verification_id'])
        return result['verification_id']
    except Exception as e:
        print(f'Failed to send code: {e}')
        raise

def check_verification_code(verification_id, code):
    try:
        result = client.verify(
            verification_id=verification_id,
            code=code
        )
        
        if result['verified']:
            print('Code verified successfully!')
            return True
        else:
            print('Verification failed:', result['message'])
            return False
    except Exception as e:
        print(f'Failed to verify code: {e}')
        raise

verification_id = verify_phone_number('+1234567890')
is_valid = check_verification_code(verification_id, '123456')
```

### Sending via Different Channels

<CodeGroup>
  ```python SMS theme={null}
  result = client.send(
      channel='sms',
      recipient='+1234567890',
      message='Your Acme Corp verification code is: {code}',
      expires_in=300
  )
  ```

  ```python Email theme={null}
  result = client.send(
      channel='email',
      recipient='user@example.com',
      subject='Verify Your Acme Corp Account',
      message='Your verification code is: {code}. This code expires in 5 minutes.',
      expires_in=600
  )
  ```

  ```python Voice theme={null}
  result = client.send(
      channel='voice',
      recipient='+1234567890',
      message='Your verification code is: {code}',
      expires_in=300
  )
  ```
</CodeGroup>

### Error Handling

The SDK raises exceptions for failed requests. Always wrap API calls in try-except blocks:

```python theme={null}
from easyotp import EasyOTP
from easyotp.exceptions import EasyOTPError, AuthenticationError, InsufficientCreditsError, RateLimitError

client = EasyOTP(api_key=os.environ.get('EASYOTP_API_KEY'))

try:
    result = client.send(
        channel='sms',
        recipient='+1234567890',
        message='Your verification code is: {code}'
    )
    
    print('Success:', result['verification_id'])
except AuthenticationError:
    print('Invalid API key')
except InsufficientCreditsError:
    print('Insufficient credits')
except RateLimitError as e:
    print(f'Rate limit exceeded. Retry after {e.retry_after} seconds')
except EasyOTPError as e:
    print(f'Error: {e.message}')
```

### Flask Integration Example

```python theme={null}
from flask import Flask, request, jsonify
from easyotp import EasyOTP
import os

app = Flask(__name__)
client = EasyOTP(api_key=os.environ.get('EASYOTP_API_KEY'))

sessions = {}

@app.route('/api/send-code', methods=['POST'])
def send_code():
    try:
        data = request.json
        phone_number = data.get('phoneNumber')
        
        result = client.send(
            channel='sms',
            recipient=phone_number,
            message='Your verification code is: {code}',
            expires_in=300
        )
        
        import uuid
        session_id = str(uuid.uuid4())
        sessions[session_id] = {
            'phone_number': phone_number,
            'verification_id': result['verification_id']
        }
        
        return jsonify({'sessionId': session_id})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/verify-code', methods=['POST'])
def verify_code():
    try:
        data = request.json
        session_id = data.get('sessionId')
        code = data.get('code')
        
        session = sessions.get(session_id)
        if not session:
            return jsonify({'error': 'Invalid session'}), 400
        
        result = client.verify(
            verification_id=session['verification_id'],
            code=code
        )
        
        if result['verified']:
            del sessions[session_id]
            return jsonify({'success': True})
        else:
            return jsonify({'success': False, 'message': result['message']})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run()
```

### Django Integration Example

```python theme={null}
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from easyotp import EasyOTP
import os
import json

client = EasyOTP(api_key=os.environ.get('EASYOTP_API_KEY'))

@csrf_exempt
@require_http_methods(["POST"])
def send_code(request):
    try:
        data = json.loads(request.body)
        phone_number = data.get('phoneNumber')
        
        result = client.send(
            channel='sms',
            recipient=phone_number,
            message='Your verification code is: {code}',
            expires_in=300
        )
        
        import uuid
        session_id = str(uuid.uuid4())
        request.session[session_id] = {
            'phone_number': phone_number,
            'verification_id': result['verification_id']
        }
        
        return JsonResponse({'sessionId': session_id})
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

@csrf_exempt
@require_http_methods(["POST"])
def verify_code(request):
    try:
        data = json.loads(request.body)
        session_id = data.get('sessionId')
        code = data.get('code')
        
        session = request.session.get(session_id)
        if not session:
            return JsonResponse({'error': 'Invalid session'}, status=400)
        
        result = client.verify(
            verification_id=session['verification_id'],
            code=code
        )
        
        if result['verified']:
            del request.session[session_id]
            return JsonResponse({'success': True})
        else:
            return JsonResponse({'success': False, 'message': result['message']})
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)
```

## Error Handling

The SDK raises custom exceptions for different error types:

### Exception Classes

* `EasyOTPError`: Base exception for all SDK errors
* `AuthenticationError`: Raised for 401 errors (invalid API key)
* `InsufficientCreditsError`: Raised for 402 errors (insufficient credits)
* `ForbiddenError`: Raised for 403 errors (API key disabled)
* `NotFoundError`: Raised for 404 errors (verification not found)
* `RateLimitError`: Raised for 429 errors (rate limit exceeded)
* `ServerError`: Raised for 500 errors (internal server error)

### Error Object Properties

All exceptions include:

* `message`: Human-readable error message
* `status_code`: HTTP status code
* `request_id`: Unique request identifier for debugging
* `retry_after`: Seconds to wait before retrying (for rate limit errors)

### Common Error Codes

| Status Code | Exception                  | Solution                          |
| ----------- | -------------------------- | --------------------------------- |
| `400`       | `EasyOTPError`             | Check your request parameters     |
| `401`       | `AuthenticationError`      | Verify your API key is correct    |
| `402`       | `InsufficientCreditsError` | Add credits to your account       |
| `403`       | `ForbiddenError`           | API key is disabled               |
| `404`       | `NotFoundError`            | Verification ID doesn't exist     |
| `429`       | `RateLimitError`           | Wait for `retry_after` seconds    |
| `500`       | `ServerError`              | Contact support with `request_id` |

## Type Hints

The SDK includes type hints for better IDE support:

```python theme={null}
from easyotp import EasyOTP
from typing import Dict, Any

client = EasyOTP(api_key='YOUR_API_KEY')

def send_otp(phone_number: str) -> Dict[str, Any]:
    return client.send(
        channel='sms',
        recipient=phone_number,
        message='Your code is: {code}'
    )

def verify_otp(verification_id: str, code: str) -> Dict[str, Any]:
    return client.verify(
        verification_id=verification_id,
        code=code
    )
```

## Best Practices

<Tip>
  **Store API keys securely**: Use environment variables and never commit them to version control.
</Tip>

<Tip>
  **Handle errors gracefully**: Always wrap SDK calls in try-except blocks and provide meaningful error messages to users.
</Tip>

<Tip>
  **Use appropriate expiration times**:

  * SMS/Voice: 2-5 minutes
  * Email: 10-15 minutes
</Tip>

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

<Note>
  **Rate limiting**: The SDK automatically handles rate limit responses. Check for `retry_after` in exception objects to inform users when they can retry.
</Note>

<Tip>
  **Use environment variables**: Store your API key in environment variables for better security:

  ```python theme={null}
  import os
  client = EasyOTP(api_key=os.environ.get('EASYOTP_API_KEY'))
  ```
</Tip>

## Additional Resources

<CardGroup cols={2}>
  <Card title="PyPI Package" icon="package" href="https://pypi.org/project/easyotp/">
    View on PyPI
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete REST API documentation
  </Card>

  <Card title="Quickstart Guide" icon="rocket" href="/quickstart">
    Get started in 5 minutes
  </Card>

  <Card title="Support" icon="life-ring" href="mailto:support@easyotp.dev">
    Get help from our team
  </Card>
</CardGroup>
