All notable changes to assinafy are documented in this file.
AssinafyClientnow rejects a plaintexthttp://base_urlfor every non-loopback host, not only whenapi_keyortokenis set. The previous exemption for credential-free clients was unsound:authentication.login(),social_login(),change_password(),reset_password()andcreate_api_key()put passwords and provider tokens in the request body, and those are exactly the calls a client makes before it has any credential to configure. A credential-free client pointed athttp://host/v1sent them in the clear. Loopback hosts still accepthttp://, so local and mock servers keep working atlocalhost/127.0.0.1.upload_and_request_signatures()now attachesdocument_idand thesigner_idscreated so far to thecontextof anyAssinafyErrorraised after the upload succeeds. The docstring already told callers to inspect the error context for cleanup, but nothing populated it, so a failure during signer or assignment creation left orphaned records whose IDs the caller could not recover.
AssinafyClientnow rejects abase_urlthat embeds credentials (https://user:pass@host/v1). Such a URL made HTTPX derive anAuthorization: Basicheader that replaced the configuredapi_keyortokenon every request, and put those URL credentials into any proxy or access log along the way.AssinafyClientnow rejects abase_urlcarrying a query string or fragment. Previously the request path was appended to the wrong URL component —https://host/v1?x=1sent every call tohttps://host/v1?x=1/accounts/...while still attaching the API key.AssinafyClientnow rejects a plaintexthttp://base_urlpointing at a non-loopback host whileapi_keyortokenis set, so a mistyped or misconfigured URL can no longer send credentials in the clear. Loopback hosts and credential-free clients still accepthttp://, keeping local and mock servers usable.
assignments.list()documents that the API scopes results to the authenticated credential's current account, so passing a differentaccount_iddoes not re-scope the endpoint.webhooks.list_dispatches()documents the wire values thedeliveredfilter accepts.templates.get()records that the route is deployed and answers on the live API even though the published schema lists onlytemplates.list().- The README is reorganised as a single end-to-end flow — install, authenticate, configure, then the seven signing stages from upload to certified download — with a table of contents ahead of the flat resource reference.
- A complete document-signing flow, request/response references, live-smoke modes, and a release checklist in the README and public method docstrings.
- A main-branch, read-only sandbox workflow using environment secrets, plus weekly updates for pinned GitHub Actions.
- Every production and sandbox request now uses
User-Agent: Assinafy-Python-SDK/v<package-version>, even when callers override the underlying HTTP headers. - Client credentials are withheld from public, signer-code-only, cross-origin, and out-of-base-path requests while remaining attached to protected routes.
- Composite document workflows validate all signer, assignment, expiration, and wait options before the first write and preserve Email and WhatsApp channel behavior.
- Template, signer, webhook, tag, upload-source, query-alias, and RFC 3339 validation now fails before malformed requests are sent.
- Signer lookup paginates exact email matches, response-shape failures use the SDK error hierarchy, and binary endpoints accept their documented media types.
- Virtual assignment signing accepts the required empty item list; template creation omits unset options and leaves role-aware step checks to the API.
- Read-only live smoke runs cannot execute preference restoration writes, and missing created-resource IDs fail immediately while cleanup still runs.
- Supported HTTPX releases are constrained to
>=0.27.0,<1. - Mirrored branch pushes run the full Python 3.10–3.14, Ruff, strict mypy, minimum-HTTPX, test, and distribution gates.
- Account and authenticated-user resources, including themes, logos, KPI routes, and notification preferences.
- Social-login linking, channel-neutral signer-code verification, and the
padesdocument artifact.
- Signer verification and terms-acceptance now send access codes in the
documented query parameter; signer updates now forward
government_id. - Account deletion serializes
forcecorrectly and makes it keyword-only; explicit empty account IDs can no longer fall back to the default workspace. - Document readiness polling preserves API/authentication errors, retries only transient failures, and returns the refreshed ready document.
- Path IDs, request mappings, response shapes, upload I/O, and destructive boolean flags now fail through the SDK's typed error hierarchy.
- GitHub Actions use immutable action revisions, test Python 3.10 through 3.14
plus the minimum supported
httpx, and verify distributions before release. - API examples use synthetic data and document request/response shapes and irreversible operation boundaries.
client.signer_documents.search(signer_id, signer_access_code, search=None)—GET /signers/{signer_id}/documents/search. Lightweight, compact counterpart tosigner_documents.list(), matching howdocuments.search()was added in 1.4.0.client.signers.upload_signature(..., reuse=None)— documentedreusequery parameter onPOST /signature, controlling the signer'sis_signature_reusableflag.client.signers.confirm_data()now also acceptsfull_nameandgovernment_id, matching the documented request schema (kept the existingwhatsapp_phone_number/has_accepted_termscompatibility fields).client.upload_and_request_signatures(..., wait_timeout=30.0, wait_poll_interval=2.0)— forwarded todocuments.wait_until_ready(previously hardcoded).
client.documents.create_from_template()— anoptionsdict containing its ownsignerskey could silently override the already-validatedsignersargument with an empty list.signersnow always wins.client.documents.wait_until_ready()no longer swallows a persistent404(document not found) into a generic timeout error; it now re-raises theApiErrorimmediately, since waiting can never resolve it.client.assignments.create()now requiressignersunconditionally (matching its own documented schema — the siblingestimate_cost()keeps its more lenient rule wherecollectmay omit signers). Previously amethod: "collect"request with nosignerswas sent to the API with no client-side error.client.assignments.create()'s log line now counts signers from the normalized request body instead of the raw payload, so it no longer reports 0 signers when the caller uses the legacysigner_idsalias.client.fields.update()silently dropped an explicit{"regex": None}, making it impossible to clear a field's regex. It now mirrorstags.update()'s handling ofcolor: None.client.webhooks.register()treated an explicitevents=[]the same as "omitted" and replaced it with the curated default — an empty list is now preserved as-is.client.webhooks.register()no longer silently reactivates an inactivated subscription or collapses a custom event list on a partial update (e.g. only rotatingurl): an omittedevents/is_activenow defaults from the current subscription instead of a hardcoded default, so a partial call can't clobber existing configuration. First-time registration (no existing subscription) is unaffected.client.signer_documents.list()now requiressigner_access_code(previously optional), aligned with every sibling signer-facing method.scripts/live_smoke.pynow saves the workspace's webhook subscription before its register/inactivate test and restores it exactly at the end, instead of relying on a human to notice and fix it out-of-band afterward.
- Breaking:
client.documents.upload(source, options=None)is nowupload(source, account_id=None), matching every sibling resource method'saccount_idconvention. Migration: replacedocuments.upload(source, {"account_id": "..."})withdocuments.upload(source, "..."). fields.create()'s docstring no longer listsis_read_only/is_visibleas accepted input; they are server-controlled response fields only.signers.confirm_data()now raisesValidationErroron an empty body instead of silently sending{}, matchingsigners.update().- Corrected several docstring examples to match the published
contract:
create_from_template(dropped undocumentedcopy_receivers, addedtags),estimate_cost_from_template(dropped an undocumentedidfield from the example),signers.get_self(added the documentedis_signature_reusableflag),documents.statuses()(full 11-status list),fields.list()(added the missingresourcefield). - CI: added
permissions: contents: readto both workflows, a concurrency group torelease.yml,ruff format --check, andpytest --covreporting. assinafy.types.SignerReferenceis now actually used inassignments.py's signer-normalization signatures instead of sitting unused in__all__.- Simplified
BaseResource._read_header's deadhasattrguard (every real and test-mockedheadersobject has.get).
Adds documented signing-workflow endpoints. No breaking changes.
client.documents.rename(document_id, name)—PATCH /documents/{id}. Renames a document while it is still inuploaded/metadata_readystatus (the API locks the name once signing starts). Name is capped at 255 characters.client.documents.search(params, account_id)—GET /accounts/{id}/documents/search. Lightweight, compact document search (no expandedassignment/pages), ideal for autocomplete. Acceptssearch,status, and pagination params.client.assignments.list(params, account_id)—GET /assignments. Lists the account's assignments (account context is supplied automatically as theaccountIdquery parameter). Returns the standard{"data": [...], "meta": {...}}.
- CI/release workflows: bumped
actions/checkoutandactions/setup-pythonfrom v6 to v7 (latest majors).
- Corrected the response payload examples in the docstrings for
assignments.whatsapp_notifications,webhooks.list_dispatches, andwebhooks.retry_dispatchto match the documented object shapes. Also aligned thefields.validateexample request/response so the value matches the echoed field type. Docstrings only — no code or behavior changes.
client.webhooks.delete()—DELETE /accounts/{account_id}/webhooks/subscriptionsis not a documented endpoint. The supported way to stop delivery isclient.webhooks.inactivate(), which preserves the configured URL/events. Migration: replace anywebhooks.delete()call withwebhooks.inactivate().
assignments.reset_expiration()now acceptsexpires_at=Noneto clear an assignment's expiration, matching the documented behavior ("a null value means no expiration"). Previously the SDK rejectedNone, making this documented operation impossible. An empty string is still rejected as malformed.assignments.create()/estimate_cost()now forward each signer's optionalstepfield, enabling sequential (multi-step) signing order as documented. Previouslystepwas silently dropped.authentication.get_api_key()is now typeddict | Noneand returnsNonewhen no API key has been generated yet (the API returns a nulldata).
WebhookVerifier.get_event_payload(),get_event_subject(), andget_event_object()accessors matching the documented webhook envelope (payloadfor event params;subject/objectfor the polymorphic entities).get_event_data()is retained as a backward-compatible alias ofget_event_object().- Python 3.14 added to the CI test matrix and the package classifiers.
WebhookVerifierdocstrings now state plainly that the public Delivery Contract documents no signature header/HMAC scheme;verify()is for accounts that have separately negotiated one.webhooks.register()documents that an omittedeventslist falls back to a curated subset; pass explicit events (seelist_event_types()) for full control.- Internal:
BaseResourceerror handling consolidated behind a single_guardboundary, and bare-array/object unwrapping centralized in_call_plain_list/_call_plain_dict(removes ~10 duplicated coercion sites). No behavior change.
client.tagsresource coveringGET/POST/PUT/DELETE /accounts/{account_id}/tags.- Document tag helpers covering list, replace, append, and detach endpoints under
/accounts/{account_id}/documents/{document_id}/tags. - Unit and live-smoke coverage for tag CRUD and document tag attachment flows.
__version__constant exposed at the package root.- Comprehensive docstrings on every public method covering the HTTP verb,
endpoint path, accepted parameters, and notable server-side rules
(e.g.
documents.deletedeletable statuses,signers.updateverification integrity rules). scripts/live_smoke.py— runnable live-API smoke test covering read paths, signer CRUD, document upload, and cost estimation.
User-Agentheader now includes the SDK version.documents.create_from_templateanddocuments.estimate_cost_from_templatenow validate thatsignersis non-empty before sending the request.WebhookVerifierclass docstring documents the assumed HMAC-SHA256 scheme and how to subclass for accounts using a different scheme.
- Distribution renamed from
assinafy-sdktoassinafyon PyPI. Install withpip install assinafy. Import path is unchanged.
signers.createnow follows the documented API exactly: itPOSTs the payload directly without an implicit "find by email then short-circuit" lookup or a 409-recovery refetch.signers.updatenow requires at least one documented field (full_name,email, orwhatsapp_phone_number).upload_and_request_signaturesnow expectsfull_name(matching the API) instead ofname.BaseResourceis now typed againsthttpx.ClientandLogger; the no-op logger is exposed via theLoggerProtocol.
py.typedmarker (PEP 561) so consumers get inline type hints.
documents.is_fully_signedanddocuments.get_signing_progress— derive fromdocuments.get(id)instead.AssignmentVerificationMethodandAssignmentNotificationMethodaliases (they were juststr).
- Initial synchronous Python SDK release with
httpx. - Core resources for documents, signers, assignments, webhooks, and workspaces.
WebhookVerifierwith HMAC-SHA256 verification.- Pytest test suite.