achek-sdk 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ .env
2
+ env
3
+ node_modules
4
+ .env
5
+ .env
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: achek-sdk
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for Achek — WhatsApp OTP, AI chatbots & business messaging for Nigeria
5
+ Project-URL: Homepage, https://achek.com.ng
6
+ Project-URL: Documentation, https://docs.achek.com.ng
7
+ Project-URL: Repository, https://github.com/CalebDevX/AchekVerify
8
+ Project-URL: Issues, https://github.com/CalebDevX/AchekVerify/issues
9
+ Author-email: Achek <dev@achek.com.ng>
10
+ License: MIT
11
+ Keywords: 2fa,achek,chatbot,nigeria,otp,verification,whatsapp
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.8
24
+ Requires-Dist: requests>=2.28.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # achek-sdk
28
+
29
+ Official Python SDK for [Achek](https://achek.com.ng) — WhatsApp OTP, AI chatbots & business messaging for Nigerian developers.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install achek-sdk
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from achekverify import AchekConnect
41
+
42
+ client = AchekConnect("your_api_key")
43
+
44
+ # Send a WhatsApp OTP
45
+ result = client.send_otp("+2348XXXXXXXXX")
46
+ print(result["sessionId"])
47
+
48
+ # Verify the OTP the user enters
49
+ check = client.verify_otp("+2348XXXXXXXXX", otp="482910")
50
+ print(check["valid"]) # True / False
51
+
52
+ # Send a plain WhatsApp message
53
+ client.send_message("+2348XXXXXXXXX", "Hello from Achek!")
54
+ ```
55
+
56
+ ## Authentication
57
+
58
+ Get your API key from the [Achek dashboard](https://console.achek.com.ng).
59
+
60
+ ```python
61
+ client = AchekConnect("ak_live_your_key_here")
62
+ ```
63
+
64
+ ## Features
65
+
66
+ - **WhatsApp OTP** — Send and verify one-time passwords via WhatsApp
67
+ - **Messaging** — Send plain and templated WhatsApp messages
68
+ - **AI Bot** — Create and manage AI chatbot agents
69
+ - **Webhooks** — Verify and parse incoming webhook events
70
+ - **Broadcasts** — Send bulk messages to multiple numbers
71
+
72
+ ## Webhook Verification
73
+
74
+ ```python
75
+ from achekverify import Webhooks
76
+
77
+ wh = Webhooks("your_webhook_secret")
78
+ payload = wh.verify(raw_body, signature_header)
79
+ print(payload["event"]) # e.g. "otp.verified"
80
+ ```
81
+
82
+ ## Links
83
+
84
+ - [Documentation](https://docs.achek.com.ng)
85
+ - [Dashboard](https://console.achek.com.ng)
86
+ - [GitHub](https://github.com/CalebDevX/AchekVerify)
87
+ - [Support](https://achek.com.ng/support)
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,65 @@
1
+ # achek-sdk
2
+
3
+ Official Python SDK for [Achek](https://achek.com.ng) — WhatsApp OTP, AI chatbots & business messaging for Nigerian developers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install achek-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from achekverify import AchekConnect
15
+
16
+ client = AchekConnect("your_api_key")
17
+
18
+ # Send a WhatsApp OTP
19
+ result = client.send_otp("+2348XXXXXXXXX")
20
+ print(result["sessionId"])
21
+
22
+ # Verify the OTP the user enters
23
+ check = client.verify_otp("+2348XXXXXXXXX", otp="482910")
24
+ print(check["valid"]) # True / False
25
+
26
+ # Send a plain WhatsApp message
27
+ client.send_message("+2348XXXXXXXXX", "Hello from Achek!")
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ Get your API key from the [Achek dashboard](https://console.achek.com.ng).
33
+
34
+ ```python
35
+ client = AchekConnect("ak_live_your_key_here")
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - **WhatsApp OTP** — Send and verify one-time passwords via WhatsApp
41
+ - **Messaging** — Send plain and templated WhatsApp messages
42
+ - **AI Bot** — Create and manage AI chatbot agents
43
+ - **Webhooks** — Verify and parse incoming webhook events
44
+ - **Broadcasts** — Send bulk messages to multiple numbers
45
+
46
+ ## Webhook Verification
47
+
48
+ ```python
49
+ from achekverify import Webhooks
50
+
51
+ wh = Webhooks("your_webhook_secret")
52
+ payload = wh.verify(raw_body, signature_header)
53
+ print(payload["event"]) # e.g. "otp.verified"
54
+ ```
55
+
56
+ ## Links
57
+
58
+ - [Documentation](https://docs.achek.com.ng)
59
+ - [Dashboard](https://console.achek.com.ng)
60
+ - [GitHub](https://github.com/CalebDevX/AchekVerify)
61
+ - [Support](https://achek.com.ng/support)
62
+
63
+ ## License
64
+
65
+ MIT
@@ -0,0 +1,43 @@
1
+ """
2
+ AchekConnect Python SDK
3
+ ======================
4
+ Official client for the AchekConnect API — WhatsApp OTP, messaging, and AI bots.
5
+
6
+ Quick start::
7
+
8
+ from achekconnect import AchekConnect
9
+
10
+ client = AchekConnect("your-api-key")
11
+
12
+ # Send OTP
13
+ result = client.send_otp("+2348012345678")
14
+ print(result["sessionId"])
15
+
16
+ # Verify OTP
17
+ check = client.verify_otp("+2348012345678", otp="482910")
18
+ print(check["valid"]) # True / False
19
+ """
20
+
21
+ from .client import AchekConnect
22
+ from .errors import (
23
+ AchekError,
24
+ AuthenticationError,
25
+ NetworkError,
26
+ NotFoundError,
27
+ RateLimitError,
28
+ ValidationError,
29
+ )
30
+ from .webhooks import Webhooks
31
+
32
+ __all__ = [
33
+ "AchekConnect",
34
+ "AchekError",
35
+ "AuthenticationError",
36
+ "RateLimitError",
37
+ "ValidationError",
38
+ "NotFoundError",
39
+ "NetworkError",
40
+ "Webhooks",
41
+ ]
42
+
43
+ __version__ = "1.0.0"
@@ -0,0 +1,195 @@
1
+ import time
2
+ from typing import Any, Dict, Optional
3
+
4
+ import requests
5
+
6
+ from .errors import (
7
+ AchekError,
8
+ AuthenticationError,
9
+ NetworkError,
10
+ NotFoundError,
11
+ RateLimitError,
12
+ ValidationError,
13
+ )
14
+ from .webhooks import Webhooks
15
+
16
+ DEFAULT_BASE_URL = "https://api.achek.com.ng/v1"
17
+ DEFAULT_TIMEOUT = 30
18
+ DEFAULT_RETRIES = 3
19
+ SDK_VERSION = "achekconnect-python/1.0.0"
20
+
21
+
22
+ class AchekConnect:
23
+ """
24
+ Official AchekConnect Python client.
25
+
26
+ Usage::
27
+
28
+ from achekconnect import AchekConnect
29
+
30
+ client = AchekConnect("your-api-key")
31
+ result = client.send_otp("+2348012345678")
32
+ print(result["sessionId"])
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ api_key: str,
38
+ *,
39
+ base_url: str = DEFAULT_BASE_URL,
40
+ timeout: int = DEFAULT_TIMEOUT,
41
+ retries: int = DEFAULT_RETRIES,
42
+ webhook_secret: str = "",
43
+ ):
44
+ if not api_key:
45
+ raise AuthenticationError("api_key is required")
46
+ self._base_url = base_url.rstrip("/")
47
+ self._timeout = timeout
48
+ self._retries = retries
49
+ self._session = requests.Session()
50
+ self._session.headers.update(
51
+ {
52
+ "Authorization": f"Bearer {api_key}",
53
+ "Content-Type": "application/json",
54
+ "X-SDK": SDK_VERSION,
55
+ }
56
+ )
57
+ self.webhooks = Webhooks(webhook_secret)
58
+
59
+ # ──────────────────────────────── Transport ────────────────────────────────
60
+
61
+ def _request(self, method: str, path: str, **kwargs) -> Dict[str, Any]:
62
+ url = f"{self._base_url}{path}"
63
+ attempt = 0
64
+ while True:
65
+ try:
66
+ res = self._session.request(method, url, timeout=self._timeout, **kwargs)
67
+ except requests.exceptions.Timeout:
68
+ if attempt < self._retries:
69
+ attempt += 1
70
+ time.sleep(min(0.5 * 2 ** attempt, 10))
71
+ continue
72
+ raise NetworkError("Request timed out")
73
+ except requests.exceptions.RequestException as exc:
74
+ if attempt < self._retries:
75
+ attempt += 1
76
+ time.sleep(min(0.5 * 2 ** attempt, 10))
77
+ continue
78
+ raise NetworkError(str(exc)) from exc
79
+
80
+ if res.status_code == 401:
81
+ raise AuthenticationError()
82
+
83
+ if res.status_code == 429:
84
+ retry_after = int(res.headers.get("Retry-After", 60))
85
+ if attempt < self._retries:
86
+ time.sleep(retry_after)
87
+ attempt += 1
88
+ continue
89
+ raise RateLimitError(retry_after)
90
+
91
+ data = res.json()
92
+
93
+ if not data.get("success"):
94
+ err = data.get("error", {})
95
+ msg = err.get("message", "Request failed")
96
+ code = err.get("code", "UNKNOWN")
97
+ if res.status_code == 422:
98
+ raise ValidationError(msg)
99
+ if res.status_code == 404:
100
+ raise NotFoundError(msg)
101
+ raise AchekError(msg, code, res.status_code)
102
+
103
+ return data
104
+
105
+ # ──────────────────────────────── OTP ─────────────────────────────────────
106
+
107
+ def send_otp(
108
+ self,
109
+ phone: str,
110
+ *,
111
+ length: int = 6,
112
+ expires_in: int = 600,
113
+ template: Optional[str] = None,
114
+ sender_number: Optional[str] = None,
115
+ ) -> Dict[str, Any]:
116
+ """
117
+ Send a WhatsApp OTP to a phone number.
118
+
119
+ Args:
120
+ phone: E.164 number e.g. ``"+2348012345678"``.
121
+ length: OTP digit length — 4 or 6 (default 6).
122
+ expires_in: Seconds until expiry (default 600).
123
+ template: Custom message with ``{{otp}}`` placeholder.
124
+ sender_number: Override the default sender number.
125
+
126
+ Returns:
127
+ dict with ``sessionId``, ``phone``, ``expiresAt``, ``remaining``.
128
+ """
129
+ body: Dict[str, Any] = {"phone": phone, "length": length, "expiresIn": expires_in}
130
+ if template is not None:
131
+ body["template"] = template
132
+ if sender_number is not None:
133
+ body["senderNumber"] = sender_number
134
+ return self._request("POST", "/otp/send", json=body)["data"]
135
+
136
+ def verify_otp(self, phone: str, otp: str) -> Dict[str, Any]:
137
+ """
138
+ Verify a code entered by the user.
139
+
140
+ Returns:
141
+ dict with ``valid`` (bool), ``phone``, ``sessionId``.
142
+ """
143
+ return self._request("POST", "/otp/verify", json={"phone": phone, "otp": otp})["data"]
144
+
145
+ # ──────────────────────────────── Messaging ────────────────────────────────
146
+
147
+ def send_message(
148
+ self,
149
+ phone: str,
150
+ message: str,
151
+ *,
152
+ sender_number: Optional[str] = None,
153
+ ) -> Dict[str, Any]:
154
+ """Send a plain WhatsApp message."""
155
+ body: Dict[str, Any] = {"phone": phone, "message": message}
156
+ if sender_number is not None:
157
+ body["senderNumber"] = sender_number
158
+ return self._request("POST", "/messages/send", json=body)["data"]
159
+
160
+ # ──────────────────────────────── AI Agents ────────────────────────────────
161
+
162
+ def create_agent(
163
+ self,
164
+ name: str,
165
+ system_prompt: str,
166
+ *,
167
+ provider: str = "openai",
168
+ model: Optional[str] = None,
169
+ welcome_message: Optional[str] = None,
170
+ webhook_url: Optional[str] = None,
171
+ phone_number: Optional[str] = None,
172
+ ) -> Dict[str, Any]:
173
+ """Create a new AI WhatsApp agent."""
174
+ body: Dict[str, Any] = {
175
+ "name": name,
176
+ "systemPrompt": system_prompt,
177
+ "provider": provider,
178
+ }
179
+ if model is not None:
180
+ body["model"] = model
181
+ if welcome_message is not None:
182
+ body["welcomeMessage"] = welcome_message
183
+ if webhook_url is not None:
184
+ body["webhookUrl"] = webhook_url
185
+ if phone_number is not None:
186
+ body["phoneNumber"] = phone_number
187
+ return self._request("POST", "/agents", json=body)["data"]
188
+
189
+ def get_agent(self, agent_id: str) -> Dict[str, Any]:
190
+ """Retrieve an agent by ID."""
191
+ return self._request("GET", f"/agents/{agent_id}")["data"]
192
+
193
+ def delete_agent(self, agent_id: str) -> Dict[str, Any]:
194
+ """Delete an agent permanently."""
195
+ return self._request("DELETE", f"/agents/{agent_id}")["data"]
@@ -0,0 +1,55 @@
1
+ class AchekError(Exception):
2
+ """Base exception for all AchekConnect SDK errors."""
3
+
4
+ def __init__(
5
+ self,
6
+ message: str,
7
+ code: str = "UNKNOWN",
8
+ status_code: int = None,
9
+ request_id: str = None,
10
+ ):
11
+ super().__init__(message)
12
+ self.message = message
13
+ self.code = code
14
+ self.status_code = status_code
15
+ self.request_id = request_id
16
+
17
+ def __repr__(self) -> str:
18
+ return f"{self.__class__.__name__}(code={self.code!r}, message={self.message!r})"
19
+
20
+
21
+ class AuthenticationError(AchekError):
22
+ """Raised when the API key is missing or invalid (HTTP 401)."""
23
+
24
+ def __init__(self, message: str = "Invalid or missing API key"):
25
+ super().__init__(message, "AUTHENTICATION_ERROR", 401)
26
+
27
+
28
+ class RateLimitError(AchekError):
29
+ """Raised when the rate limit is exceeded (HTTP 429)."""
30
+
31
+ def __init__(self, retry_after: int = 60, message: str = "Rate limit exceeded"):
32
+ super().__init__(message, "RATE_LIMIT_ERROR", 429)
33
+ self.retry_after = retry_after
34
+
35
+
36
+ class ValidationError(AchekError):
37
+ """Raised when request validation fails (HTTP 422)."""
38
+
39
+ def __init__(self, message: str, field: str = None):
40
+ super().__init__(message, "VALIDATION_ERROR", 422)
41
+ self.field = field
42
+
43
+
44
+ class NotFoundError(AchekError):
45
+ """Raised when a resource is not found (HTTP 404)."""
46
+
47
+ def __init__(self, resource: str = "Resource"):
48
+ super().__init__(f"{resource} not found", "NOT_FOUND", 404)
49
+
50
+
51
+ class NetworkError(AchekError):
52
+ """Raised for connection failures, timeouts, and other transport errors."""
53
+
54
+ def __init__(self, message: str):
55
+ super().__init__(message, "NETWORK_ERROR")
@@ -0,0 +1,56 @@
1
+ import hmac
2
+ import hashlib
3
+ import json
4
+ from typing import Any, Dict
5
+
6
+
7
+ class Webhooks:
8
+ """Helpers for verifying and parsing incoming AchekConnect webhook events."""
9
+
10
+ def __init__(self, secret: str):
11
+ self._secret = secret
12
+
13
+ def verify(self, payload: bytes, signature: str) -> bool:
14
+ """
15
+ Return True if the HMAC-SHA256 signature matches the payload.
16
+
17
+ Args:
18
+ payload: Raw request body bytes.
19
+ signature: Value of the X-AchekConnect-Signature header.
20
+ """
21
+ if not self._secret:
22
+ return False
23
+ expected = hmac.new(
24
+ self._secret.encode("utf-8"), payload, hashlib.sha256
25
+ ).hexdigest()
26
+ return hmac.compare_digest(expected, signature)
27
+
28
+ def parse(self, payload: str) -> Dict[str, Any]:
29
+ """Parse a raw JSON payload string into a dict."""
30
+ return json.loads(payload)
31
+
32
+ def construct_event(self, payload: bytes, signature: str) -> Dict[str, Any]:
33
+ """
34
+ Verify the signature then parse the payload.
35
+
36
+ Use this in your webhook route handler::
37
+
38
+ @app.route("/webhook", methods=["POST"])
39
+ def webhook():
40
+ event = client.webhooks.construct_event(
41
+ request.get_data(),
42
+ request.headers.get("X-AchekConnect-Signature", "")
43
+ )
44
+ if event["type"] == "otp.verified":
45
+ ...
46
+ return "", 200
47
+
48
+ Raises:
49
+ ValueError: If the signature does not match.
50
+ """
51
+ if not self.verify(payload, signature):
52
+ raise ValueError(
53
+ "Webhook signature verification failed. "
54
+ "Ensure you are passing the raw request body and the correct secret."
55
+ )
56
+ return self.parse(payload.decode("utf-8"))
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "achek-sdk"
7
+ version = "1.0.0"
8
+ description = "Official Python SDK for Achek — WhatsApp OTP, AI chatbots & business messaging for Nigeria"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Achek", email = "dev@achek.com.ng" }]
13
+ keywords = ["achek", "otp", "whatsapp", "nigeria", "verification", "2fa", "chatbot"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Communications",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
27
+ dependencies = ["requests>=2.28.0"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://achek.com.ng"
31
+ Documentation = "https://docs.achek.com.ng"
32
+ Repository = "https://github.com/CalebDevX/AchekVerify"
33
+ Issues = "https://github.com/CalebDevX/AchekVerify/issues"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["achekverify"]