podpislon-sdk
🇷🇺 Asynchronous Python SDK for the Podpislon document-signing API. Built on
httpx+pydantic v2. Ships first-class typing, rate limiting, retries, paginated iterators, and a webhook handler that drops straight into FastAPI / aiogram / aiohttp.
⚠️ Disclaimer
Этот проект — НЕОФИЦИАЛЬНЫЙ SDK.
Он создан и поддерживается сообществом. Этот SDK не является официальным, не имеет отношения к компании, владеющей сервисом https://podpislon.ru, не одобрен и не спонсирован ею. Все упомянутые торговые марки и названия принадлежат их законным владельцам.
Используете на свой страх и риск. Авторы и контрибьюторы не несут ответственности за любые последствия использования этой библиотеки.
This project is an UNOFFICIAL SDK.
It is community-maintained and is NOT affiliated with, endorsed by, sponsored by, or in any way officially connected with the company operating https://podpislon.ru. All trademarks and product names mentioned belong to their respective owners.
Use at your own risk. The authors and contributors are not liable for any consequences arising from the use of this library.
Содержание
- Установка
- Быстрый старт
- Аутентификация
- Работа с документами
- Информация о компании
- Платёжные системы
- Вебхуки
- Интеграция с FastAPI
- Интеграция с aiogram (телеграм-боты)
- Конфигурация клиента
- Обработка ошибок
- Логирование
- Внутреннее устройство
- Разработка и тестирование
- Релизы и версионирование
- Публикация на PyPI
- Contributing
- Лицензия
Установка
pip install podpislon-sdk
Опциональные «extras»:
# для готовых FastAPI-помощников
pip install "podpislon-sdk[fastapi]"
# для разработки (тесты, линтеры, type-checking)
pip install "podpislon-sdk[dev]"
Поддерживаемые версии Python: 3.9 — 3.13.
Быстрый старт
import asyncio
from pathlib import Path
from podpislon import PodpislonClient
async def main() -> None:
async with PodpislonClient(api_key="ВАШ_API_КЛЮЧ") as client:
# Сколько документов осталось на тарифе?
info = await client.company.get_info()
print(f"Компания: {info.company.name}, остаток: {info.signings_left}")
# Отправить документ на подпись
result = await client.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[Path("contract.pdf")],
)
print(f"Создан документ id={result.first_id}")
# Получить подписанный PDF и сохранить его
await client.documents.save_file(result.first_id, "signed.pdf")
asyncio.run(main())
Аутентификация
Создайте API-ключ в личном кабинете на странице «Интеграции» и передайте его в клиент:
client = PodpislonClient(api_key="ваш_api_ключ")
SDK автоматически добавляет заголовок X-Api-Key ко всем исходящим запросам.
Совет: не храните ключ в коде. Используйте переменные окружения и
os.environ["PODPISLON_API_KEY"], а в продакшене — менеджер секретов вашей платформы.
Работа с документами
Все эндпоинты «Document» доступны через client.documents.
Список документов
page = await client.documents.list(page=1)
print(f"Страница {page.pagination.current_page} из {page.pagination.page_count}")
print(f"Всего документов: {page.pagination.total_count}")
for doc in page:
print(doc.id, doc.name, doc.status_text)
Постраничный обход
iter_all сам обходит все страницы, пока не дойдёт до конца:
async for doc in client.documents.iter_all():
process(doc)
Фильтрация
from podpislon import DocumentStatus, Filter
page = await client.documents.list(
filter=Filter(
status=DocumentStatus.SIGNED,
fio="Иван Иванов",
phone="89999999999",
dates={">=": "1667941200", "<=": "1668041200"},
),
expand="package", # включить package_id (нужен для resend)
)
DocumentStatus экспортирует все возможные статусы (CREATED, SENT, OPENED, SIGNED, CANCEL_REQUESTED, CANCELLED) и для каждого даёт человекочитаемое описание:
DocumentStatus.SIGNED.description # "Подписан"
Отправка документа на подпись
from pathlib import Path
from podpislon import Contact, Payment
result = await client.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[Path("contract.pdf"), Path("addendum.pdf")],
second_name="Иванович", # опционально
contacts=[ # дополнительные подписанты
Contact(
name="Пётр",
last_name="Петров",
phone="+79111111111",
),
],
payment=Payment(pid="2", sum="1200"),
redirect_url="https://example.com/done",
sign_by_time=1744035750, # дедлайн (Unix timestamp)
)
print(result.ids) # [101, 102]
Получить ссылку вместо SMS
Передайте no_sms=True, чтобы получить готовые ссылки на подписание:
result = await client.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[Path("contract.pdf")],
no_sms=True,
)
for link in result.links:
print("Подписать:", link)
JSON вместо multipart
По умолчанию SDK отправляет файлы через multipart/form-data (без накладных расходов на base64). Если нужен JSON-режим — передайте use_multipart=False:
await client.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[b"%PDF-1.4 fake bytes"],
file_names=["contract.pdf"],
use_multipart=False,
)
Получение PDF
pdf_bytes = await client.documents.get_file(file_id=101)
Path("signed.pdf").write_bytes(pdf_bytes)
# Или короче:
await client.documents.save_file(101, "signed.pdf")
Переотправка ссылки
await client.documents.resend(
package_id="cb4b683", # берётся из document.package при expand="package"
contact="ODYxMjI=", # sid конкретного подписанта (опционально)
)
Удаление документа
await client.documents.delete(file_id=101)
Информация о компании
info = await client.company.get_info()
print(info.company.name) # ООО "Рога и копыта"
print(info.company.inn) # 1001982736
print(info.company.kpp) # 123456789
print(info.signings_left) # 259
Платёжные системы
systems = await client.payments.list_systems()
for system in systems:
print(system.id, system.name)
Вебхуки
Подпислон отправляет события на ваш URL в формате application/x-www-form-urlencoded. SDK даёт типобезопасный парсер и асинхронный диспетчер.
Парсинг событий
from podpislon import parse_event
event = parse_event(b"EVENT=DOCUMENT_SIGNED&FILE_ID=1234&COMPANY_ID=12&SIGNATURE=abc")
print(event.event) # WebhookEventType.DOCUMENT_SIGNED
print(event.file_id) # 1234
Поддерживаются все три типа событий из спецификации:
| Событие | Модель |
|---|---|
DOCUMENT_OPENED |
DocumentOpenedEvent |
DOCUMENT_SIGNED |
DocumentSignedEvent |
CLIENT_DATA_REQUEST_SUBMITTED |
ClientDataRequestSubmittedEvent |
Регистрация обработчиков
from podpislon import (
DocumentOpenedEvent,
DocumentSignedEvent,
WebhookEventType,
WebhookHandler,
)
handler = WebhookHandler()
@handler.on(WebhookEventType.DOCUMENT_SIGNED)
async def signed(event: DocumentSignedEvent) -> None:
await db.mark_signed(event.file_id)
@handler.on(WebhookEventType.DOCUMENT_OPENED)
async def opened(event: DocumentOpenedEvent) -> None:
print(f"Документ {event.file_id} открыт телефоном {event.contact}")
@handler.on_any
async def audit(event) -> None:
await audit_log.write(event)
Обработчики могут быть как async, так и обычными функциями.
Проверка подписи
Алгоритм подписи Подпислона официально не задокументирован. Если служба поддержки сообщит вам нужный алгоритм — передайте его в
compute=. По умолчанию используется HMAC-SHA256 с секретом из аргумента.
from podpislon import WebhookHandler, WebhookSignatureVerifier
handler = WebhookHandler(
signature_verifier=WebhookSignatureVerifier(secret="ВАШ_СЕКРЕТ"),
)
# или со своим алгоритмом:
verifier = WebhookSignatureVerifier(
secret="ВАШ_СЕКРЕТ",
compute=lambda raw, key: my_custom_signature(raw, key),
)
Интеграция с FastAPI
from fastapi import FastAPI, Request, Response, HTTPException
from podpislon import (
DocumentSignedEvent,
PodpislonWebhookError,
WebhookEventType,
WebhookHandler,
)
app = FastAPI()
handler = WebhookHandler()
@handler.on(WebhookEventType.DOCUMENT_SIGNED)
async def on_signed(event: DocumentSignedEvent) -> None:
# ваша бизнес-логика
print("Подписан документ", event.file_id)
@app.post("/webhooks/podpislon")
async def podpislon_webhook(request: Request) -> Response:
body = await request.body()
try:
await handler.dispatch(body, raw_body=body)
except PodpislonWebhookError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return Response(status_code=200)
И клиент рядом, чтобы инициировать новую отправку:
from contextlib import asynccontextmanager
from podpislon import PodpislonClient
@asynccontextmanager
async def lifespan(app):
app.state.podpislon = PodpislonClient(api_key=os.environ["PODPISLON_API_KEY"])
try:
yield
finally:
await app.state.podpislon.aclose()
app = FastAPI(lifespan=lifespan)
@app.post("/contracts/{contract_id}/sign")
async def send_to_sign(contract_id: int, request: Request):
client = request.app.state.podpislon
result = await client.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[await render_contract_pdf(contract_id)],
no_sms=True,
)
return {"ids": result.ids, "links": result.links}
Интеграция с aiogram (телеграм-боты)
from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import CommandStart
from aiogram.types import Message
from podpislon import PodpislonClient
router = Router()
podpislon = PodpislonClient(api_key="...")
@router.message(CommandStart())
async def start(message: Message) -> None:
await message.answer("Пришлите PDF-документ — отправлю его на подпись.")
@router.message(F.document)
async def send_to_sign(message: Message, bot: Bot) -> None:
file = await bot.get_file(message.document.file_id)
pdf_bytes = await bot.download_file(file.file_path)
result = await podpislon.documents.add(
name="Иван",
last_name="Иванов",
phone="+79991112233",
files=[pdf_bytes.read()],
file_names=[message.document.file_name or "document.pdf"],
no_sms=True,
)
for link in result.links:
await message.answer(f"Ссылка для подписания: {link}")
async def main() -> None:
bot = Bot(token="TELEGRAM_BOT_TOKEN")
dp = Dispatcher()
dp.include_router(router)
try:
await dp.start_polling(bot)
finally:
await podpislon.aclose()
await bot.session.close()
Конфигурация клиента
from httpx import Timeout
from podpislon import PodpislonClient
client = PodpislonClient(
api_key="...",
base_url="https://podpislon.ru/integration", # переопределить редко нужно
timeout=Timeout(30.0, connect=5.0),
max_retries=3, # ретраи 429/5xx с экспоненциальной паузой
rate_limit=4, # клиентская защита (4 RPS)
user_agent="my-app/1.2.3",
)
Если у вас уже есть свой httpx.AsyncClient (например, c прокси или mTLS), передайте его в http_client=:
import httpx
from podpislon import PodpislonClient
shared = httpx.AsyncClient(proxies="http://proxy:3128")
client = PodpislonClient(api_key="...", http_client=shared)
# ВАЖНО: при пользовательском http_client SDK НЕ закроет его сам.
Обработка ошибок
Все исключения наследуются от PodpislonError:
from podpislon import (
PodpislonAuthenticationError, # HTTP 401
PodpislonPermissionError, # HTTP 403
PodpislonNotFoundError, # HTTP 404
PodpislonRateLimitError, # HTTP 429 после исчерпания ретраев
PodpislonServerError, # HTTP 5xx
PodpislonAPIError, # любой иной API-ответ с ошибкой
PodpislonValidationError, # ошибка валидации параметров на стороне клиента
PodpislonTransportError, # таймаут / сеть / DNS
PodpislonError, # базовый класс
)
try:
await client.documents.delete(123)
except PodpislonNotFoundError:
print("Документ не найден")
except PodpislonAuthenticationError:
print("Неверный API-ключ")
except PodpislonError as exc:
print(f"Что-то пошло не так: {exc}")
У PodpislonAPIError есть полезные атрибуты: status_code, response_body, request_id.
Логирование
SDK пишет отладочные сообщения в стандартный модуль logging под именем podpislon:
import logging
logging.getLogger("podpislon").setLevel(logging.DEBUG)
Внутреннее устройство
podpislon/
├── client.py # PodpislonClient — фасад
├── _transport.py # httpx-обёртка: rate limit, ретраи, маппинг ошибок
├── _utils.py # rate-limiter, base64, form-encoding helpers
├── exceptions.py # иерархия PodpislonError
├── enums.py # DocumentStatus, WebhookEventType
├── models/ # pydantic v2 модели
├── resources/ # Documents / Company / Payments
└── webhooks/ # parser + handler + signature verifier
Ключевые свойства:
- Async-first. Под капотом
httpx.AsyncClient. Никаких блокирующих вызовов на горячем пути. - Type-safe. Pydantic v2 для всех моделей, экспортируется
py.typedмаркер. - Rate-limiter из коробки. Скользящее окно 4 RPS на ключ — соответствует ограничению API.
- Ретраи с экспоненциальным backoff на
429и5xx, с уважением к заголовкуRetry-After. - Никаких глобальных стейтов. Один клиент = один пул соединений; легко работать с несколькими ключами одновременно.
- Webhook-handler. Декораторный API в духе
aiogram.
Разработка и тестирование
git clone https://github.com/IceOne-i/podpislon-sdk.git
cd podpislon-sdk
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windows
pip install -e ".[dev]"
# тесты с покрытием
pytest --cov=podpislon
# линт и формат
ruff check src tests
ruff format src tests
# type-checking
mypy src
Релизы и версионирование
Версии собираются автоматически из git-тегов с помощью hatch-vcs. Чтобы выпустить новую версию:
git tag v1.2.3
git push origin v1.2.3
GitHub Actions подхватит тег, соберёт sdist + wheel и опубликует пакет на PyPI (см. workflow ниже).
Соблюдается SemVer:
MAJOR— несовместимые изменения публичного API;MINOR— обратносовместимые новые возможности;PATCH— обратносовместимые исправления.
Публикация на PyPI
В репозитории два workflow:
.github/workflows/tests.yml— на каждый PR/push прогоняет тесты на Python 3.9 — 3.13..github/workflows/publish.yml— на каждый push тегаv*собирает дистрибутивы и публикует их на PyPI через Trusted Publishing (без хранения токенов в репозитории).
Чтобы включить публикацию:
- Зарегистрируйте проект на PyPI (
pypi.org/manage/account/publishing/) — добавьте Trusted Publisher с owner =IceOne-i, repo =podpislon-sdk, workflow =publish.yml, environment =pypi. - В GitHub-репозитории создайте окружение
pypi(Settings → Environments). - Запушьте тег
vX.Y.Z— workflow сработает сам.
Если вы предпочитаете API-токен вместо Trusted Publishing, добавьте в секреты репозитория PYPI_API_TOKEN и раскомментируйте соответствующий шаг в publish.yml.
Contributing
Pull requests welcome! Но прежде чем большой PR — заведите issue с обсуждением.
- Форкните репозиторий и создайте ветку.
- Установите dev-зависимости:
pip install -e ".[dev]". - Добавьте тесты на новый функционал.
- Прогоните
pytest,ruff,mypy. - Откройте PR с осмысленным описанием.
Лицензия
MIT — самая разрешительная из распространённых лицензий. Можно использовать в коммерческих и закрытых проектах при условии сохранения исходного уведомления об авторских правах. Подробнее в файле LICENSE.
Полезные ссылки
- Сайт сервиса (не аффилирован с этим SDK): https://podpislon.ru/
- OpenAPI спецификация: https://api.podpislon.ru/podpislon-api.yaml
- Личный кабинет → Интеграции: https://podpislon.ru/lk/integrations
- Issues / запросы: https://github.com/IceOne-i/podpislon-sdk/issues
Metadata
Release files for podpislon-sdk 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| podpislon_sdk-1.3.0.tar.gz | 40.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| podpislon_sdk-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 82.5 kB
Release files / podpislon_sdk-1.3.0.tar.gz
| Download URL | podpislon_sdk-1.3.0.tar.gz |
|---|---|
| Size | 40.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
be08902bae7d23ed6f574e9a4ba18eec1b1e454a5aefb14ead60ba024364e70e
|
|
BLAKE2b-256 checksum How to use checksums |
38bdca7906207ab7d4eb99a4457c66f634bcfc557ecb677c4f94a9a35658c803
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 28, 2026.
Transparency logRelease files / podpislon_sdk-1.3.0-py3-none-any.whl
| Download URL | podpislon_sdk-1.3.0-py3-none-any.whl |
|---|---|
| Size | 42.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
321e689e7afbad9c25d213a10e006155b94027ece55391d0d0ceadb294a645e8
|
|
BLAKE2b-256 checksum How to use checksums |
b1df25582f76175d55f535b5b7671530690059ccb27c04e1d0148d2730fdc771
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 28, 2026.
Transparency log