getricky 0.1.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,9 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .venv/
5
+ __pycache__/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
getricky-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ricky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.5
2
+ Name: getricky
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Ricky REST API
5
+ Project-URL: Homepage, https://getricky.ai/docs/api
6
+ Project-URL: Documentation, https://getricky.ai/docs/api
7
+ Project-URL: Repository, https://github.com/ricardostmalo/ricky-api
8
+ Project-URL: Issues, https://github.com/ricardostmalo/ricky-api/issues
9
+ Project-URL: Changelog, https://github.com/ricardostmalo/ricky-api/blob/main/CHANGELOG.md
10
+ Author-email: Ricky <ricardo@getricky.ai>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: api,conversations,crm,instagram,messenger,ricky,sdk,whatsapp
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: httpx<1,>=0.23
28
+ Requires-Dist: typing-extensions>=4.10
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Ricky Python SDK
32
+
33
+ The official Python client for the [Ricky](https://getricky.ai) REST API: contacts, conversations, messages, templates and webhooks of one organization.
34
+
35
+ - Guides and reference: <https://getricky.ai/docs/api>
36
+ - Source and OpenAPI description: <https://github.com/ricardostmalo/ricky-api>
37
+
38
+ Python 3.9 or later. Its only dependencies are `httpx` and `typing-extensions`.
39
+
40
+ ```bash
41
+ # Until it is on PyPI:
42
+ pip install "git+https://github.com/ricardostmalo/ricky-api#subdirectory=sdks/python"
43
+ # Once published:
44
+ pip install getricky
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ Create a key in Ricky under **Settings → API** and keep it on your server. `Ricky()` reads it from `RICKY_API_KEY` when you pass none.
50
+
51
+ ```python
52
+ import time
53
+
54
+ from getricky import Ricky
55
+
56
+ ricky = Ricky() # or Ricky("rky_live_…")
57
+
58
+ # The channels the organization can send from.
59
+ channels = ricky.list_channels()["data"]
60
+ whatsapp = next(channel for channel in channels if channel["type"] == "whatsapp")
61
+
62
+ # Send an approved WhatsApp template to a phone number. Ricky finds or creates
63
+ # the contact and its conversation, and answers with the send it queued.
64
+ send = ricky.start_conversation(
65
+ {
66
+ "channel_id": whatsapp["id"],
67
+ "phone": "+50760001234",
68
+ "template": {
69
+ "id": "1234567890",
70
+ "components": [{"type": "body", "parameters": [{"type": "text", "text": "Ana"}]}],
71
+ },
72
+ },
73
+ idempotency_key="order-10482-ready",
74
+ )["data"]
75
+
76
+ # Follow it until the provider accepts it.
77
+ while send["status"] == "queued":
78
+ time.sleep(2)
79
+ send = ricky.get_send(send["id"])["data"]
80
+ print(send["status"], send["failure_reason"])
81
+ ```
82
+
83
+ Every operation of the API is a method named after its `operationId` in snake case (`listContacts` is `list_contacts`). Path parameters come first, then the request body as a dict, then query parameters as keywords. Methods return the response body as the API sends it: `{"data": ...}` for one resource and `{"data": [...], "next_cursor": ...}` for a list. Requests and responses are typed with `TypedDict`s from `getricky.types`, so your editor and type checker know every field, and fields the API adds later reach you without an upgrade.
84
+
85
+ `AsyncRicky` has the same methods for `asyncio`:
86
+
87
+ ```python
88
+ from getricky import AsyncRicky
89
+
90
+ async with AsyncRicky() as ricky:
91
+ contact = (await ricky.get_contact(contact_id))["data"]
92
+ ```
93
+
94
+ ## Pagination
95
+
96
+ `paginate` walks every page of a list method and yields its items; `.pages()` yields whole pages:
97
+
98
+ ```python
99
+ for contact in ricky.paginate(ricky.list_contacts, tag_id=tag_id):
100
+ print(contact["id"], contact["first_name"])
101
+
102
+ for page in ricky.paginate(ricky.list_messages, conversation_id).pages():
103
+ print(len(page["data"]))
104
+ ```
105
+
106
+ With `AsyncRicky`, use `async for`.
107
+
108
+ ## Errors
109
+
110
+ A failed request raises `RickyError` with the API's `code`, the HTTP `status`, the `param` that caused it when known, and the `request_id` to quote to support. A request that never got an answer raises `RickyConnectionError`.
111
+
112
+ ```python
113
+ from getricky import RickyError
114
+
115
+ try:
116
+ ricky.send_message(conversation_id, {"type": "text", "text": "Hola"})
117
+ except RickyError as error:
118
+ if error.code == "outside_messaging_window":
119
+ ... # send an approved template instead
120
+ else:
121
+ raise
122
+ ```
123
+
124
+ ## Retries and idempotency
125
+
126
+ Writes that accept an `Idempotency-Key` get a random one unless you pass `idempotency_key=`. Pass your own, derived from your data, to make a retry from your side safe too: the same key and body replay the first response instead of repeating the write.
127
+
128
+ Reads, and writes that carry a key, are retried twice on connection errors, `429` and `5xx`, waiting as `Retry-After` says or backing off exponentially. Change it with `Ricky(max_retries=…)`. A write the API may have applied before failing answers `request_processing` on retry: read the resource before trying again.
129
+
130
+ ## Webhooks
131
+
132
+ `verify_webhook` checks the `X-Ricky-Signature` and `X-Ricky-Timestamp` headers against the raw body, rejects deliveries more than five minutes old, and returns the event. With Flask:
133
+
134
+ ```python
135
+ from flask import Flask, request
136
+ from getricky import WebhookVerificationError, verify_webhook
137
+
138
+ app = Flask(__name__)
139
+
140
+
141
+ @app.post("/ricky/webhooks")
142
+ def ricky_webhook():
143
+ try:
144
+ event = verify_webhook(request.get_data(), request.headers, WEBHOOK_SECRET)
145
+ except WebhookVerificationError:
146
+ return "", 401
147
+ if event["type"] == "message.received":
148
+ ... # queue the work; deduplicate by event["id"]
149
+ return "", 204
150
+ ```
151
+
152
+ Answer within 10 seconds. Deliveries can repeat, so deduplicate by `event["id"]`. New event types ship without a new API version: handle the ones you know and ignore the rest.
153
+
154
+ ## Configuration
155
+
156
+ ```python
157
+ Ricky(
158
+ "rky_live_…",
159
+ base_url="https://getricky.ai/api/v1",
160
+ max_retries=2,
161
+ timeout=120.0, # seconds per attempt; a file send downloads the file before it answers
162
+ http_client=None, # or your own httpx.Client
163
+ )
164
+ ```
165
+
166
+ ## License
167
+
168
+ [MIT](LICENSE)
@@ -0,0 +1,138 @@
1
+ # Ricky Python SDK
2
+
3
+ The official Python client for the [Ricky](https://getricky.ai) REST API: contacts, conversations, messages, templates and webhooks of one organization.
4
+
5
+ - Guides and reference: <https://getricky.ai/docs/api>
6
+ - Source and OpenAPI description: <https://github.com/ricardostmalo/ricky-api>
7
+
8
+ Python 3.9 or later. Its only dependencies are `httpx` and `typing-extensions`.
9
+
10
+ ```bash
11
+ # Until it is on PyPI:
12
+ pip install "git+https://github.com/ricardostmalo/ricky-api#subdirectory=sdks/python"
13
+ # Once published:
14
+ pip install getricky
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ Create a key in Ricky under **Settings → API** and keep it on your server. `Ricky()` reads it from `RICKY_API_KEY` when you pass none.
20
+
21
+ ```python
22
+ import time
23
+
24
+ from getricky import Ricky
25
+
26
+ ricky = Ricky() # or Ricky("rky_live_…")
27
+
28
+ # The channels the organization can send from.
29
+ channels = ricky.list_channels()["data"]
30
+ whatsapp = next(channel for channel in channels if channel["type"] == "whatsapp")
31
+
32
+ # Send an approved WhatsApp template to a phone number. Ricky finds or creates
33
+ # the contact and its conversation, and answers with the send it queued.
34
+ send = ricky.start_conversation(
35
+ {
36
+ "channel_id": whatsapp["id"],
37
+ "phone": "+50760001234",
38
+ "template": {
39
+ "id": "1234567890",
40
+ "components": [{"type": "body", "parameters": [{"type": "text", "text": "Ana"}]}],
41
+ },
42
+ },
43
+ idempotency_key="order-10482-ready",
44
+ )["data"]
45
+
46
+ # Follow it until the provider accepts it.
47
+ while send["status"] == "queued":
48
+ time.sleep(2)
49
+ send = ricky.get_send(send["id"])["data"]
50
+ print(send["status"], send["failure_reason"])
51
+ ```
52
+
53
+ Every operation of the API is a method named after its `operationId` in snake case (`listContacts` is `list_contacts`). Path parameters come first, then the request body as a dict, then query parameters as keywords. Methods return the response body as the API sends it: `{"data": ...}` for one resource and `{"data": [...], "next_cursor": ...}` for a list. Requests and responses are typed with `TypedDict`s from `getricky.types`, so your editor and type checker know every field, and fields the API adds later reach you without an upgrade.
54
+
55
+ `AsyncRicky` has the same methods for `asyncio`:
56
+
57
+ ```python
58
+ from getricky import AsyncRicky
59
+
60
+ async with AsyncRicky() as ricky:
61
+ contact = (await ricky.get_contact(contact_id))["data"]
62
+ ```
63
+
64
+ ## Pagination
65
+
66
+ `paginate` walks every page of a list method and yields its items; `.pages()` yields whole pages:
67
+
68
+ ```python
69
+ for contact in ricky.paginate(ricky.list_contacts, tag_id=tag_id):
70
+ print(contact["id"], contact["first_name"])
71
+
72
+ for page in ricky.paginate(ricky.list_messages, conversation_id).pages():
73
+ print(len(page["data"]))
74
+ ```
75
+
76
+ With `AsyncRicky`, use `async for`.
77
+
78
+ ## Errors
79
+
80
+ A failed request raises `RickyError` with the API's `code`, the HTTP `status`, the `param` that caused it when known, and the `request_id` to quote to support. A request that never got an answer raises `RickyConnectionError`.
81
+
82
+ ```python
83
+ from getricky import RickyError
84
+
85
+ try:
86
+ ricky.send_message(conversation_id, {"type": "text", "text": "Hola"})
87
+ except RickyError as error:
88
+ if error.code == "outside_messaging_window":
89
+ ... # send an approved template instead
90
+ else:
91
+ raise
92
+ ```
93
+
94
+ ## Retries and idempotency
95
+
96
+ Writes that accept an `Idempotency-Key` get a random one unless you pass `idempotency_key=`. Pass your own, derived from your data, to make a retry from your side safe too: the same key and body replay the first response instead of repeating the write.
97
+
98
+ Reads, and writes that carry a key, are retried twice on connection errors, `429` and `5xx`, waiting as `Retry-After` says or backing off exponentially. Change it with `Ricky(max_retries=…)`. A write the API may have applied before failing answers `request_processing` on retry: read the resource before trying again.
99
+
100
+ ## Webhooks
101
+
102
+ `verify_webhook` checks the `X-Ricky-Signature` and `X-Ricky-Timestamp` headers against the raw body, rejects deliveries more than five minutes old, and returns the event. With Flask:
103
+
104
+ ```python
105
+ from flask import Flask, request
106
+ from getricky import WebhookVerificationError, verify_webhook
107
+
108
+ app = Flask(__name__)
109
+
110
+
111
+ @app.post("/ricky/webhooks")
112
+ def ricky_webhook():
113
+ try:
114
+ event = verify_webhook(request.get_data(), request.headers, WEBHOOK_SECRET)
115
+ except WebhookVerificationError:
116
+ return "", 401
117
+ if event["type"] == "message.received":
118
+ ... # queue the work; deduplicate by event["id"]
119
+ return "", 204
120
+ ```
121
+
122
+ Answer within 10 seconds. Deliveries can repeat, so deduplicate by `event["id"]`. New event types ship without a new API version: handle the ones you know and ignore the rest.
123
+
124
+ ## Configuration
125
+
126
+ ```python
127
+ Ricky(
128
+ "rky_live_…",
129
+ base_url="https://getricky.ai/api/v1",
130
+ max_retries=2,
131
+ timeout=120.0, # seconds per attempt; a file send downloads the file before it answers
132
+ http_client=None, # or your own httpx.Client
133
+ )
134
+ ```
135
+
136
+ ## License
137
+
138
+ [MIT](LICENSE)
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"] # 1.27 reads SPDX license; newer ones need Python 3.10 to build
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "getricky"
7
+ dynamic = ["version"]
8
+ description = "Official Python SDK for the Ricky REST API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "Ricky", email = "ricardo@getricky.ai" }]
14
+ keywords = ["ricky", "whatsapp", "instagram", "messenger", "crm", "conversations", "api", "sdk"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = ["httpx>=0.23,<1", "typing-extensions>=4.10"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://getricky.ai/docs/api"
33
+ Documentation = "https://getricky.ai/docs/api"
34
+ Repository = "https://github.com/ricardostmalo/ricky-api"
35
+ Issues = "https://github.com/ricardostmalo/ricky-api/issues"
36
+ Changelog = "https://github.com/ricardostmalo/ricky-api/blob/main/CHANGELOG.md"
37
+
38
+ [dependency-groups]
39
+ test = [
40
+ "pytest==8.4.2; python_version < '3.10'",
41
+ "pytest==9.1.1; python_version >= '3.10'",
42
+ "respx==0.23.1",
43
+ "mypy==2.4.0; python_version >= '3.10'",
44
+ ]
45
+
46
+ [tool.hatch.version]
47
+ path = "src/getricky/_version.py"
48
+
49
+ [tool.hatch.build.targets.sdist]
50
+ include = ["src/getricky", "tests", "README.md", "LICENSE"]
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/getricky"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
@@ -0,0 +1,22 @@
1
+ """The official Python SDK for the Ricky REST API: https://getricky.ai/docs/api"""
2
+
3
+ from ._client import DEFAULT_BASE_URL, AsyncPaginator, AsyncRicky, Paginator, Ricky
4
+ from ._errors import RickyConnectionError, RickyError, WebhookVerificationError
5
+ from ._page import Page, Resource
6
+ from ._version import __version__
7
+ from ._webhooks import verify_webhook
8
+
9
+ __all__ = [
10
+ "DEFAULT_BASE_URL",
11
+ "AsyncPaginator",
12
+ "AsyncRicky",
13
+ "Page",
14
+ "Paginator",
15
+ "Resource",
16
+ "Ricky",
17
+ "RickyConnectionError",
18
+ "RickyError",
19
+ "WebhookVerificationError",
20
+ "__version__",
21
+ "verify_webhook",
22
+ ]