The most complete async Python client for Russian self-employed tax service (Moy Nalog / lknpd.nalog.ru)
Project description
moy-nalog-api
The most complete and modern Python client for Russian self-employed tax service (lknpd.nalog.ru).
Unofficial Python client for "Moy Nalog" API (self-employed, NPD tax regime).
Why moy-nalog-api?
There are several Python libraries for the Moy Nalog API. Here's why you should choose this one:
| Feature | moy-nalog-api | Others |
|---|---|---|
| Async/await support | Native httpx async | Often sync-only or requests-based |
| Sync wrapper included | Yes, for non-async code | Usually one or the other |
| SMS authentication | Full support (request + verify) | Often missing or broken |
| Session persistence | Built-in JSON file storage | Manual implementation required |
| Auto token refresh | Automatic before expiration | Manual refresh needed |
| Type hints | 100% typed, mypy-compatible | Partial or none |
| Pydantic v2 | Full validation and serialization | Often dict-based or Pydantic v1 |
| Modern Python | 3.10+ with latest syntax | Often 3.7+ with legacy code |
| Error handling | Typed exception hierarchy | Generic exceptions |
| Retry logic | Exponential backoff built-in | Usually none |
| Multiple items | Native support for multi-item receipts | Single item only |
| Proxy support | HTTP, HTTPS, SOCKS4/5 | Often none |
| Documentation | Comprehensive with examples | Often minimal |
Features
- Async (httpx) and sync client support
- Password and SMS authentication
- Automatic token refresh with session persistence
- Retry with exponential backoff
- Full Pydantic v2 validation
- Multiple receipt items in single receipt
- All client types (individual, legal entity, foreign)
- Income list with pagination and filtering
- Receipt cancellation with reason
- HTTP/HTTPS and SOCKS proxy support
- Complete type hints for IDE support
Installation
pip install moy-nalog-api
For SOCKS proxy support:
pip install moy-nalog-api[socks]
For development:
pip install moy-nalog-api[dev]
Quick Start
Async (Recommended)
import asyncio
from decimal import Decimal
from moy_nalog import MoyNalogClient
async def main():
# Create client with session persistence
async with MoyNalogClient(session_file="session.json") as client:
# First run: authenticate
if not client.is_authenticated:
await client.auth_by_password("your_inn", "your_password")
# Create receipt
receipt = await client.create_receipt(
name="Consulting services",
amount=Decimal("5000.00")
)
print(f"Receipt created: {receipt.print_url}")
asyncio.run(main())
Sync
from decimal import Decimal
from moy_nalog import MoyNalogClientSync
with MoyNalogClientSync(session_file="session.json") as client:
if not client.is_authenticated:
client.auth_by_password("your_inn", "your_password")
receipt = client.create_receipt(
name="Consulting services",
amount=Decimal("5000.00")
)
print(f"Receipt created: {receipt.print_url}")
Authentication
Password Authentication
Use your INN (tax identification number) or phone and password from nalog.ru:
profile = await client.auth_by_password(
username="123456789012", # INN (12 digits) or phone
password="your_password"
)
print(f"Authenticated as: {profile.display_name}")
print(f"INN: {profile.inn}")
print(f"Status: {profile.status}")
SMS Authentication
Two-step process for phone-based authentication:
# Step 1: Request SMS code
phone = "79001234567" # Format: 7XXXXXXXXXX (11 digits)
challenge = await client.request_sms_code(phone)
print(f"SMS sent! Code expires in {challenge.expire_in} seconds")
# Step 2: Enter code and authenticate
code = input("Enter 6-digit code from SMS: ")
profile = await client.auth_by_sms(phone, challenge.challenge_token, code)
print(f"Authenticated as: {profile.display_name}")
Session Persistence
Save and restore authentication tokens automatically:
# Session file stores tokens between runs
client = MoyNalogClient(session_file="session.json")
# Check if already authenticated from previous session
if client.is_authenticated:
print("Session restored from file")
else:
# Authenticate (tokens saved automatically)
await client.auth_by_password(username, password)
# Tokens auto-refresh when expired
# Session auto-saves on close
Session file contains:
- Access token (for API requests)
- Refresh token (for token renewal)
- Token expiration time
- User INN and device ID
Creating Receipts
Simple Receipt
from decimal import Decimal
receipt = await client.create_receipt(
name="Web development",
amount=Decimal("15000.00")
)
print(f"UUID: {receipt.uuid}")
print(f"Amount: {receipt.total_amount} RUB")
print(f"Print URL: {receipt.print_url}")
print(f"JSON URL: {receipt.json_url}")
Multiple Items
from decimal import Decimal
from moy_nalog import ServiceItem
items = [
ServiceItem(name="Consulting", amount=Decimal("3000"), quantity=2),
ServiceItem(name="Development", amount=Decimal("10000"), quantity=1),
ServiceItem(name="Support", amount=Decimal("500"), quantity=4),
]
receipt = await client.create_receipt_multi(items)
# Total: 3000*2 + 10000*1 + 500*4 = 18000 RUB
print(f"Total: {receipt.total_amount} RUB")
With Client Information
Individual Client (default)
from moy_nalog import Client, IncomeType
client_info = Client(
income_type=IncomeType.INDIVIDUAL,
display_name="Ivan Petrov",
contact_phone="+79001234567"
)
receipt = await client.create_receipt(
name="Service",
amount=Decimal("1000"),
client=client_info
)
Legal Entity (Company)
company = Client(
income_type=IncomeType.LEGAL_ENTITY,
display_name="OOO Romashka",
inn="7712345678" # 10 digits for companies
)
receipt = await client.create_receipt(
name="B2B Service",
amount=Decimal("50000"),
client=company
)
Foreign Organization
foreign = Client(
income_type=IncomeType.FOREIGN_AGENCY,
display_name="Acme Corporation",
inn="9909123456"
)
receipt = await client.create_receipt(
name="International consulting",
amount=Decimal("100000"),
client=foreign
)
Payment Types
from moy_nalog import PaymentType
# Cash or card payment (default)
receipt = await client.create_receipt(
name="Service",
amount=Decimal("1000"),
payment_type=PaymentType.CASH
)
# Bank transfer (requires legal entity client with INN)
receipt = await client.create_receipt(
name="Service",
amount=Decimal("50000"),
client=company, # Must have INN
payment_type=PaymentType.WIRE
)
Canceling Receipts
Cancel a receipt within the same tax period:
from moy_nalog import CancelReason
# Client requested refund
await client.cancel_receipt(
receipt_uuid="abc123",
reason=CancelReason.REFUND
)
# Receipt created by mistake
await client.cancel_receipt(
receipt_uuid="abc123",
reason=CancelReason.MISTAKE
)
Viewing Receipts
Get Income List
from datetime import datetime
# Get recent receipts (default: last 50)
incomes = await client.get_incomes()
for receipt in incomes.items:
status = "CANCELLED" if receipt.is_cancelled else "ACTIVE"
print(f"{receipt.uuid}: {receipt.total_amount} RUB [{status}]")
print(f"Total count: {incomes.total}")
print(f"Has more: {incomes.has_more}")
With Filters and Pagination
incomes = await client.get_incomes(
from_date=datetime(2024, 1, 1),
to_date=datetime(2024, 12, 31),
offset=0,
limit=50
)
# Load more if needed
if incomes.has_more:
more = await client.get_incomes(offset=50, limit=50)
Get Receipt Details
# Get full receipt data as dict
data = await client.get_receipt("receipt_uuid")
if data:
print(f"Services: {data['services']}")
print(f"Payment type: {data['paymentType']}")
# Get printable URL
url = client.get_receipt_print_url("receipt_uuid")
Error Handling
from moy_nalog import (
MoyNalogError,
AuthenticationError,
InvalidCredentialsError,
TokenExpiredError,
SMSError,
SMSRateLimitError,
InvalidSMSCodeError,
ReceiptError,
ValidationError,
NetworkError,
RateLimitError,
)
try:
await client.auth_by_password(username, password)
except InvalidCredentialsError:
print("Wrong username or password")
except TokenExpiredError:
print("Session expired, re-authenticate")
except AuthenticationError as e:
print(f"Auth failed: {e.message}")
try:
await client.request_sms_code(phone)
except SMSRateLimitError:
print("Too many SMS requests, wait a minute")
except SMSError as e:
print(f"SMS error: {e.message}")
try:
await client.create_receipt("Service", Decimal("1000"))
except ReceiptError as e:
print(f"Receipt error: {e.message}")
print(f"Error code: {e.code}")
print(f"API response: {e.response}")
try:
# Network issues are retried automatically
await client.get_incomes()
except NetworkError:
print("Network unavailable after retries")
except RateLimitError:
print("API rate limit exceeded")
Configuration
client = MoyNalogClient(
# Timezone for receipt timestamps (default: Europe/Moscow)
timezone="Europe/Moscow",
# Request timeout in seconds (default: 30)
timeout=30.0,
# Retry attempts for failed requests (default: 3)
max_retries=3,
# Path to session file for persistence (optional)
session_file="session.json",
# Auto-refresh tokens before expiration (default: True)
auto_refresh_token=True,
# Proxy server URL (optional)
proxy="http://proxy.example.com:8080",
)
Proxy Support
The client supports HTTP, HTTPS, and SOCKS proxies for all API requests.
HTTP/HTTPS Proxy
# HTTP proxy (works out of the box)
client = MoyNalogClient(proxy="http://proxy.example.com:8080")
# With authentication
client = MoyNalogClient(proxy="http://user:password@proxy.example.com:8080")
# HTTPS proxy
client = MoyNalogClient(proxy="https://proxy.example.com:8080")
SOCKS Proxy
SOCKS proxy support requires an additional dependency:
pip install moy-nalog-api[socks]
# SOCKS5 proxy
client = MoyNalogClient(proxy="socks5://proxy.example.com:1080")
# SOCKS5 with authentication
client = MoyNalogClient(proxy="socks5://user:password@proxy.example.com:1080")
# SOCKS4 proxy
client = MoyNalogClient(proxy="socks4://proxy.example.com:1080")
Sync Client
The synchronous wrapper also supports proxies:
client = MoyNalogClientSync(proxy="http://proxy.example.com:8080")
User Profile
profile = await client.get_user_profile()
print(f"ID: {profile.id}")
print(f"INN: {profile.inn}")
print(f"Phone: {profile.phone}")
print(f"Email: {profile.email}")
print(f"Name: {profile.display_name}")
print(f"Full name: {profile.full_name}")
print(f"Status: {profile.status}")
print(f"Registration date: {profile.registration_date}")
Testing
Unit Tests
# Install dev dependencies
pip install -e ".[dev]"
# Run unit tests
pytest
# Run with coverage
pytest --cov=moy_nalog
Integration Test
Interactive script for testing all API functionality with a real account.
python scripts/integration_test.py
What it tests:
- Password and SMS authentication
- Session persistence (save/restore tokens)
- Simple receipt creation (1 item, cash payment)
- Multi-item receipt (3 items with quantities)
- Receipt with individual client info
- Receipt with legal entity client (INN required)
- Receipt with bank transfer payment (WIRE)
- Income list retrieval with pagination
- Receipt data retrieval by UUID
- Receipt cancellation
How it works:
- Choose authentication method (password or SMS)
- Enter credentials
- Script runs all tests sequentially
- All created receipts are cancelled automatically
- Detailed log and JSON report are saved to
test_output/directory
Output:
test_output/<timestamp>/test_log_*.log- detailed execution logtest_output/<timestamp>/test_report_*.json- JSON report with resultstest_output/<timestamp>/receipts/- downloaded receipt files (JSON/HTML)
Requirements
- Python 3.10+
- httpx >= 0.25.0
- pydantic >= 2.0.0
Disclaimer
This is an unofficial client. The API may change without notice. Use at your own risk. The author is not responsible for any issues with tax authorities.
Always verify receipts in your personal cabinet at lknpd.nalog.ru.
Author
Kirill Nikulin (c) 2025 kirodev.eu
License
MIT License - see LICENSE file.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
pytest - Run linting:
ruff check . - Submit a pull request
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
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 moy_nalog_api-1.0.2.tar.gz.
File metadata
- Download URL: moy_nalog_api-1.0.2.tar.gz
- Upload date:
- Size: 33.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69579a2ad2d632588050530a1e09bbb5908b7a957638cf7918efce61e9059549
|
|
| MD5 |
768d6c5c59620a7b22bf0460ac3dc932
|
|
| BLAKE2b-256 |
62a1ddc5f67ab9d50580c74557d9db7688ebf3e412574c5a03018825523f2994
|
File details
Details for the file moy_nalog_api-1.0.2-py3-none-any.whl.
File metadata
- Download URL: moy_nalog_api-1.0.2-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce93afc3039706ef5e36ad79eb04b524a23dc9e57c8c5b83aed170b54979f2c1
|
|
| MD5 |
55dc4cf8697abcaae9fb48537e6efb58
|
|
| BLAKE2b-256 |
60f58dabf9fd129e1a1ff8b0bd18a61a500ee1d41e6dcb83aac067a38cd03734
|