getanyapi 0.35.3__tar.gz → 0.37.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {getanyapi-0.35.3 → getanyapi-0.37.0}/PKG-INFO +1 -1
  2. {getanyapi-0.35.3 → getanyapi-0.37.0}/pyproject.toml +1 -1
  3. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/__init__.py +1 -1
  4. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_account.py +16 -3
  5. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_async_client.py +1 -1
  6. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_client.py +1 -1
  7. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/company_enrichment.py +1 -1
  8. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/google.py +2 -2
  9. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/instagram.py +11 -7
  10. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/maps.py +6 -6
  11. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/perplexity.py +2 -2
  12. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/reddit.py +4 -4
  13. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/threads.py +5 -1
  14. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/tiktok.py +4 -2
  15. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/tripadvisor.py +4 -4
  16. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/trustpilot.py +5 -5
  17. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/twitter.py +0 -2
  18. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/yelp.py +3 -3
  19. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/youtube.py +170 -26
  20. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_account.py +35 -0
  21. {getanyapi-0.35.3 → getanyapi-0.37.0}/.gitignore +0 -0
  22. {getanyapi-0.35.3 → getanyapi-0.37.0}/README.md +0 -0
  23. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_errors.py +0 -0
  24. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_idempotency.py +0 -0
  25. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_pagination.py +0 -0
  26. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/_transport.py +0 -0
  27. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/discovery_types.py +0 -0
  28. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/__init__.py +0 -0
  29. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/ahrefs.py +0 -0
  30. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/airbnb.py +0 -0
  31. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/alibaba.py +0 -0
  32. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/amazon.py +0 -0
  33. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/apollo.py +0 -0
  34. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/appstore.py +0 -0
  35. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/bluesky.py +0 -0
  36. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/booking.py +0 -0
  37. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/capterra.py +0 -0
  38. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/chatgpt.py +0 -0
  39. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/coinmarketcap.py +0 -0
  40. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/company.py +0 -0
  41. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/company_search.py +0 -0
  42. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/congress.py +0 -0
  43. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/dexscreener.py +0 -0
  44. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/douyin.py +0 -0
  45. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/ebay.py +0 -0
  46. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/email.py +0 -0
  47. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/email_finding.py +0 -0
  48. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/email_verification.py +0 -0
  49. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/facebook.py +0 -0
  50. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/fiverr.py +0 -0
  51. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/g2.py +0 -0
  52. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/gemini.py +0 -0
  53. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/github.py +0 -0
  54. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/glassdoor.py +0 -0
  55. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/google_ads.py +0 -0
  56. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/google_finance.py +0 -0
  57. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/google_shopping.py +0 -0
  58. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/hackernews.py +0 -0
  59. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/indeed.py +0 -0
  60. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/job_search.py +0 -0
  61. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/linkedin.py +0 -0
  62. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/mobile_phone.py +0 -0
  63. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/naver.py +0 -0
  64. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/pandaexpress.py +0 -0
  65. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/people_search.py +0 -0
  66. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/person.py +0 -0
  67. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/person_enrichment.py +0 -0
  68. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/pinterest.py +0 -0
  69. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/playstore.py +0 -0
  70. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/polymarket.py +0 -0
  71. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/realtor.py +0 -0
  72. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/redfin.py +0 -0
  73. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/rednote.py +0 -0
  74. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/sec.py +0 -0
  75. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/semrush.py +0 -0
  76. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/seo.py +0 -0
  77. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/snapchat.py +0 -0
  78. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/social.py +0 -0
  79. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/spotify.py +0 -0
  80. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/substack.py +0 -0
  81. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/technographics.py +0 -0
  82. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/tiktok_shop.py +0 -0
  83. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/truthsocial.py +0 -0
  84. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/upwork.py +0 -0
  85. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/walmart.py +0 -0
  86. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/web.py +0 -0
  87. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/weibo.py +0 -0
  88. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/yahoo_finance.py +0 -0
  89. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/zhihu.py +0 -0
  90. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/platforms/zillow.py +0 -0
  91. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/py.typed +0 -0
  92. {getanyapi-0.35.3 → getanyapi-0.37.0}/src/getanyapi/types.py +0 -0
  93. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/conftest.py +0 -0
  94. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_client.py +0 -0
  95. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_durable_requests.py +0 -0
  96. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_envelope.py +0 -0
  97. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_errors.py +0 -0
  98. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_fixture_roundtrip.py +0 -0
  99. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_fixture_sweep.py +0 -0
  100. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_pagination.py +0 -0
  101. {getanyapi-0.35.3 → getanyapi-0.37.0}/tests/test_transport.py +0 -0
  102. {getanyapi-0.35.3 → getanyapi-0.37.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: getanyapi
3
- Version: 0.35.3
3
+ Version: 0.37.0
4
4
  Summary: Official typed Python SDK for AnyAPI: any API, one wallet, USD, no subscriptions.
5
5
  Project-URL: Homepage, https://getanyapi.com
6
6
  Project-URL: Documentation, https://getanyapi.com/docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "getanyapi"
7
- version = "0.35.3"
7
+ version = "0.37.0"
8
8
  description = "Official typed Python SDK for AnyAPI: any API, one wallet, USD, no subscriptions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -54,7 +54,7 @@ from .types import (
54
54
  unwrap,
55
55
  )
56
56
 
57
- __version__ = "0.35.3"
57
+ __version__ = "0.37.0"
58
58
 
59
59
  __all__ = [
60
60
  # clients + top-level functions
@@ -49,13 +49,26 @@ def catalog_request(category: str | None) -> tuple[str, dict[str, str]]:
49
49
 
50
50
 
51
51
  def search_request(
52
- query: str,
52
+ query: str | None,
53
53
  category: str | None,
54
54
  platform: str | None,
55
55
  limit: int | None,
56
56
  ) -> tuple[str, dict[str, str]]:
57
- """Path and query params for dedicated ranked discovery search."""
58
- params = {"q": query}
57
+ """Path and query params for dedicated ranked discovery search.
58
+
59
+ The gateway needs at least one of query, category or platform, in any
60
+ combination, so a scope with no query at all is a valid search. An absent or
61
+ empty query omits ``q`` entirely, because an empty ``q`` is a different
62
+ request.
63
+ """
64
+ if not query and category is None and platform is None:
65
+ raise AnyAPIError(
66
+ "search needs at least one of query, category, or platform",
67
+ status=0,
68
+ )
69
+ params: dict[str, str] = {}
70
+ if query:
71
+ params["q"] = query
59
72
  if category is not None:
60
73
  params["category"] = category
61
74
  if platform is not None:
@@ -370,7 +370,7 @@ class AsyncAnyAPI:
370
370
  async def search(
371
371
  self,
372
372
  *,
373
- query: str,
373
+ query: str | None = None,
374
374
  category: str | None = None,
375
375
  platform: str | None = None,
376
376
  limit: int | None = None,
@@ -398,7 +398,7 @@ class AnyAPI:
398
398
  def search(
399
399
  self,
400
400
  *,
401
- query: str,
401
+ query: str | None = None,
402
402
  category: str | None = None,
403
403
  platform: str | None = None,
404
404
  limit: int | None = None,
@@ -275,7 +275,7 @@ class CompanyEnrichmentCrustdataV3Data(BaseModel):
275
275
  naics: CompanyEnrichmentCrustdataV3Naic | None = Field(
276
276
  default=None, description="Primary NAICS industry classification."
277
277
  )
278
- name: str = Field(description="Company name.")
278
+ name: str | None = Field(default=None, description="Company name.")
279
279
  region_metrics: CompanyEnrichmentCrustdataV3RegionMetric | None = Field(
280
280
  default=None,
281
281
  alias="regionMetrics",
@@ -705,7 +705,7 @@ class GoogleNamespace:
705
705
  as cursor to walk further, or use google.search_100 for up to 100 ranked
706
706
  results in one call, which is cheaper past roughly 20 results.
707
707
 
708
- Price: $0.00099 per request.
708
+ Price: $0.0009 per request.
709
709
 
710
710
  Example:
711
711
  res = client.google.search(gl="us", hl="en", limit=10, query="best coffee maker")
@@ -970,7 +970,7 @@ class AsyncGoogleNamespace:
970
970
  as cursor to walk further, or use google.search_100 for up to 100 ranked
971
971
  results in one call, which is cheaper past roughly 20 results.
972
972
 
973
- Price: $0.00099 per request.
973
+ Price: $0.0009 per request.
974
974
 
975
975
  Example:
976
976
  res = client.google.search(gl="us", hl="en", limit=10, query="best coffee maker")
@@ -156,7 +156,7 @@ class InstagramPostInput(TypedDict, total=False):
156
156
  requirePlayCount: NotRequired[bool]
157
157
  """Set true to be served only by a source that reports a reel's play count. The default cheapest source does not carry play counts, so `plays` is absent from its responses; opting in guarantees the field when Instagram exposes it, at a higher price per request."""
158
158
  url: Required[str]
159
- """Full Instagram post or reel URL."""
159
+ """Full Instagram post or reel URL, carrying the media shortcode: /p/, /reel/, /reels/, or /tv/. A profile URL such as https://www.instagram.com/username names no post, so it is rejected instead of charged for an empty result."""
160
160
 
161
161
 
162
162
  class InstagramPostCommentsInput(TypedDict, total=False):
@@ -1876,7 +1876,7 @@ class InstagramNamespace:
1876
1876
  Fetch a single Instagram post or reel by URL (media URLs, like count, owner,
1877
1877
  type) as normalized JSON.
1878
1878
 
1879
- Price: $0.0009 per request.
1879
+ Price: $0.0015 per request.
1880
1880
 
1881
1881
  Example:
1882
1882
  res = client.instagram.post(url="https://www.instagram.com/reel/DWzrfE2kaY8/")
@@ -1897,7 +1897,7 @@ class InstagramNamespace:
1897
1897
  List the comments on an Instagram post or reel by URL with cursor pagination
1898
1898
  (text, author, likes).
1899
1899
 
1900
- Price: $0.0015 per request.
1900
+ Price: $0.00144 per request.
1901
1901
 
1902
1902
  Example:
1903
1903
  res = client.instagram.post_comments(url="https://www.instagram.com/reel/DWzrfE2kaY8/")
@@ -2050,7 +2050,9 @@ class InstagramNamespace:
2050
2050
  than Instagram's own hashtag feed. That is what lets it filter by date and
2051
2051
  media type and return reels whose like counts have settled, and it is also
2052
2052
  why results skew older (median around three months) and stop at roughly 110
2053
- per hashtag. For Instagram's own live ranking of a tag use
2053
+ per hashtag. If that web search index is unavailable, a first-page request
2054
+ that sets no date and no media type is served from Instagram's own live top
2055
+ feed instead. For Instagram's own live ranking of a tag use
2054
2056
  instagram.hashtag_top_posts, and for the chronological feed use
2055
2057
  instagram.hashtag_recent_posts.
2056
2058
 
@@ -2741,7 +2743,7 @@ class AsyncInstagramNamespace:
2741
2743
  Fetch a single Instagram post or reel by URL (media URLs, like count, owner,
2742
2744
  type) as normalized JSON.
2743
2745
 
2744
- Price: $0.0009 per request.
2746
+ Price: $0.0015 per request.
2745
2747
 
2746
2748
  Example:
2747
2749
  res = client.instagram.post(url="https://www.instagram.com/reel/DWzrfE2kaY8/")
@@ -2762,7 +2764,7 @@ class AsyncInstagramNamespace:
2762
2764
  List the comments on an Instagram post or reel by URL with cursor pagination
2763
2765
  (text, author, likes).
2764
2766
 
2765
- Price: $0.0015 per request.
2767
+ Price: $0.00144 per request.
2766
2768
 
2767
2769
  Example:
2768
2770
  res = client.instagram.post_comments(url="https://www.instagram.com/reel/DWzrfE2kaY8/")
@@ -2915,7 +2917,9 @@ class AsyncInstagramNamespace:
2915
2917
  than Instagram's own hashtag feed. That is what lets it filter by date and
2916
2918
  media type and return reels whose like counts have settled, and it is also
2917
2919
  why results skew older (median around three months) and stop at roughly 110
2918
- per hashtag. For Instagram's own live ranking of a tag use
2920
+ per hashtag. If that web search index is unavailable, a first-page request
2921
+ that sets no date and no media type is served from Instagram's own live top
2922
+ feed instead. For Instagram's own live ranking of a tag use
2919
2923
  instagram.hashtag_top_posts, and for the chronological feed use
2920
2924
  instagram.hashtag_recent_posts.
2921
2925
 
@@ -65,7 +65,7 @@ class MapsReviewsInput(TypedDict, total=False):
65
65
  language: NotRequired[str]
66
66
  """Two-letter language code for the review details (e.g. en). Default: en."""
67
67
  limit: NotRequired[int]
68
- """Maximum number of results to return (1-100, default 100). You are billed per result returned, so a lower limit costs less. Range: 1 to 100."""
68
+ """Maximum number of results to return (1-100, default 100). Range: 1 to 100."""
69
69
  placeId: Required[str]
70
70
  """The Google Maps place ID to fetch reviews for (e.g. ChIJj61dQgK6j4AR4GeTYWZsKWw)."""
71
71
  postedLimit: NotRequired[Literal["24h", "week", "month", "year"]]
@@ -308,7 +308,7 @@ class MapsReviewsItem(BaseModel):
308
308
  place_id: str | None = Field(
309
309
  default=None,
310
310
  alias="placeId",
311
- description="Google Maps place id the review belongs to. Populated whenever the provider has data for the entity. Present whenever the upstream returns this record.",
311
+ description="Google Maps place id the review belongs to. Echoes the requested placeId; a lane that does not repeat it per review omits it.",
312
312
  )
313
313
  published_ago: str | None = Field(
314
314
  default=None,
@@ -468,10 +468,10 @@ class MapsNamespace:
468
468
  Fetch up to 100 Google Maps reviews for a place by place ID, sorted the way
469
469
  you need, in one normalized response.
470
470
 
471
- Price: $0.00006 per request plus $0.00044 per result (maximum $0.0441).
471
+ Price: $0.0035 per request.
472
472
 
473
473
  Example:
474
- res = client.maps.reviews(limit=3, placeId="ChIJN1t_tDeuEmsRUsoyG83frY4", postedLimit="year")
474
+ res = client.maps.reviews(limit=3, placeId="ChIJN1t_tDeuEmsRUsoyG83frY4")
475
475
  """
476
476
  raw = self._client._run_raw( # pyright: ignore[reportPrivateUsage]
477
477
  "maps.reviews", dict(input), options
@@ -556,10 +556,10 @@ class AsyncMapsNamespace:
556
556
  Fetch up to 100 Google Maps reviews for a place by place ID, sorted the way
557
557
  you need, in one normalized response.
558
558
 
559
- Price: $0.00006 per request plus $0.00044 per result (maximum $0.0441).
559
+ Price: $0.0035 per request.
560
560
 
561
561
  Example:
562
- res = client.maps.reviews(limit=3, placeId="ChIJN1t_tDeuEmsRUsoyG83frY4", postedLimit="year")
562
+ res = client.maps.reviews(limit=3, placeId="ChIJN1t_tDeuEmsRUsoyG83frY4")
563
563
  """
564
564
  raw = await self._client._arun_raw( # pyright: ignore[reportPrivateUsage]
565
565
  "maps.reviews", dict(input), options
@@ -66,7 +66,7 @@ class PerplexityNamespace:
66
66
  Ask Perplexity a web-grounded question and receive an answer with source
67
67
  citations.
68
68
 
69
- Price: $0.00006 per request plus $0.011 per result (maximum $0.0111).
69
+ Price: $0.0036 per request.
70
70
 
71
71
  Example:
72
72
  res = client.perplexity.search(prompt="What is AnyAPI at getanyapi.com, and what does it offer?")
@@ -94,7 +94,7 @@ class AsyncPerplexityNamespace:
94
94
  Ask Perplexity a web-grounded question and receive an answer with source
95
95
  citations.
96
96
 
97
- Price: $0.00006 per request plus $0.011 per result (maximum $0.0111).
97
+ Price: $0.0036 per request.
98
98
 
99
99
  Example:
100
100
  res = client.perplexity.search(prompt="What is AnyAPI at getanyapi.com, and what does it offer?")
@@ -710,7 +710,7 @@ class RedditNamespace:
710
710
  Fetch a single Reddit post by URL, including its full body text, score,
711
711
  comment count, upvote ratio, and subreddit, as normalized JSON.
712
712
 
713
- Price: $0.0009 per request.
713
+ Price: $0.0012 per request.
714
714
 
715
715
  Example:
716
716
  res = client.reddit.post(url="https://www.reddit.com/r/IAmA/comments/z1c9z/i_am_barack_obama_president_of_the_united_states/")
@@ -815,7 +815,7 @@ class RedditNamespace:
815
815
 
816
816
  Search Reddit posts across all subreddits by query.
817
817
 
818
- Price: $0.0009 per request.
818
+ Price: $0.0012 per request.
819
819
 
820
820
  Example:
821
821
  res = client.reddit.search(query="mechanical keyboard")
@@ -1083,7 +1083,7 @@ class AsyncRedditNamespace:
1083
1083
  Fetch a single Reddit post by URL, including its full body text, score,
1084
1084
  comment count, upvote ratio, and subreddit, as normalized JSON.
1085
1085
 
1086
- Price: $0.0009 per request.
1086
+ Price: $0.0012 per request.
1087
1087
 
1088
1088
  Example:
1089
1089
  res = client.reddit.post(url="https://www.reddit.com/r/IAmA/comments/z1c9z/i_am_barack_obama_president_of_the_united_states/")
@@ -1188,7 +1188,7 @@ class AsyncRedditNamespace:
1188
1188
 
1189
1189
  Search Reddit posts across all subreddits by query.
1190
1190
 
1191
- Price: $0.0009 per request.
1191
+ Price: $0.0012 per request.
1192
1192
 
1193
1193
  Example:
1194
1194
  res = client.reddit.search(query="mechanical keyboard")
@@ -36,10 +36,14 @@ class ThreadsProfileInput(TypedDict, total=False):
36
36
  class ThreadsSearchInput(TypedDict, total=False):
37
37
  """Input for Threads Search."""
38
38
 
39
+ endDate: NotRequired[str]
40
+ """Only return posts published on or before this date, format YYYY-MM-DD (e.g. 2026-08-07). Threads pages backwards in time from here, so narrowing this is how you walk older results."""
39
41
  preferLatencyUnderMs: NotRequired[int]
40
42
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
41
43
  query: Required[str]
42
- """Keyword or hashtag to search public Threads posts for; the # prefix is optional (e.g. AI agents)."""
44
+ """Keyword or hashtag to search public Threads posts for; the # prefix is optional (e.g. AI agents). Threads matches text in posts, so search a brand or topic rather than a URL - a query that is only a domain is searched by its name (wander.com is searched as wander)."""
45
+ startDate: NotRequired[str]
46
+ """Only return posts published on or after this date, format YYYY-MM-DD (e.g. 2026-08-01)."""
43
47
 
44
48
 
45
49
  class ThreadsSearchUsersInput(TypedDict, total=False):
@@ -465,9 +465,11 @@ class TiktokTrendingHashtagsInput(TypedDict, total=False):
465
465
  class TiktokVideoInput(TypedDict, total=False):
466
466
  """Input for TikTok Video."""
467
467
 
468
+ id: NotRequired[str]
469
+ """TikTok video ID, the numeric run at the end of a video URL. Use it when a listing SKU handed you an id and no URL."""
468
470
  preferLatencyUnderMs: NotRequired[int]
469
471
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
470
- url: Required[str]
472
+ url: NotRequired[str]
471
473
  """Full TikTok video URL."""
472
474
 
473
475
 
@@ -1588,7 +1590,7 @@ class TiktokVideoTranscriptFullWord(BaseModel):
1588
1590
  model_config = ConfigDict(extra="allow", populate_by_name=True)
1589
1591
 
1590
1592
  confidence: float = Field(
1591
- description="Recognizer confidence for this word, 0 to 1. Low values mark words the recognizer guessed; they are common on names, jargon, and music. Range: 0 to 1."
1593
+ description="Recognizer score for this word, exactly as the recognizer reported it. It is normally an alignment probability between 0 and 1, but on audio the recognizer could not align it reports a negative log-scale score instead, so read the sign before treating the number as a probability. Either way, lower means less certain, and low values are common on names, jargon, and music."
1592
1594
  )
1593
1595
  end_seconds: float | None = Field(
1594
1596
  default=None,
@@ -178,7 +178,7 @@ class TripadvisorNamespace:
178
178
  attraction by its page URL: rating, text, date, and trip details as
179
179
  normalized JSON.
180
180
 
181
- Price: $0.0036 per request.
181
+ Price: $0.003 per request.
182
182
 
183
183
  Example:
184
184
  res = client.tripadvisor.reviews(limit=3, url="https://www.tripadvisor.com/Hotel_Review-g60763-d93450-Reviews-The_Plaza-New_York_City_New_York.html")
@@ -200,7 +200,7 @@ class TripadvisorNamespace:
200
200
  destination and get rich place records (ratings, review counts, contact
201
201
  details, pricing) as normalized JSON.
202
202
 
203
- Price: $0.0036 per request.
203
+ Price: $0.003 per request.
204
204
 
205
205
  Example:
206
206
  res = client.tripadvisor.search(limit=3, query="Paris")
@@ -229,7 +229,7 @@ class AsyncTripadvisorNamespace:
229
229
  attraction by its page URL: rating, text, date, and trip details as
230
230
  normalized JSON.
231
231
 
232
- Price: $0.0036 per request.
232
+ Price: $0.003 per request.
233
233
 
234
234
  Example:
235
235
  res = client.tripadvisor.reviews(limit=3, url="https://www.tripadvisor.com/Hotel_Review-g60763-d93450-Reviews-The_Plaza-New_York_City_New_York.html")
@@ -251,7 +251,7 @@ class AsyncTripadvisorNamespace:
251
251
  destination and get rich place records (ratings, review counts, contact
252
252
  details, pricing) as normalized JSON.
253
253
 
254
- Price: $0.0036 per request.
254
+ Price: $0.003 per request.
255
255
 
256
256
  Example:
257
257
  res = client.tripadvisor.search(limit=3, query="Paris")
@@ -21,9 +21,9 @@ class TrustpilotReviewsInput(TypedDict, total=False):
21
21
  company: Required[str]
22
22
  """Brand name or Trustpilot review-page URL to fetch reviews for (e.g. nike or https://www.trustpilot.com/review/nike.com)."""
23
23
  countries: NotRequired[list[str]]
24
- """Only return reviews from reviewers in these ISO 3166-1 alpha-2 countries (e.g. ["US", "GB"]); omit for all countries."""
24
+ """Only return reviews from reviewers in these ISO 3166-1 alpha-2 countries (e.g. ["US", "GB"]). Omit this field for all countries and to stay on the cheapest price; a country filter routes to the dearest source."""
25
25
  languages: NotRequired[list[str]]
26
- """Only return reviews in these ISO 639-1 languages (e.g. ["en", "de"]); omit for all languages."""
26
+ """Only return reviews in these ISO 639-1 languages (e.g. ["en", "de"]). Omit this field for all languages and to stay on the cheapest price; a language filter routes to the dearest source."""
27
27
  limit: NotRequired[int]
28
28
  """Maximum number of results to return (1-200, default 200). Trustpilot serves at most 200 reviews per company. Range: 1 to 200. Default: 200."""
29
29
  preferLatencyUnderMs: NotRequired[int]
@@ -31,11 +31,11 @@ class TrustpilotReviewsInput(TypedDict, total=False):
31
31
  sortBy: NotRequired[str]
32
32
  """Review ordering: auto, relevancy, or recent (e.g. recent). Default: auto."""
33
33
  stars: NotRequired[str]
34
- """Limit reviews to a single star rating from 1 to 5 (e.g. 5); omit for all ratings."""
34
+ """Limit reviews to a single star rating from 1 to 5 (e.g. 5). Omit this field to stay on the cheapest price; a star filter routes to a dearer source."""
35
35
  startDate: NotRequired[str]
36
- """Only return reviews on or after this date, inclusive, in YYYY-MM-DD format (e.g. 2026-01-01)."""
36
+ """Only return reviews on or after this date, inclusive, in YYYY-MM-DD format (e.g. 2026-01-01). Omit this field to stay on the cheapest price; a date floor routes to the dearest source."""
37
37
  verifiedOnly: NotRequired[bool]
38
- """Set true to return only verified reviews (e.g. true). Default: false."""
38
+ """Set true to return only verified reviews (e.g. true). Omit this field, or send false, to stay on the cheapest price; true routes to a dearer source. Default: false."""
39
39
 
40
40
 
41
41
  class TrustpilotReviewsData(BaseModel):
@@ -103,8 +103,6 @@ class TwitterSearchInput(TypedDict, total=False):
103
103
 
104
104
  cursor: NotRequired[str]
105
105
  """Opaque pagination cursor from a previous response's nextCursor. Omit for the first page; pass it to fetch the next page of search results."""
106
- lang: NotRequired[str]
107
- """Optional ISO 639-1 language code to restrict tweets to (e.g. en)."""
108
106
  limit: NotRequired[int]
109
107
  """Per-page maximum number of results to return (1-50, default 20). A provider may return a smaller native page; follow nextCursor for more. Range: 1 to 50. Default: 20."""
110
108
  preferLatencyUnderMs: NotRequired[int]
@@ -19,7 +19,7 @@ class YelpSearchInput(TypedDict, total=False):
19
19
  """Input for Yelp Search."""
20
20
 
21
21
  limit: NotRequired[int]
22
- """Maximum number of results to return (1 to 20, default 20). Range: 1 to 20."""
22
+ """Maximum number of results to return (1 to 20, default 20). Range: 1 to 20. Default: 20."""
23
23
  location: Required[str]
24
24
  """City and state defining the search area (e.g. San Francisco, CA)."""
25
25
  preferLatencyUnderMs: NotRequired[int]
@@ -113,7 +113,7 @@ class YelpNamespace:
113
113
  Search Yelp for businesses by keyword and location: up to 20 listings with
114
114
  ratings, categories, and core business info per request.
115
115
 
116
- Price: $0.044 per request plus $0.00083 per result (maximum $0.0605).
116
+ Price: $0.0035 per request.
117
117
 
118
118
  Example:
119
119
  res = client.yelp.search(limit=5, location="Chicago, IL", query="pizza")
@@ -138,7 +138,7 @@ class AsyncYelpNamespace:
138
138
  Search Yelp for businesses by keyword and location: up to 20 listings with
139
139
  ratings, categories, and core business info per request.
140
140
 
141
- Price: $0.044 per request plus $0.00083 per result (maximum $0.0605).
141
+ Price: $0.0035 per request.
142
142
 
143
143
  Example:
144
144
  res = client.yelp.search(limit=5, location="Chicago, IL", query="pizza")
@@ -165,8 +165,23 @@ class YoutubeSearchHashtagInput(TypedDict, total=False):
165
165
  """Hashtag to search for (without the leading #)."""
166
166
  preferLatencyUnderMs: NotRequired[int]
167
167
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
168
- type: NotRequired[Literal["all", "shorts"]]
169
- """Content filter."""
168
+ type: NotRequired[Literal["all"]]
169
+ """Content filter. Only "all" is served: no source we buy returns a Shorts row with the channel and publish time this endpoint requires."""
170
+
171
+
172
+ class YoutubeSearchShortsInput(TypedDict, total=False):
173
+ """Input for YouTube Shorts Search."""
174
+
175
+ cursor: NotRequired[str]
176
+ """Continuation token from a previous response for pagination."""
177
+ preferLatencyUnderMs: NotRequired[int]
178
+ """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
179
+ query: Required[str]
180
+ """The keyword to search Shorts for."""
181
+ sortBy: NotRequired[Literal["relevance", "popular"]]
182
+ """Sort order: "relevance" (default) or "popular" (most-viewed). Default: relevance."""
183
+ uploadDate: NotRequired[Literal["today", "this_week", "this_month", "this_year"]]
184
+ """Filter by upload recency. Omit for any time."""
170
185
 
171
186
 
172
187
  class YoutubeTrendingShortsInput(TypedDict, total=False):
@@ -180,11 +195,11 @@ class YoutubeVideoInput(TypedDict, total=False):
180
195
  """Input for YouTube Video."""
181
196
 
182
197
  id: NotRequired[str]
183
- """YouTube video ID."""
198
+ """YouTube video ID. A Short uses the same ID as any other video."""
184
199
  preferLatencyUnderMs: NotRequired[int]
185
200
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
186
201
  url: NotRequired[str]
187
- """Full YouTube video URL."""
202
+ """Full YouTube video URL. Shorts (youtube.com/shorts/...), youtu.be, live, and embed URLs all work."""
188
203
 
189
204
 
190
205
  class YoutubeVideoCommentsInput(TypedDict, total=False):
@@ -197,7 +212,7 @@ class YoutubeVideoCommentsInput(TypedDict, total=False):
197
212
  preferLatencyUnderMs: NotRequired[int]
198
213
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
199
214
  url: Required[str]
200
- """Full YouTube video URL."""
215
+ """Full YouTube video URL. Shorts (youtube.com/shorts/...) and youtu.be URLs also work."""
201
216
 
202
217
 
203
218
  class YoutubeVideoSponsorsInput(TypedDict, total=False):
@@ -215,11 +230,11 @@ class YoutubeVideoTranscriptInput(TypedDict, total=False):
215
230
  """Input for YouTube Video Transcript."""
216
231
 
217
232
  id: NotRequired[str]
218
- """YouTube video ID."""
233
+ """YouTube video ID. A Short uses the same ID as any other video."""
219
234
  preferLatencyUnderMs: NotRequired[int]
220
235
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
221
236
  url: NotRequired[str]
222
- """Full YouTube video URL."""
237
+ """Full YouTube video URL. Shorts (youtube.com/shorts/...), youtu.be, live, and embed URLs all work."""
223
238
 
224
239
 
225
240
  class YoutubeVideoTranscriptFullInput(TypedDict, total=False):
@@ -232,7 +247,7 @@ class YoutubeVideoTranscriptFullInput(TypedDict, total=False):
232
247
  preferLatencyUnderMs: NotRequired[int]
233
248
  """Optional; omit it and routing is unchanged, with the cheapest source serving. Prefer sources whose typical response time (median over the trailing 30 days, as published on this endpoint's lane health) is under this many milliseconds; among those, the cheapest serves. This can raise your price: when the cheapest source misses the target, a faster and dearer one serves, and you are quoted and charged its price. If no source is that fast the request is still served, by whichever source offers the best speed for its price - it is never refused for being slow. Sources we have not timed are tried last. This is a preference, not a guarantee: the median describes past requests and is not a ceiling on this one, and it excludes any wait this request itself asks for. On a paginated walk it applies to the first page only: later pages stay with the source that page chose, at the price it was quoted. Minimum: 1."""
234
249
  url: Required[str]
235
- """YouTube video URL (e.g. "https://www.youtube.com/watch?v=dQw4w9WgXcQ")."""
250
+ """YouTube video or Short URL (e.g. "https://www.youtube.com/watch?v=dQw4w9WgXcQ" or "https://www.youtube.com/shorts/Fir1x9cw2vg")."""
236
251
 
237
252
 
238
253
  class YoutubeChannelData(BaseModel):
@@ -388,8 +403,9 @@ class YoutubeChannelShortsShort(BaseModel):
388
403
  likes: int = Field(
389
404
  description="Public like count when supplied by the upstream response. Minimum: 0."
390
405
  )
391
- title: str = Field(
392
- description="Public title or caption for the Short. Populated whenever the provider has data for the entity."
406
+ title: str | None = Field(
407
+ default=None,
408
+ description="Public title or caption for the Short. Populated whenever the provider has data for the entity. Present whenever the upstream returns this record.",
393
409
  )
394
410
  url: str = Field(
395
411
  description="Public YouTube URL for the Short. Populated whenever the provider has data for the entity."
@@ -597,6 +613,44 @@ class YoutubeSearchHashtagVideo(BaseModel):
597
613
  views: int
598
614
 
599
615
 
616
+ class YoutubeSearchShortsData(BaseModel):
617
+ model_config = ConfigDict(populate_by_name=True)
618
+
619
+ next_cursor: str | None = Field(
620
+ default=None,
621
+ alias="nextCursor",
622
+ description="Opaque cursor for the next page of results, or null when there are no more. Pass it back as cursor to continue.",
623
+ )
624
+ shorts: list[YoutubeSearchShortsShort] = Field(
625
+ description="Populated whenever the provider has data for the entity."
626
+ )
627
+
628
+
629
+ class YoutubeSearchShortsShort(BaseModel):
630
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
631
+
632
+ content_type: str = Field(
633
+ alias="contentType",
634
+ description='Always "short": every row here comes from YouTube\'s Shorts shelf.',
635
+ )
636
+ id: str = Field(
637
+ description="YouTube video id of the Short. Populated whenever the provider has data for the entity."
638
+ )
639
+ title: str = Field(
640
+ description="Title of the Short. Populated whenever the provider has data for the entity."
641
+ )
642
+ url: str = Field(
643
+ description="Watch URL for the Short. Populated whenever the provider has data for the entity."
644
+ )
645
+ views: int = Field(
646
+ description="View count, or 0 when the source did not publish one. Read viewsAvailable before trusting a 0."
647
+ )
648
+ views_available: bool = Field(
649
+ alias="viewsAvailable",
650
+ description="False when the source published no view count, so views is a placeholder rather than a measured zero.",
651
+ )
652
+
653
+
600
654
  class YoutubeTrendingShortsData(BaseModel):
601
655
  shorts: list[YoutubeTrendingShortsShort] = Field(
602
656
  description="Populated whenever the provider has data for the entity."
@@ -1203,6 +1257,50 @@ class YoutubeNamespace:
1203
1257
  options=options,
1204
1258
  )
1205
1259
 
1260
+ def search_shorts(
1261
+ self,
1262
+ *,
1263
+ options: RequestOptions | None = None,
1264
+ **input: Unpack[YoutubeSearchShortsInput],
1265
+ ) -> RunResult[YoutubeSearchShortsData]:
1266
+ """YouTube Shorts Search
1267
+
1268
+ Search YouTube Shorts by keyword and get matching Shorts (title, views, URL)
1269
+ with cursor pagination as normalized JSON.
1270
+
1271
+ Price: $0.002 per request.
1272
+
1273
+ Example:
1274
+ res = client.youtube.search_shorts(query="cats")
1275
+ """
1276
+ raw = self._client._run_raw( # pyright: ignore[reportPrivateUsage]
1277
+ "youtube.search_shorts", dict(input), options
1278
+ )
1279
+ return RunResult[YoutubeSearchShortsData].model_validate(raw)
1280
+
1281
+ def iter_search_shorts(
1282
+ self,
1283
+ *,
1284
+ options: RequestOptions | None = None,
1285
+ **input: Unpack[YoutubeSearchShortsInput],
1286
+ ) -> Paginator[YoutubeSearchShortsShort, YoutubeSearchShortsData]:
1287
+ """Iterate YouTube Shorts Search results, following pagination cursors.
1288
+
1289
+ Yields validated `YoutubeSearchShortsShort` items from the `shorts` field of
1290
+ each page. Use `.pages()` on the returned paginator to walk whole
1291
+ `RunResult` pages.
1292
+ """
1293
+ return paginate(
1294
+ self._client,
1295
+ "youtube.search_shorts",
1296
+ dict(input),
1297
+ "shorts",
1298
+ item_model=YoutubeSearchShortsShort,
1299
+ data_model=YoutubeSearchShortsData,
1300
+ bare=False,
1301
+ options=options,
1302
+ )
1303
+
1206
1304
  def trending_shorts(
1207
1305
  self,
1208
1306
  *,
@@ -1232,8 +1330,8 @@ class YoutubeNamespace:
1232
1330
  ) -> RunResult[YoutubeVideoData]:
1233
1331
  """YouTube Video
1234
1332
 
1235
- Fetch a YouTube video's metadata (title, channel, views, likes, duration,
1236
- publish date) by URL or ID.
1333
+ Fetch a YouTube video or Short's metadata (title, channel, views, likes,
1334
+ duration, publish date) by URL or ID.
1237
1335
 
1238
1336
  Price: $0.00125 per request.
1239
1337
 
@@ -1253,8 +1351,8 @@ class YoutubeNamespace:
1253
1351
  ) -> RunResult[YoutubeVideoCommentsData]:
1254
1352
  """YouTube Video Comments
1255
1353
 
1256
- List the comments on a YouTube video by URL with cursor pagination (text,
1257
- author, likes, reply count).
1354
+ List the comments on a YouTube video or Short by URL with cursor pagination
1355
+ (text, author, likes, reply count).
1258
1356
 
1259
1357
  Price: $0.002 per request.
1260
1358
 
@@ -1318,7 +1416,7 @@ class YoutubeNamespace:
1318
1416
  ) -> RunResult[YoutubeVideoTranscriptData]:
1319
1417
  """YouTube Video Transcript
1320
1418
 
1321
- Fetch the transcript/captions of a YouTube video by URL or ID.
1419
+ Fetch the transcript/captions of a YouTube video or Short by URL or ID.
1322
1420
 
1323
1421
  Price: $0.00125 per request.
1324
1422
 
@@ -1338,10 +1436,11 @@ class YoutubeNamespace:
1338
1436
  ) -> RunResult[YoutubeVideoTranscriptFullData]:
1339
1437
  """YouTube Video Transcript (Provenance)
1340
1438
 
1341
- Fetch a YouTube transcript with timed segments and its provenance: whether
1342
- the words are creator-written captions or machine speech recognition.
1439
+ Fetch a YouTube video or Short transcript with timed segments and its
1440
+ provenance: whether the words are creator-written captions or machine speech
1441
+ recognition.
1343
1442
 
1344
- Price: $0.00308 per request plus $0 per result (maximum $0.00308).
1443
+ Price: $0.001 per request.
1345
1444
 
1346
1445
  Example:
1347
1446
  res = client.youtube.video_transcript_full(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
@@ -1775,6 +1874,50 @@ class AsyncYoutubeNamespace:
1775
1874
  options=options,
1776
1875
  )
1777
1876
 
1877
+ async def search_shorts(
1878
+ self,
1879
+ *,
1880
+ options: RequestOptions | None = None,
1881
+ **input: Unpack[YoutubeSearchShortsInput],
1882
+ ) -> RunResult[YoutubeSearchShortsData]:
1883
+ """YouTube Shorts Search
1884
+
1885
+ Search YouTube Shorts by keyword and get matching Shorts (title, views, URL)
1886
+ with cursor pagination as normalized JSON.
1887
+
1888
+ Price: $0.002 per request.
1889
+
1890
+ Example:
1891
+ res = client.youtube.search_shorts(query="cats")
1892
+ """
1893
+ raw = await self._client._arun_raw( # pyright: ignore[reportPrivateUsage]
1894
+ "youtube.search_shorts", dict(input), options
1895
+ )
1896
+ return RunResult[YoutubeSearchShortsData].model_validate(raw)
1897
+
1898
+ def iter_search_shorts(
1899
+ self,
1900
+ *,
1901
+ options: RequestOptions | None = None,
1902
+ **input: Unpack[YoutubeSearchShortsInput],
1903
+ ) -> AsyncPaginator[YoutubeSearchShortsShort, YoutubeSearchShortsData]:
1904
+ """Iterate YouTube Shorts Search results, following pagination cursors.
1905
+
1906
+ Yields validated `YoutubeSearchShortsShort` items from the `shorts` field of
1907
+ each page. Use `.pages()` on the returned paginator to walk whole
1908
+ `RunResult` pages.
1909
+ """
1910
+ return apaginate(
1911
+ self._client,
1912
+ "youtube.search_shorts",
1913
+ dict(input),
1914
+ "shorts",
1915
+ item_model=YoutubeSearchShortsShort,
1916
+ data_model=YoutubeSearchShortsData,
1917
+ bare=False,
1918
+ options=options,
1919
+ )
1920
+
1778
1921
  async def trending_shorts(
1779
1922
  self,
1780
1923
  *,
@@ -1804,8 +1947,8 @@ class AsyncYoutubeNamespace:
1804
1947
  ) -> RunResult[YoutubeVideoData]:
1805
1948
  """YouTube Video
1806
1949
 
1807
- Fetch a YouTube video's metadata (title, channel, views, likes, duration,
1808
- publish date) by URL or ID.
1950
+ Fetch a YouTube video or Short's metadata (title, channel, views, likes,
1951
+ duration, publish date) by URL or ID.
1809
1952
 
1810
1953
  Price: $0.00125 per request.
1811
1954
 
@@ -1825,8 +1968,8 @@ class AsyncYoutubeNamespace:
1825
1968
  ) -> RunResult[YoutubeVideoCommentsData]:
1826
1969
  """YouTube Video Comments
1827
1970
 
1828
- List the comments on a YouTube video by URL with cursor pagination (text,
1829
- author, likes, reply count).
1971
+ List the comments on a YouTube video or Short by URL with cursor pagination
1972
+ (text, author, likes, reply count).
1830
1973
 
1831
1974
  Price: $0.002 per request.
1832
1975
 
@@ -1890,7 +2033,7 @@ class AsyncYoutubeNamespace:
1890
2033
  ) -> RunResult[YoutubeVideoTranscriptData]:
1891
2034
  """YouTube Video Transcript
1892
2035
 
1893
- Fetch the transcript/captions of a YouTube video by URL or ID.
2036
+ Fetch the transcript/captions of a YouTube video or Short by URL or ID.
1894
2037
 
1895
2038
  Price: $0.00125 per request.
1896
2039
 
@@ -1910,10 +2053,11 @@ class AsyncYoutubeNamespace:
1910
2053
  ) -> RunResult[YoutubeVideoTranscriptFullData]:
1911
2054
  """YouTube Video Transcript (Provenance)
1912
2055
 
1913
- Fetch a YouTube transcript with timed segments and its provenance: whether
1914
- the words are creator-written captions or machine speech recognition.
2056
+ Fetch a YouTube video or Short transcript with timed segments and its
2057
+ provenance: whether the words are creator-written captions or machine speech
2058
+ recognition.
1915
2059
 
1916
- Price: $0.00308 per request plus $0 per result (maximum $0.00308).
2060
+ Price: $0.001 per request.
1917
2061
 
1918
2062
  Example:
1919
2063
  res = client.youtube.video_transcript_full(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
@@ -12,6 +12,7 @@ import pytest
12
12
 
13
13
  from conftest import json_response, make_async_client, make_sync_client
14
14
  from getanyapi import (
15
+ AnyAPIError,
15
16
  DiscoveryPricing,
16
17
  FlatPricingOffer,
17
18
  LinearPricingOffer,
@@ -331,6 +332,40 @@ def test_search_reads_every_shared_ranked_field_and_forwards_filters() -> None:
331
332
  assert found.model_dump(by_alias=True, exclude_defaults=True) == body
332
333
 
333
334
 
335
+ def test_search_scopes_without_a_query_and_omits_q_entirely() -> None:
336
+ body = discovery_search()
337
+
338
+ def respond(req: httpx.Request) -> httpx.Response:
339
+ assert req.url.path == "/catalog/search"
340
+ assert dict(req.url.params) == {"platform": "reddit"}
341
+ return json_response(200, body)
342
+
343
+ client, _ = make_sync_client(respond)
344
+ assert client.search(platform="reddit").results
345
+
346
+
347
+ def test_search_omits_an_empty_query_rather_than_sending_it_empty() -> None:
348
+ body = discovery_search()
349
+
350
+ def respond(req: httpx.Request) -> httpx.Response:
351
+ assert "q" not in dict(req.url.params)
352
+ return json_response(200, body)
353
+
354
+ client, _ = make_sync_client(respond)
355
+ assert client.search(query="", category="social").results
356
+
357
+
358
+ def test_search_without_query_category_or_platform_never_calls_the_gateway() -> None:
359
+ client, recorder = make_sync_client(
360
+ lambda _req: json_response(200, discovery_search())
361
+ )
362
+ with pytest.raises(
363
+ AnyAPIError, match="at least one of query, category, or platform"
364
+ ):
365
+ client.search(limit=5)
366
+ assert recorder.requests == []
367
+
368
+
334
369
  def test_search_drops_safe_additive_result_and_envelope_fields() -> None:
335
370
  body = discovery_search()
336
371
  results = cast("list[dict[str, object]]", body["results"])
File without changes
File without changes
File without changes
File without changes