gatewerk
Python SDK for Gatewerk — the open-source human oversight station for AI agents.
Install
pip install gatewerk
Quick Start
from gatewerk import create_client
gw = create_client(
api_key="gw_key_...",
url="https://api.gatewerk.com",
)
# Submit a review request
review = gw.reviews.create(
"email-review",
payload={"to": "ceo@acme.com", "subject": "Q4 Report", "body": draft},
callback_url="https://my-agent.com/webhook",
priority="high",
)
print(review.id) # gw_rev_...
print(review.status) # pending
Async Support
from gatewerk import create_async_client
async with create_async_client(api_key="gw_key_...") as gw:
review = await gw.reviews.create("email-review", payload={...})
feedback = await gw.feedback.query(template="email-review")
Framework integrations
| Framework | Install | Status |
|---|---|---|
| LangGraph | pip install gatewerk[langgraph] |
available |
| CrewAI | pip install gatewerk[crewai] |
available |
LangGraph in 30 seconds:
from gatewerk import create_client
from gatewerk.integrations.langgraph import gatewerk_interrupt
gw = create_client(api_key="gw_key_...")
def gated_node(state):
decision = gatewerk_interrupt(gw, template="refund_approval", payload={...})
return {"approved": decision.approved}
See gatewerk/integrations/langgraph/README.md for the full guide, including the polling alternative (await_decision), async variants, and the LangGraph re-execution caveat.
CrewAI in 30 seconds:
from crewai import Agent, Task, Crew
from gatewerk import create_client
from gatewerk.integrations.crewai import GatewerkApprovalTool
gw = create_client(api_key="gw_key_...")
approval_tool = GatewerkApprovalTool(gw, template="refund_approval")
agent = Agent(role="Customer Support", goal="...", tools=[approval_tool])
Crew(agents=[agent], tasks=[Task(description="...", agent=agent, expected_output="...")]).kickoff()
The tool pauses the agent, routes the request to the Gatewerk dashboard, and returns "APPROVED", "REJECTED", or "CHANGES_REQUESTED: ..." once a human decides. See gatewerk/integrations/crewai/README.md for the full guide.
Resources
| Resource | Methods |
|---|---|
gw.reviews |
create() get() list() decide() retry() update() cancel_request() versions() create_token() list_auto_paginate() |
gw.chains |
create() get() get_for_review() |
gw.notes |
create() get() list() list_auto_paginate() update() delete() pin() unpin() tags() |
gw.templates |
list() get() create() update() delete() stats() |
gw.feedback |
query() |
gw.audit |
query() |
gw.stats |
get() |
gw.webhooks |
verify() deliveries() |
gw.key_info() |
Introspect API key scopes |
Typed Responses
All methods return Pydantic models:
from gatewerk import Review, ReviewList, Template, FeedbackList
review = gw.reviews.get("gw_rev_...")
review.id # str
review.status # str
review.payload # dict[str, Any]
review.current_version # int
review.auto_approved # bool | None
Auto-Pagination
# Iterate all reviews without managing offsets
for review in gw.reviews.list_auto_paginate(status="pending"):
print(review.id)
# Async
async for review in gw.reviews.list_auto_paginate(status="pending"):
print(review.id)
Retry & Resilience
The SDK automatically retries on transient errors (429, 500, 502, 503, 504) with exponential backoff and jitter. Rate limit Retry-After headers are respected.
# Customize retry behavior
gw = create_client(
api_key="gw_key_...",
max_retries=5, # default: 2, set 0 to disable
timeout=60.0, # default: 30s
)
Error Handling
from gatewerk import (
GatewerkError, # Base — all errors
InvalidRequestError, # 400
AuthenticationError, # 401
ForbiddenError, # 403
NotFoundError, # 404
ConflictError, # 409
RateLimitError, # 429 (includes .retry_after)
)
try:
review = gw.reviews.get("gw_rev_nonexistent")
except NotFoundError as e:
print(e.message) # "Review not found"
print(e.status_code) # 404
print(e.code) # "review_not_found"
except RateLimitError as e:
print(e.retry_after) # seconds until retry
Webhook Verification
Every delivery carries two signature headers. Verify either depending on whether you need replay protection.
v1 (authenticity only, no replay protection)
The SDK's gw.webhooks.verify() helper accepts the v1 X-Webhook-Signature header:
# In your Flask/FastAPI webhook handler
payload = gw.webhooks.verify(
raw_body=request.body,
signature_header=request.headers["X-Webhook-Signature"],
secret="whsec_...",
)
# payload is a trusted dict: {"event": "review.decided", ...}
v2 (replay-safe; recommended for new integrations)
The X-Webhook-Signature-V2 header carries t=<unix-seconds>,v1=<hex> where hex = HMAC(f"{t}.{body}", secret). Parse, enforce freshness, then constant-time compare. No SDK helper yet — follow the manual pattern:
import hmac
import hashlib
import time
def verify_v2(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts["t"])
received_hex = parts["v1"]
# Freshness: reject replays outside the tolerance window.
if abs(time.time() - ts) > tolerance_seconds:
return False
expected_hex = hmac.new(
secret.encode(),
f"{ts}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected_hex, received_hex)
assert verify_v2(
request.body,
request.headers["X-Webhook-Signature-V2"],
"whsec_...",
)
Receivers should also dedup on X-Webhook-Id (stable across retries) to survive the legitimate retry flow. A v2-verified payload MAY still be a legitimate retry; the header id is the idempotency key.
Logging
The SDK logs via Python's standard logging module under the gatewerk logger:
import logging
logging.getLogger("gatewerk").setLevel(logging.DEBUG)
Environment Variables
| Variable | Description |
|---|---|
GATEWERK_API_KEY |
API key (fallback if not passed to create_client) |
GATEWERK_URL |
API URL (default: http://localhost:3100) |
License
Apache-2.0
Metadata
Release files for gatewerk 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gatewerk-0.1.1.tar.gz | 47.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gatewerk-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 109.2 kB
Release files / gatewerk-0.1.1.tar.gz
| Download URL | gatewerk-0.1.1.tar.gz |
|---|---|
| Size | 47.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0dca12c7bddceb390298891980ee9d34c69a699cc1fea5ed3fe982963486b18f
|
|
BLAKE2b-256 checksum How to use checksums |
5823ec0617c4bbe84260845e7c39e80361c1cddd414f3cdbb95afe19b001a1af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 18, 2026.
Transparency logRelease files / gatewerk-0.1.1-py3-none-any.whl
| Download URL | gatewerk-0.1.1-py3-none-any.whl |
|---|---|
| Size | 62.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
262de9bb3a0c6a0e985fa133c4bf0bc9e6186fc9e9a07abe7317c331dc2e2c50
|
|
BLAKE2b-256 checksum How to use checksums |
be45b7213ec54808d1aff2ef03dc9c8944441f12644f02b6f1e63f5b394f78ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 18, 2026.
Transparency log