Skip to main content

The most complete async Python client for Russian self-employed tax service (Moy Nalog / lknpd.nalog.ru)

Project description

moy-nalog-api

GitHub Python 3.10+ PyPI version License: MIT Code style: ruff

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) and password from nalog.ru. To sign in by phone, use SMS authentication instead:

profile = await client.auth_by_password(
    username="123456789012",  # INN (10-12 digits)
    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")

Download Receipt

Download receipt as raw bytes for saving or processing:

# Download as JSON
json_bytes = await client.download_receipt_raw("receipt_uuid", format="json")
with open("receipt.json", "wb") as f:
    f.write(json_bytes)

# Download as PNG image (for printing)
png_bytes = await client.download_receipt_raw("receipt_uuid", format="print")
with open("receipt.png", "wb") as f:
    f.write(png_bytes)

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",

    # Custom User-Agent header (optional)
    user_agent="MyApp/1.0",
)

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:

  1. Choose authentication method (password or SMS)
  2. Enter credentials
  3. Script runs all tests sequentially
  4. All created receipts are cancelled automatically
  5. Detailed log and JSON report are saved to test_output/ directory

Output:

  • test_output/<timestamp>/test_log_*.log - detailed execution log
  • test_output/<timestamp>/test_report_*.json - JSON report with results
  • test_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:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests: pytest
  5. Run linting: ruff check .
  6. 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

moy_nalog_api-1.0.5.tar.gz (38.5 kB view details)

Uploaded Source

Built Distribution

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

moy_nalog_api-1.0.5-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file moy_nalog_api-1.0.5.tar.gz.

File metadata

  • Download URL: moy_nalog_api-1.0.5.tar.gz
  • Upload date:
  • Size: 38.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for moy_nalog_api-1.0.5.tar.gz
Algorithm Hash digest
SHA256 3c4440a5556ec3ced08b001fc3c7eb20cf9135dc57b2931af72c177661dae404
MD5 34a21ffdc63033f572cc6a25218d9c94
BLAKE2b-256 a9d41896222f46641380332df6a7f151859f29b0a61a95458bebaed8a6a75f5b

See more details on using hashes here.

File details

Details for the file moy_nalog_api-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: moy_nalog_api-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for moy_nalog_api-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 4394772248f115a80f192f50722572744213bacbb7274f0aa966fbe929b5bb81
MD5 67f07db894283af52a9a0207c1be6c09
BLAKE2b-256 ae5e5dc32efa9ef8dd2f978a50731bbddb60a2444f359a09aa28c51b77812dc9

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