Skip to main content

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

Project description

py-vchasno

Disclaimer: This is an unofficial SDK, not affiliated with or endorsed by Vchasno. It was built based on publicly available information about the Vchasno.EDO API v2.

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.1.tar.gz (26.3 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.1-py3-none-any.whl (35.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: py_vchasno-0.1.1.tar.gz
  • Upload date:
  • Size: 26.3 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.1.tar.gz
Algorithm Hash digest
SHA256 2819ff28be063264531339fed49eb751a9323bbf740ab0e07aa2731a00c86d2c
MD5 a77484d5ed6ce05850370e42920fde2f
BLAKE2b-256 c4aeef7d0e0eb6f920d1613c61c5d2d2553bdbc69e4d9ff0c9209f52e30e5383

See more details on using hashes here.

File details

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

File metadata

  • Download URL: py_vchasno-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 35.1 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e9e21f5b2c9410f3ace92c6c5eeec6e822160a501bbc91e0af98cd874437a8b7
MD5 9f79b2584fa21c693d5f7c8938d55860
BLAKE2b-256 a91d86c9b0a75d261899d7fbf33b41c04ae57e49d7b7d9511d7a187edde26173

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