easycontract — SDK de Python
SDK oficial del API de firma electrónica de easycontract (SES/AdES bajo eIDAS, con paquete de evidencias defendible en litigio).
pip install easycontract
from easycontract import EasyContract
client = EasyContract(api_key="ec_test_...") # ec_live_... en producción
print(client.whoami())
- Modo test de primera clase: una clave
ec_test_…opera sobre datos aislados (TSA/PAdES simulados). Nadie debería firmar un documento real para probar la integración. - Idempotencia automática: todo POST lleva
Idempotency-Key(uuid4); pásala tú misma para reintentos seguros de tu lado. - Reintentos: 429 (respetando
Retry-After) y 5xx en GETs. - Errores tipados con el
codeestable del API:AuthenticationError,RateLimitError,ForbiddenError(cuota/feature/ regla de membresía),InvalidRequestError,NotFoundError,ConflictError,ServerError.
Receta 1 — Enviar un envelope desde plantilla (el flujo RootedCON)
Una sola llamada: cubre los roles, envía, y recibe los enlaces de firma.
result = client.templates.create_envelope(
template_id,
signers={"ponente": {"full_name": "Ada Lovelace", "email": "ada@example.com"}},
title="Contrato ponencia RootedCON 2027 — Ada Lovelace",
send=True, # congela hashes, plan y retención, y acuña enlaces
deliver_emails=True, # easycontract envía los emails con los enlaces
)
envelope_id = result["envelope"]["id"]
Creación de la plantilla (una vez):
template = client.templates.create(name="Contrato ponente", retention_period_months=72)
doc = client.templates.add_document(template["id"], file="contrato-ponente.pdf")
client.templates.add_role(template["id"], key="ponente", label="Ponente")
client.templates.add_field(
template["id"],
document_id=doc["id"],
role="ponente",
anchor_text="Fdo. el ponente:", # la firma se estampa donde el PDF lo dice
)
Receta 2 — Firma embebida (embedded signing)
El firmante firma dentro de tu aplicación (el caso RootedArbiter). Tres pasos:
- Registra una vez los orígenes de tu web (https;
http://localhost:…solo en desarrollo). La lista se sustituye entera:
client.organization.update(embed_allowed_origins=["https://arbiter.rootedcon.com"])
- Envía sin emails y, cuando el ponente esté delante de tu página, acuña una sesión efímera para ESE firmante (60 minutos por defecto, caduca al firmar):
result = client.templates.create_envelope(
template_id,
signers={...},
send=True,
deliver_emails=False,
)
signer = result["signers"][0]
session = client.envelopes.create_embedded_session(
result["envelope"]["id"],
signer["id"],
origin="https://arbiter.rootedcon.com",
return_url="https://arbiter.rootedcon.com/contratos/42/firmado", # opcional
language="es",
)
embed_url = session["url"] # token em_…: un firmante, un origen, caduca
- Ábrela en un iframe y escucha los eventos, o redirige al ponente y recíbelo
de vuelta en
return_urlcon?easycontract_event=signed&envelope_id=…&signer_id=…:
<iframe src="{{ embed_url }}" allow="camera" style="width:100%;height:80vh;border:0"></iframe>
<script>
window.addEventListener("message", (e) => {
if (e.origin !== "https://app.easycontract.online") return; // el origen de easycontract
if (e.data?.source !== "easycontract") return;
// e.data.event ∈ ready | signed | declined | expired; e.data.envelope_id, signer_id
});
</script>
La página de firma responde con Content-Security-Policy: frame-ancestors <tu origen> y no depende de cookies (funciona con las de terceros
bloqueadas); el OTP y la verificación de identidad, si el firmante los exige,
se piden dentro del mismo marco (allow="camera" para la captura del
documento). El postMessage es solo señal de interfaz: confirma siempre en
servidor con el webhook signer.signed o client.envelopes.get(...). Cada
sesión y su apertura (con el origen) quedan en el expediente de evidencias.
El sign_url clásico de deliver_emails=False sigue existiendo para el
redirect simple (14 días, expires_in_days para cambiarlo).
Receta 3 — Webhooks vs. polling
Webhooks (recomendado): registra un endpoint y verifica CADA entrega.
endpoint = client.webhooks.create(url="https://miapp.com/hooks/easycontract")
WEBHOOK_SECRET = endpoint["secret"] # whsec_… — se muestra UNA sola vez
# En tu receptor (Django/Flask/FastAPI):
from easycontract import webhooks
event = webhooks.verify_signature(
payload=request.body,
header=request.headers["X-EasyContract-Signature"],
secret=WEBHOOK_SECRET,
)
if event["type"] == "envelope.completed":
...
Reconciliación / polling: los webhooks son la proyección de un event log consultable; si pierdes una entrega, reconcilia:
for event in client.events.auto_paging_iter(type="envelope.completed"):
...
Receta 4 — Descargar el paquete de evidencias
El ZIP completo (documentos, certificado, audit trail JSON verificable offline, sellos TSA) — lo que un tenant llevaría a un juzgado:
client.envelopes.download_evidence(envelope_id, to="evidencias.zip")
client.envelopes.download_certificate(envelope_id, to="certificado.pdf")
Receta 5 — Gestión de plantillas
for template in client.templates.list()["data"]:
print(template["name"])
detail = client.templates.get(template_id, expand=["documents", "roles", "fields"])
client.templates.delete(old_template_id)
Envelope manual (sin plantilla)
envelope = client.envelopes.create(
title="NDA",
retention_period_months=60, # retención SIEMPRE explícita
)
client.envelopes.add_document(envelope["id"], file="nda.pdf")
client.envelopes.add_signer(
envelope["id"],
full_name="Grace Hopper",
email="grace@example.com",
auth_methods=["email_link", "sms_otp"],
phone="+34600111222", # OTP: plan Pro+
)
sent = client.envelopes.send(envelope["id"])
Organización, miembros e invitaciones
org = client.organization.get() # plan, límites y features del plan
members = client.members.list()
invite = client.invitations.create(email="colega@example.com", role="member")
# invite["accept_url"] — entrégalo tú o deja que easycontract lo envíe por email
Validar la identidad del firmante en tu plataforma (KYC)
Con el proceso propio de easycontract, el firmante fotografía su documento y se hace un selfie; la decisión es de la organización. Puedes tomarla desde tu propio sistema:
from easycontract import EasyContract
client = EasyContract(api_key="ec_live_...")
# 1. Suscríbete a los eventos (o sondea GET /events/): signer.kyc_submitted avisa de
# cada caso pendiente; signer.kyc_verified / signer.kyc_failed, de la decisión.
for case in client.identity.pending_reviews()["data"]:
envelope_id, signer = case["envelope_id"], case["signer"]
identity = case["identity_verification"] # estado, comprobaciones, datos declarados
# 2. Recupera las imágenes (cada descarga queda en el audit trail del documento)
for document in identity["documents"]:
client.identity.download_document(
envelope_id, signer["id"], document["id"], to=f"/tmp/{document['kind']}.jpg"
)
# 3. Valida con tus reglas: número declarado, caducidad, cotejo visual o tu propio motor
ok = identity["declared_document_number"] == mi_registro[signer["email"]].dni
# 4. Decide; el firmante recibe el resultado por correo
client.identity.review(
envelope_id,
signer["id"],
decision="approve" if ok else "reject",
verified_name=signer["full_name"],
reason="" if ok else "El número de documento no coincide con nuestro registro",
)
Las imágenes se purgan a los N días de la decisión (organization.kyc.image_retention_days);
el resultado completo sigue disponible en el paquete de evidencias con
download_evidence(..., include_identity_proofs=True).
Metadata
Release files for easycontract 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 | Provenance | |
|---|---|---|---|---|
| easycontract-0.1.1.tar.gz | 23.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| easycontract-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.5 kB
Release files / easycontract-0.1.1.tar.gz
| Download URL | easycontract-0.1.1.tar.gz |
|---|---|
| Size | 23.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
933afd9ad6286edb1f677283a0a00eb2b0b57376d3d80aacf3a6cd36c166fc04
|
|
BLAKE2b-256 checksum How to use checksums |
65d922c9553d97889d8d4e0737592cc5142e0982489e26baffa0ae1e92d89efa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / easycontract-0.1.1-py3-none-any.whl
| Download URL | easycontract-0.1.1-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
376285ca0373dbb994e1e52e8733312a2dbadbd87e309e0ce52881dcae1584f7
|
|
BLAKE2b-256 checksum How to use checksums |
8adfe2fb6eb1fc83ac05fe4bd20f4a864908e4c3cb9eeb64b3714e13fae2eb14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for easycontract 0.1.1
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.