Sign inTry it

Search YouTube videos with channel, views, length and upload date.

Searches YouTube like the search box: title, channel, view count, length in seconds, how long ago it was published, live flag, thumbnail and link. Sort by relevance, upload date, view count or rating. Shorts shelves, ads and channel/playlist cards are left out; results in the user's own browser follow their YouTube settings.

No reports yetRead-only, no login
Site
YouTube, United States
Version
v2 of 2
Runs, last 7 days
No reports yet
sha256
c0ab2ac6d76b…

Run it with your agent

after installing Wield

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

“Search YouTube videos with channel, views, length and upload date.”

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

{
  "tool": "run",
  "arguments": {
    "recipe_id": "youtube.com/search-videos",
    "params": {
      "limit": 10,
      "query": "lasagne recipe"
    }
  }
}

Requests

sent from your browser, in order
  1. 1POST/youtubei/v1/search?prettyPrintYouTube's own search API (InnerTube), the call the results page makes; read-only POST. The client version comes from the page's ytcfg. The one-item foreach maps sort to YouTube's filter code (videos only, plus the sort).json bodyrepeats, max 1

Parameters

from your agent
countrystringRegion for results (ISO country code) = "US"
limitenum 5 | 10 | 20 = 10
querystringrequiredWhat to search for, e.g. 'lasagne recipe', 'how to replace a bike chain'
sortenum "relevance" | "upload_date" | "view_count" | "rating" = "relevance"

Returns

to your agent
estimated_resultsinteger
querystring
videosobject[]channel, channel_url, duration_s, duration_text, id, live, published, thumbnail, title, url, views

Where it can send

  • www.youtube.com

Anything else is blocked by the extension.

Reads from your session

  • client_version from dom

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.videos) > `5`
  • length(output.videos[?starts_with(url, 'https://www.youtube.com/watch?v=')]) == length(output.videos)
  • length(output.videos[?views != `null`]) > `3`
  • length(output.videos[?duration_s > `0`]) > `3`
  • output.videos[0].channel != `null`

Raw recipe

Served as canonical JSON from /api/v1/recipes/youtube.com/search-videos, so its sha256 matches the ETag and the index.

Check it yourself
curl -s https://wield.run/api/v1/recipes/youtube.com/search-videos | shasum -a 256
# c0ab2ac6d76b37992f77f8cf3f1767ca92b268426a02b73188f889da9edfbbbb
youtube.com/search-videos.json, v2
{
  "auth": {
    "derive": [
      {
        "from": "dom",
        "name": "client_version",
        "regex": "\"INNERTUBE_CLIENT_VERSION\":\"([0-9.]+)\"",
        "selector": "script:not([src])"
      }
    ]
  },
  "description": "Searches YouTube like the search box: title, channel, view count, length in seconds, how long ago it was published, live flag, thumbnail and link. Sort by relevance, upload date, view count or rating. Shorts shelves, ads and channel/playlist cards are left out; results in the user's own browser follow their YouTube settings.",
  "effect": "read",
  "id": "youtube.com/search-videos",
  "keywords": [
    "youtube",
    "video",
    "videos",
    "suchen",
    "anleitung",
    "tutorial",
    "how to",
    "music",
    "review",
    "watch",
    "channel",
    "rezept video"
  ],
  "output": {
    "schema": {
      "properties": {
        "estimated_results": {
          "type": [
            "integer",
            "null"
          ]
        },
        "query": {
          "type": "string"
        },
        "videos": {
          "items": {
            "properties": {
              "channel": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "channel_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "duration_s": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "duration_text": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "live": {
                "type": "boolean"
              },
              "published": {
                "description": "As YouTube says it, e.g. '1 month ago'",
                "type": [
                  "string",
                  "null"
                ]
              },
              "thumbnail": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "views": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "value": "let $lim = params.limit || `10` in let $all = map(&(let $v = @, $d = @.lengthText.simpleText, $vc = @.viewCountText.simpleText || (@.viewCountText.runs[0].text) in {id: $v.videoId, title: join('', $v.title.runs[].text), channel: $v.ownerText.runs[0].text || `null`, channel_url: ($v.ownerText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl && join('', ['https://www.youtube.com', $v.ownerText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl])) || `null`, views: ($vc && to_number(replace(replace(replace(replace($vc, ',', ''), ' views', ''), ' view', ''), ' watching', ''))) || ($vc == 'No views' && `0`) || `null`, duration_s: ($d && (let $p = split($d, ':') in ((length($p) == `3` && ((to_number(trim_left($p[0], '0')) || `0`) * `3600` + (to_number(trim_left($p[1], '0')) || `0`) * `60` + (to_number(trim_left($p[2], '0')) || `0`))) || ((to_number(trim_left($p[0], '0')) || `0`) * `60` + (to_number(trim_left($p[1], '0')) || `0`))))) || `null`, duration_text: $d || `null`, published: $v.publishedTimeText.simpleText || `null`, live: length(($v.badges || `[]`)[?metadataBadgeRenderer.style == 'BADGE_STYLE_TYPE_LIVE_NOW']) > `0`, thumbnail: ($v.thumbnail.thumbnails[-1].url) || `null`, url: join('', ['https://www.youtube.com/watch?v=', $v.videoId])}), (steps.search[0].videos || `[]`)[?videoId]) in {query: params.query, estimated_results: steps.search[0].estimated && to_number(steps.search[0].estimated) || `null`, videos: ($lim == `5` && $all[:5]) || ($lim == `20` && $all[:20]) || $all[:10]}"
  },
  "params": {
    "additionalProperties": false,
    "properties": {
      "country": {
        "default": "US",
        "description": "Region for results (ISO country code)",
        "pattern": "^[A-Z]{2}$",
        "type": "string"
      },
      "limit": {
        "default": 10,
        "enum": [
          5,
          10,
          20
        ],
        "type": "integer"
      },
      "query": {
        "description": "What to search for, e.g. 'lasagne recipe', 'how to replace a bike chain'",
        "maxLength": 150,
        "minLength": 1,
        "type": "string"
      },
      "sort": {
        "default": "relevance",
        "enum": [
          "relevance",
          "upload_date",
          "view_count",
          "rating"
        ],
        "type": "string"
      }
    },
    "required": [
      "query"
    ],
    "type": "object"
  },
  "provenance": {
    "method": "browser-inspect",
    "notes": "The results page loads the first page server-side and calls POST /youtubei/v1/search for more; the same API answers the first page too. Body: context.client {clientName WEB, clientVersion from ytcfg INNERTUBE_CLIENT_VERSION in the page's inline script, hl, gl}, query, params (filter code: EgIQAQ== videos; CAISAhAB / CAMSAhAB / CAESAhAB = videos sorted by upload date / view count / rating). Videos are videoRenderer items inside itemSectionRenderer; shorts come as gridShelfViewModel shelves and are left out. hl is fixed to en so view counts and durations parse. v2 (2026-11-07 22:38): read_only_post on 1 step only; requests and output unchanged.",
    "recorded_at": "2026-11-07T18:50:00+01:00",
    "recorded_by": "C4"
  },
  "requires_login": false,
  "schema": "https://wield.run/schema/recipe/v1.json",
  "site": {
    "country": "US",
    "id": "youtube.com",
    "language": "en",
    "name": "YouTube",
    "origin": "https://www.youtube.com",
    "origins": [
      "https://www.youtube.com"
    ]
  },
  "steps": [
    {
      "description": "YouTube's own search API (InnerTube), the call the results page makes; read-only POST. The client version comes from the page's ytcfg. The one-item foreach maps sort to YouTube's filter code (videos only, plus the sort).",
      "expect": {
        "assert": "body.contents != `null`",
        "content_type": "json",
        "status": [
          200
        ]
      },
      "extract": {
        "json": {
          "estimated": "estimatedResults",
          "videos": "contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[].itemSectionRenderer.contents[][].videoRenderer"
        }
      },
      "foreach": {
        "as": "f",
        "in": "[{sp: (params.sort == 'upload_date' && 'CAISAhAB') || (params.sort == 'view_count' && 'CAMSAhAB') || (params.sort == 'rating' && 'CAESAhAB') || 'EgIQAQ=='}]",
        "max": 1
      },
      "id": "search",
      "read_only_post": true,
      "request": {
        "body": {
          "type": "json",
          "value": {
            "context": {
              "client": {
                "clientName": "WEB",
                "clientVersion": "{{vars.client_version}}",
                "gl": "{{params.country}}",
                "hl": "en"
              }
            },
            "params": "{{f.sp}}",
            "query": "{{params.query}}"
          }
        },
        "headers": {
          "Content-Type": "application/json",
          "X-Youtube-Client-Name": "1",
          "X-Youtube-Client-Version": "{{vars.client_version}}"
        },
        "method": "POST",
        "query": {
          "prettyPrint": "false"
        },
        "url": "/youtubei/v1/search"
      }
    }
  ],
  "task": "Search YouTube videos with channel, views, length and upload date",
  "tos": {
    "notes": "YouTube's terms restrict automated access; the official YouTube Data API is the sanctioned route for bulk use. This recipe makes one search request per call in the user's own browser, as the user would.",
    "reviewed": false
  },
  "verify": {
    "checks": [
      "length(output.videos) > `5`",
      "length(output.videos[?starts_with(url, 'https://www.youtube.com/watch?v=')]) == length(output.videos)",
      "length(output.videos[?views != `null`]) > `3`",
      "length(output.videos[?duration_s > `0`]) > `3`",
      "output.videos[0].channel != `null`"
    ],
    "example_params": {
      "limit": 10,
      "query": "lasagne recipe"
    },
    "max_ms": 4000
  },
  "version": 2
}