Skip to main content

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 code estable 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:

  1. 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"])
  1. 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
  1. Ábrela en un iframe y escucha los eventos, o redirige al ponente y recíbelo de vuelta en return_url con ?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)

Source distribution for easycontract 0.1.1
File Size Uploaded Provenance
easycontract-0.1.1.tar.gz 23.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for easycontract 0.1.1
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page