Python SDK
A client for Python 3.9 or later with no dependencies outside the standard library, generated from the same OpenAPI file as the API.
Status. The SignWren API is opening in stages and may not be on your account yet. If Settings shows an API keys section, you can make sandbox keys now; live keys come with the Pro and Business plans. SignWren is an electronic signature app for small businesses; it is not a law firm and does not give legal advice.
How do I install the Python SDK?
It isn't on PyPI yet, so it comes as a package file: email hello@signwren.com for the wheel and install that file with pip. The package name, signwren, and the licence it will be published under are still to be decided, so nothing has been published.
python -m pip install ./signwren-0.1.0-py3-none-any.whlIt needs nothing but Python itself: requests go through urllib, and signatures are checked with hmac.
How do I create a client and send an envelope?
Pass your API key; everything else has a default. Answers come back as plain dictionaries shaped like the reference, and signwren.types has a typed dictionary for each one, for your editor and type checker.
import os
from signwren import SignWren
sw = SignWren(
os.environ["SIGNWREN_API_KEY"], # sw_test_... or sw_live_...
app_info="my-crm/2.1", # shown in the audit trail's user agent
)doc = sw.documents.upload("lease.pdf") # a path, bytes or a file opened with "rb"
envelope = sw.envelopes.create({
"title": "Lease, 14 Alder Court",
"document_ids": [doc["id"]],
"recipients": [{"name": "Priya Raman", "email": "you@yourcompany.example", "role": "signer"}],
"fields": [{"document_id": doc["id"], "recipient_index": 0, "type": "signature",
"page": 2, "x": 72, "y": 600, "width": 180, "height": 40}],
})
sw.envelopes.send(envelope["id"])
latest = sw.envelopes.get(envelope["id"])
if latest["status"] == "completed":
pdf = sw.envelopes.download_signed(envelope["id"]) # checked against its SHA-256
open("lease-signed.pdf", "wb").write(pdf.data)The resources mirror the API: sw.documents, sw.envelopes (with .recipients and .fields), sw.templates and sw.webhook_endpoints (with .deliveries). Every operation in the reference has a method, and the SDK's tests fail if one is missing. An app that a person connected with OAuth passes access_token= instead of a key.
What does the SDK handle for me?
- Idempotency keys. Every
POSTgets a random key, reused on each retry of that call, so a retry never does the work twice. Passoptions={"idempotency_key": "..."}to stay safe across restarts. - Retries. 429, 500, 502, 503, 504 and network errors are retried twice by default with exponential backoff and jitter, waiting for
Retry-Afterwhen the API sends one. - Typed errors. One class per kind of failure, each with the API's
code,message,param,detailsandrequest_id. Two names differ from the Node SDK, because Python already uses them:PermissionDeniedErrorandAPIConnectionError. - Pagination.
iterate()walks every page for you. - Download checks. PDFs are checked against the SHA-256 the API sends with them.
- Webhook checks.
webhooks.construct_event()verifies the signature header with a constant-time comparison and a 5-minute window. - Key safety. The key never appears in
repr(), plainhttp://is refused except for localhost, and redirects are never followed.
How do I handle errors?
Catch the error classes you care about and look at code for the details.
from signwren import ConflictError, InvalidRequestError, NotFoundError
try:
sw.envelopes.send(envelope_id)
except InvalidRequestError as err:
if err.code == "WARNING_UNACKNOWLEDGED":
print(err.details["categories"]) # what to show the sender before acknowledging
else:
raise
except ConflictError:
pass # already sent, or not a draft
except NotFoundError:
pass # wrong id, or the key is in the other environmentHow do I read every page of a list?
Use iterate(), which asks for the next page only when your loop reaches it.
for envelope in sw.envelopes.iterate(status="sent", limit=100):
print(envelope["id"], envelope["status_label"])How do I check a webhook in Python?
Pass the raw request body, exactly as it arrived, with the SignWren-Signature header and your endpoint's secret. Parsing the JSON first changes the bytes, and the check fails. The webhooks page explains the header.
from signwren import WebhookSignatureError, webhooks
def handle(raw_body: bytes, signature_header: str):
try:
event = webhooks.construct_event(raw_body, signature_header, os.environ["SIGNWREN_WEBHOOK_SECRET"])
except WebhookSignatureError:
return 400
# Skip event["id"] values you have already handled; retries keep the same id.
return 200How do I test code that uses the SDK?
Point it at the mock server in signwren.testing, which answers from the OpenAPI file and behaves like the API for keys, errors, idempotency and rate limits, or at the sandbox with a sw_test_ key. The mock stores nothing; the sandbox page covers the differences.
from signwren import SignWren
from signwren.testing import start_mock_server
with start_mock_server() as mock:
sw = SignWren("sw_test_" + "x" * 43, base_url=mock.url)
mock.fail(429, retry_after=1) # the next request is rate limited
sw.me() # waits a second, then succeedsLast checked against the API spec on .