Python SDK for the HAX (Human Approval eXchange) API
Project description
HAX Python SDK
Python client for the HAX (Human Approval eXchange) API. Enables agents and automated systems to programmatically collect human input.
Installation
The Python SDK is not yet published to PyPI. Install from source:
pip install -e sdks/python # from the repo root
# or
pip install -e . # from sdks/python/
Quick Start
from hax import HaxClient
client = HaxClient(
api_key="hax_live_...",
base_url="http://localhost:3000/api/v1",
)
request = client.create_request(
type="text-approval-v1",
payload={"text": "Deploy main to prod?", "approveLabel": "Ship it", "denyLabel": "Hold"},
webhook_url="https://myapp.com/webhook",
)
print("Share with approver:", request.url)
# Poll until completed/expired/cancelled
request = client.wait_for_response(request.id, timeout=300)
if request.is_completed:
print("Decision:", request.response.get("decision"))
Features
- Pydantic models: Typed request/response models with validation
- FormBuilder: Fluent API for building typed forms with runtime type inference
- E2E encryption: RSA-OAEP + AES-GCM hybrid encryption for sensitive responses
- Webhook verification: HMAC-SHA256 signature verification
- Delivery: Send requests via email or SMS
- Polling: Built-in
wait_for_responsewith configurable timeout - Error handling: Typed exception hierarchy
Request Methods
# Create a request
request = client.create_request(
type="text-approval-v1",
payload={"text": "Approve this action?"},
title="Optional title",
description="Optional description",
webhook_url="https://myapp.com/webhook",
expires_in_seconds=3600,
metadata={"pr_number": 123},
)
# Send via email
request = client.request_via_email(
type="confirm-action-v1",
payload={"title": "Approve?", "confirmPhrase": "YES"},
to_email="approver@example.com",
subject="Approval Required",
)
# Send via SMS
request = client.request_via_sms(
type="text-approval-v1",
payload={"text": "Approve?"},
to_phone="+15551234567",
)
# Get a request by ID
request = client.get_request("req_123")
# List recent requests
requests = client.list_requests()
# Cancel a pending request
cancelled = client.cancel_request("req_123")
# Submit a response (for testing)
completed = client.submit_response("req_123", {"decision": "approve"})
# Wait for completion with timeout
result = client.wait_for_response("req_123", poll_interval=2.0, timeout=60)
# List available template types
types = client.list_types()
Status Helpers
if request.is_pending:
print("Waiting for response...")
if request.is_completed:
print("Response:", request.response)
if request.is_expired:
print("Request expired")
if request.is_cancelled:
print("Request was cancelled")
FormBuilder
Build typed forms with a fluent API:
from hax import HaxClient, FormBuilder
client = HaxClient(api_key="hax_live_...")
form = (FormBuilder()
.title("Event Registration")
.input("name", label="Full Name", required=True)
.input("email", label="Email", variant="email", required=True)
.number("age", label="Age", min=0, max=120)
.checkbox("newsletter", checkbox_label="Subscribe to newsletter"))
handle = client.create_form_request(form,
webhook_url="https://myapp.com/webhook")
print(f"Form URL: {handle.url}")
# Wait for typed response
response = handle.wait_for_response(timeout=300)
print(response.values.name) # str
print(response.values.email) # str
print(response.values.age) # float
print(response.values.newsletter) # bool
Available Field Types
| Method | Output Type | Description |
|---|---|---|
.input(id) |
str |
Text input (variants: text, email, url, tel) |
.textarea(id) |
str |
Multi-line text input |
.select(id, options=...) |
str |
Dropdown select |
.radio_group(id, options=...) |
str |
Radio button group |
.date(id) |
str |
Date picker (ISO format) |
.number(id) |
float |
Numeric input |
.slider(id, min=, max=) |
float |
Slider control |
.checkbox(id) |
bool |
Single checkbox |
.switch(id) |
bool |
Toggle switch |
.checkbox_group(id, options=...) |
list[str] |
Multi-select checkboxes |
.hidden(id, value) |
type(value) |
Hidden field |
Webhooks
Verify and parse webhook events:
from hax import verify_signature, parse_event
# In your webhook handler
def handle_webhook(request):
# Verify signature
is_valid = verify_signature(
payload=request.body,
signature=request.headers["X-Hax-Signature"],
secret="whsec_...",
)
if not is_valid:
return 400, "Invalid signature"
# Parse the event
event = parse_event(request.body)
if event.event_type == "completed":
print(f"Request {event.request_id} completed!")
print(f"Response: {event.response}")
elif event.event_type == "expired":
print(f"Request {event.request_id} expired")
return 200, "OK"
Event Types
request.sent— Notification was delivered (email/SMS)request.opened— Human opened the request linkrequest.completed— Human submitted a responserequest.expired— Request expired without action
Encryption
For sensitive response data, use end-to-end encryption:
from hax import HaxClient
# Passphrase-based (automatic encrypt/decrypt)
client = HaxClient(
api_key="hax_live_...",
encryption_key="my-secret-passphrase",
)
# Public key is automatically sent with requests
request = client.create_request(
type="text-approval-v1",
payload={"text": "Approve this sensitive action?"},
)
# Response is automatically decrypted when retrieved
completed = client.get_request(request.id)
print(completed.response) # Decrypted plaintext
Manual Decryption
from hax import generate_key_pair, decrypt_response, is_encrypted_response
public_key, private_key = generate_key_pair("my-secret")
# Use public_key when creating the client
client = HaxClient(api_key="...", public_key=public_key)
# Later, manually decrypt
request = client.get_request("req_123")
if is_encrypted_response(request.response):
decrypted = decrypt_response(request.response["_encrypted"], private_key)
Error Handling
from hax import (
HaxError, # Base error
AuthenticationError, # Invalid API key (401)
ValidationError, # Invalid request data (400/422)
NotFoundError, # Resource not found (404)
RateLimitError, # Too many requests (429)
ServerError, # Server error (500+)
DecryptionError, # Decryption failure
)
try:
request = client.create_request(...)
except AuthenticationError:
print("Check your API key")
except ValidationError as e:
print(f"Invalid request: {e}")
except RateLimitError:
print("Rate limited, try again later")
except HaxError as e:
print(f"API error: {e}")
Template Types
| Template | Description |
|---|---|
text-approval-v1 |
Show text and collect an approve/deny decision |
confirm-action-v1 |
Require typing a specific phrase to confirm a destructive action |
collect-email-v1 |
Prompt the user for an email address |
form-builder |
Advanced forms with field types, layouts, validation, and conditional logic |
multi-choice-selection-v1 |
Single or multiple selection from customizable option cards |
code-changes-v1 |
GitHub-style diff view with inline line comments |
rich-text-editor-v1 |
Markdown-formatted text editing for documents and reports |
file-upload-v1 |
Collect files (documents, images, CSVs) from users |
signature-capture-v1 |
Capture e-signatures with optional signer name and legal text |
data-table-review-v1 |
Review, select, or edit tabular data |
scheduling-picker-v1 |
Date/time slot selection with optional recurring schedules |
multi-step-wizard-v1 |
Sequential steps with navigation and progress indicator |
side-by-side-comparison-v1 |
Compare two versions with diff highlighting |
terminal-output-v1 |
Display command output/logs with approve-to-continue |
Notes
- Auth is API key only. Provide the key via
HaxClient(api_key=...); Clerk/session auth is not required for API access. - API responses wrap resources (e.g.,
{"request": {...}}); the SDK unwraps this automatically. - Template payloads and responses are flexible; consult the template configs for the fields each template expects/returns.
- The client supports context manager usage:
with HaxClient(...) as client:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hax_sdk-0.2.4rc6.tar.gz.
File metadata
- Download URL: hax_sdk-0.2.4rc6.tar.gz
- Upload date:
- Size: 30.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb244e0b461ea6e7fbad051096bb5459a4f3ac7b47f2cc9edbda5eff2178f600
|
|
| MD5 |
21a67db3fea5facf1ed1900744086fee
|
|
| BLAKE2b-256 |
6c9b633ed3fb5a4365fcacc72f8964a407ad0bf85c3cac1dac3b45ef5ca2983f
|
File details
Details for the file hax_sdk-0.2.4rc6-py3-none-any.whl.
File metadata
- Download URL: hax_sdk-0.2.4rc6-py3-none-any.whl
- Upload date:
- Size: 23.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
612ec1548d007e7291ccf3a0859d7f36c296a8886eed9445ea03729b286a088f
|
|
| MD5 |
886dc9a0d7412c7f72bf33563ab74abf
|
|
| BLAKE2b-256 |
51ee2fb339d4379bd48b810d91b5907917593ad789f17458e197a758d2d63002
|