Metadata-Version: 2.4
Name: safwatech-otp
Version: 0.1.0
Summary: Official Python SDK for the Safwatech OTP / verification platform (OTP, TOTP, magic links, silent push, WebAuthn).
Project-URL: Homepage, https://otp.safwatech.com.ly
Project-URL: Documentation, https://otp.safwatech.com.ly/docs
Project-URL: Source, https://github.com/safwatech/otp-python
Project-URL: Issues, https://github.com/safwatech/otp-python/issues
Author-email: Safwatech <dev@safwatech.com.ly>
License-Expression: MIT
License-File: LICENSE
Keywords: 2fa,authentication,magic-link,otp,passwordless,safwatech,totp,webauthn
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: requests>=2.28
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Requires-Dist: responses>=0.24; extra == 'test'
Description-Content-Type: text/markdown

# safwatech-otp

Official Python SDK for the **Safwatech OTP / verification platform** — OTP
(push / SMS / email / WhatsApp), TOTP authenticator (RFC 6238) with backup
codes, magic links, silent push approval, and WebAuthn / passkeys.

```bash
pip install safwatech-otp
```

Requires Python ≥ 3.9. Single runtime dependency: `requests`.

## Quick start

```python
from safwatech_otp import OTPClient

client = OTPClient(
    api_key="sk_test_…",                       # or sk_live_…
    base_url="https://otp.safwatech.com.ly",
)

sent = client.send_otp("+218911234567", channel="sms", reference_id="order-42")
# When using a sandbox key, the response includes the otp_code so you can
# round-trip without hitting a real SMS provider:
if code := sent["result"].get("otp_code"):
    client.verify_otp("+218911234567", code, reference_id="order-42")
```

API keys are issued from the client portal. `sk_test_*` keys put the call in
**sandbox** mode — no real delivery, no quota consumption, responses are
stamped with `X-Sandbox: true`.

## Response envelope

All endpoints return a JSON envelope; `OTPClient` returns the decoded body
verbatim so you can read the platform's payload structure directly:

```python
{
  "status": "success",
  "result": {...}             # endpoint-specific
}
```

Errors raise `OTPClientError` with the HTTP status, decoded payload, and
the `Retry-After` value when present:

```python
from safwatech_otp import OTPClientError

try:
    client.send_otp("+218911234567")
except OTPClientError as exc:
    if exc.status == 429:
        time.sleep(exc.retry_after or 1)
        ...
```

## Methods

| Surface | Methods |
| --- | --- |
| **OTP** | `send_otp` · `verify_otp` · `get_status` · `usage` |
| **Scheduled** | `schedule_otp` · `cancel_scheduled` · `list_scheduled` |
| **TOTP** | `enroll_totp` · `verify_totp` · `totp_status` · `unenroll_totp` · `generate_backup_codes` |
| **Magic links** | `send_magic_link` · `get_magic_link` · `cancel_magic_link` · `wait_for_magic_link` |
| **Silent push auth** | `request_auth` · `get_auth_request` · `cancel_auth_request` · `wait_for_auth` |
| **WebAuthn / passkeys** | `webauthn_register` · `webauthn_authenticate` · `get_webauthn` · `cancel_webauthn` · `wait_for_webauthn` |

The `wait_for_*` helpers poll the matching `get_*` endpoint until the request
reaches a terminal state (e.g. `consumed | expired | cancelled` for magic
links) or the timeout elapses; they return the final polled response.

## Verifying webhooks

```python
from safwatech_otp import verify_webhook

if not verify_webhook(
    raw_body=request.body,
    secret="the callback_secret you set on your service",
    signature_header=request.headers.get("X-Webhook-Signature", ""),
    timestamp_header=request.headers.get("X-Webhook-Timestamp", ""),
):
    abort(401)
```

The header format is `X-Webhook-Signature: v1=<hex>` over `"{timestamp}.{body}"`
(HMAC-SHA256). A 300 s tolerance window is enforced by default.

## Examples & docs

- Live API reference: <https://otp.safwatech.com.ly/docs>
- Runnable scripts: `sdks/examples/` in the source repo (`python_example.py`,
  `magic_link.py`, `silent_auth.py`, `totp_enroll.py`, `webauthn.py`,
  `verify_webhook.py`).

## Development

```bash
pip install -e ".[test]"
pytest
```

## License

MIT. See [LICENSE](LICENSE).
