Sign inTry it

Search deals on mydealz.

Searches mydealz, Germany's largest deal community, like its search: deal title, price, the next best price elsewhere, shop, temperature (community votes; hotter is better), whether shipping is free, voucher code if any, when it was posted (UTC), whether it has expired, comment count, and the link. Up to 30 deals in mydealz's order; expired deals are kept and marked (only_active drops them).

No reports yetRead-only, no login
Site
mydealz, Germany
Version
v1
Runs, last 7 days
No reports yet
sha256
66cb4cf1a297…

Run it with your agent

after installing Wield

Ask in plain words. Your agent finds this recipe and calls it:

“Search deals on mydealz.”

Read-only: it changes nothing on the site. No login needed.

{
  "tool": "run",
  "arguments": {
    "recipe_id": "mydealz.de/search-deals",
    "params": {
      "query": "airpods"
    }
  }
}

Requests

sent from your browser, in order
  1. 1GET/search?qmydealz's search page (/search?q=…). Each article.thread card holds its deal as JSON in a data-vue3 attribute (component ThreadMainListItemNormalizer, props.thread), which the recipe parses per card (html_items parse: json).

Parameters

from your agent
only_activebooleanDrop expired deals = false
querystringrequiredProduct or shop, e.g. 'airpods', 'playstation 5', 'lego'

Returns

to your agent
dealsobject[]comments, expired, free_shipping, merchant, next_best_price_eur, posted_at, price_eur, temperature, title, type, url, voucher_code
querystring

Where it can send

  • www.mydealz.de

Anything else is blocked by the extension.

Reads from your session

Nothing. Your browser attaches the site's cookies by itself.

Used only in requests to the addresses on the left. Never sent to wield.run or your agent.

Checks

a run passes when
  • length(output.deals) > `10`
  • length(output.deals[?starts_with(url, 'https://www.mydealz.de/')]) == length(output.deals)
  • length(output.deals[?posted_at != `null` && temperature != `null`]) == length(output.deals)
  • length(output.deals[?price_eur != `null`]) > `5`
  • length(output.deals[?merchant != `null`]) > `5`

Raw recipe

Served as canonical JSON from /api/v1/recipes/mydealz.de/search-deals, so its sha256 matches the ETag and the index.

Check it yourself
curl -s https://wield.run/api/v1/recipes/mydealz.de/search-deals | shasum -a 256
# 66cb4cf1a2977ef324ab44d2fccc8a7ddc564c05845ced575a9d2c109a0c96a7
mydealz.de/search-deals.json, v1
{
  "description": "Searches mydealz, Germany's largest deal community, like its search: deal title, price, the next best price elsewhere, shop, temperature (community votes; hotter is better), whether shipping is free, voucher code if any, when it was posted (UTC), whether it has expired, comment count, and the link. Up to 30 deals in mydealz's order; expired deals are kept and marked (only_active drops them).",
  "effect": "read",
  "id": "mydealz.de/search-deals",
  "keywords": [
    "mydealz",
    "deals",
    "angebote",
    "schnäppchen",
    "gutschein",
    "rabatt",
    "preisfehler",
    "günstig",
    "kaufen",
    "deal",
    "discount",
    "bargain",
    "coupon",
    "germany"
  ],
  "output": {
    "schema": {
      "properties": {
        "deals": {
          "items": {
            "properties": {
              "comments": {
                "type": "integer"
              },
              "expired": {
                "type": "boolean"
              },
              "free_shipping": {
                "type": "boolean"
              },
              "merchant": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "next_best_price_eur": {
                "description": "Cheapest price elsewhere, as the poster found it",
                "type": [
                  "number",
                  "null"
                ]
              },
              "posted_at": {
                "description": "ISO 8601, UTC",
                "type": [
                  "string",
                  "null"
                ]
              },
              "price_eur": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "temperature": {
                "description": "Community votes in degrees; above 100 is hot",
                "type": [
                  "number",
                  "null"
                ]
              },
              "title": {
                "type": "string"
              },
              "type": {
                "description": "Deal, Voucher, …",
                "type": [
                  "string",
                  "null"
                ]
              },
              "url": {
                "type": "string"
              },
              "voucher_code": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": "array"
        },
        "query": {
          "type": "string"
        }
      },
      "required": [
        "query",
        "deals"
      ],
      "type": "object"
    },
    "value": "let $active = params.only_active == `true` in {query: params.query, deals: map(&(let $d = @ in {title: $d.title, type: $d.type || `null`, price_eur: $d.price || `null`, next_best_price_eur: ($d.next_best > `0` && $d.next_best) || `null`, merchant: $d.merchant || `null`, temperature: $d.temperature || `null`, free_shipping: $d.free_shipping == `1` || $d.free_shipping == `true`, voucher_code: $d.voucher || `null`, posted_at: ($d.published && let $t = $d.published in let $z = $t // `86400` + `719468` in let $era = $z // `146097` in let $doe = $z - $era * `146097` in let $yoe = ($doe - $doe // `1460` + $doe // `36524` - $doe // `146096`) // `365` in let $doy = $doe - (`365` * $yoe + $yoe // `4` - $yoe // `100`) in let $mp = (`5` * $doy + `2`) // `153` in let $mon = ($mp < `10` && $mp + `3`) || $mp - `9`, $day = $doy - (`153` * $mp + `2`) // `5` + `1`, $sec = $t % `86400` in join('', [to_string($yoe + $era * `400` + (($mon <= `2` && `1`) || `0`)), '-', pad_left(to_string($mon), `2`, '0'), '-', pad_left(to_string($day), `2`, '0'), 'T', pad_left(to_string($sec // `3600`), `2`, '0'), ':', pad_left(to_string(($sec % `3600`) // `60`), `2`, '0'), ':', pad_left(to_string($sec % `60`), `2`, '0'), 'Z'])) || `null`, expired: $d.expired == `true`, comments: $d.comments || `0`, url: $d.href}), steps.search.deals[?title != `null` && href != `null` && (!$active || expired != `true`)] || `[]`)}"
  },
  "params": {
    "additionalProperties": false,
    "properties": {
      "only_active": {
        "default": false,
        "description": "Drop expired deals",
        "type": "boolean"
      },
      "query": {
        "description": "Product or shop, e.g. 'airpods', 'playstation 5', 'lego'",
        "maxLength": 100,
        "minLength": 2,
        "type": "string"
      }
    },
    "required": [
      "query"
    ],
    "type": "object"
  },
  "provenance": {
    "method": "browser-inspect",
    "notes": "mydealz's search page /search?q=… renders 30 article.thread cards whose visible markup holds only title and description; the deal itself is the JSON in the card's div.js-vue3 data-vue3 attribute (name ThreadMainListItemNormalizer, props.thread: threadId, title, type, price, nextBestPrice, merchant.merchantName, temperature, publishedAt (epoch seconds), isExpired, shipping.isFree, voucherCode, commentCount). The recipe parses it per card (html_items parse: json + path), so it needs an extension whose interpreter lists html_json (0.1.12 or later). The link is the card's a.thread-link. Banner ads (BannerAdInListingEmbed) sit outside the cards and are never read. A search without matches shows 'Die heißesten Deals von heute' instead, as cards with the class precache--exclude, which the selector skips (zero results). posted_at is the epoch converted in JMESPath (civil-from-days).",
    "recorded_at": "2026-11-07T22:45:00+01:00",
    "recorded_by": "C4"
  },
  "requires_login": false,
  "schema": "https://wield.run/schema/recipe/v1.json",
  "site": {
    "country": "DE",
    "id": "mydealz.de",
    "language": "de",
    "name": "mydealz",
    "origin": "https://www.mydealz.de",
    "origins": [
      "https://www.mydealz.de"
    ]
  },
  "steps": [
    {
      "description": "mydealz's search page (/search?q=…). Each article.thread card holds its deal as JSON in a data-vue3 attribute (component ThreadMainListItemNormalizer, props.thread), which the recipe parses per card (html_items parse: json).",
      "expect": {
        "content_type": "html",
        "status": [
          200
        ]
      },
      "extract": {
        "html_items": {
          "deals": {
            "fields": {
              "comments": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.commentCount",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "expired": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.isExpired",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "free_shipping": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.shipping.isFree",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "href": {
                "attr": "href",
                "selector": "a.thread-link"
              },
              "merchant": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.merchant.merchantName",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "next_best": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.nextBestPrice",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "price": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.price",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "published": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.publishedAt",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "temperature": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.temperature",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "title": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.title",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "type": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.type",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              },
              "voucher": {
                "attr": "data-vue3",
                "parse": "json",
                "path": "props.thread.voucherCode",
                "selector": "[data-vue3*=\"ThreadMainListItemNormalizer\"]"
              }
            },
            "max": 30,
            "selector": "article.thread:not(.precache--exclude)"
          }
        }
      },
      "id": "search",
      "request": {
        "headers": {
          "Accept": "text/html"
        },
        "method": "GET",
        "query": {
          "q": "{{params.query}}"
        },
        "url": "/search"
      }
    }
  ],
  "task": "Search deals on mydealz",
  "tos": {
    "notes": "robots.txt disallows /search for bots (and filter parameters such as hide_expired and priceFrom): this recipe calls the search page itself, once per user request, from the user's own browser, without filter parameters. No robots rules for Claude agents and no AI content signals. One request per call; no votes, comments or alerts.",
    "reviewed": false
  },
  "verify": {
    "checks": [
      "length(output.deals) > `10`",
      "length(output.deals[?starts_with(url, 'https://www.mydealz.de/')]) == length(output.deals)",
      "length(output.deals[?posted_at != `null` && temperature != `null`]) == length(output.deals)",
      "length(output.deals[?price_eur != `null`]) > `5`",
      "length(output.deals[?merchant != `null`]) > `5`"
    ],
    "example_params": {
      "query": "airpods"
    },
    "max_ms": 4000
  },
  "version": 1
}