Pre-release
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
Metadata
Release files for permissio 0.1.0b1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| permissio-0.1.0b1.tar.gz | 120.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| permissio-0.1.0b1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 136.1 kB
Release files / permissio-0.1.0b1.tar.gz
| Download URL | permissio-0.1.0b1.tar.gz |
|---|---|
| Size | 120.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
43783e0be17d139de9b0b90d22e1b14db4ccb24b0b5729b0edceb6f9a27317e8
|
|
BLAKE2b-256 checksum How to use checksums |
e5acca8cc485e4c1bbdf6bbfdb2ba54c6503b0e7f5cefd9943c60d44f72eb4b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / permissio-0.1.0b1-py3-none-any.whl
| Download URL | permissio-0.1.0b1-py3-none-any.whl |
|---|---|
| Size | 15.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2f223a43b5330294d9aeca07e37fc1ae7206a901f0214c5537600d2355ffac4c
|
|
BLAKE2b-256 checksum How to use checksums |
311e2fffb5b2c58f38ccdb74be2c32a3266a9c7a93cd593420f549747c428087
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|