Cliente Python para a Clicksign API v3 (JSON:API). Requer Python >= 3.10, sem dependências de runtime (apenas stdlib). Documentação detalhada em docs/.
- Instalação
- Configuração
- Multi-conta
- Timeouts, retry e instrumentação
- Início rápido
- Fluxo de assinatura (notarial) (criar → ativar → notificar → eventos)
- Filtros, ordenação e paginação
- Outros recursos
- Tratamento de erros
- Ambientes
- Async (FastAPI, asyncio)
- HTTP transport e connection pool
- Limitações e produção
- Desenvolvimento
Fluxo completo:
docs/WORKFLOW.md— envelope → documento → signatário → ativação passo a passo.
Examples:
docs/examples/— receitas prontas para retry, webhooks, multi-tenant, observabilidade e connection pool.
Contrato do SDK:
docs/SDK_CONTRACT.md— timeouts, retry, JSON:API, erros e paginação.
Observabilidade:
docs/OBSERVABILITY.md— hooks, log, correlation id, structlog, OpenTelemetry, PII.
Versão atual: 0.1.2 (disponível no TestPyPI; PyPI de produção em breve).
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
clicksign-python-sdk==0.1.2Quando publicado no PyPI de produção:
pip install clicksign-python-sdk==0.1.2O módulo importado continua sendo clicksign (import clicksign).
Extras opcionais:
pip install clicksign-python-sdk[httpx] # connection pooling (alto QPS)
pip install clicksign-python-sdk[async] # AsyncClicksignClient (requer httpx)Dois padrões principais — escolha com base em como sua aplicação gerencia credenciais:
| Padrão | Quando usar |
|---|---|
configure() + resources |
Uma API key por processo; scripts; workers simples |
ClicksignClient |
Múltiplas keys; dependências explícitas; código novo |
import clicksign
from clicksign import Envelope, Document
clicksign.configure(
api_key="YOUR_API_KEY",
environment="sandbox", # or "production"
)
envelopes = Envelope.list()
envelope = Envelope.create(name="Contrato", locale="pt-BR")
document = Document.create(
envelope.id,
filename="contrato.pdf",
content_base64="...",
)from clicksign import ClicksignClient
client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")
envelopes = client.notarial.envelopes.list()
envelope = client.notarial.envelopes.create(name="Contrato", locale="pt-BR")
# Alias direto (shorthand)
envelope = client.envelopes.retrieve(envelope.id)
envelope.update(name="Contrato atualizado")Para apps multi-tenant onde cada requisição/job usa uma API key diferente, use Services para vincular um cliente à thread atual:
from clicksign import Envelope, Services
tenant = Services(api_key="TENANT_API_KEY", environment="sandbox")
with tenant.use():
Envelope.list() # roteado pelo cliente deste tenantServices.use() define cliente HTTP e bulk para a thread. Em runtime async/fiber, prefira ClicksignClient ou AsyncClicksignClient explícito — contexto thread-local pode não se propagar.
from clicksign import ClicksignClient, RequestOptions
client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")
client.envelopes.create(
name="Contrato",
options=RequestOptions(
read_timeout=30.0,
open_timeout=5.0,
),
)Timeouts separados: open_timeout (TCP connect), write_timeout (envio do corpo), read_timeout (resposta).
Padrão 3 retries com full-jitter exponential backoff. Desative por chamada:
client.envelopes.retrieve("uuid", options={"max_retries": 0})BulkRequirement.create retenta apenas TimeoutError — operações atômicas não são idempotentes.
from clicksign import RequestOptions, correlation_id
client.envelopes.retrieve(
"uuid",
options=RequestOptions(headers=correlation_id("req-123")),
)Em falhas, use error.request_id (header X-Request-Id) para tickets de suporte da Clicksign.
import clicksign
clicksign.on_request(lambda e: print(e["method"], e["path"], e["status"]))
clicksign.on_retry(lambda e: print(e["attempt"], e["wait_ms"]))
clicksign.on_error(lambda e: print(e["error"]))Veja docs/OBSERVABILITY.md para exemplos com structlog, OpenTelemetry e Prometheus, e como evitar PII em handlers on_error.
export CLICKSIGN_LOG=debug # ou info, warn, errorclicksign.configure(log="debug")
# Usa stdlib logger "clicksign"; header Authorization nunca é logado.from clicksign import ClicksignClient
client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")
# Criar envelope
envelope = client.notarial.envelopes.create(name="Contrato NDA", locale="pt-BR")
# Adicionar documento
doc = client.notarial.documents.create(
envelope.id,
filename="nda.pdf",
content_base64="...",
)
# Adicionar signatário
signer = client.notarial.signers.create(
envelope.id,
name="Ana Souza",
email="ana@example.com",
has_documentation=True,
)
# Requisito de assinatura (atomic)
client.notarial.bulk_requirements.create(
envelope.id,
block=lambda ops: ops.add_agree(
signer_id=signer.id,
document_id=doc.id,
role="sign",
),
)
# Ativar envelope
envelope.update(status="running")envelope = client.notarial.envelopes.create(name="Contrato", locale="pt-BR")
envelope.update(deadline_at="2025-12-31T23:59:59Z")import base64
doc = client.notarial.documents.create(
envelope.id,
filename="contrato.pdf",
content_base64=base64.b64encode(open("contrato.pdf", "rb").read()).decode(),
)signer = client.notarial.signers.create(
envelope.id,
name="João Silva",
email="joao@example.com",
has_documentation=True,
documentation="123.456.789-09",
)client.notarial.bulk_requirements.create(
envelope.id,
block=lambda ops: (
ops.add_agree(signer_id=signer.id, document_id=doc.id, role="sign"),
ops.add_provide_evidence(signer_id=signer.id, document_id=doc.id, auth="email"),
),
)envelope.update(status="running")
# ou: Envelope.activate(envelope.id) — POST /envelopes/:id/activatefrom clicksign.resources.notarial.signer import Signer
# Um signatário (envelope_id e signer_id obrigatórios)
Signer.notify(envelope.id, signer.id, message="Seu contrato está disponível para assinatura.")
# Todos os pendentes do envelope
envelope.notify(message="Seu contrato está disponível para assinatura.")Detalhes: docs/WORKFLOW.md (seção 6).
from clicksign.resources.notarial.envelope import Envelope
for event in Envelope.list_events(envelope.id):
print(event.name, event.created)
# Eventos por documento (ex.: read, sign, custom)
from clicksign.resources.notarial.document import Document
for event in Document.list_events(doc.id, envelope_id=envelope.id):
print(event.name, event.data)
# Callbacks em tempo real: docs/examples/03-webhooks.md# QueryProxy chain
drafts = (
client.envelopes
.filter(status="draft")
.order("-created_at")
.per(20)
.with_includes("folder")
.to_list()
)
# Auto-paginação (itera todas as páginas)
for envelope in client.envelopes.filter(status="running"):
print(envelope.id)
# page() + per() explícitos
page1 = client.envelopes.page(1).per(10).to_list()per() máximo 50 — veja docs/PAGINATION.md.
envelopes = client.envelopes.with_includes("folder").to_list()
print(envelopes[0].folder.name) # sem chamada HTTP extra| Namespace | Resource |
|---|---|
client.notarial.envelopes |
Envelope |
client.notarial.documents |
Document |
client.notarial.signers |
Signer |
client.notarial.requirements |
Requirement |
client.notarial.bulk_requirements |
BulkRequirement |
client.notarial.signature_watchers |
SignatureWatcher |
| — (nested) | Event — Envelope.list_events, Document.list_events, Event.create_for_document |
client.webhooks |
Webhook |
client.users |
User |
client.templates / client.template_fields |
Template, TemplateField |
client.memberships / client.groups |
Membership, Group |
client.folders |
Folder |
client.access_control_lists |
AccessControlList |
client.envelope_bulk_creations |
EnvelopeBulkCreation |
client.acceptance_term.whatsapps |
Whatsapp |
client.auto_signature.terms |
Term |
Endpoints sem resource dedicado:
raw = client.raw_request("get", "/beta/feature")
envelope = client.deserialize(raw, Envelope)
print(envelope.last_response.status)clicksign.set_app_info("My CRM", "2.1.0", "https://example.com")
# User-Agent: clicksign-python/x.y.z Python/3.10.x My_CRM/2.1.0clicksign.configure(enable_telemetry=True)Envia métricas de latência anonimizadas (sem API keys nem corpos). Desativado por padrão.
from clicksign.errors import (
AuthenticationError,
PermissionError,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
TimeoutError,
)
try:
client.envelopes.create(name="")
except ValidationError as err:
print(err.message) # primeiro detalhe (compatível)
print(err.error_code) # código do primeiro erro
print(err.source_pointer) # ex: /data/attributes/name
for api_error in err.api_errors:
print(api_error.pointer, api_error.detail)
except RateLimitError as err:
print(err.rate_limit_reset)
except ServerError as err:
if err.retryable:
...| Exceção | Status HTTP |
|---|---|
AuthenticationError |
401 |
PermissionError |
403 |
NotFoundError |
404 |
ValidationError |
400, 422 |
RateLimitError |
429 |
ServerError |
5xx |
TimeoutError |
timeout de rede |
| Ambiente | Base URL |
|---|---|
sandbox |
https://sandbox.clicksign.com/api/v3 |
production |
https://app.clicksign.com/api/v3 |
client = ClicksignClient(api_key="...", environment="production")Requer pip install clicksign-python-sdk[async]. Receita completa: docs/examples/13-async-fastapi.md.
import asyncio
from clicksign import AsyncClicksignClient
async def main():
async with AsyncClicksignClient(api_key="YOUR_API_KEY", environment="sandbox") as client:
envelopes = await client.notarial.envelopes.list()
async for env in client.envelopes.filter(status="draft"):
print(env.id)
envelope = await client.envelopes.retrieve("uuid")
await envelope.update_async(status="running")
# Ativar: await client.notarial.envelopes.activate(envelope.id)
asyncio.run(main())- Não use
Services.use()no asyncio — useAsyncClicksignClientexplícito. - Paginação:
.page(n)só vale com.first();.to_list()auto-pagina desde a página 1 —docs/PAGINATION.md.
Padrão: UrllibHTTPClient (stdlib, sem connection pool — um handshake TCP/TLS por requisição). Adequado para scripts e baixo QPS.
Alto QPS: instale clicksign-python-sdk[httpx] e injete um cliente compartilhado:
from clicksign import ClicksignClient, HttpxHTTPClient
http = HttpxHTTPClient() # um por processo / worker
client = ClicksignClient(api_key="...", environment="production", http_client=http)AsyncClicksignClient usa httpx com connection pool no event loop.
Receita singleton: docs/examples/12-http-connection-pool.md.
UrllibHTTPClientnão reutiliza conexões — useHttpxHTTPClientsob carga.Services.use()usathreading.local()— incompatível com asyncio/trio.- Operações bulk atômicas retentam apenas
TimeoutError(não são idempotentes). - Includes aninhados dependem do que a API retorna; tipos desconhecidos fazem fallback para
Resourcebase.
Veja docs/examples/08-production-limitations.md.
pip install -e ".[dev]"
pytest
pytest --cov=clicksign --cov-report=term-missing
ruff format .
ruff check .
mypyMIT — veja LICENSE.