Python helpers for interacting with the eMAG Marketplace API: sending offers, bulk uploads, and exporting category metadata. All scripts rely on Basic Auth credentials provided by eMAG.
- send_payload.py– Send a JSON payload with one or more offers.
- send_payload_json_strict.py– Minimal example that posts a single offer.
- upload_products.py– Convert a CSV sheet into offer requests with validation, retries, and response logging.
- list_category_ids.py– Read marketplace categories (ID + name) and optionally export to XLSX/JSON.
- Python 3.9+ (tested with 3.13).
- pip install requests python-dotenvfor all scripts.
- pip install openpyxlwhen using- list_category_ids.py --xlsx.
- Network access to https://marketplace-api.emag.rofrom a whitelisted IP.
- .envfile or environment variables:- EMAG_API_USER
- EMAG_API_KEY
- Optional: EMAG_API_HOST(defaults tohttps://marketplace-api.emag.ro)
- Optional: EMAG_DEFAULT_HANDLING_TIME(defaults to10if missing)
- Optional: EMAG_DEFAULT_SUPPLIER_LEAD_TIME(defaults to14if missing)
 
Create .env in the project root:
EMAG_API_USER=your-user
EMAG_API_KEY=your-key
EMAG_API_HOST=https://marketplace-api.emag.roA lightweight Flask UI is included for local automation. It wraps the existing helpers so you can trigger uploads, inspect logs, and track how many products were updated without leaving the browser.
python -m venv .venv
.venv\Scripts\activate  # PowerShell: .\.venv\Scripts\Activate.ps1
pip install -r requirements.txtEnsure the .env file contains your EMAG_API_USER, EMAG_API_KEY, and optional EMAG_API_HOST just like the CLI scripts.
python app.pyBy default the application listens on http://127.0.0.1:5000/.
- Send JSON payloads directly to product_offer/saveand see the parsed response.
- Upload CSV files produced from bulk_products_template.csv; validation is identical toupload_products.py.
- Dry-run mode for CSV uploads to validate rows without sending API requests.
- Automatic defaults for handling_time(10) andsupplier_lead_time(14) unless you override them in the CSV/JSON payload or via environment variables.
- Background uploads that keep running even if you leave the page, with a resumable progress bar and live row counters.
- Supplier & inventory hub backed by SQLite: manage suppliers, import full product batches, refresh stock from CSV, and push only the delta to eMAG once you approve it.
- Activity logs pulled from results_product_offer_save.csv(and enhanced copies inui_actions.jsonl).
- Live counters showing total rows processed and how many products were successfully updated (OKstatus).
Existing log files (responses_product_offer_save.jsonl and results_product_offer_save.csv) are reused, so the UI stays in sync with the CLI tooling.
The UI now ships with an integrated SQLite database (emag.db by default, override via EMAG_DB_PATH). Use it to stage offers per supplier before touching the API:
- Manage suppliers – add new supplier codes (the field supplier_tng) or reuse existing ones from the Suppliers card.
- Import products – upload the full eMAG template under Import Products into Database while selecting a supplier (or creating a new one on the fly). All required offer fields, images, and stock are stored locally.
- Refresh stock – drop a stock-only CSV in Import Supplier Stock. The database captures the new figures without pinging eMAG.
- Review changes – the Pending Stock Changes table lists products whose stock differs from what was last synced to eMAG. Filter by supplier to focus on a single feed.
- Push updates – click Update Stock on eMAG to send a minimal payload per product (stock + mandatory offer fields). Successful updates automatically reset the pending list.
This workflow lets you merge multiple stock files, eyeball deltas, and only hit the API once you're confident in the data.
- All requests use Basic Authorization; credentials must have API rights.
- eMAG enforces rate limits: typically 3 requests/sec for non-order endpoints.
- Save API responses and monitor isErrorin every reply.
- When the API responds with HTTP 401/403, verify IP whitelisting and credentials.
Utility for sending a ready-made payload (list of offers) to product_offer/save. Includes an auth sanity check (vat/read) and retries on HTTP 429.
python send_payload.py payload.json- payload.json– Required path to a JSON file. Accepts:- Array of offer objects.
- Object with key productscontaining the array (legacy format).
 
- Verifies EMAG_API_USER/EMAG_API_KEY.
- Performs a test request to /api-3/vat/readto confirm access.
- Sends the payload to /api-3/product_offer/savewith 6 exponential backoff retries on HTTP 429.
- Prints the parsed JSON response; exits with an error code if isErroris true or JSON parsing fails.
python send_payload.py payload.jsonUse when you have already constructed a request body and need a quick sender.
Minimal script that posts a hard-coded sample offer (feel free to edit the product dict). Useful for testing credentials or inspecting the raw API response.
python send_payload_json_strict.pyBehavior:
- Loads credentials from .env.
- Posts the productpayload (list with one element) to/api-3/product_offer/save.
- Prints HTTP status code and JSON response (falls back to raw text if JSON parsing fails).
Edit the product dictionary before running to match your data.
Bulk uploader driven by a CSV template (bulk_products_template.csv). Handles field normalization, validation, automatic id_key → id mapping, optional dry-runs, retries, and response logging.
python upload_products.py --csv bulk_products_template.csv [options]- Converts CSV rows into product_offer/savepayloads.
- Validates required fields, price range, stock, and main image presence.
- Retries up to 6 times on HTTP 429 with exponential backoff.
- Writes JSON responses to responses_product_offer_save.jsonl.
- Generates a CSV report (results_product_offer_save.csv) summarizing each row.
- --csv PATH(required) – Input CSV (UTF-8). Empty lines are skipped.
- --host URL– Override API host (default- EMAG_API_HOST).
- --user USER/- --key KEY– Override credentials (otherwise use env).
- --verbose– Print debug logging for each row.
- --dry-run– Skip HTTP calls; still validate/write report.
- --out PATH– Response log file (default- responses_product_offer_save.jsonl).
- --report PATH– Report CSV path (default- results_product_offer_save.csv).
- Core fields: id,category_id,name,brand,part_number,description,vat_id,status,sale_price,min_sale_price,max_sale_price.
- ean[0],- ean[1], … for multiple barcodes.
- images[0].url,- images[0].display_type(1 = main), etc.
- stock[0].warehouse_id,- stock[0].value, …
- id_keyauto-fills- idif- idcolumn is empty.
Fetch every active marketplace category with optional localization and export formats. Paginates category/read until completion and enforces the documented itemsPerPage ≤ 100.
python list_category_ids.py [options]- --language CODE– Return category names in a specific language (- EN,- RO,- HU,- BG,- PL,- GR,- DE).
- --items-per-page N– Page size (1–100, default 100).
- --timeout SECONDS– HTTP request timeout (default 30).
- --json– Print the full response objects instead of- id,name.
- --xlsx PATH– Write results to an Excel file (- Category ID,- Category Name); requires- openpyxl.
- --no-stdout– Suppress CSV output (useful with- --jsonor- --xlsx).
Export CSV to terminal:
python list_category_ids.py --language ENSave to Excel only:
python list_category_ids.py --language RO --xlsx data/categories.xlsx --no-stdoutRetrieve raw JSON with custom timeout:
python list_category_ids.py --json --timeout 120 > categories.jsonErrors (e.g., HTTP 401, isError: true) are reported on stderr. Increase --timeout or reduce --items-per-page if the API is slow.
- 401 Unauthorized – Check credentials, IP whitelisting, or account API rights.
- 429 Too Many Requests – Respect rate limits; scripts already retry.
- UnicodeEncodeError on Windows – list_category_ids.pyforces UTF-8 output; redirect to a UTF-8 capable console or file.
- SSL / connection issues – Ensure the host is reachable and TLS interception is not blocking requests.
Internal use only (no explicit license provided). Update this section if you plan to distribute the scripts externally.