shield-python 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.
- shield_python-0.1.0/PKG-INFO +266 -0
- shield_python-0.1.0/README.md +255 -0
- shield_python-0.1.0/pyproject.toml +16 -0
- shield_python-0.1.0/setup.cfg +4 -0
- shield_python-0.1.0/setup.py +8 -0
- shield_python-0.1.0/shield/__init__.py +5 -0
- shield_python-0.1.0/shield/client.py +130 -0
- shield_python-0.1.0/shield/exceptions.py +23 -0
- shield_python-0.1.0/shield/resources/__init__.py +0 -0
- shield_python-0.1.0/shield/resources/events.py +40 -0
- shield_python-0.1.0/shield/resources/sessions.py +47 -0
- shield_python-0.1.0/shield/resources/verify.py +19 -0
- shield_python-0.1.0/shield_python.egg-info/PKG-INFO +266 -0
- shield_python-0.1.0/shield_python.egg-info/SOURCES.txt +15 -0
- shield_python-0.1.0/shield_python.egg-info/dependency_links.txt +1 -0
- shield_python-0.1.0/shield_python.egg-info/requires.txt +1 -0
- shield_python-0.1.0/shield_python.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shield-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Shield SDK for Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://getshield.dev
|
|
7
|
+
Project-URL: Documentation, https://docs.getshield.dev
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.28.0
|
|
11
|
+
|
|
12
|
+
# Shield Python SDK
|
|
13
|
+
|
|
14
|
+
Official Python SDK for [Shield](https://getshield.dev) — tamper-evident session recording for real estate transactions.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install shield-python
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import shield
|
|
26
|
+
|
|
27
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
28
|
+
|
|
29
|
+
# Create a session
|
|
30
|
+
session = client.sessions.create(title="123 Main St Closing")
|
|
31
|
+
session_id = session["id"]
|
|
32
|
+
|
|
33
|
+
# Record events
|
|
34
|
+
client.events.create(
|
|
35
|
+
session_id=session_id,
|
|
36
|
+
event_type="shield.party.joined",
|
|
37
|
+
actor="agent@example.com",
|
|
38
|
+
data={"role": "listing_agent", "name": "Jane Smith"},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
client.events.create(
|
|
42
|
+
session_id=session_id,
|
|
43
|
+
event_type="shield.content.uploaded",
|
|
44
|
+
actor="agent@example.com",
|
|
45
|
+
data={"filename": "purchase_agreement.pdf", "hash": "sha256:abc123..."},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
client.events.create(
|
|
49
|
+
session_id=session_id,
|
|
50
|
+
event_type="shield.agreement.signed",
|
|
51
|
+
actor="buyer@example.com",
|
|
52
|
+
data={"document": "purchase_agreement.pdf"},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Verify session integrity
|
|
56
|
+
result = client.verify.session(session_id)
|
|
57
|
+
print(result["intact"]) # True
|
|
58
|
+
|
|
59
|
+
# Export session
|
|
60
|
+
pdf_bytes = client.sessions.export(session_id, format="pdf")
|
|
61
|
+
with open("audit_trail.pdf", "wb") as f:
|
|
62
|
+
f.write(pdf_bytes)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## HMAC Authentication
|
|
66
|
+
|
|
67
|
+
For enhanced security, provide an HMAC secret to sign every request:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
client = shield.Client(
|
|
71
|
+
api_key="sk_live_your_api_key",
|
|
72
|
+
hmac_secret="your_hmac_secret",
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When an HMAC secret is configured, the SDK computes a signature for each request:
|
|
77
|
+
|
|
78
|
+
- `X-Shield-Timestamp` — Unix timestamp of the request
|
|
79
|
+
- `X-Shield-Signature` — HMAC-SHA256 of `{timestamp}.{METHOD}.{path}.{SHA256(body)}`
|
|
80
|
+
|
|
81
|
+
The server validates these headers to ensure requests have not been tampered with or replayed.
|
|
82
|
+
|
|
83
|
+
## Flask Integration
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from flask import Flask, request, jsonify
|
|
87
|
+
import shield
|
|
88
|
+
|
|
89
|
+
app = Flask(__name__)
|
|
90
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
91
|
+
|
|
92
|
+
@app.route("/sessions", methods=["POST"])
|
|
93
|
+
def create_session():
|
|
94
|
+
data = request.get_json()
|
|
95
|
+
session = client.sessions.create(title=data["title"])
|
|
96
|
+
|
|
97
|
+
# Record who created the session
|
|
98
|
+
client.events.create(
|
|
99
|
+
session_id=session["id"],
|
|
100
|
+
event_type="shield.session.created",
|
|
101
|
+
actor=data["user_email"],
|
|
102
|
+
)
|
|
103
|
+
return jsonify(session), 201
|
|
104
|
+
|
|
105
|
+
@app.route("/sessions/<session_id>/upload", methods=["POST"])
|
|
106
|
+
def upload_document(session_id):
|
|
107
|
+
file = request.files["file"]
|
|
108
|
+
# ... save file logic ...
|
|
109
|
+
|
|
110
|
+
client.events.create(
|
|
111
|
+
session_id=session_id,
|
|
112
|
+
event_type="shield.content.uploaded",
|
|
113
|
+
actor=request.headers.get("X-User-Email"),
|
|
114
|
+
data={"filename": file.filename},
|
|
115
|
+
)
|
|
116
|
+
return jsonify({"status": "uploaded"}), 200
|
|
117
|
+
|
|
118
|
+
@app.route("/sessions/<session_id>/sign", methods=["POST"])
|
|
119
|
+
def sign_agreement(session_id):
|
|
120
|
+
data = request.get_json()
|
|
121
|
+
|
|
122
|
+
client.events.create(
|
|
123
|
+
session_id=session_id,
|
|
124
|
+
event_type="shield.agreement.signed",
|
|
125
|
+
actor=data["signer_email"],
|
|
126
|
+
data={"document": data["document_name"], "ip": request.remote_addr},
|
|
127
|
+
)
|
|
128
|
+
return jsonify({"status": "signed"}), 200
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## FastAPI Integration
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from fastapi import FastAPI, UploadFile, Header
|
|
135
|
+
from pydantic import BaseModel
|
|
136
|
+
import shield
|
|
137
|
+
|
|
138
|
+
app = FastAPI()
|
|
139
|
+
client = shield.Client(
|
|
140
|
+
api_key="sk_live_your_api_key",
|
|
141
|
+
hmac_secret="your_hmac_secret",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
class CreateSessionRequest(BaseModel):
|
|
145
|
+
title: str
|
|
146
|
+
user_email: str
|
|
147
|
+
|
|
148
|
+
class SignRequest(BaseModel):
|
|
149
|
+
signer_email: str
|
|
150
|
+
document_name: str
|
|
151
|
+
|
|
152
|
+
@app.post("/sessions")
|
|
153
|
+
async def create_session(body: CreateSessionRequest):
|
|
154
|
+
session = client.sessions.create(title=body.title)
|
|
155
|
+
client.events.create(
|
|
156
|
+
session_id=session["id"],
|
|
157
|
+
event_type="shield.session.created",
|
|
158
|
+
actor=body.user_email,
|
|
159
|
+
)
|
|
160
|
+
return session
|
|
161
|
+
|
|
162
|
+
@app.post("/sessions/{session_id}/upload")
|
|
163
|
+
async def upload_document(
|
|
164
|
+
session_id: str,
|
|
165
|
+
file: UploadFile,
|
|
166
|
+
x_user_email: str = Header(...),
|
|
167
|
+
):
|
|
168
|
+
# ... save file logic ...
|
|
169
|
+
|
|
170
|
+
client.events.create(
|
|
171
|
+
session_id=session_id,
|
|
172
|
+
event_type="shield.content.uploaded",
|
|
173
|
+
actor=x_user_email,
|
|
174
|
+
data={"filename": file.filename},
|
|
175
|
+
)
|
|
176
|
+
return {"status": "uploaded"}
|
|
177
|
+
|
|
178
|
+
@app.post("/sessions/{session_id}/sign")
|
|
179
|
+
async def sign_agreement(session_id: str, body: SignRequest):
|
|
180
|
+
client.events.create(
|
|
181
|
+
session_id=session_id,
|
|
182
|
+
event_type="shield.agreement.signed",
|
|
183
|
+
actor=body.signer_email,
|
|
184
|
+
data={"document": body.document_name},
|
|
185
|
+
)
|
|
186
|
+
return {"status": "signed"}
|
|
187
|
+
|
|
188
|
+
@app.get("/sessions/{session_id}/verify")
|
|
189
|
+
async def verify_session(session_id: str):
|
|
190
|
+
return client.verify.session(session_id)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Event Types Reference
|
|
194
|
+
|
|
195
|
+
Shield Standard Event Taxonomy v1.0 — 37 event types across 7 categories:
|
|
196
|
+
|
|
197
|
+
### Party Events
|
|
198
|
+
| Event Type | Description |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `shield.party.joined` | A party joined the session |
|
|
201
|
+
| `shield.party.left` | A party left the session |
|
|
202
|
+
| `shield.party.identity.verified` | Party identity was verified |
|
|
203
|
+
| `shield.party.identity.failed` | Party identity verification failed |
|
|
204
|
+
| `shield.party.role.assigned` | A role was assigned to a party |
|
|
205
|
+
|
|
206
|
+
### Session Events
|
|
207
|
+
| Event Type | Description |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `shield.session.created` | Session was created |
|
|
210
|
+
| `shield.session.opened` | Session was opened |
|
|
211
|
+
| `shield.session.closed` | Session was closed |
|
|
212
|
+
| `shield.session.expired` | Session expired |
|
|
213
|
+
| `shield.session.archived` | Session was archived |
|
|
214
|
+
|
|
215
|
+
### Content Events
|
|
216
|
+
| Event Type | Description |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `shield.content.uploaded` | Content was uploaded |
|
|
219
|
+
| `shield.content.viewed` | Content was viewed |
|
|
220
|
+
| `shield.content.downloaded` | Content was downloaded |
|
|
221
|
+
| `shield.content.deleted` | Content was deleted |
|
|
222
|
+
| `shield.content.hash.verified` | Content hash was verified |
|
|
223
|
+
|
|
224
|
+
### Negotiation Events
|
|
225
|
+
| Event Type | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `shield.negotiation.terms.proposed` | Terms were proposed |
|
|
228
|
+
| `shield.negotiation.terms.accepted` | Terms were accepted |
|
|
229
|
+
| `shield.negotiation.terms.rejected` | Terms were rejected |
|
|
230
|
+
| `shield.negotiation.terms.modified` | Terms were modified |
|
|
231
|
+
| `shield.negotiation.terms.expired` | Terms expired |
|
|
232
|
+
| `shield.negotiation.message.sent` | Negotiation message sent |
|
|
233
|
+
| `shield.negotiation.message.read` | Negotiation message read |
|
|
234
|
+
|
|
235
|
+
### Agreement Events
|
|
236
|
+
| Event Type | Description |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `shield.agreement.drafted` | Agreement was drafted |
|
|
239
|
+
| `shield.agreement.reviewed` | Agreement was reviewed |
|
|
240
|
+
| `shield.agreement.approved` | Agreement was approved |
|
|
241
|
+
| `shield.agreement.signed` | Agreement was signed |
|
|
242
|
+
| `shield.agreement.countersigned` | Agreement was countersigned |
|
|
243
|
+
| `shield.agreement.voided` | Agreement was voided |
|
|
244
|
+
| `shield.agreement.reached` | Agreement was reached |
|
|
245
|
+
|
|
246
|
+
### Access Events
|
|
247
|
+
| Event Type | Description |
|
|
248
|
+
|---|---|
|
|
249
|
+
| `shield.access.granted` | Access was granted |
|
|
250
|
+
| `shield.access.revoked` | Access was revoked |
|
|
251
|
+
| `shield.access.attempted` | Access was attempted |
|
|
252
|
+
| `shield.access.denied` | Access was denied |
|
|
253
|
+
|
|
254
|
+
### Disclosure Events
|
|
255
|
+
| Event Type | Description |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `shield.disclosure.presented` | Disclosure was presented |
|
|
258
|
+
| `shield.disclosure.acknowledged` | Disclosure was acknowledged |
|
|
259
|
+
| `shield.disclosure.declined` | Disclosure was declined |
|
|
260
|
+
|
|
261
|
+
### Evidence Events
|
|
262
|
+
| Event Type | Description |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `shield.evidence.exported` | Evidence was exported |
|
|
265
|
+
| `shield.evidence.verified` | Evidence was verified |
|
|
266
|
+
| `shield.evidence.tampered_detected` | Evidence tampering was detected |
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Shield Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [Shield](https://getshield.dev) — tamper-evident session recording for real estate transactions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install shield-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import shield
|
|
15
|
+
|
|
16
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
17
|
+
|
|
18
|
+
# Create a session
|
|
19
|
+
session = client.sessions.create(title="123 Main St Closing")
|
|
20
|
+
session_id = session["id"]
|
|
21
|
+
|
|
22
|
+
# Record events
|
|
23
|
+
client.events.create(
|
|
24
|
+
session_id=session_id,
|
|
25
|
+
event_type="shield.party.joined",
|
|
26
|
+
actor="agent@example.com",
|
|
27
|
+
data={"role": "listing_agent", "name": "Jane Smith"},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
client.events.create(
|
|
31
|
+
session_id=session_id,
|
|
32
|
+
event_type="shield.content.uploaded",
|
|
33
|
+
actor="agent@example.com",
|
|
34
|
+
data={"filename": "purchase_agreement.pdf", "hash": "sha256:abc123..."},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
client.events.create(
|
|
38
|
+
session_id=session_id,
|
|
39
|
+
event_type="shield.agreement.signed",
|
|
40
|
+
actor="buyer@example.com",
|
|
41
|
+
data={"document": "purchase_agreement.pdf"},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Verify session integrity
|
|
45
|
+
result = client.verify.session(session_id)
|
|
46
|
+
print(result["intact"]) # True
|
|
47
|
+
|
|
48
|
+
# Export session
|
|
49
|
+
pdf_bytes = client.sessions.export(session_id, format="pdf")
|
|
50
|
+
with open("audit_trail.pdf", "wb") as f:
|
|
51
|
+
f.write(pdf_bytes)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## HMAC Authentication
|
|
55
|
+
|
|
56
|
+
For enhanced security, provide an HMAC secret to sign every request:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
client = shield.Client(
|
|
60
|
+
api_key="sk_live_your_api_key",
|
|
61
|
+
hmac_secret="your_hmac_secret",
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
When an HMAC secret is configured, the SDK computes a signature for each request:
|
|
66
|
+
|
|
67
|
+
- `X-Shield-Timestamp` — Unix timestamp of the request
|
|
68
|
+
- `X-Shield-Signature` — HMAC-SHA256 of `{timestamp}.{METHOD}.{path}.{SHA256(body)}`
|
|
69
|
+
|
|
70
|
+
The server validates these headers to ensure requests have not been tampered with or replayed.
|
|
71
|
+
|
|
72
|
+
## Flask Integration
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from flask import Flask, request, jsonify
|
|
76
|
+
import shield
|
|
77
|
+
|
|
78
|
+
app = Flask(__name__)
|
|
79
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
80
|
+
|
|
81
|
+
@app.route("/sessions", methods=["POST"])
|
|
82
|
+
def create_session():
|
|
83
|
+
data = request.get_json()
|
|
84
|
+
session = client.sessions.create(title=data["title"])
|
|
85
|
+
|
|
86
|
+
# Record who created the session
|
|
87
|
+
client.events.create(
|
|
88
|
+
session_id=session["id"],
|
|
89
|
+
event_type="shield.session.created",
|
|
90
|
+
actor=data["user_email"],
|
|
91
|
+
)
|
|
92
|
+
return jsonify(session), 201
|
|
93
|
+
|
|
94
|
+
@app.route("/sessions/<session_id>/upload", methods=["POST"])
|
|
95
|
+
def upload_document(session_id):
|
|
96
|
+
file = request.files["file"]
|
|
97
|
+
# ... save file logic ...
|
|
98
|
+
|
|
99
|
+
client.events.create(
|
|
100
|
+
session_id=session_id,
|
|
101
|
+
event_type="shield.content.uploaded",
|
|
102
|
+
actor=request.headers.get("X-User-Email"),
|
|
103
|
+
data={"filename": file.filename},
|
|
104
|
+
)
|
|
105
|
+
return jsonify({"status": "uploaded"}), 200
|
|
106
|
+
|
|
107
|
+
@app.route("/sessions/<session_id>/sign", methods=["POST"])
|
|
108
|
+
def sign_agreement(session_id):
|
|
109
|
+
data = request.get_json()
|
|
110
|
+
|
|
111
|
+
client.events.create(
|
|
112
|
+
session_id=session_id,
|
|
113
|
+
event_type="shield.agreement.signed",
|
|
114
|
+
actor=data["signer_email"],
|
|
115
|
+
data={"document": data["document_name"], "ip": request.remote_addr},
|
|
116
|
+
)
|
|
117
|
+
return jsonify({"status": "signed"}), 200
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## FastAPI Integration
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from fastapi import FastAPI, UploadFile, Header
|
|
124
|
+
from pydantic import BaseModel
|
|
125
|
+
import shield
|
|
126
|
+
|
|
127
|
+
app = FastAPI()
|
|
128
|
+
client = shield.Client(
|
|
129
|
+
api_key="sk_live_your_api_key",
|
|
130
|
+
hmac_secret="your_hmac_secret",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
class CreateSessionRequest(BaseModel):
|
|
134
|
+
title: str
|
|
135
|
+
user_email: str
|
|
136
|
+
|
|
137
|
+
class SignRequest(BaseModel):
|
|
138
|
+
signer_email: str
|
|
139
|
+
document_name: str
|
|
140
|
+
|
|
141
|
+
@app.post("/sessions")
|
|
142
|
+
async def create_session(body: CreateSessionRequest):
|
|
143
|
+
session = client.sessions.create(title=body.title)
|
|
144
|
+
client.events.create(
|
|
145
|
+
session_id=session["id"],
|
|
146
|
+
event_type="shield.session.created",
|
|
147
|
+
actor=body.user_email,
|
|
148
|
+
)
|
|
149
|
+
return session
|
|
150
|
+
|
|
151
|
+
@app.post("/sessions/{session_id}/upload")
|
|
152
|
+
async def upload_document(
|
|
153
|
+
session_id: str,
|
|
154
|
+
file: UploadFile,
|
|
155
|
+
x_user_email: str = Header(...),
|
|
156
|
+
):
|
|
157
|
+
# ... save file logic ...
|
|
158
|
+
|
|
159
|
+
client.events.create(
|
|
160
|
+
session_id=session_id,
|
|
161
|
+
event_type="shield.content.uploaded",
|
|
162
|
+
actor=x_user_email,
|
|
163
|
+
data={"filename": file.filename},
|
|
164
|
+
)
|
|
165
|
+
return {"status": "uploaded"}
|
|
166
|
+
|
|
167
|
+
@app.post("/sessions/{session_id}/sign")
|
|
168
|
+
async def sign_agreement(session_id: str, body: SignRequest):
|
|
169
|
+
client.events.create(
|
|
170
|
+
session_id=session_id,
|
|
171
|
+
event_type="shield.agreement.signed",
|
|
172
|
+
actor=body.signer_email,
|
|
173
|
+
data={"document": body.document_name},
|
|
174
|
+
)
|
|
175
|
+
return {"status": "signed"}
|
|
176
|
+
|
|
177
|
+
@app.get("/sessions/{session_id}/verify")
|
|
178
|
+
async def verify_session(session_id: str):
|
|
179
|
+
return client.verify.session(session_id)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Event Types Reference
|
|
183
|
+
|
|
184
|
+
Shield Standard Event Taxonomy v1.0 — 37 event types across 7 categories:
|
|
185
|
+
|
|
186
|
+
### Party Events
|
|
187
|
+
| Event Type | Description |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `shield.party.joined` | A party joined the session |
|
|
190
|
+
| `shield.party.left` | A party left the session |
|
|
191
|
+
| `shield.party.identity.verified` | Party identity was verified |
|
|
192
|
+
| `shield.party.identity.failed` | Party identity verification failed |
|
|
193
|
+
| `shield.party.role.assigned` | A role was assigned to a party |
|
|
194
|
+
|
|
195
|
+
### Session Events
|
|
196
|
+
| Event Type | Description |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `shield.session.created` | Session was created |
|
|
199
|
+
| `shield.session.opened` | Session was opened |
|
|
200
|
+
| `shield.session.closed` | Session was closed |
|
|
201
|
+
| `shield.session.expired` | Session expired |
|
|
202
|
+
| `shield.session.archived` | Session was archived |
|
|
203
|
+
|
|
204
|
+
### Content Events
|
|
205
|
+
| Event Type | Description |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `shield.content.uploaded` | Content was uploaded |
|
|
208
|
+
| `shield.content.viewed` | Content was viewed |
|
|
209
|
+
| `shield.content.downloaded` | Content was downloaded |
|
|
210
|
+
| `shield.content.deleted` | Content was deleted |
|
|
211
|
+
| `shield.content.hash.verified` | Content hash was verified |
|
|
212
|
+
|
|
213
|
+
### Negotiation Events
|
|
214
|
+
| Event Type | Description |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `shield.negotiation.terms.proposed` | Terms were proposed |
|
|
217
|
+
| `shield.negotiation.terms.accepted` | Terms were accepted |
|
|
218
|
+
| `shield.negotiation.terms.rejected` | Terms were rejected |
|
|
219
|
+
| `shield.negotiation.terms.modified` | Terms were modified |
|
|
220
|
+
| `shield.negotiation.terms.expired` | Terms expired |
|
|
221
|
+
| `shield.negotiation.message.sent` | Negotiation message sent |
|
|
222
|
+
| `shield.negotiation.message.read` | Negotiation message read |
|
|
223
|
+
|
|
224
|
+
### Agreement Events
|
|
225
|
+
| Event Type | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `shield.agreement.drafted` | Agreement was drafted |
|
|
228
|
+
| `shield.agreement.reviewed` | Agreement was reviewed |
|
|
229
|
+
| `shield.agreement.approved` | Agreement was approved |
|
|
230
|
+
| `shield.agreement.signed` | Agreement was signed |
|
|
231
|
+
| `shield.agreement.countersigned` | Agreement was countersigned |
|
|
232
|
+
| `shield.agreement.voided` | Agreement was voided |
|
|
233
|
+
| `shield.agreement.reached` | Agreement was reached |
|
|
234
|
+
|
|
235
|
+
### Access Events
|
|
236
|
+
| Event Type | Description |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `shield.access.granted` | Access was granted |
|
|
239
|
+
| `shield.access.revoked` | Access was revoked |
|
|
240
|
+
| `shield.access.attempted` | Access was attempted |
|
|
241
|
+
| `shield.access.denied` | Access was denied |
|
|
242
|
+
|
|
243
|
+
### Disclosure Events
|
|
244
|
+
| Event Type | Description |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `shield.disclosure.presented` | Disclosure was presented |
|
|
247
|
+
| `shield.disclosure.acknowledged` | Disclosure was acknowledged |
|
|
248
|
+
| `shield.disclosure.declined` | Disclosure was declined |
|
|
249
|
+
|
|
250
|
+
### Evidence Events
|
|
251
|
+
| Event Type | Description |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `shield.evidence.exported` | Evidence was exported |
|
|
254
|
+
| `shield.evidence.verified` | Evidence was verified |
|
|
255
|
+
| `shield.evidence.tampered_detected` | Evidence tampering was detected |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shield-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Shield SDK for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = ["requests>=2.28.0"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://getshield.dev"
|
|
16
|
+
Documentation = "https://docs.getshield.dev"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac as hmac_mod
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .exceptions import ShieldError
|
|
11
|
+
from .resources.sessions import Sessions
|
|
12
|
+
from .resources.events import Events
|
|
13
|
+
from .resources.verify import Verify
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Client:
|
|
17
|
+
"""Official Shield Python SDK client.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
api_key: Your Shield API key.
|
|
21
|
+
base_url: Base URL for the Shield API. Defaults to https://getshield.dev/api/v1.
|
|
22
|
+
hmac_secret: Optional HMAC secret for request signing.
|
|
23
|
+
timeout: Request timeout in seconds. Defaults to 30.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
api_key: str,
|
|
29
|
+
base_url: str = "https://getshield.dev/api/v1",
|
|
30
|
+
hmac_secret: Optional[str] = None,
|
|
31
|
+
timeout: int = 30,
|
|
32
|
+
):
|
|
33
|
+
self.api_key = api_key
|
|
34
|
+
self.base_url = base_url.rstrip("/")
|
|
35
|
+
self.hmac_secret = hmac_secret
|
|
36
|
+
self.timeout = timeout
|
|
37
|
+
self._session = requests.Session()
|
|
38
|
+
|
|
39
|
+
# Resource instances
|
|
40
|
+
self.sessions = Sessions(self)
|
|
41
|
+
self.events = Events(self)
|
|
42
|
+
self.verify = Verify(self)
|
|
43
|
+
|
|
44
|
+
def _request(
|
|
45
|
+
self,
|
|
46
|
+
method: str,
|
|
47
|
+
path: str,
|
|
48
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
49
|
+
params: Optional[Dict[str, Any]] = None,
|
|
50
|
+
raw_response: bool = False,
|
|
51
|
+
) -> Any:
|
|
52
|
+
"""Make an authenticated request to the Shield API.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
method: HTTP method (GET, POST, PUT, DELETE).
|
|
56
|
+
path: API path (e.g. /sessions).
|
|
57
|
+
json_data: JSON body for POST/PUT requests.
|
|
58
|
+
params: Query parameters.
|
|
59
|
+
raw_response: If True, return raw response content (bytes).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Parsed JSON response as dict, or bytes if raw_response is True.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ShieldError: On non-2xx responses or request failures.
|
|
66
|
+
"""
|
|
67
|
+
url = f"{self.base_url}{path}"
|
|
68
|
+
method = method.upper()
|
|
69
|
+
|
|
70
|
+
headers = {
|
|
71
|
+
"X-Shield-Key": self.api_key,
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Serialize body
|
|
76
|
+
body = b""
|
|
77
|
+
if json_data is not None:
|
|
78
|
+
body = json.dumps(json_data, separators=(",", ":")).encode("utf-8")
|
|
79
|
+
|
|
80
|
+
# HMAC signing
|
|
81
|
+
if self.hmac_secret:
|
|
82
|
+
timestamp = str(int(time.time()))
|
|
83
|
+
body_hash = hashlib.sha256(body).hexdigest()
|
|
84
|
+
message = f"{timestamp}.{method}.{path}.{body_hash}"
|
|
85
|
+
signature = hmac_mod.new(
|
|
86
|
+
self.hmac_secret.encode("utf-8"),
|
|
87
|
+
message.encode("utf-8"),
|
|
88
|
+
hashlib.sha256,
|
|
89
|
+
).hexdigest()
|
|
90
|
+
headers["X-Shield-Signature"] = signature
|
|
91
|
+
headers["X-Shield-Timestamp"] = timestamp
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
response = self._session.request(
|
|
95
|
+
method=method,
|
|
96
|
+
url=url,
|
|
97
|
+
headers=headers,
|
|
98
|
+
data=body if body else None,
|
|
99
|
+
params=params,
|
|
100
|
+
timeout=self.timeout,
|
|
101
|
+
)
|
|
102
|
+
except requests.RequestException as e:
|
|
103
|
+
raise ShieldError(
|
|
104
|
+
message=f"Request failed: {str(e)}",
|
|
105
|
+
status_code=0,
|
|
106
|
+
code="request_error",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if not (200 <= response.status_code < 300):
|
|
110
|
+
error_message = response.text
|
|
111
|
+
error_code = "api_error"
|
|
112
|
+
try:
|
|
113
|
+
error_body = response.json()
|
|
114
|
+
error_message = error_body.get("error", error_message)
|
|
115
|
+
error_code = error_body.get("code", error_code)
|
|
116
|
+
except (ValueError, KeyError):
|
|
117
|
+
pass
|
|
118
|
+
raise ShieldError(
|
|
119
|
+
message=error_message,
|
|
120
|
+
status_code=response.status_code,
|
|
121
|
+
code=error_code,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if raw_response:
|
|
125
|
+
return response.content
|
|
126
|
+
|
|
127
|
+
if not response.content:
|
|
128
|
+
return {}
|
|
129
|
+
|
|
130
|
+
return response.json()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class ShieldError(Exception):
|
|
2
|
+
"""Base exception for Shield SDK errors."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, message: str, status_code: int = None, code: str = None):
|
|
5
|
+
super().__init__(message)
|
|
6
|
+
self.message = message
|
|
7
|
+
self.status_code = status_code
|
|
8
|
+
self.code = code
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
parts = []
|
|
12
|
+
if self.status_code:
|
|
13
|
+
parts.append(f"[{self.status_code}]")
|
|
14
|
+
if self.code:
|
|
15
|
+
parts.append(f"({self.code})")
|
|
16
|
+
parts.append(self.message)
|
|
17
|
+
return " ".join(parts)
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return (
|
|
21
|
+
f"ShieldError(message={self.message!r}, "
|
|
22
|
+
f"status_code={self.status_code!r}, code={self.code!r})"
|
|
23
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Events:
|
|
5
|
+
"""Record events within Shield sessions."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, client):
|
|
8
|
+
self._client = client
|
|
9
|
+
|
|
10
|
+
def create(
|
|
11
|
+
self,
|
|
12
|
+
session_id: str,
|
|
13
|
+
event_type: str,
|
|
14
|
+
actor: str,
|
|
15
|
+
data: Optional[Dict[str, Any]] = None,
|
|
16
|
+
) -> Dict[str, Any]:
|
|
17
|
+
"""Create a new event in a session.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
session_id: The session ID to record the event in.
|
|
21
|
+
event_type: Event type from the Shield Standard Event Taxonomy
|
|
22
|
+
(e.g. "shield.party.joined").
|
|
23
|
+
actor: Identifier for the actor performing the action.
|
|
24
|
+
data: Optional additional event data.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Created event object.
|
|
28
|
+
"""
|
|
29
|
+
payload: Dict[str, Any] = {
|
|
30
|
+
"event_type": event_type,
|
|
31
|
+
"actor": actor,
|
|
32
|
+
}
|
|
33
|
+
if data is not None:
|
|
34
|
+
payload["data"] = data
|
|
35
|
+
|
|
36
|
+
return self._client._request(
|
|
37
|
+
"POST",
|
|
38
|
+
f"/sessions/{session_id}/events",
|
|
39
|
+
json_data=payload,
|
|
40
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Sessions:
|
|
5
|
+
"""Manage Shield sessions."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, client):
|
|
8
|
+
self._client = client
|
|
9
|
+
|
|
10
|
+
def create(self, title: str) -> Dict[str, Any]:
|
|
11
|
+
"""Create a new session.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
title: Human-readable title for the session.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Created session object.
|
|
18
|
+
"""
|
|
19
|
+
return self._client._request("POST", "/sessions", json_data={"title": title})
|
|
20
|
+
|
|
21
|
+
def retrieve(self, session_id: str) -> Dict[str, Any]:
|
|
22
|
+
"""Retrieve a session by ID.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
session_id: The session ID.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Session object.
|
|
29
|
+
"""
|
|
30
|
+
return self._client._request("GET", f"/sessions/{session_id}")
|
|
31
|
+
|
|
32
|
+
def export(self, session_id: str, format: str = "json"):
|
|
33
|
+
"""Export a session in the specified format.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
session_id: The session ID.
|
|
37
|
+
format: Export format — "json" or "pdf". Defaults to "json".
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
dict if format is "json", bytes for binary formats like "pdf".
|
|
41
|
+
"""
|
|
42
|
+
raw = format != "json"
|
|
43
|
+
return self._client._request(
|
|
44
|
+
"GET",
|
|
45
|
+
f"/sessions/{session_id}/export/{format}",
|
|
46
|
+
raw_response=raw,
|
|
47
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Verify:
|
|
5
|
+
"""Verify Shield session integrity."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, client):
|
|
8
|
+
self._client = client
|
|
9
|
+
|
|
10
|
+
def session(self, session_id: str) -> Dict[str, Any]:
|
|
11
|
+
"""Verify the integrity of a session's event chain.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
session_id: The session ID to verify.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Verification result with integrity status.
|
|
18
|
+
"""
|
|
19
|
+
return self._client._request("GET", f"/sessions/{session_id}/verify")
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shield-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Shield SDK for Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://getshield.dev
|
|
7
|
+
Project-URL: Documentation, https://docs.getshield.dev
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.28.0
|
|
11
|
+
|
|
12
|
+
# Shield Python SDK
|
|
13
|
+
|
|
14
|
+
Official Python SDK for [Shield](https://getshield.dev) — tamper-evident session recording for real estate transactions.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install shield-python
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import shield
|
|
26
|
+
|
|
27
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
28
|
+
|
|
29
|
+
# Create a session
|
|
30
|
+
session = client.sessions.create(title="123 Main St Closing")
|
|
31
|
+
session_id = session["id"]
|
|
32
|
+
|
|
33
|
+
# Record events
|
|
34
|
+
client.events.create(
|
|
35
|
+
session_id=session_id,
|
|
36
|
+
event_type="shield.party.joined",
|
|
37
|
+
actor="agent@example.com",
|
|
38
|
+
data={"role": "listing_agent", "name": "Jane Smith"},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
client.events.create(
|
|
42
|
+
session_id=session_id,
|
|
43
|
+
event_type="shield.content.uploaded",
|
|
44
|
+
actor="agent@example.com",
|
|
45
|
+
data={"filename": "purchase_agreement.pdf", "hash": "sha256:abc123..."},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
client.events.create(
|
|
49
|
+
session_id=session_id,
|
|
50
|
+
event_type="shield.agreement.signed",
|
|
51
|
+
actor="buyer@example.com",
|
|
52
|
+
data={"document": "purchase_agreement.pdf"},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Verify session integrity
|
|
56
|
+
result = client.verify.session(session_id)
|
|
57
|
+
print(result["intact"]) # True
|
|
58
|
+
|
|
59
|
+
# Export session
|
|
60
|
+
pdf_bytes = client.sessions.export(session_id, format="pdf")
|
|
61
|
+
with open("audit_trail.pdf", "wb") as f:
|
|
62
|
+
f.write(pdf_bytes)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## HMAC Authentication
|
|
66
|
+
|
|
67
|
+
For enhanced security, provide an HMAC secret to sign every request:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
client = shield.Client(
|
|
71
|
+
api_key="sk_live_your_api_key",
|
|
72
|
+
hmac_secret="your_hmac_secret",
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When an HMAC secret is configured, the SDK computes a signature for each request:
|
|
77
|
+
|
|
78
|
+
- `X-Shield-Timestamp` — Unix timestamp of the request
|
|
79
|
+
- `X-Shield-Signature` — HMAC-SHA256 of `{timestamp}.{METHOD}.{path}.{SHA256(body)}`
|
|
80
|
+
|
|
81
|
+
The server validates these headers to ensure requests have not been tampered with or replayed.
|
|
82
|
+
|
|
83
|
+
## Flask Integration
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from flask import Flask, request, jsonify
|
|
87
|
+
import shield
|
|
88
|
+
|
|
89
|
+
app = Flask(__name__)
|
|
90
|
+
client = shield.Client(api_key="sk_live_your_api_key")
|
|
91
|
+
|
|
92
|
+
@app.route("/sessions", methods=["POST"])
|
|
93
|
+
def create_session():
|
|
94
|
+
data = request.get_json()
|
|
95
|
+
session = client.sessions.create(title=data["title"])
|
|
96
|
+
|
|
97
|
+
# Record who created the session
|
|
98
|
+
client.events.create(
|
|
99
|
+
session_id=session["id"],
|
|
100
|
+
event_type="shield.session.created",
|
|
101
|
+
actor=data["user_email"],
|
|
102
|
+
)
|
|
103
|
+
return jsonify(session), 201
|
|
104
|
+
|
|
105
|
+
@app.route("/sessions/<session_id>/upload", methods=["POST"])
|
|
106
|
+
def upload_document(session_id):
|
|
107
|
+
file = request.files["file"]
|
|
108
|
+
# ... save file logic ...
|
|
109
|
+
|
|
110
|
+
client.events.create(
|
|
111
|
+
session_id=session_id,
|
|
112
|
+
event_type="shield.content.uploaded",
|
|
113
|
+
actor=request.headers.get("X-User-Email"),
|
|
114
|
+
data={"filename": file.filename},
|
|
115
|
+
)
|
|
116
|
+
return jsonify({"status": "uploaded"}), 200
|
|
117
|
+
|
|
118
|
+
@app.route("/sessions/<session_id>/sign", methods=["POST"])
|
|
119
|
+
def sign_agreement(session_id):
|
|
120
|
+
data = request.get_json()
|
|
121
|
+
|
|
122
|
+
client.events.create(
|
|
123
|
+
session_id=session_id,
|
|
124
|
+
event_type="shield.agreement.signed",
|
|
125
|
+
actor=data["signer_email"],
|
|
126
|
+
data={"document": data["document_name"], "ip": request.remote_addr},
|
|
127
|
+
)
|
|
128
|
+
return jsonify({"status": "signed"}), 200
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## FastAPI Integration
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from fastapi import FastAPI, UploadFile, Header
|
|
135
|
+
from pydantic import BaseModel
|
|
136
|
+
import shield
|
|
137
|
+
|
|
138
|
+
app = FastAPI()
|
|
139
|
+
client = shield.Client(
|
|
140
|
+
api_key="sk_live_your_api_key",
|
|
141
|
+
hmac_secret="your_hmac_secret",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
class CreateSessionRequest(BaseModel):
|
|
145
|
+
title: str
|
|
146
|
+
user_email: str
|
|
147
|
+
|
|
148
|
+
class SignRequest(BaseModel):
|
|
149
|
+
signer_email: str
|
|
150
|
+
document_name: str
|
|
151
|
+
|
|
152
|
+
@app.post("/sessions")
|
|
153
|
+
async def create_session(body: CreateSessionRequest):
|
|
154
|
+
session = client.sessions.create(title=body.title)
|
|
155
|
+
client.events.create(
|
|
156
|
+
session_id=session["id"],
|
|
157
|
+
event_type="shield.session.created",
|
|
158
|
+
actor=body.user_email,
|
|
159
|
+
)
|
|
160
|
+
return session
|
|
161
|
+
|
|
162
|
+
@app.post("/sessions/{session_id}/upload")
|
|
163
|
+
async def upload_document(
|
|
164
|
+
session_id: str,
|
|
165
|
+
file: UploadFile,
|
|
166
|
+
x_user_email: str = Header(...),
|
|
167
|
+
):
|
|
168
|
+
# ... save file logic ...
|
|
169
|
+
|
|
170
|
+
client.events.create(
|
|
171
|
+
session_id=session_id,
|
|
172
|
+
event_type="shield.content.uploaded",
|
|
173
|
+
actor=x_user_email,
|
|
174
|
+
data={"filename": file.filename},
|
|
175
|
+
)
|
|
176
|
+
return {"status": "uploaded"}
|
|
177
|
+
|
|
178
|
+
@app.post("/sessions/{session_id}/sign")
|
|
179
|
+
async def sign_agreement(session_id: str, body: SignRequest):
|
|
180
|
+
client.events.create(
|
|
181
|
+
session_id=session_id,
|
|
182
|
+
event_type="shield.agreement.signed",
|
|
183
|
+
actor=body.signer_email,
|
|
184
|
+
data={"document": body.document_name},
|
|
185
|
+
)
|
|
186
|
+
return {"status": "signed"}
|
|
187
|
+
|
|
188
|
+
@app.get("/sessions/{session_id}/verify")
|
|
189
|
+
async def verify_session(session_id: str):
|
|
190
|
+
return client.verify.session(session_id)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Event Types Reference
|
|
194
|
+
|
|
195
|
+
Shield Standard Event Taxonomy v1.0 — 37 event types across 7 categories:
|
|
196
|
+
|
|
197
|
+
### Party Events
|
|
198
|
+
| Event Type | Description |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `shield.party.joined` | A party joined the session |
|
|
201
|
+
| `shield.party.left` | A party left the session |
|
|
202
|
+
| `shield.party.identity.verified` | Party identity was verified |
|
|
203
|
+
| `shield.party.identity.failed` | Party identity verification failed |
|
|
204
|
+
| `shield.party.role.assigned` | A role was assigned to a party |
|
|
205
|
+
|
|
206
|
+
### Session Events
|
|
207
|
+
| Event Type | Description |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `shield.session.created` | Session was created |
|
|
210
|
+
| `shield.session.opened` | Session was opened |
|
|
211
|
+
| `shield.session.closed` | Session was closed |
|
|
212
|
+
| `shield.session.expired` | Session expired |
|
|
213
|
+
| `shield.session.archived` | Session was archived |
|
|
214
|
+
|
|
215
|
+
### Content Events
|
|
216
|
+
| Event Type | Description |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `shield.content.uploaded` | Content was uploaded |
|
|
219
|
+
| `shield.content.viewed` | Content was viewed |
|
|
220
|
+
| `shield.content.downloaded` | Content was downloaded |
|
|
221
|
+
| `shield.content.deleted` | Content was deleted |
|
|
222
|
+
| `shield.content.hash.verified` | Content hash was verified |
|
|
223
|
+
|
|
224
|
+
### Negotiation Events
|
|
225
|
+
| Event Type | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `shield.negotiation.terms.proposed` | Terms were proposed |
|
|
228
|
+
| `shield.negotiation.terms.accepted` | Terms were accepted |
|
|
229
|
+
| `shield.negotiation.terms.rejected` | Terms were rejected |
|
|
230
|
+
| `shield.negotiation.terms.modified` | Terms were modified |
|
|
231
|
+
| `shield.negotiation.terms.expired` | Terms expired |
|
|
232
|
+
| `shield.negotiation.message.sent` | Negotiation message sent |
|
|
233
|
+
| `shield.negotiation.message.read` | Negotiation message read |
|
|
234
|
+
|
|
235
|
+
### Agreement Events
|
|
236
|
+
| Event Type | Description |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `shield.agreement.drafted` | Agreement was drafted |
|
|
239
|
+
| `shield.agreement.reviewed` | Agreement was reviewed |
|
|
240
|
+
| `shield.agreement.approved` | Agreement was approved |
|
|
241
|
+
| `shield.agreement.signed` | Agreement was signed |
|
|
242
|
+
| `shield.agreement.countersigned` | Agreement was countersigned |
|
|
243
|
+
| `shield.agreement.voided` | Agreement was voided |
|
|
244
|
+
| `shield.agreement.reached` | Agreement was reached |
|
|
245
|
+
|
|
246
|
+
### Access Events
|
|
247
|
+
| Event Type | Description |
|
|
248
|
+
|---|---|
|
|
249
|
+
| `shield.access.granted` | Access was granted |
|
|
250
|
+
| `shield.access.revoked` | Access was revoked |
|
|
251
|
+
| `shield.access.attempted` | Access was attempted |
|
|
252
|
+
| `shield.access.denied` | Access was denied |
|
|
253
|
+
|
|
254
|
+
### Disclosure Events
|
|
255
|
+
| Event Type | Description |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `shield.disclosure.presented` | Disclosure was presented |
|
|
258
|
+
| `shield.disclosure.acknowledged` | Disclosure was acknowledged |
|
|
259
|
+
| `shield.disclosure.declined` | Disclosure was declined |
|
|
260
|
+
|
|
261
|
+
### Evidence Events
|
|
262
|
+
| Event Type | Description |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `shield.evidence.exported` | Evidence was exported |
|
|
265
|
+
| `shield.evidence.verified` | Evidence was verified |
|
|
266
|
+
| `shield.evidence.tampered_detected` | Evidence tampering was detected |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
shield/__init__.py
|
|
5
|
+
shield/client.py
|
|
6
|
+
shield/exceptions.py
|
|
7
|
+
shield/resources/__init__.py
|
|
8
|
+
shield/resources/events.py
|
|
9
|
+
shield/resources/sessions.py
|
|
10
|
+
shield/resources/verify.py
|
|
11
|
+
shield_python.egg-info/PKG-INFO
|
|
12
|
+
shield_python.egg-info/SOURCES.txt
|
|
13
|
+
shield_python.egg-info/dependency_links.txt
|
|
14
|
+
shield_python.egg-info/requires.txt
|
|
15
|
+
shield_python.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shield
|