OxTenderAbstract
Ruby library for working with Russian procurement system API (zakupki.gov.ru) via SOAP-XML. Provides modular architecture for fetching and parsing tender data.
Features
- 🚀 Modular architecture with separate components
- 📊 Complete XML tender document parsing
- 📁 Automatic archive processing (GZIP + ZIP)
- 🔧 Simple configuration and usage
- 📝 Returns structured Hash results
- 🛡️ Error handling and logging
Installation
Add to your Gemfile:
gem 'ox-tender-abstract'
Or install directly:
gem install ox-tender-abstract
Quick Start
Configuration
require 'ox-tender-abstract'
# Global configuration
OxTenderAbstract.configure do |config|
config.token = key
config.timeout_open = 30
config.timeout_read = 120
config.logger = Logger.new(STDOUT)
end
Search tenders by region and date
# Search tenders for a specific date
result = OxTenderAbstract.search_tenders(
org_region: "77", # Region code (77 = Moscow)
exact_date: "2025-07-09" # Date in YYYY-MM-DD format
)
if result.success?
puts "Found tenders: #{result.data[:tenders].size}"
result.data[:tenders].first(3).each do |tender|
puts "Registry Number: #{tender[:reestr_number]}"
puts "Price: #{tender[:max_price]} rubles"
puts "Organization: #{tender[:organization_name]}"
puts "Contact: #{tender[:contact_email]}"
puts "---"
end
else
puts "Error: #{result.error}"
end
# Example output:
# Found tenders: 564
# Registry Number: 0373100066925000138
# Price: 25606.51 rubles
# Organization: FEDERAL STATE INSTITUTION "STATE INSTITUTION FOR THE OPERATION OF ADMINISTRATIVE BUILDINGS..."
# Contact: [email protected]
Enhanced search with detailed information
The enhanced_search_tenders
method automatically downloads and processes archives to extract comprehensive tender information including attachments, contact details, addresses, and metadata.
# Enhanced search with detailed information including attachments
result = OxTenderAbstract.enhanced_search_tenders(
org_region: "77", # Region code (77 = Moscow)
exact_date: "2025-07-09", # Date in YYYY-MM-DD format
include_attachments: true # Include attachment information
)
if result.success?
puts "Found tenders: #{result.data[:tenders].size}"
result.data[:tenders].first(3).each do |tender|
puts "Registry Number: #{tender[:reestr_number]}"
puts "Title: #{tender[:title]}"
puts "Price: #{tender[:max_price]} #{tender[:currency]}"
puts "Organization: #{tender[:organization_name]}"
puts "Contact Person: #{tender[:contact_person_name]}"
puts "Contact Email: #{tender[:contact_email]}"
puts "Contact Phone: #{tender[:contact_phone]}"
puts "Address: #{tender[:post_address]}"
puts "ETP: #{tender[:etp_name]} (#{tender[:etp_url]})"
puts "Start Date: #{tender[:start_date]}"
puts "End Date: #{tender[:end_date]}"
puts "Attachments: #{tender[:attachments_count]}"
if tender[:attachments]
tender[:attachments].each do ||
puts " - #{[:file_name]} (#{[:file_size]} bytes)"
puts " Description: #{[:description]}"
puts " URL: #{[:url]}"
end
end
puts "---"
end
else
puts "Error: #{result.error}"
end
Get documents by registry number
result = OxTenderAbstract.get_docs_by_reestr_number(
reestr_number: "0373100013125000767"
)
if result.success?
puts "Found archives: #{result.data[:archive_urls].size}"
end
Working with client directly
# Create client with token
client = OxTenderAbstract.client(token: "your_token")
# Or use global configuration
client = OxTenderAbstract.client
# Search by region
result = client.get_docs_by_region(
org_region: "77",
subsystem_type: "PRIZ",
document_type: "epNotificationEF2020",
exact_date: "2025-07-09"
)
# Download and process archive
if result.success?
result.data[:archive_urls].each do |archive_url|
archive_result = client.download_archive_data(archive_url)
if archive_result.success?
files = archive_result.data[:files]
puts "Files in archive: #{files.size}"
# Parse XML files
xml_files = files.select { |name, _| name.downcase.end_with?(".xml") }
xml_files.each do |file_name, file_data|
xml_result = client.parse_xml_document(file_data[:content])
if xml_result.success? && xml_result.data[:document_type] == :tender
tender = xml_result.data[:content]
puts "Tender: #{tender[:reestr_number]} - #{tender[:max_price]} rubles"
end
end
end
end
end
Architecture
The library consists of the following modules:
- Client - main SOAP API client
- XmlParser - XML document parser
- ArchiveProcessor - archive handler
- Configuration - library configuration
- Result - result structure
- DocumentTypes - document types and constants
Supported subsystem types
SUBSYSTEM_TYPES = %w[
PRIZ RPEC RPGZ RJ RDI BTK RPKLKP RPNZ RGK EA UR REC RPP RVP RRK RRA
RNP RKPO PPRF615 RD615 LKOK OZ OD223 RD223 MSP223 IPVP223 TRU223
RJ223 RPP223 RPZ223 RI223 RZ223 OV223 TPOZ223 POZ223 RNP223 POM223 ZC
]
Electronic notification types
ELECTRONIC_NOTIFICATION_TYPES = %w[
epNotificationEF2020 epNotificationEF epNotificationOK2020
epNotificationEP2020 epNotificationZK2020 epNotificationZP2020
epNotificationISM2020 fcsNotificationEF fcsNotificationOK
fcsNotificationEP fcsNotificationZK fcsNotificationZP
fcsNotificationISM fcsPlacement fcsPlacementResult
]
Result structure
{
success: true,
data: {
tenders: [
{
reestr_number: "0373100066925000138",
title: nil, # May be empty for some tenders
max_price: "25606.51",
currency: "РОССИЙСКИЙ РУБЛЬ",
organization_name: "ФЕДЕРАЛЬНОЕ КАЗЕННОЕ УЧРЕЖДЕНИЕ \"ГОСУДАРСТВЕННОЕ УЧРЕЖДЕНИЕ ПО ЭКСПЛУАТАЦИИ АДМИНИСТРАТИВНЫХ ЗДАНИЙ И ДАЧНОГО ХОЗЯЙСТВА МИНИСТЕРСТВА ФИНАНСОВ РОССИЙСКОЙ ФЕДЕРАЦИИ\"",
contact_email: "[email protected]",
contact_phone: nil,
contact_name: nil,
start_date: nil,
end_date: nil,
placement_type: "Электронный аукцион",
# Additional metadata
source_file: "epNotificationEF2020_0373100066925000138_1.xml",
archive_url: "https://int.zakupki.gov.ru/dstore/ip/download/PRIZ/file.zip?ticket=...",
processed_at: 2025-07-10 12:51:57 +0300
}
],
total_archives: 6,
total_files: 564,
processed_at: 2025-07-10 12:51:57 +0300
},
error: nil,
metadata: {}
}
Error handling
begin
result = OxTenderAbstract.search_tenders(
org_region: "77",
exact_date: "2025-07-09"
)
rescue OxTenderAbstract::AuthenticationError => e
puts "Authentication error: #{e.}"
rescue OxTenderAbstract::NetworkError => e
puts "Network error: #{e.}"
rescue OxTenderAbstract::ParseError => e
puts "Parsing error: #{e.}"
end
Real-world performance
Based on production testing with zakupki.gov.ru API:
- 564 tenders processed in ~14 seconds
- 6 archives downloaded and extracted
- 564 XML files parsed successfully
- Average processing time: ~25ms per tender
# Real example from July 9, 2025 - Moscow region
result = OxTenderAbstract.search_tenders(
org_region: "77",
exact_date: "2025-07-09"
)
puts result.data[:tenders].size # => 564
puts result.data[:total_files] # => 564
puts result.data[:total_archives] # => 6
# Processing typically takes 10-15 seconds for a full day's data
Error Handling
The library uses the Result
pattern for error handling:
result = OxTenderAbstract.search_tenders(org_region: '77', exact_date: '2024-01-01')
if result.success?
puts "Found tenders: #{result.data[:tenders].size}"
else
puts "Error: #{result.error}"
# Check error type for special handling
if result.[:error_type] == :blocked
retry_after = result.[:retry_after] || 600
puts "API blocked for #{retry_after} seconds"
end
end
Handling API Blocks
When making frequent requests, the API may block archive downloads for 10 minutes. The library automatically detects such blocks:
result = OxTenderAbstract.search_tenders(org_region: '77', exact_date: '2024-01-01')
if result.failure? && result.[:error_type] == :blocked
retry_after = result.[:retry_after] # 600 seconds (10 minutes)
puts "Download blocked, retry in #{retry_after} seconds"
end
For detailed guidance on using with Sidekiq background jobs, see SIDEKIQ_USAGE.md.
Requirements
- Ruby >= 3.0.0
- API token for zakupki.gov.ru
Development
After cloning the repository:
bin/setup
To run tests:
bundle exec rspec
License
This gem is available as open source under the MIT License.