rightos-sdk 0.1.0__py3-none-any.whl
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.
rightos/__init__.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""RightOS Python SDK (zero-dependency, urllib-based).
|
|
2
|
+
|
|
3
|
+
RightOS is privacy-preserving rights verification infrastructure:
|
|
4
|
+
digital QR tickets ("Right Tokens") for queues, reservations, EV charging,
|
|
5
|
+
and package pickup. It verifies that a valid right is present - never who
|
|
6
|
+
the person is.
|
|
7
|
+
|
|
8
|
+
API reference: https://rightos.i-s3.com/openapi.json
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> from rightos import RightOS
|
|
12
|
+
>>> client = RightOS(api_key="rk_live_...")
|
|
13
|
+
>>> issued = client.issue_token(location_id="loc_...", title="Queue ticket")
|
|
14
|
+
>>> # Hand issued["walletUrl"] (QR page) to your customer.
|
|
15
|
+
>>> outcome = client.verify_token(issued["token"]["id"], issued["verificationCode"])
|
|
16
|
+
>>> outcome["result"]
|
|
17
|
+
'success'
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import urllib.error
|
|
24
|
+
import urllib.request
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
__all__ = ["RightOS", "RightOSError", "DEFAULT_BASE_URL"]
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
|
|
30
|
+
DEFAULT_BASE_URL = "https://rightos.i-s3.com"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RightOSError(Exception):
|
|
34
|
+
"""Raised for any non-2xx API response.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
status: HTTP status code.
|
|
38
|
+
code: Machine-readable error code (e.g. "invalid_api_key",
|
|
39
|
+
"policy_transfer_disabled", "transfer_limit_reached").
|
|
40
|
+
retry_after_sec: Seconds to wait before retrying (present on 429).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, status: int, code: str, retry_after_sec: Optional[int] = None):
|
|
44
|
+
super().__init__(f"RightOS API error {status}: {code}")
|
|
45
|
+
self.status = status
|
|
46
|
+
self.code = code
|
|
47
|
+
self.retry_after_sec = retry_after_sec
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RightOS:
|
|
51
|
+
"""RightOS API client.
|
|
52
|
+
|
|
53
|
+
Operator methods require ``api_key``. Public methods (verify_token,
|
|
54
|
+
transfer_token, get_token, get_location_policy, list_plans,
|
|
55
|
+
register_organization) work without a key.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, api_key: Optional[str] = None, base_url: str = DEFAULT_BASE_URL, timeout: float = 15.0):
|
|
59
|
+
self.api_key = api_key
|
|
60
|
+
self.base_url = base_url.rstrip("/")
|
|
61
|
+
self.timeout = timeout
|
|
62
|
+
|
|
63
|
+
# ---------- internals ----------
|
|
64
|
+
|
|
65
|
+
def _request(self, method: str, path: str, body: Any = ...) -> Any:
|
|
66
|
+
headers = {"User-Agent": f"rightos-sdk-python/{__version__}"}
|
|
67
|
+
data = None
|
|
68
|
+
if body is not ...:
|
|
69
|
+
headers["Content-Type"] = "application/json"
|
|
70
|
+
data = json.dumps(body).encode("utf-8")
|
|
71
|
+
if self.api_key:
|
|
72
|
+
headers["x-rightos-key"] = self.api_key
|
|
73
|
+
req = urllib.request.Request(
|
|
74
|
+
f"{self.base_url}{path}", data=data, headers=headers, method=method
|
|
75
|
+
)
|
|
76
|
+
try:
|
|
77
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as res:
|
|
78
|
+
return json.loads(res.read().decode("utf-8"))
|
|
79
|
+
except urllib.error.HTTPError as e:
|
|
80
|
+
code = "unknown_error"
|
|
81
|
+
try:
|
|
82
|
+
payload = json.loads(e.read().decode("utf-8"))
|
|
83
|
+
code = payload.get("error", code)
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
retry_after = e.headers.get("Retry-After")
|
|
87
|
+
raise RightOSError(
|
|
88
|
+
e.code, code, int(retry_after) if retry_after else None
|
|
89
|
+
) from None
|
|
90
|
+
|
|
91
|
+
# ---------- Public endpoints (no API key required) ----------
|
|
92
|
+
|
|
93
|
+
def list_plans(self) -> list[dict]:
|
|
94
|
+
"""List pricing plans (globally uniform pricing)."""
|
|
95
|
+
return self._request("GET", "/api/rightos/plans")["plans"]
|
|
96
|
+
|
|
97
|
+
def get_token(self, token_id: str) -> dict:
|
|
98
|
+
"""Get a Right Token (never includes the secret hash)."""
|
|
99
|
+
return self._request("GET", f"/api/rightos/tokens/{token_id}")["token"]
|
|
100
|
+
|
|
101
|
+
def verify_token(self, token_id: str, verification_code: str) -> dict:
|
|
102
|
+
"""Verify a Right Token by its verificationCode.
|
|
103
|
+
|
|
104
|
+
Returns ``{"result": "success" | "failed" | "expired" | "cancelled"
|
|
105
|
+
| "already_used", "token": {...}}``. Rate limited: 10/min per
|
|
106
|
+
IP+token, 60/min per IP.
|
|
107
|
+
"""
|
|
108
|
+
return self._request(
|
|
109
|
+
"POST",
|
|
110
|
+
f"/api/rightos/tokens/{token_id}/verify",
|
|
111
|
+
{"verificationCode": verification_code},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def transfer_token(self, token_id: str, current_verification_code: str) -> dict:
|
|
115
|
+
"""Transfer a right (re-keying). Only the current holder can transfer.
|
|
116
|
+
|
|
117
|
+
Subject to the location policy - raises RightOSError with code
|
|
118
|
+
"policy_transfer_disabled" or "transfer_limit_reached" (HTTP 409).
|
|
119
|
+
The new verificationCode and walletUrl are returned exactly once.
|
|
120
|
+
"""
|
|
121
|
+
return self._request(
|
|
122
|
+
"POST",
|
|
123
|
+
f"/api/rightos/tokens/{token_id}/transfer",
|
|
124
|
+
{"verificationCode": current_verification_code},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def get_location_policy(self, location_id: str) -> dict:
|
|
128
|
+
"""Get a location's effective policy (public, for transparency)."""
|
|
129
|
+
return self._request("GET", f"/api/rightos/locations/{location_id}/policy")
|
|
130
|
+
|
|
131
|
+
def register_organization(
|
|
132
|
+
self, name: str, contact_email: str, plan_id: str = "free", country: str = "JP"
|
|
133
|
+
) -> dict:
|
|
134
|
+
"""Register an organization.
|
|
135
|
+
|
|
136
|
+
The returned apiKey is shown EXACTLY ONCE - store it securely.
|
|
137
|
+
Rate limited: 5/hour per IP.
|
|
138
|
+
"""
|
|
139
|
+
return self._request(
|
|
140
|
+
"POST",
|
|
141
|
+
"/api/rightos/organizations",
|
|
142
|
+
{"name": name, "contactEmail": contact_email, "planId": plan_id, "country": country},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# ---------- Operator endpoints (API key required) ----------
|
|
146
|
+
|
|
147
|
+
def list_locations(self) -> list[dict]:
|
|
148
|
+
"""List your organization's locations."""
|
|
149
|
+
return self._request("GET", "/api/rightos/locations")["locations"]
|
|
150
|
+
|
|
151
|
+
def create_location(
|
|
152
|
+
self,
|
|
153
|
+
name: str,
|
|
154
|
+
address: str = "",
|
|
155
|
+
type: str = "other",
|
|
156
|
+
timezone: str = "Asia/Tokyo",
|
|
157
|
+
) -> dict:
|
|
158
|
+
"""Create a location. Raises 402 when the plan's location limit is exceeded."""
|
|
159
|
+
return self._request(
|
|
160
|
+
"POST",
|
|
161
|
+
"/api/rightos/locations",
|
|
162
|
+
{"name": name, "address": address, "type": type, "timezone": timezone},
|
|
163
|
+
)["location"]
|
|
164
|
+
|
|
165
|
+
def set_location_policy(self, location_id: str, patch: Optional[dict]) -> dict:
|
|
166
|
+
"""Override a location's policy (partial update).
|
|
167
|
+
|
|
168
|
+
Pass ``None`` to reset to the industry preset.
|
|
169
|
+
"""
|
|
170
|
+
return self._request(
|
|
171
|
+
"PUT", f"/api/rightos/locations/{location_id}/policy", patch
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def issue_token(
|
|
175
|
+
self,
|
|
176
|
+
location_id: str,
|
|
177
|
+
title: str,
|
|
178
|
+
description: str = "",
|
|
179
|
+
start_time: Optional[str] = None,
|
|
180
|
+
end_time: Optional[str] = None,
|
|
181
|
+
) -> dict:
|
|
182
|
+
"""Issue a Right Token.
|
|
183
|
+
|
|
184
|
+
The verificationCode and walletUrl are returned exactly once -
|
|
185
|
+
hand the walletUrl (QR page) to the end user. Raises 402 when the
|
|
186
|
+
plan's monthly token limit is exceeded.
|
|
187
|
+
"""
|
|
188
|
+
body: dict[str, Any] = {"locationId": location_id, "title": title}
|
|
189
|
+
if description:
|
|
190
|
+
body["description"] = description
|
|
191
|
+
if start_time:
|
|
192
|
+
body["startTime"] = start_time
|
|
193
|
+
if end_time:
|
|
194
|
+
body["endTime"] = end_time
|
|
195
|
+
return self._request("POST", "/api/rightos/tokens/issue", body)
|
|
196
|
+
|
|
197
|
+
def use_token(self, token_id: str) -> dict:
|
|
198
|
+
"""Mark a token as used (own organization only)."""
|
|
199
|
+
return self._request("POST", f"/api/rightos/tokens/{token_id}/use")["token"]
|
|
200
|
+
|
|
201
|
+
def cancel_token(self, token_id: str) -> dict:
|
|
202
|
+
"""Cancel a token (own organization only)."""
|
|
203
|
+
return self._request("POST", f"/api/rightos/tokens/{token_id}/cancel")["token"]
|
|
204
|
+
|
|
205
|
+
def export_data(self) -> dict:
|
|
206
|
+
"""Export all organization data as JSON (no lock-in; contains no secrets)."""
|
|
207
|
+
return self._request("GET", "/api/rightos/export")
|
|
208
|
+
|
|
209
|
+
def rotate_api_key(self) -> str:
|
|
210
|
+
"""Re-issue the API key. The old key is invalidated immediately.
|
|
211
|
+
|
|
212
|
+
The new key is returned exactly once. This client switches to the
|
|
213
|
+
new key automatically.
|
|
214
|
+
"""
|
|
215
|
+
new_key = self._request("POST", "/api/rightos/organizations/rotate-key")["apiKey"]
|
|
216
|
+
self.api_key = new_key
|
|
217
|
+
return new_key
|
|
218
|
+
|
|
219
|
+
def delete_organization(self, confirm_name: str) -> dict:
|
|
220
|
+
"""Permanently delete the organization and all its data (irreversible).
|
|
221
|
+
|
|
222
|
+
``confirm_name`` must exactly match the organization name.
|
|
223
|
+
"""
|
|
224
|
+
return self._request(
|
|
225
|
+
"POST", "/api/rightos/organizations/delete", {"confirm": confirm_name}
|
|
226
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rightos-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for RightOS - privacy-preserving rights verification API (digital QR tickets for queues, reservations, EV charging, package pickup).
|
|
5
|
+
Project-URL: Homepage, https://rightos.i-s3.com/software/rightos/docs
|
|
6
|
+
Project-URL: Repository, https://github.com/suomimasuda/rightos-sdk
|
|
7
|
+
Project-URL: API Spec, https://rightos.i-s3.com/openapi.json
|
|
8
|
+
Author: I-S3 Software Division
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: digital-ticket,i-s3,qr,queue,rightos,verification
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# rightos-sdk — RightOS Python SDK
|
|
19
|
+
|
|
20
|
+
Official Python SDK for [RightOS](https://rightos.i-s3.com/software/rightos) — privacy-preserving rights verification infrastructure. Issue and verify digital QR tickets ("Right Tokens") for queues, reservations, EV charging, and package pickup, without ever collecting end users' names or phone numbers.
|
|
21
|
+
|
|
22
|
+
- Zero dependencies (standard library only), Python ≥ 3.9
|
|
23
|
+
- Machine-readable spec: [openapi.json](https://rightos.i-s3.com/openapi.json) · AI-agent docs: [llms-full.txt](https://rightos.i-s3.com/llms-full.txt)
|
|
24
|
+
|
|
25
|
+
> RightOS is not a taxi or ride-hailing service. It does not arrange vehicles, set fares, assign drivers, or broker dispatch.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install rightos-sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart (60 seconds)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from rightos import RightOS
|
|
37
|
+
|
|
38
|
+
# 1. Register once (the apiKey is returned EXACTLY ONCE — store it securely)
|
|
39
|
+
pub = RightOS()
|
|
40
|
+
reg = pub.register_organization(name="My Shop", contact_email="you@example.com", plan_id="free")
|
|
41
|
+
api_key = reg["apiKey"]
|
|
42
|
+
|
|
43
|
+
# 2. Operator client
|
|
44
|
+
client = RightOS(api_key=api_key)
|
|
45
|
+
|
|
46
|
+
# 3. Issue a digital QR ticket
|
|
47
|
+
location = client.list_locations()[0]
|
|
48
|
+
issued = client.issue_token(location_id=location["id"], title="Queue ticket")
|
|
49
|
+
print("Hand this to your customer:", issued["walletUrl"])
|
|
50
|
+
|
|
51
|
+
# 4. Verify on arrival (no API key needed)
|
|
52
|
+
outcome = RightOS().verify_token(issued["token"]["id"], issued["verificationCode"])
|
|
53
|
+
print(outcome["result"]) # "success"
|
|
54
|
+
|
|
55
|
+
# 5. Mark as used after service
|
|
56
|
+
client.use_token(issued["token"]["id"])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Location policies (Policy Engine)
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
policy = client.get_location_policy(location["id"])["policy"]
|
|
63
|
+
# {"transferable": True, "maxTransfers": 3, "defaultValidityMinutes": 720, ...}
|
|
64
|
+
|
|
65
|
+
client.set_location_policy(location["id"], {"transferable": False}) # override
|
|
66
|
+
client.set_location_policy(location["id"], None) # reset to industry preset
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Error handling
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from rightos import RightOS, RightOSError
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
RightOS().transfer_token(token_id, code)
|
|
76
|
+
except RightOSError as e:
|
|
77
|
+
if e.code == "policy_transfer_disabled":
|
|
78
|
+
... # this location forbids transfers
|
|
79
|
+
elif e.status == 429:
|
|
80
|
+
... # rate limited; wait e.retry_after_sec
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Common codes: `missing_api_key` / `invalid_api_key` (401), plan limits (402), `policy_transfer_disabled` / `transfer_limit_reached` (409), `rate_limited` (429).
|
|
84
|
+
|
|
85
|
+
## Try it against the live demo
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
demo = RightOS(api_key="rk_demo_00000000000000000000") # shared demo org
|
|
89
|
+
print(demo.list_locations())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT © I-S3 Inc.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
rightos/__init__.py,sha256=5jfdJpWpzk8FNadCoqoQO6s_muFu0ltzHdIXxl0gTN4,8459
|
|
2
|
+
rightos_sdk-0.1.0.dist-info/METADATA,sha256=8eOKbLj25FXhcoz-ytpyoDMmum40_8jrLblKTKso-ms,3313
|
|
3
|
+
rightos_sdk-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
4
|
+
rightos_sdk-0.1.0.dist-info/RECORD,,
|