Files
kleinanzeigen-bot/docs/AD_CONFIGURATION.md
Jens 96734cc5ed docs: clarify download naming examples (#1016)
## ℹ️ Description
Documentation-only update to clarify how `ad_files` differs from
download naming templates and how published IDs are handled.

- Link to the related discussion:
https://github.com/Second-Hand-Friends/kleinanzeigen-bot/discussions/839
- Describe the motivation and context for this change: reduce confusion
around downloaded ad naming and local file behavior.

## 📋 Changes Summary
- Clarified `ad_files` as a file-selection glob, separate from download
naming.
- Added concrete sample ID/title render examples for folder and file
templates.
- Documented that publish updates the YAML `id` in place and does not
auto-rename local files or folders.
- Regenerated config docs and schema artifacts to keep generated output
in sync.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist
Before requesting a review, confirm the following:
- [x] I have reviewed my changes to ensure they meet the project's
standards.
- [x] I have tested my changes and ensured that all tests pass (`pdm run
test`).
- [x] I have formatted the code (`pdm run format`).
- [x] I have verified that linting passes (`pdm run lint`).
- [x] I have updated documentation where necessary.

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Expanded ad configuration and setup documentation with improved
guidance on file pattern matching and download directory configuration.
* Clarified how placeholder templates work in file naming, including
required and optional parameters.
* Added comprehensive examples demonstrating template rendering and
configuration best practices.
* Documented republish behavior and workflow guidance for managing
downloaded ad files.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-23 22:15:01 +02:00

17 KiB
Raw Blame History

Ad Configuration Reference

Complete reference for ad YAML files in kleinanzeigen-bot.

File Format

Each ad is described in a separate JSON or YAML file. The bot loads whichever files match the ad_files glob in config.yaml. Many users keep the default ad_ prefix for downloaded files (for example, ad_laptop.yaml), but the filename itself is user-controlled. Examples below use YAML, but JSON uses the same keys and structure.

Parameter values specified in the ad_defaults section of config.yaml don't need to be specified again in the ad configuration file.

Quick Start

Generate sample ad files using the download command:

# Download all ads from your profile
kleinanzeigen-bot download --ads=all

# Download only new ads (not locally saved yet)
kleinanzeigen-bot download --ads=new

# Download specific ads by ID
kleinanzeigen-bot download --ads=1,2,3

For full JSON schema with IDE autocompletion support, see:

📖 Complete Main Configuration Reference →

Full documentation for config.yaml including all options, timeouts, browser settings, update checks, and ad_defaults.

Configuration Structure

Basic Ad Properties

Description values can be multiline. See https://yaml-multiline.info/ for YAML syntax examples.

# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json
active: true
type: OFFER
title: "Your Ad Title"
description: |
  Your ad description here.
  Supports multiple lines.

Description Prefix and Suffix

You can add prefix and suffix text to your ad descriptions in two ways:

In your config.yaml file you can specify a description_prefix and description_suffix under the ad_defaults section:

ad_defaults:
  description_prefix: "Prefix text"
  description_suffix: "Suffix text"

Legacy Format

In your ad configuration file you can specify a description_prefix and description_suffix:

description_prefix: "Prefix text"
description_suffix: "Suffix text"

Precedence

The ad-level setting has precedence over the config.yaml default. If you specify both, the ad-level setting will be used. We recommend using the config.yaml defaults as it is more flexible and easier to manage.

Category

Built-in category name, custom category name from config.yaml, or category ID.

# Built-in category name (see default list at
# https://github.com/Second-Hand-Friends/kleinanzeigen-bot/blob/main/src/kleinanzeigen_bot/resources/categories.yaml)
category: "Elektronik > Notebooks"

# Custom category name (defined in config.yaml)
category: "Verschenken & Tauschen > Tauschen"

# Category ID
category: 161/278

Price and Price Type

price:  # Price in euros; decimals allowed but will be rounded to nearest whole euro on processing
        # (prefer whole euros for predictability)
price_type:  # one of: FIXED, NEGOTIABLE, GIVE_AWAY (default: NEGOTIABLE)

Automatic Price Reduction

When auto_price_reduction.enabled is set to true, the bot evaluates whether a price reduction is due each time the ad is republished via the publish command — the price is only lowered when all eligibility gates (repost cycle, day delay, minimum floor) are satisfied.

Important: By default, price reductions only apply when using the publish command (which deletes the old ad and creates a new one). Using the update command does NOT advance the reduction cycle. Set on_update: true (see below) to also apply reductions during update runs. Regardless of the on_update setting, the effective reduced price is always restored before submitting an update — this prevents previously applied reductions from being silently lost.

repost_count is tracked for every ad (and persisted inside the corresponding ad_*.yaml) so reductions continue across runs.

min_price is required whenever enabled is true and must be less than or equal to price; this makes an explicit floor (including 0) mandatory. If min_price equals the current price, the bot will log a warning and perform no reduction.

Note: repost_count and price reduction counters are only incremented and persisted after a successful publish. Failed publish attempts do not advance the counters.

When automatic price reduction is enabled, each publish (and optionally update) run logs one clear INFO message per ad summarizing the outcome—whether the price was reduced, kept, or the reduction was delayed (and why). The verify command also previews these outcomes for all configured ads so you can validate your pricing configuration without triggering a publish cycle. Ads without auto_price_reduction configured are silently skipped at default log level.

If you run with -v / --verbose, the bot additionally logs structured decision details (repost counts, cycle state, day delay, reference timestamps) and the full cycle-by-cycle calculation trace (base price, reduction value, rounded step result, and floor clamp).

auto_price_reduction:
  enabled:  # true or false to enable automatic price reduction on reposts (default: false)
  strategy:  # "PERCENTAGE" or "FIXED" (required when enabled is true)
  amount:    # Reduction amount; interpreted as percent for PERCENTAGE or currency units for FIXED
             # (prefer whole euros for predictability)
  min_price:  # Required when enabled is true; minimum price floor
              # (use 0 for no lower bound, prefer whole euros for predictability)
  delay_reposts:  # Number of reposts to wait before first reduction (default: 0)
  delay_days:     # Number of days to wait after publication before reductions (default: 0)
  on_update:      # Also apply price reductions during the update command (default: false)

Note: All prices are rounded to whole euros after each reduction step.

PERCENTAGE Strategy Example

price: 150
price_type: FIXED
auto_price_reduction:
  enabled: true
  strategy: PERCENTAGE
  amount: 10
  min_price: 90
  delay_reposts: 0
  delay_days: 0

This posts the ad at 150 € the first time, then 135 € (10%), 122 € (10%), 110 € (10%), 99 € (10%), and stops decreasing at 90 €.

Note: The bot applies commercial rounding (ROUND_HALF_UP) to full euros after each reduction step. For example, 121.5 rounds to 122, and 109.8 rounds to 110. This step-wise rounding affects the final price progression, especially for percentage-based reductions.

FIXED Strategy Example

price: 150
price_type: FIXED
auto_price_reduction:
  enabled: true
  strategy: FIXED
  amount: 15
  min_price: 90
  delay_reposts: 0
  delay_days: 0

This posts the ad at 150 € the first time, then 135 € (15 €), 120 € (15 €), 105 € (15 €), and stops decreasing at 90 €.

Note on delay_days Behavior

The delay_days parameter counts complete 24-hour periods (whole days) since the ad was published. For example, if delay_days: 7 and the ad was published 6 days and 23 hours ago, the reduction will not yet apply. This ensures predictable behavior and avoids partial-day ambiguity.

Combined timeline example: with republication_interval: 3, delay_reposts: 1, and delay_days: 2, the first reduction is typically applied on the third publish cycle (around day 8 in a steady schedule, because due ads are republished after more than 3 full days):

  • day 0: first publish, no reduction
  • day 4: second publish, still waiting for repost delay
  • day 8: third publish, first reduction can apply

Update-Mode Price Reductions (on_update)

By default (on_update: false), price reductions only apply during publish (full republish). Set on_update: true to also apply reductions when running the update command (in-place ad modification).

Update-mode semantics differ from publish-mode in two ways:

  • delay_days applies normally — the bot still checks that enough days have elapsed since the last publication before reducing.
  • delay_reposts is ignored — update-mode reductions always evaluate based on the current price_reduction_count regardless of repost_count, because update does not increment repost_count.

This means an ad with delay_reposts: 2 that has only been published once can still receive a price reduction via update if on_update: true and delay_days has elapsed.

Restore-First Behavior

When on_update is enabled, the reduced effective price is preserved across mixed publish/update runs:

  1. The ad file keeps the configured base price.
  2. The bot persists price_reduction_count after successful runs and recalculates the effective reduced price from price + price_reduction_count on each run.
  3. If you manually edit the price field in the ad file, the next run uses the new value as the base for recalculation.

This ensures price reductions accumulate correctly regardless of whether you use publish, update, or both.

Verify Command Preview

The verify command previews pricing outcomes for both modes:

  • Publish preview: Always shown when auto_price_reduction.enabled is true. Shows the effective price after applying reductions based on the current repost_count, price_reduction_count, and delay settings.
  • Update preview: Always shown when auto_price_reduction.enabled is true. Reports the update-mode outcome: if on_update is true, it shows the reduction result; if on_update is false (default), it reports that update-mode reductions are disabled and shows the restored effective price (no new cycle).
# Example: enable reductions for both publish and update
auto_price_reduction:
  enabled: true
  strategy: PERCENTAGE
  amount: 10
  min_price: 90
  delay_reposts: 0
  delay_days: 7
  on_update: true

With this configuration, the price is reduced on every publish and also on update runs, as long as at least 7 days have passed since the last publication.

Set auto_price_reduction.enabled: false (or omit the entire auto_price_reduction section) to keep the existing behavior—prices stay fixed and repost_count only acts as tracked metadata for future changes.

You can configure auto_price_reduction once under ad_defaults in config.yaml. The min_price and on_update can be set there or overridden per ad file as needed.

Special Attributes

Special attributes are category-specific key/value pairs. Use the download command to inspect existing ads in your category and reuse the keys you see under special_attributes.

For condition values, use the API values from downloaded ads instead of translated display labels. Common API values are new, like_new, ok, alright, and defect (for example API like_new corresponds to the German UI label Sehr Gut), but available values can vary by category.

special_attributes:
  # condition_s: like_new  # common values include new, like_new, ok, alright, defect

  # Example for rental properties
  # haus_mieten.zimmer_d: "3"  # Number of rooms

Shipping Configuration

shipping_type:  # one of: PICKUP, SHIPPING, NOT_APPLICABLE (default: SHIPPING)
shipping_costs:  # e.g. 2.95 (for individual postage, keep shipping_type SHIPPING and leave shipping_options empty)

# Specify shipping options / packages.
# It is possible to select multiple packages, but only from one size group (S, M, L)!
# See the "Shipping Options Reference" table below for all available options.
shipping_options: []

# Example (size S only):
# shipping_options:
#   - DHL_2
#   - Hermes_Päckchen

sell_directly:  # true or false, requires shipping_type SHIPPING (with shipping_options or shipping_costs) to take effect (default: false)

Shipping Options Reference

You can select multiple options, but only from one size group (S, M, or L). Each option corresponds to a specific carrier package. Prices are set by the carrier and may change over time.

Config Name Size Group Carrier Package Max Dimensions / Weight
Size S (Klein)
DHL_2 S DHL DHL Paket 2 kg 2 kg, max 60 × 30 × 15 cm
Hermes_Päckchen S Hermes Hermes Päckchen L+shortest side ≤ 37 cm, max 25 kg
Hermes_S S Hermes Hermes S-Paket L+W+H ≤ 50 cm, max 25 kg
Size M (Mittel)
DHL_5 M DHL DHL Paket 5 kg 5 kg
Hermes_M M Hermes Hermes M-Paket Standard medium package
Size L (Groß)
DHL_10 L DHL DHL Paket 10 kg 10 kg
DHL_20 L DHL DHL Paket 20 kg 20 kg
DHL_31,5 L DHL DHL Paket 31,5 kg 31.5 kg
Hermes_L L Hermes Hermes L-Paket Standard large package

Shipping types:

  • PICKUP - Buyer picks up the item
  • SHIPPING - Item is shipped (requires shipping costs or options)
  • NOT_APPLICABLE - Shipping not applicable for this item

Sell Directly: When sell_directly: true, buyers can purchase the item directly through the platform without contacting the seller first. This feature requires shipping_type: SHIPPING (with either predefined shipping_options or individual shipping_costs) and a fixed or negotiable price.

Images

List of wildcard patterns to select images. If relative paths are specified, they are relative to this ad configuration file.

images:
  # - laptop_*.{jpg,png}

Contact Information

Contact details for the ad. These override defaults from config.yaml.

contact:
  name:
  street:
  zipcode:
  phone: ""  # IMPORTANT: surround phone number with quotes to prevent removal of leading zeros

Republication Interval

How often the ad should be republished (in days). Overrides ad_defaults.republication_interval from config.yaml.

republication_interval:  # every X days the ad should be re-published (default: 7)

Auto-Managed Fields

The following fields are automatically managed by the bot. Do not manually edit these unless you know what you're doing.

id:  # The ID assigned by kleinanzeigen.de
created_on:  # ISO timestamp when the ad was first published
updated_on:  # ISO timestamp when the ad was last published
content_hash:  # Hash of the ad content, used to detect changes
repost_count:  # How often the ad has been (re)published; used for automatic price reductions
price_reduction_count:  # Auto-managed reduction cycle counter (starts at 0); used with price to recompute effective reduced price

price_reduction_count is maintained by the bot after successful runs and should not be edited manually.

Complete Example

# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/refs/heads/main/schemas/ad.schema.json
active: true
type: OFFER
title: "Example Ad Title"
description: |
  This is a multi-line description.
  You can add as much detail as you want here.
  The bot will preserve line breaks and formatting.

description_prefix: "For sale: "  # Optional ad-level override; defaults can live in config.yaml
description_suffix: " Please message if interested!"  # Optional ad-level override

category: "Elektronik > Notebooks"

price: 150
price_type: FIXED

auto_price_reduction:
  enabled: true
  strategy: PERCENTAGE
  amount: 10
  min_price: 90
  delay_reposts: 0
  delay_days: 0
  on_update: false

shipping_type: SHIPPING
shipping_options:
  - DHL_2
  - Hermes_Päckchen
sell_directly: true

images:
  - "images/laptop_*.jpg"

contact:
  name: "John Doe"
  street: "Main Street 123"
  zipcode: "12345"
  phone: "0123456789"

republication_interval: 7

Best Practices

  1. Use meaningful filenames: Name your ad files descriptively, e.g., ad_laptop_hp_15.yaml
  2. Set defaults in config.yaml: Put common values in ad_defaults to avoid repetition
  3. Test before bulk publishing: Use --ads=changed or --ads=new to test changes before republishing all ads
  4. Back up your ad files: Keep them in version control if you want to track changes
  5. Use price reductions carefully: Set appropriate min_price to avoid underpricing
  6. Check shipping options: Ensure your shipping options match the actual package size and cost

Troubleshooting

  • Schema validation errors: Run kleinanzeigen-bot verify (binary) or pdm run app verify (source) to see which fields fail validation.
  • Price reduction not applying: Confirm auto_price_reduction.enabled is true, min_price is set, and you are using publish (not update, unless on_update: true). Run kleinanzeigen-bot verify to preview outcomes, or add -v for detailed decision data including repost/day-delay state. Remember ad-level values override ad_defaults.
  • Shipping configuration issues: Use shipping_type: SHIPPING when setting shipping_costs or shipping_options, and pick options from a single size group (S/M/L).
  • Category not found: Verify the category name or ID and check any custom mappings in config.yaml.
  • File naming/prefix mismatch: Ensure ad files match your ad_files glob and prefix (default ad_).
  • Image path resolution: Relative paths are resolved from the ad file location; use absolute paths and check file permissions if images are not found.