Sign inTry it

Search Tori for second-hand items.

Searches Tori, Finland's largest marketplace, like its search page: title, price in EUR, place, when it was published (UTC), private or business seller, shipping and Tori 'buy now' flags, map position, image and link. Filters: price range, condition, seller type, for sale / giveaway / wanted; sort by relevance, date or price. 53 listings per page.

No reports yetRead-only, no login
Site
Tori, Finland
Version
v1
Runs, last 7 days
No reports yet
sha256
01e052a583e4…

Run it with your agent

after installing Wield

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

“Search Tori for second-hand items.”

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

{
  "tool": "run",
  "arguments": {
    "recipe_id": "tori.fi/search-marketplace",
    "params": {
      "max_price": 200,
      "query": "polkupyörä",
      "sort": "newest"
    }
  }
}

Requests

sent from your browser, in order
  1. 1GET/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON?condition&dealer_segment&page&price_from&price_to&q&sort&trade_typeTori's own search API (/recommerce/forsale/search/api, the apiUrl in the search page's props; the page calls it when the search changes). The one-item foreach maps the params to its filter codes.repeats, max 1

Parameters

from your agent
conditionenumnew (unopened), like_new, good, used (visibly used), needs_repair "new" | "like_new" | "good" | "used" | "needs_repair"
max_pricenumberEUR
min_pricenumberEUR
pageinteger = 1
querystringrequiredWhat to look for, e.g. 'polkupyörä', 'sohva', 'iphone 13'
sellerenum "any" | "private" | "business" = "any"
sortenum "relevance" | "newest" | "oldest" | "price_low" | "price_high" = "relevance"
typeenumfor_sale, giveaway (free) or wanted (people looking to buy) "for_sale" | "giveaway" | "wanted" = "for_sale"

Returns

to your agent
last_pageinteger
listingsobject[]buy_now, currency, image, latitude, location, longitude, price, published_at, seller, shipping, title, url
pageinteger
querystring
totalinteger

Where it can send

  • www.tori.fi

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.listings) > `40`
  • output.total > `1000`
  • length(output.listings[?price > `200`]) == `0`
  • length(output.listings[?starts_with(url, 'https://www.tori.fi/recommerce/forsale/item/')]) == length(output.listings)
  • output.listings[*].published_at == reverse(sort(output.listings[*].published_at))
  • length(output.listings[?currency == 'EUR']) == length(output.listings)

Raw recipe

Served as canonical JSON from /api/v1/recipes/tori.fi/search-marketplace, so its sha256 matches the ETag and the index.

Check it yourself
curl -s https://wield.run/api/v1/recipes/tori.fi/search-marketplace | shasum -a 256
# 01e052a583e44898cf5d8d402d06f0a753f847f496b15b03a04ee22c367179a3
tori.fi/search-marketplace.json, v1
{
  "description": "Searches Tori, Finland's largest marketplace, like its search page: title, price in EUR, place, when it was published (UTC), private or business seller, shipping and Tori 'buy now' flags, map position, image and link. Filters: price range, condition, seller type, for sale / giveaway / wanted; sort by relevance, date or price. 53 listings per page.",
  "effect": "read",
  "id": "tori.fi/search-marketplace",
  "keywords": [
    "tori",
    "käytetty",
    "myydään",
    "annetaan",
    "ostetaan",
    "polkupyörä",
    "huonekalut",
    "osta",
    "myy",
    "second hand",
    "used",
    "marketplace",
    "finland",
    "classifieds"
  ],
  "output": {
    "schema": {
      "properties": {
        "last_page": {
          "type": [
            "integer",
            "null"
          ]
        },
        "listings": {
          "items": {
            "properties": {
              "buy_now": {
                "description": "Can be bought directly on Tori",
                "type": "boolean"
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "image": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "latitude": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "location": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "longitude": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "price": {
                "description": "EUR",
                "type": [
                  "number",
                  "null"
                ]
              },
              "published_at": {
                "description": "ISO 8601, UTC",
                "type": [
                  "string",
                  "null"
                ]
              },
              "seller": {
                "description": "private or business",
                "type": "string"
              },
              "shipping": {
                "description": "Can be shipped",
                "type": "boolean"
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "type": "array"
        },
        "page": {
          "type": "integer"
        },
        "query": {
          "type": "string"
        },
        "total": {
          "type": [
            "integer",
            "null"
          ]
        }
      },
      "required": [
        "query",
        "listings"
      ],
      "type": "object"
    },
    "value": "let $s = steps.search[0] in {query: params.query, total: $s.total || `0`, page: params.page || `1`, last_page: $s.last_page || `null`, listings: map(&(let $x = @ in {title: $x.title, price: $x.price || `null`, currency: $x.currency || `null`, location: $x.location || `null`, published_at: ($x.ts && let $t = $x.ts // `1000` 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`, seller: (contains($x.flags || `[]`, 'retailer') && 'business') || 'private', shipping: contains($x.flags || `[]`, 'shipping_exists'), buy_now: contains($x.flags || `[]`, 'buy_now'), latitude: $x.lat || `null`, longitude: $x.lon || `null`, image: $x.image || `null`, url: $x.url}), $s.docs || `[]`)}"
  },
  "params": {
    "additionalProperties": false,
    "properties": {
      "condition": {
        "description": "new (unopened), like_new, good, used (visibly used), needs_repair",
        "enum": [
          "new",
          "like_new",
          "good",
          "used",
          "needs_repair"
        ],
        "type": "string"
      },
      "max_price": {
        "description": "EUR",
        "minimum": 0,
        "type": "number"
      },
      "min_price": {
        "description": "EUR",
        "minimum": 0,
        "type": "number"
      },
      "page": {
        "default": 1,
        "maximum": 50,
        "minimum": 1,
        "type": "integer"
      },
      "query": {
        "description": "What to look for, e.g. 'polkupyörä', 'sohva', 'iphone 13'",
        "maxLength": 100,
        "minLength": 2,
        "type": "string"
      },
      "seller": {
        "default": "any",
        "enum": [
          "any",
          "private",
          "business"
        ],
        "type": "string"
      },
      "sort": {
        "default": "relevance",
        "enum": [
          "relevance",
          "newest",
          "oldest",
          "price_low",
          "price_high"
        ],
        "type": "string"
      },
      "type": {
        "default": "for_sale",
        "description": "for_sale, giveaway (free) or wanted (people looking to buy)",
        "enum": [
          "for_sale",
          "giveaway",
          "wanted"
        ],
        "type": "string"
      }
    },
    "required": [
      "query"
    ],
    "type": "object"
  },
  "provenance": {
    "method": "browser-inspect",
    "notes": "The search page https://www.tori.fi/recommerce/forsale/search?q=… is server-rendered from a base64 react-query state; its props name apiUrl https://www.tori.fi/recommerce/forsale/search/api, and the page calls /search/SEARCH_ID_BAP_COMMON with the same URL parameters as the page (q, price_from, price_to, condition 1–5, dealer_segment 1 private / 3 business, trade_type 1 sale / 2 giveaway / 3 wanted, sort RELEVANCE / PUBLISHED_DESC / PUBLISHED_ASC / PRICE_ASC / PRICE_DESC, page). Answer: docs [{type 'bap', heading, location, price.amount/currency_code, timestamp (ms), flags private/retailer/shipping_exists/buy_now, coordinates, image.url, canonical_url}], metadata.result_size.match_count, metadata.paging.last. Display ads are a separate advertising slot, not docs; the recipe keeps type 'bap' only. published_at is the epoch converted in JMESPath (civil-from-days). A clone of blocket.se/search-marketplace: tori.fi now runs on the same Vend recommerce platform as FINN and Blocket (search lives at /recommerce/forsale/search; the API answered 200 with the same docs shape). Images are links to img.tori.net (never called).",
    "recorded_at": "2026-11-07T22:16:00+01:00",
    "recorded_by": "C4"
  },
  "requires_login": false,
  "schema": "https://wield.run/schema/recipe/v1.json",
  "site": {
    "country": "FI",
    "id": "tori.fi",
    "language": "fi",
    "name": "Tori",
    "origin": "https://www.tori.fi",
    "origins": [
      "https://www.tori.fi"
    ]
  },
  "steps": [
    {
      "description": "Tori's own search API (/recommerce/forsale/search/api, the apiUrl in the search page's props; the page calls it when the search changes). The one-item foreach maps the params to its filter codes.",
      "expect": {
        "assert": "body.docs != `null`",
        "content_type": "json",
        "status": [
          200
        ]
      },
      "extract": {
        "json": {
          "docs": "docs[?type == 'bap'].{title: heading, price: price.amount, currency: price.currency_code, location: location, ts: timestamp, flags: flags, lat: coordinates.lat, lon: coordinates.lon, image: image.url, url: canonical_url}",
          "last_page": "metadata.paging.last",
          "total": "metadata.result_size.match_count"
        }
      },
      "foreach": {
        "as": "f",
        "in": "let $sort = params.sort || 'relevance', $cond = params.condition || `null`, $seller = params.seller || 'any', $type = params.type || 'for_sale' in [{sort: ($sort != 'relevance' && (`[[\"relevance\",\"RELEVANCE\"],[\"newest\",\"PUBLISHED_DESC\"],[\"oldest\",\"PUBLISHED_ASC\"],[\"price_low\",\"PRICE_ASC\"],[\"price_high\",\"PRICE_DESC\"]]`[?@[0] == $sort] | [0][1])) || `null`, condition: (`[[\"new\",\"1\"],[\"like_new\",\"2\"],[\"good\",\"3\"],[\"used\",\"4\"],[\"needs_repair\",\"5\"]]`[?@[0] == $cond] | [0][1]) || `null`, dealer: (`[[\"private\",\"1\"],[\"business\",\"3\"]]`[?@[0] == $seller] | [0][1]) || `null`, trade: (`[[\"for_sale\",\"1\"],[\"giveaway\",\"2\"],[\"wanted\",\"3\"]]`[?@[0] == $type] | [0][1]) || '1'}]",
        "max": 1
      },
      "id": "search",
      "request": {
        "headers": {
          "Accept": "application/json"
        },
        "method": "GET",
        "query": {
          "condition": "{{f.condition}}",
          "dealer_segment": "{{f.dealer}}",
          "page": "{{params.page}}",
          "price_from": "{{params.min_price}}",
          "price_to": "{{params.max_price}}",
          "q": "{{params.query}}",
          "sort": "{{f.sort}}",
          "trade_type": "{{f.trade}}"
        },
        "url": "/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON"
      }
    }
  ],
  "task": "Search Tori for second-hand items",
  "tos": {
    "notes": "One request per call, the same search the page makes. robots.txt allows the search and its API but disallows URLs with sort= for bots; sort= is sent only when the user asks for another order than relevance. No messages, offers or favourites.",
    "reviewed": false
  },
  "verify": {
    "checks": [
      "length(output.listings) > `40`",
      "output.total > `1000`",
      "length(output.listings[?price > `200`]) == `0`",
      "length(output.listings[?starts_with(url, 'https://www.tori.fi/recommerce/forsale/item/')]) == length(output.listings)",
      "output.listings[*].published_at == reverse(sort(output.listings[*].published_at))",
      "length(output.listings[?currency == 'EUR']) == length(output.listings)"
    ],
    "example_params": {
      "max_price": 200,
      "query": "polkupyörä",
      "sort": "newest"
    },
    "max_ms": 3000
  },
  "version": 1
}