This release is a pre-release and may not be stable for production use.
permissio
Official Python SDK for the Permissio Partner API. Build document-signing workflows directly into your product.
Installation
pip install permissio
Requires Python 3.9+ and httpx.
Quick start
from permissio import Permissio
client = Permissio(api_key="sk_live_…") # or sk_test_… for sandbox
# 1. Upload a template PDF
result = client.templates.get_upload_url()
import httpx
httpx.put(
result["upload_url"],
content=open("nda.pdf", "rb").read(),
headers={"Content-Type": "application/pdf"},
)
# 2. Create a template
template = client.templates.create(
name="NDA — Mutual Confidentiality Agreement",
document_url=result["object_path"],
recipients=[
{"role_name": "Disclosing Party", "signing_order": 1},
{"role_name": "Receiving Party", "signing_order": 2},
],
variables=[
{"name": "effective_date", "type": "date", "required": True},
{"name": "party_a_name", "type": "string", "required": True},
],
)
# 3. Create an envelope (draft)
envelope = client.envelopes.create(
template_id=template["id"],
title="NDA — Acme Corp",
signers=[
{"role_name": "Disclosing Party", "email": "alice@example.com", "name": "Alice Nguyen"},
{"role_name": "Receiving Party", "email": "bob@example.com", "name": "Bob Smith"},
],
variables={
"effective_date": "2026-05-04",
"party_a_name": "Smartshares Limited",
},
expires_in_days=14,
)
# 4. Send it — dispatches signer invite emails
client.envelopes.send(envelope["id"])
# 5. Check status
detail = client.envelopes.get(envelope["id"])
print(detail["status"]) # "sent", "in_progress", "completed", …
# 6. Download the executed PDF when complete
if detail["status"] == "completed":
response = client.envelopes.get_signed_document(envelope["id"])
with open("signed.pdf", "wb") as f:
f.write(response.content)
Async client
import asyncio
from permissio import AsyncPermissio
async def main():
async with AsyncPermissio(api_key="sk_live_…") as client:
envelope = await client.envelopes.create(
template_id="tpl_…",
signers=[{"role_name": "Signer", "email": "a@b.com", "name": "Alice"}],
)
await client.envelopes.send(envelope["id"])
asyncio.run(main())
Client options
client = Permissio(
api_key="sk_live_…", # required
base_url="https://app.permissio.us/api", # optional — default shown
timeout=30.0, # optional — seconds, default 30
http_client=httpx.Client(...), # optional — custom httpx client
)
Error handling
All API errors are raised as PermissioApiError:
from permissio import Permissio, PermissioApiError
try:
client.envelopes.send("env_…")
except PermissioApiError as err:
print(err.code) # e.g. "invalid_state"
print(err.status) # HTTP status, e.g. 409
print(err.request_id) # include in support tickets
print(err.details) # additional structured info
Common error codes
| Code | Status | Meaning |
|---|---|---|
unauthorized |
401 | Invalid or missing API key |
forbidden |
403 | Key lacks the required scope |
not_found |
404 | Resource does not exist or belongs to another tenant |
invalid_state |
409 | Operation not allowed in the envelope's current state |
idempotency_conflict |
409 | Same idempotency key, different request body |
idempotency_in_progress |
425 | Concurrent request with the same key is still in flight |
validation_error |
422 | Request body failed validation |
Idempotency
Write operations auto-generate a UUID as the Idempotency-Key header. Override to replay a specific request:
import uuid
key = str(uuid.uuid4())
envelope = client.envelopes.create(template_id="tpl_…", signers=[…], idempotency_key=key)
# Safe to retry with the same key — server returns the same envelope:
envelope2 = client.envelopes.create(template_id="tpl_…", signers=[…], idempotency_key=key)
assert envelope["id"] == envelope2["id"]
Webhooks
from flask import Flask, request, abort
from permissio.namespaces import WebhooksNamespace
app = Flask(__name__)
@app.post("/webhooks/permissio")
def handle_webhook():
if not WebhooksNamespace.verify_signature(
request.data,
request.headers.get("Permissio-Signature", ""),
ENDPOINT_SECRET,
):
abort(400)
event = request.json
if event["type"] == "envelope.completed":
print(f"Envelope {event['data']['envelope']['id']} completed!")
return "", 200
Namespaces
| Namespace | Methods |
|---|---|
client.envelopes |
list(), create(), get(), send(), void(), get_signed_document(), get_certificate() |
client.templates |
list(), get(), create(), get_upload_url() |
client.webhooks |
list(), create(), get(), verify_signature() |
Resources
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 permissio-0.1.0b1.tar.gz.
File metadata
- Download URL: permissio-0.1.0b1.tar.gz
- Upload date:
- Size: 120.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43783e0be17d139de9b0b90d22e1b14db4ccb24b0b5729b0edceb6f9a27317e8
|
|
| MD5 |
ced458aadb041c9c26fe951c4b97d0de
|
|
| BLAKE2b-256 |
e5acca8cc485e4c1bbdf6bbfdb2ba54c6503b0e7f5cefd9943c60d44f72eb4b1
|
File details
Details for the file permissio-0.1.0b1-py3-none-any.whl.
File metadata
- Download URL: permissio-0.1.0b1-py3-none-any.whl
- Upload date:
- Size: 15.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f223a43b5330294d9aeca07e37fc1ae7206a901f0214c5537600d2355ffac4c
|
|
| MD5 |
bf95ddccfb89f36ca30da0099c5ff4a0
|
|
| BLAKE2b-256 |
311e2fffb5b2c58f38ccdb74be2c32a3266a9c7a93cd593420f549747c428087
|