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.
Python SDK for Vchasno.EDO API v2 — Ukrainian electronic document management service.
Features
- Sync & Async clients —
VchasnoandAsyncVchasno - Full API coverage — all 19 endpoint groups (documents, signatures, comments, reviews, tags, archive, cloud signer, etc.)
- Automatic retry on
429 Too Many Requestswith 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=1500000means 15,000.00 UAH. - Datetime format:
YYYY-MM-DDorYYYY-MM-DDTHH:MM. - File limits: single file up to 15 MB; ZIP archive up to 500 files / 100 MB.
- Pagination: use
cursor/next_cursorpattern for all list endpoints.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2819ff28be063264531339fed49eb751a9323bbf740ab0e07aa2731a00c86d2c
|
|
| MD5 |
a77484d5ed6ce05850370e42920fde2f
|
|
| BLAKE2b-256 |
c4aeef7d0e0eb6f920d1613c61c5d2d2553bdbc69e4d9ff0c9209f52e30e5383
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9e21f5b2c9410f3ace92c6c5eeec6e822160a501bbc91e0af98cd874437a8b7
|
|
| MD5 |
9f79b2584fa21c693d5f7c8938d55860
|
|
| BLAKE2b-256 |
a91d86c9b0a75d261899d7fbf33b41c04ae57e49d7b7d9511d7a187edde26173
|