sempera 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.
sempera-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sempera Labs, Inc.
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.
sempera-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: sempera
3
+ Version: 0.1.0
4
+ Summary: The official Python client for the Sempera API: a method for every operation, retries, idempotency keys, paging and webhook verification
5
+ Author-email: "Sempera Labs, Inc." <hello@sempera.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Sempera-AI/sempera-python
8
+ Project-URL: Issues, https://github.com/Sempera-AI/sempera-python/issues
9
+ Keywords: sempera,voice,phone,ai,sdk
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # sempera
19
+
20
+ [Documentation](https://docs.sempera.org) | [SDKs](https://docs.sempera.org/sdks) | [OpenAPI document](https://api.sempera.org/openapi.json) | [MIT licence](LICENSE)
21
+
22
+ The official Python client for the [Sempera](https://sempera.org) API at `https://api.sempera.org`.
23
+
24
+ * One method for every operation in the public OpenAPI document (`https://api.sempera.org/openapi.json`), generated from it, so the client follows the API.
25
+ * A small hand-written core: bearer-key authentication, typed errors, retries with exponential backoff, automatic idempotency keys, cursor paging and webhook signature verification.
26
+ * Standard library only. Python 3.9 or newer.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install sempera
32
+ ```
33
+
34
+ ## Quick start
35
+
36
+ Create an API key in the Sempera App and keep it on your server. Pass it to the constructor, or set `SEMPERA_API_KEY` and omit it.
37
+
38
+ ```python
39
+ from sempera import Sempera
40
+
41
+ client = Sempera("sempera_sk_example") # or Sempera() with SEMPERA_API_KEY set
42
+ print(client.get_me())
43
+ ```
44
+
45
+ Methods are named after the operation id in the contract, in snake_case (`list_agents`, `create_call`, `send_sms`). Path and query parameters are named exactly as in the contract. A request body is a plain dict, passed first. Answers are plain dicts.
46
+
47
+ ### Agents
48
+
49
+ ```python
50
+ from sempera import paginate
51
+
52
+ page = client.list_agents(limit=20)
53
+ for agent in page["data"]:
54
+ print(agent["id"])
55
+
56
+ # Or follow every page:
57
+ for agent in paginate(lambda cursor: client.list_agents(limit=100, cursor=cursor)):
58
+ print(agent["id"])
59
+
60
+ one = client.get_agent("agt_example")
61
+ ```
62
+
63
+ ### Calls
64
+
65
+ ```python
66
+ call = client.create_call(
67
+ {
68
+ "agent_id": "agt_example",
69
+ "mode": "prompt",
70
+ "phone_number": "+14155550123",
71
+ "task": "Confirm Friday at 3pm. Ask whether that time still suits.",
72
+ "consent_basis": "This person asked us to call them about Friday.",
73
+ },
74
+ idempotency_key="friday-3pm-ada",
75
+ )
76
+
77
+ same = client.get_call(call["id"])
78
+ recent = client.list_calls(agent_id="agt_example", limit=10)
79
+ ```
80
+
81
+ ### Phone numbers and text messages
82
+
83
+ ```python
84
+ numbers = client.list_phone_numbers(agent_id="agt_example")
85
+
86
+ client.send_sms({
87
+ "agent_id": "agt_example",
88
+ "agent_number": "+14155550100",
89
+ "user_number": "+14155550123",
90
+ "agent_message": "Your table is confirmed for Friday at 3pm.",
91
+ "consent_basis": "This person asked us to text them a confirmation.",
92
+ })
93
+ ```
94
+
95
+ ### Conversations
96
+
97
+ ```python
98
+ threads = client.messaging_conversations("agt_example", "sms")
99
+ thread = client.messaging_conversation("agt_example", "sms", "conv_example")
100
+ ```
101
+
102
+ ### Knowledge sources
103
+
104
+ ```python
105
+ sources = client.list_knowledge_sources(agent_id="agt_example")
106
+ client.create_text_knowledge_source({
107
+ "agent_id": "agt_example",
108
+ "name": "Opening hours",
109
+ "content": "Open 9 to 5, Monday to Friday.",
110
+ })
111
+ ```
112
+
113
+ ### Webhooks
114
+
115
+ Verify every delivery against the raw request body before you parse it. The helper checks the `X-Sempera-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 over "<t>." + body>`) in constant time and rejects a timestamp more than 300 seconds from your clock. Answer `400` and process nothing when it raises.
116
+
117
+ ```python
118
+ from sempera import WebhookSignatureError, construct_event
119
+
120
+ def handle(raw_body: bytes, signature_header: str) -> int:
121
+ try:
122
+ event = construct_event("whsec_example", raw_body, signature_header)
123
+ except WebhookSignatureError:
124
+ return 400
125
+ print(event["type"], event["id"])
126
+ return 200
127
+ ```
128
+
129
+ ## Errors
130
+
131
+ Every refusal raises a typed error carrying `status`, the stable `code`, an optional `reason` and `field`, and the `request_id` to quote to support. Branch on `code` or `reason`, never on the message.
132
+
133
+ ```python
134
+ from sempera import NotFoundError, RateLimitError, SemperaError
135
+
136
+ try:
137
+ client.get_agent("agt_missing")
138
+ except NotFoundError as err:
139
+ print(err.code, err.request_id)
140
+ except RateLimitError as err:
141
+ print("retry in", err.retry_after)
142
+ except SemperaError as err:
143
+ print(err.status, err.code, err.reason)
144
+ ```
145
+
146
+ Classes: `ValidationError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `IdempotencyKeyReusedError` (409), `RateLimitError` (429), `ServerError` (5xx) and `ConnectionFailure` (no answer).
147
+
148
+ ## Retries and idempotency
149
+
150
+ The client retries a `429`, a `502`, a `504`, a `503` with code `service_unavailable` and a dropped connection, two times by default, with exponential backoff and jitter, waiting the `Retry-After` it is sent. Change the count with `max_retries` on the client or on one call. A write is retried only when it carries an idempotency key (generated for you, and reused by every retry) or after a `429`. Pass your own `idempotency_key` to make a retry safe across process restarts.
151
+
152
+ ## Develop
153
+
154
+ ```bash
155
+ python -m unittest discover -s tests
156
+ ```
157
+
158
+ The tests replace the HTTP layer with a scripted fake, so they need no network and no key.
159
+
160
+ ## Licence
161
+
162
+ MIT. Copyright Sempera Labs, Inc.
@@ -0,0 +1,145 @@
1
+ # sempera
2
+
3
+ [Documentation](https://docs.sempera.org) | [SDKs](https://docs.sempera.org/sdks) | [OpenAPI document](https://api.sempera.org/openapi.json) | [MIT licence](LICENSE)
4
+
5
+ The official Python client for the [Sempera](https://sempera.org) API at `https://api.sempera.org`.
6
+
7
+ * One method for every operation in the public OpenAPI document (`https://api.sempera.org/openapi.json`), generated from it, so the client follows the API.
8
+ * A small hand-written core: bearer-key authentication, typed errors, retries with exponential backoff, automatic idempotency keys, cursor paging and webhook signature verification.
9
+ * Standard library only. Python 3.9 or newer.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install sempera
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ Create an API key in the Sempera App and keep it on your server. Pass it to the constructor, or set `SEMPERA_API_KEY` and omit it.
20
+
21
+ ```python
22
+ from sempera import Sempera
23
+
24
+ client = Sempera("sempera_sk_example") # or Sempera() with SEMPERA_API_KEY set
25
+ print(client.get_me())
26
+ ```
27
+
28
+ Methods are named after the operation id in the contract, in snake_case (`list_agents`, `create_call`, `send_sms`). Path and query parameters are named exactly as in the contract. A request body is a plain dict, passed first. Answers are plain dicts.
29
+
30
+ ### Agents
31
+
32
+ ```python
33
+ from sempera import paginate
34
+
35
+ page = client.list_agents(limit=20)
36
+ for agent in page["data"]:
37
+ print(agent["id"])
38
+
39
+ # Or follow every page:
40
+ for agent in paginate(lambda cursor: client.list_agents(limit=100, cursor=cursor)):
41
+ print(agent["id"])
42
+
43
+ one = client.get_agent("agt_example")
44
+ ```
45
+
46
+ ### Calls
47
+
48
+ ```python
49
+ call = client.create_call(
50
+ {
51
+ "agent_id": "agt_example",
52
+ "mode": "prompt",
53
+ "phone_number": "+14155550123",
54
+ "task": "Confirm Friday at 3pm. Ask whether that time still suits.",
55
+ "consent_basis": "This person asked us to call them about Friday.",
56
+ },
57
+ idempotency_key="friday-3pm-ada",
58
+ )
59
+
60
+ same = client.get_call(call["id"])
61
+ recent = client.list_calls(agent_id="agt_example", limit=10)
62
+ ```
63
+
64
+ ### Phone numbers and text messages
65
+
66
+ ```python
67
+ numbers = client.list_phone_numbers(agent_id="agt_example")
68
+
69
+ client.send_sms({
70
+ "agent_id": "agt_example",
71
+ "agent_number": "+14155550100",
72
+ "user_number": "+14155550123",
73
+ "agent_message": "Your table is confirmed for Friday at 3pm.",
74
+ "consent_basis": "This person asked us to text them a confirmation.",
75
+ })
76
+ ```
77
+
78
+ ### Conversations
79
+
80
+ ```python
81
+ threads = client.messaging_conversations("agt_example", "sms")
82
+ thread = client.messaging_conversation("agt_example", "sms", "conv_example")
83
+ ```
84
+
85
+ ### Knowledge sources
86
+
87
+ ```python
88
+ sources = client.list_knowledge_sources(agent_id="agt_example")
89
+ client.create_text_knowledge_source({
90
+ "agent_id": "agt_example",
91
+ "name": "Opening hours",
92
+ "content": "Open 9 to 5, Monday to Friday.",
93
+ })
94
+ ```
95
+
96
+ ### Webhooks
97
+
98
+ Verify every delivery against the raw request body before you parse it. The helper checks the `X-Sempera-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 over "<t>." + body>`) in constant time and rejects a timestamp more than 300 seconds from your clock. Answer `400` and process nothing when it raises.
99
+
100
+ ```python
101
+ from sempera import WebhookSignatureError, construct_event
102
+
103
+ def handle(raw_body: bytes, signature_header: str) -> int:
104
+ try:
105
+ event = construct_event("whsec_example", raw_body, signature_header)
106
+ except WebhookSignatureError:
107
+ return 400
108
+ print(event["type"], event["id"])
109
+ return 200
110
+ ```
111
+
112
+ ## Errors
113
+
114
+ Every refusal raises a typed error carrying `status`, the stable `code`, an optional `reason` and `field`, and the `request_id` to quote to support. Branch on `code` or `reason`, never on the message.
115
+
116
+ ```python
117
+ from sempera import NotFoundError, RateLimitError, SemperaError
118
+
119
+ try:
120
+ client.get_agent("agt_missing")
121
+ except NotFoundError as err:
122
+ print(err.code, err.request_id)
123
+ except RateLimitError as err:
124
+ print("retry in", err.retry_after)
125
+ except SemperaError as err:
126
+ print(err.status, err.code, err.reason)
127
+ ```
128
+
129
+ Classes: `ValidationError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `IdempotencyKeyReusedError` (409), `RateLimitError` (429), `ServerError` (5xx) and `ConnectionFailure` (no answer).
130
+
131
+ ## Retries and idempotency
132
+
133
+ The client retries a `429`, a `502`, a `504`, a `503` with code `service_unavailable` and a dropped connection, two times by default, with exponential backoff and jitter, waiting the `Retry-After` it is sent. Change the count with `max_retries` on the client or on one call. A write is retried only when it carries an idempotency key (generated for you, and reused by every retry) or after a `429`. Pass your own `idempotency_key` to make a retry safe across process restarts.
134
+
135
+ ## Develop
136
+
137
+ ```bash
138
+ python -m unittest discover -s tests
139
+ ```
140
+
141
+ The tests replace the HTTP layer with a scripted fake, so they need no network and no key.
142
+
143
+ ## Licence
144
+
145
+ MIT. Copyright Sempera Labs, Inc.
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sempera"
7
+ version = "0.1.0"
8
+ description = "The official Python client for the Sempera API: a method for every operation, retries, idempotency keys, paging and webhook verification"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = []
12
+ license = { text = "MIT" }
13
+ authors = [{ name = "Sempera Labs, Inc.", email = "hello@sempera.org" }]
14
+ keywords = ["sempera", "voice", "phone", "ai", "sdk"]
15
+ classifiers = [
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/Sempera-AI/sempera-python"
23
+ Issues = "https://github.com/Sempera-AI/sempera-python/issues"
24
+
25
+ [tool.setuptools]
26
+ packages = ["sempera"]
@@ -0,0 +1,29 @@
1
+ """The Sempera Python SDK. Quick start: https://api.sempera.org/docs/sdks.md."""
2
+
3
+ from ._generated import API_VERSION, DEFAULT_BASE_URL, ERROR_CODES, OPERATIONS, Operations
4
+ from ._runtime import (
5
+ AuthenticationError,
6
+ ConflictError,
7
+ ConnectionFailure,
8
+ IdempotencyKeyReusedError,
9
+ NotFoundError,
10
+ PermissionDeniedError,
11
+ RateLimitError,
12
+ SemperaError,
13
+ ServerError,
14
+ ValidationError,
15
+ paginate,
16
+ )
17
+ from .webhooks import WebhookSignatureError, construct_event, verify_signature
18
+
19
+
20
+ class Sempera(Operations):
21
+ """`Sempera(api_key)` then `client.list_agents()`; one method per operation, named in snake_case."""
22
+
23
+
24
+ __all__ = [
25
+ "API_VERSION", "DEFAULT_BASE_URL", "ERROR_CODES", "OPERATIONS", "Sempera", "paginate",
26
+ "SemperaError", "ValidationError", "AuthenticationError", "PermissionDeniedError", "NotFoundError",
27
+ "ConflictError", "IdempotencyKeyReusedError", "RateLimitError", "ServerError", "ConnectionFailure",
28
+ "WebhookSignatureError", "construct_event", "verify_signature",
29
+ ]