Skip to main content

Python SDK for Vchasno.EDO (Electronic Document Management) API v2

Project description

py-vchasno

PyPI version Python 3.10+ License: MIT

Python SDK for Vchasno.EDO API v2 — Ukrainian electronic document management service.

Features

  • Sync & Async clients — Vchasno and AsyncVchasno
  • Full API coverage — all 19 endpoint groups (documents, signatures, comments, reviews, tags, archive, cloud signer, etc.)
  • Automatic retry on 429 Too Many Requests with exponential backoff
  • Pydantic v2 models with full type annotations
  • py.typed — first-class support for mypy / pyright

Installation

pip

pip install py-vchasno

uv

uv add py-vchasno

poetry

poetry add py-vchasno

From source

git clone https://github.com/captainluzik/py-vchasno.git
cd py-vchasno
pip install .

Quick start

Sync client

from vchasno import Vchasno

with Vchasno(token="your-api-token") as client:
    # List signed documents
    docs = client.documents.list(status=7008)
    for doc in docs.documents:
        print(f"{doc.title}{doc.status_text}")

    # Upload a document
    result = client.documents.upload(
        "invoice.pdf",
        recipient_edrpou="12345678",
        category=2,
    )

    # Check counterparty registration
    info = client.company.check(edrpou="12345678")
    print(f"{info.name}: registered={info.is_registered}")

Async client

import asyncio
from vchasno import AsyncVchasno

async def main():
    async with AsyncVchasno(token="your-api-token") as client:
        docs = await client.documents.list(status=7008)
        incoming = await client.documents.list_incoming()
        print(f"Outgoing: {len(docs.documents)}, Incoming: {len(incoming.documents)}")

asyncio.run(main())

Authentication

Generate an API token in Vchasno.EDO settings. The token is sent as Authorization: <token> header.

client = Vchasno(token="your-api-token")

Optionally override the base URL and timeout:

client = Vchasno(
    token="your-api-token",
    base_url="https://edo.vchasno.ua",  # default
    timeout=60.0,                        # seconds, default 30
    max_retries=5,                       # retry on 429, default 3
)

API reference

All endpoints are accessible as attributes of the client object. Each group provides both sync and async versions.

Documents — client.documents

# List outgoing documents with filters
docs = client.documents.list(
    status=7008,
    date_from="2024-01-01",
    date_to="2024-12-31",
    category=1,
    with_tags=True,
)

# Paginate with cursor
page = client.documents.list()
while page.next_cursor:
    page = client.documents.list(cursor=page.next_cursor)

# Get single document
doc = client.documents.get("document-uuid")

# Upload (multipart/form-data)
result = client.documents.upload(
    "contract.pdf",
    recipient_edrpou="12345678",
    recipient_emails="recipient@company.com",
    category=3,
    amount=1500000,  # amount in kopecks (= 15000.00 UAH)
    first_sign_by="owner",
)

# Upload from file object
with open("doc.pdf", "rb") as f:
    result = client.documents.upload(f, filename="doc.pdf")

# Edit document metadata (status < 7003)
client.documents.update_info(
    "document-uuid",
    title="Updated title",
    amount=500000,
    category=1,
)

# Edit recipient
client.documents.update_recipient("document-uuid", edrpou="87654321", email="new@mail.com")

# Access settings
client.documents.update_access_settings("document-uuid", level="private")
client.documents.update_viewers("document-uuid", strategy="add", roles_ids=["role-uuid"])

# Set multilateral signers
client.documents.set_flow("document-uuid", [
    {"edrpou": "11111111", "emails": ["signer@a.com"], "order": 0, "sign_num": 1},
    {"edrpou": "22222222", "emails": ["signer@b.com"], "order": 1, "sign_num": 1},
])

# List incoming documents
incoming = client.documents.list_incoming(
    status=7004,
    date_created_from="2024-01-01",
)

# Set signers for a document
client.documents.set_signers("document-uuid", signer_entities=[
    {"type": "role", "id": "role-uuid"},
    {"type": "group", "id": "group-uuid"},
], is_parallel=False)

# Download original file
content = client.documents.download_original("document-uuid")
with open("original.pdf", "wb") as f:
    f.write(content)

# Download specific version
content = client.documents.download_original("document-uuid", version="latest")

# Download ZIP archive with signatures
archive = client.documents.download_archive("document-uuid", with_instruction=1)

# Download P7S / ASIC containers
p7s = client.documents.download_p7s("document-uuid")
asic = client.documents.download_asic("document-uuid")

# Batch download info
info = client.documents.download_documents(["uuid-1", "uuid-2"])

# XML to PDF
client.documents.xml_to_pdf_create("document-uuid", force=True)
pdf = client.documents.xml_to_pdf_download("document-uuid")

# PDF print view
printable = client.documents.pdf_print("document-uuid")

# Batch statuses (up to 500 IDs)
statuses = client.documents.statuses(["uuid-1", "uuid-2"])
for s in statuses.data_list:
    print(f"{s.document_id}: {s.status_text}")

# Reject a document
client.documents.reject("document-uuid", text="Incorrect amount")

# Send after signing
client.documents.send("document-uuid")

# Delete
client.documents.delete("document-uuid")

# Archive / unarchive
client.documents.archive(["uuid-1", "uuid-2"], directory_id="dir-uuid")
client.documents.unarchive(["uuid-1", "uuid-2"])

# Mark as processed
result = client.documents.mark_as_processed(["uuid-1", "uuid-2"])

# Structured data (sd_status must be confirmed/downloaded)
sd = client.documents.structured_data_download("document-uuid", format="json")

Signatures — client.signatures

# List signatures for a document
sigs = client.signatures.list("document-uuid")
for sig in sigs:
    print(f"{sig.signer_name} ({sig.edrpou}) at {sig.timestamp}")

# Add a detached signature (base64-encoded .p7s)
client.signatures.add("document-uuid", signature="base64...", stamp="base64...")

# Multilateral document flows
flows = client.signatures.flows("document-uuid")

Comments — client.comments

# All comments across documents
comments = client.comments.list(date_from="2024-01-01")

# Comments for a specific document
doc_comments = client.comments.list_for_document("document-uuid")

# Add a comment
client.comments.add("document-uuid", text="Please review", is_internal=True)

Reviews (Approval) — client.reviews

# Approval history
history = client.reviews.history("document-uuid")

# Current review requests
requests = client.reviews.requests("document-uuid")

# Overall status
status = client.reviews.status("document-uuid")
print(f"{status.status}, required={status.is_required}")

# Add / remove reviewer
client.reviews.add_reviewer("document-uuid", user_to_email="reviewer@company.com")
client.reviews.add_reviewer("document-uuid", group_to_name="Accounting", is_parallel=False)
client.reviews.remove_reviewer("document-uuid", user_to_email="reviewer@company.com")

Versions — client.versions

# Upload a new version
client.versions.upload("document-uuid", "contract_v2.pdf")

# Delete last version
client.versions.delete("document-uuid", "version-uuid")

Delete Requests — client.delete_requests

# Create a delete request
client.delete_requests.create("document-uuid", message="Duplicate document")

# Cancel / accept / reject
client.delete_requests.cancel("document-uuid")
client.delete_requests.accept("document-uuid")
client.delete_requests.reject("document-uuid", reject_message="Not a duplicate")

# List delete requests
requests = client.delete_requests.list(status="new")

# Lock / unlock direct deletion
client.delete_requests.lock_delete(["uuid-1", "uuid-2"])
client.delete_requests.unlock_delete(["uuid-1", "uuid-2"])

Tags — client.tags

# List company tags
tags = client.tags.list(limit=100, offset=0)

# Roles linked to a tag
roles = client.tags.roles("tag-uuid")

# Create tags and assign to documents
new_tags = client.tags.create_for_documents(
    documents_ids=["doc-uuid"],
    names=["Urgent", "Q1-2024"],
)

# Connect / disconnect existing tags
client.tags.connect_documents(documents_ids=["doc-uuid"], tags_ids=["tag-uuid"])
client.tags.disconnect_documents(documents_ids=["doc-uuid"], tags_ids=["tag-uuid"])

# Tags for roles (employees)
client.tags.create_for_roles(roles_ids=["role-uuid"], names=["Manager"])
client.tags.connect_roles(roles_ids=["role-uuid"], tags_ids=["tag-uuid"])
client.tags.disconnect_roles(roles_ids=["role-uuid"], tags_ids=["tag-uuid"])

Archive — client.archive

# List archive directories
dirs = client.archive.directories(parent_id=None, search="2024")

# Upload scans
result = client.archive.upload_scans(["scan1.pdf", "scan2.pdf"], parent_id=11)

# Import signed document (external format: original + .p7s files)
result = client.archive.import_signed_external(
    "document.pdf",
    ["signature1.p7s", "signature2.p7s"],
    title="Contract",
    amount=1000000,
)

# Import signed document (internal format: .p7s or ASiC-E container)
result = client.archive.import_signed_internal("signed_container.p7s")

Categories — client.categories

# List all document categories
cats = client.categories.list()

# Create / update / delete internal category
client.categories.create(title="Custom Type")
client.categories.update(37, title="Renamed Type")
client.categories.delete(37)

Fields — client.fields

# List custom fields
fields = client.fields.list()

# Create a new field
field = client.fields.create(name="PO Number", field_type="text", is_required=True)

# Document fields
doc_fields = client.fields.list_for_document("document-uuid")
client.fields.add_to_document("document-uuid", field_id="field-uuid", value="PO-12345")

Children — client.children

# Link / unlink child documents
client.children.add("parent-uuid", "child-uuid")
client.children.remove("parent-uuid", "child-uuid")

Groups — client.groups

# CRUD groups
groups = client.groups.list()
group = client.groups.create(name="Accounting Team")
client.groups.update("group-uuid", name="Finance Team")
client.groups.delete("group-uuid")

# Members
members = client.groups.members("group-uuid")
client.groups.add_members("group-uuid", role_ids=["role-1", "role-2"])
client.groups.remove_members("group-uuid", group_members=["member-uuid"])

Roles — client.roles

# List active employees
roles = client.roles.list()
for r in roles.roles:
    print(f"{r.email}{r.position}")

# Update permissions / notifications
client.roles.update("role-uuid", can_sign_and_reject_document=True, user_role=8001)

# Invite / create coworkers
client.roles.invite_coworkers(emails=["new@company.com"])
client.roles.create_coworker(email="new@company.com", first_name="John", last_name="Doe")

# Delete employee
client.roles.delete("role-uuid")

# Token management
client.roles.create_tokens(emails=["user@company.com"], expire_days="365")
client.roles.delete_tokens(emails=["user@company.com"])

Templates — client.templates

templates = client.templates.list()
template = client.templates.get("template-uuid")

Reports — client.reports

# Request a report (max 30-day range)
report = client.reports.request_document_actions(date_from="2024-01-01", date_to="2024-01-31")

# Check status
status = client.reports.status(report.report_id)
if status.status == "ready":
    xlsx = client.reports.download(report.report_id)
    with open(status.filename, "wb") as f:
        f.write(xlsx)

# User actions report
report = client.reports.request_user_actions(date_from="2024-01-01", date_to="2024-01-31")

Cloud Signer (Vchasno.KEP) — client.cloud_signer

# Create signing session
session = client.cloud_signer.create_session(duration=3600, client_id="key-uuid")
print(f"Session: {session.auth_session_id}")

# Poll until ready
check = client.cloud_signer.check_session(auth_session_id=session.auth_session_id)
if check.status == "ready":
    token = check.token

# Sign a document
client.cloud_signer.sign_document(
    client_id="key-uuid",
    password="key-password",
    document_id="document-uuid",
    auth_session_token=token,
)

# Refresh token flow
session = client.cloud_signer.create_session(
    duration=3600, client_id="key-uuid", use_refresh_token=True,
)
result = client.cloud_signer.check_refresh_session(auth_session_id=session.auth_session_id)
refreshed = client.cloud_signer.refresh_token(
    auth_session_id=session.auth_session_id,
    refresh_token=result.refresh_token,
)

# Create view/sign session for personal cabinet
sign_session = client.cloud_signer.create_sign_session(
    document_id="document-uuid",
    edrpou="12345678",
    email="signer@company.com",
    type="sign_session",
    on_finish_url="https://your-app.com/done",
)
print(f"Redirect to: {sign_session.url}")

Billing — client.billing

# Activate 30-day trial
client.billing.activate_trial()

Company — client.company

# Check single counterparty
info = client.company.check(edrpou="12345678")

# Bulk check from .xlsx / .csv
result = client.company.check_upload("counterparties.xlsx")
for c in result.companies:
    print(f"{c.edrpou} {c.name}: {c.is_registered}")

Enums

The SDK provides enums for all known constants:

from vchasno.models.enums import (
    DocumentStatus,
    DocumentCategory,
    FirstSignBy,
    ReviewState,
    StructuredDataStatus,
    DeleteRequestStatus,
    CloudSignerSessionStatus,
    AccessSettingsLevel,
)

# Document statuses
DocumentStatus.UPLOADED          # 7000
DocumentStatus.READY_TO_SIGN     # 7001
DocumentStatus.FULLY_SIGNED      # 7008
DocumentStatus.ANNULLED          # 7011

# Document categories
DocumentCategory.CONTRACT        # 3
DocumentCategory.INVOICE         # 2
DocumentCategory.OTHER           # 15

Error handling

from vchasno import (
    Vchasno,
    VchasnoError,
    VchasnoAPIError,
    AuthenticationError,
    RateLimitError,
    NotFoundError,
    BadRequestError,
)

with Vchasno(token="xxx") as client:
    try:
        doc = client.documents.get("non-existent-id")
    except NotFoundError:
        print("Document not found")
    except AuthenticationError:
        print("Invalid or expired token")
    except RateLimitError:
        # Automatic retry handles most 429s;
        # this only fires after max_retries exhausted
        print("Rate limit exceeded after retries")
    except BadRequestError as e:
        print(f"Bad request: {e.response_body}")
    except VchasnoAPIError as e:
        print(f"API error {e.status_code}: {e}")
    except VchasnoError as e:
        print(f"SDK error: {e}")

Rate limiting

Vchasno API allows 10 requests/second per company. The SDK automatically retries 429 responses with exponential backoff (1s, 2s, 4s by default, configurable via max_retries).

Important notes

  • Amounts are always in kopecks (1 UAH = 100 kopecks). Example: amount=1500000 means 15,000.00 UAH.
  • Datetime format: YYYY-MM-DD or YYYY-MM-DDTHH:MM.
  • File limits: single file up to 15 MB; ZIP archive up to 500 files / 100 MB.
  • Pagination: use cursor / next_cursor pattern for all list endpoints.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_vchasno-0.1.0.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_vchasno-0.1.0-py3-none-any.whl (32.3 kB view details)

Uploaded Python 3

File details

Details for the file py_vchasno-0.1.0.tar.gz.

File metadata

  • Download URL: py_vchasno-0.1.0.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for py_vchasno-0.1.0.tar.gz
Algorithm Hash digest
SHA256 17fc4e6ab6503e33c8f68cc228d8695b893f202f411f74d6720324b123022994
MD5 0331406ec8af13ad04ae4faf1d6a7d76
BLAKE2b-256 47110556c4831f59bb3968e904c5ff31add8c0262c23dc8f736bf8070ee535a8

See more details on using hashes here.

File details

Details for the file py_vchasno-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: py_vchasno-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 32.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for py_vchasno-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 710f6f95c40ef6506dd81a4b4349c5250f2efbf23b9c1e2839c394b80d6d7c70
MD5 301516e24611ffd20636e4e6b78b4f2c
BLAKE2b-256 b7c05c5a655c66f0ee1e8b65177abcd676e516389409194bd318c8ca3fe25cdd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page