All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
facebook.get_post_detail(post_id, url=...)andfacebook.get_post_comments(post_id, url=...)— a Facebook post URL comes in several shapes (/reel/,permalink.php,{page}/posts/{pfbid}), andurloverridespost_idfor all of them. The parameter has always worked on the API; it was missing from the SDK. Reported in SB-001096.facebook.search_marketplace(..., radius=...)— search radius around the location, in km (miles in the US). Honoured on search; ignored on category browse.zillow.get_multifamily_building(url=...)— the untyped facade twin ofzillow.properties.get_buildingadded in 0.44.0.- TikTok Shop —
tiktok.tiktok_shop_root_categories,tiktok_shop_category_subcategories_top_products,tiktok_shop_product_detail,tiktok_shop_product_reviews,search_tiktok_shop_productsandtiktok_shop_store_products.
- Nothing in the SDK, but worth knowing if you use the Facebook endpoints: the page/profile feed served one post per call with an empty page 2, post detail 404'd every URL shape, and comment threads always came back empty. All four are fixed on the API side (SB-001096) — no SDK change was needed.
zillow.properties.get_building(url)— Zillow's for-rent inventory is mostly apartment communities served on/apartments/...and/b/...pages, whichget_propertycannot read (it now raises a clear 400 pointing here). Returns floor plans and every available unit with rent, base rent, required monthly fees, sqft, beds/baths and move-in date, plus amenities, unit features, policies, special offers, office hours, pet policy, schools, photos and walk/transit/bike scores. Requested in SB-001087.Building,BuildingFloorPlan,BuildingUnit,BuildingResponse,UnitSummarymodels.- Building fields on
Listing—is_building,building_name,lot_id,units_available,unit_summaries[]. Building search cards now also carry aprice/price_raw: only paid cards expose a base-rent range, so the rest fall back to the per-bedroom rollup (they were previously priceless).
- Image input on
chatgpt.ask.askandgemini.ask.ask— passimage_urland the model looks at the picture and answers about it (JPEG, PNG, GIF, WEBP, BMP; up to 5 MB). An image ask is slower than a text one — allow 90-150 s. Requested in SB-001078. imageson both ask responses (MediaItem[]) — the pictures the answer itself displayed, with favicon-sized assets excluded.
Neither endpoint will GENERATE an image. Anonymous chatgpt.com and
gemini.google.com both gate image generation behind a login, so images is
only ever what the answer showed, never something the model drew.
-
ebay.search.search_by_image()— eBay's own visual search, the one behind the camera icon in eBay's search bar. Give it a picture asimage_url(a public http(s) URL) orimage_base64(JPEG or PNG, up to 10 MB decoded; adata:image/jpeg;base64,...URL is accepted too) and get back the sameSearchResponsethatsearch()returns, withqueryset toNone. Takes the usual SRP filters —domain,category_id,page,per_page,condition,buying_format,min_price,max_price,free_shipping,location,language. Costs 10 credits (two eBay fetches: the upload, then the results page). Passing neither or both images raisesValueErrorlocally rather than costing a round trip.There is deliberately no
sort_by: eBay ignores sorting on a visual results page, and a parameter that silently does nothing is worse than no parameter.
wait_for_foundandjs_scenario_reportonScrapeResult— whether thewait_forselector appeared, and a per-step trace of thejs_scenario(step,action,selector,ok,error,url) so a flow that does not end where you expected can be read step by step.
web.scrape(): ajs_scenarionow runs beforewait_for, and a missedwait_foror a failed scenario step is a free422(wait_for_timeout/js_scenario_failed) instead of a billed copy of the page you started on. Scenario steps useaction(click,fill,scroll,wait,evaluate).
- Instagram: seven more methods flagged temporarily unavailable —
users.related,media.oembed,media.comment_likers,hashtags.get,hashtags.recent,search.hashtagsandsearch.topneed the authenticated Instagram tier, which is offline; they return503 temporarily_unavailable(not billed) until it is back. Their docstrings now say so, matching the rest of the gated set. No signature changes. (SCR-42, support SB-001065)
catalog_idongoogle.shopping.offers— look up multi-seller Google Shopping offers by Google's own Shopping catalog id (thecatalog_idon everyshopping.searchtile, orprds=catalogid:<id>in a Google Shopping URL) as an alternative tobarcode. The response carriescatalog_idandtotal_offers;barcodeis now optional (exactly one of the two is required).
gallery_imagesonRedditPost— multi-image gallery posts now expose every image at full source resolution, in the order the poster arranged them. Previously a gallery returned onlyis_gallery: true, a ~140pxthumbnail, and aurlpointing atreddit.com/gallery/<id>— the images themselves were unreachable. Populated on every post-returning endpoint:posts.get,search.posts, subreddit/user/domain listings, trending and duplicates.- New
RedditGalleryImagemodel —media_id,url,width,height,mime_type, andmp4_urlfor animated items (whoseurlis the.gif). Exported from bothscrapebadgerandscrapebadger.reddit.
- New
bookingnamespace — the Booking.com API. Five operations onclient.booking:search_properties,search_destinations,get_property_detail,get_property_reviewsandget_room_types_and_live_rates. Global coverage, any Booking-supported currency and locale, no upstream Booking account or key required. get_room_types_and_live_ratesreturns the full per-room rate table for a property and dates — every room type with every rate bookable on it (price, price before discount, price per night, discounts, badges, occupancy, meal plan), plus per-room facilities, alternative bed layouts, photos and a priced child/crib/extra-bed policy.search_propertiesreturns only the single cheapest rate per property.
- Spec-generated additions on the v1 surface (zero breaking changes): Facebook Ad Library advertiser page search (
facebook.search_advertiser_pages) and page info/transparency (facebook.get_advertiser_page_info), theapp_storeandgoogle_playnamespaces, Google ads-transparency operations, TikTok Ad Library detail/advertiser search, and other newly added API operations. Generated methods return raw dicts until typed models land.
- Spec-generated additions on the v1 surface (zero breaking changes): new
facebook,idealista,perplexityandaccountnamespaces, plus previously missing operations (Twitter stream/filter-rule management, ChatGPT/Gemini POST variants, apartments/depop/redfin/zillow detail routes and more). Generated methods return raw dicts/objects until typed models land. - SDK is now regenerated automatically from the ScrapeBadger OpenAPI spec on every API change; a surface-lock CI check guarantees existing methods never change.
- A transient
500no longer kills a long-running job.500was missing from the defaultretry_on_status, so while a502was retried, a500raisedServerErroron the spot and burned the remaining 9 retries. Real-world sequences look like502 → 500 → 200; the middle hop ended runs that were thousands of pages deep. The default is now(500, 502, 503, 504). Overrideretry_on_statusonClientConfigif you want the old behaviour. - Retry every transient transport failure, not just three of them. The retry path caught only
ConnectError,ReadTimeoutandWriteTimeout, so aConnectTimeout,PoolTimeout,ProxyError, or a server dropping a pooled connection (RemoteProtocolError) failed immediately — the last of which is common on long-lived connection pools.UnsupportedProtocolandLocalProtocolErrorare still raised straight away: they are caller/config mistakes that no retry can fix.
- Google Lens
price— each visual match now carriespriceas{value, currency, extracted}alongside the rawtagchip it is parsed from, so a shoppable match gives you a number to compute on instead of a"$115"string.currencyis the symbol Google printed, not an ISO code ($is ambiguous between USD/CAD/AUD).tagis unchanged. (SCR-150)
- Vinted
catalog_idssearch filter —client.vinted.search.search("nike", catalog_ids="221")restricts a search to one or more Vinted categories. Vinted applies it before the search runs (sub-categories included), so you get whole pages of in-category results instead of filtering them out client-side. A catalog ID is thecatalog[]value in a Vinted category URL (vinted.fr/catalog?catalog[]=221); IDs are per market. (SCR-159) - Vinted item fields the SDK was dropping —
VintedItemSummaryandVintedItemDetailnow modelpath,service_fee,total_item_price,is_visibleandpromoted(pluscontent_sourceon the summary, andcolor2/can_bundle/can_reserve/is_favouriteon the detail). The API had always returned these; the models usedextra="ignore", so they were silently discarded. (SCR-159)
- Vinted
service_fee/total_item_priceare plain decimal strings — on search results they used to come back as a stringified Python dict ("{'amount': '0.75', 'currency_code': 'EUR'}") that no client could parse; the item endpoint already returned"0.75". Both now return the amount alone, inprice.currency_code. (SCR-159) - Vinted
VintedItemDetail.categorydocumented as a breadcrumb list — it is the category path, root first, localized to the market;upload_dateis Vinted's relative "listed" label, not an ISO timestamp. (SCR-159) - Corrected Vinted search billing docs — a search is a flat 5 credits whatever filters you pass.
seller_countryseller lookups were never billed on top, despite the old "1 credit + 1 per uncached seller" note. (SCR-159)
- eBay
pagination.has_more— the stop signal for paging through completed/sold listings.total_pages/total_resultsareNoneon eBay's sold grid, and past the last page eBay re-serves that page instead of returning empty, so looping "until the results are empty" never terminated. Loopwhile r.pagination.has_moreinstead; the API now also returns an empty page once you page past the end. Thepageceiling is raised to 1000 (a broad sold search runs ~133 pages deep atper_page=240, ~26k listings). (SCR-124)
- eBay
is_sponsoredis nowbool | Noneand alwaysNone— previously typedbooland reportedTruefor 100% of results. eBay renders its "Sponsored" badge into every result card as anti-scraping bait, so promoted placements cannot be distinguished from organic ones in the response. The field reportsNone(unknown) rather than a value that was wrong on every row. (SCR-124)
- Google Flights
sort_by—client.google.flights.search(..., sort_by="price")now returns the FULL price-sorted inventory (every carrier, the cheap long-layover fares) plus Google's own price floor, typical range, and price history, instead of only the ~6-8 "best" preview. Defaultsort_by="top"keeps the fast best-picks behaviour. (SCR-123)
- eBay completed-search sold dates —
SearchResultnow exposessold_date(sale date text as rendered by eBay, e.g. "2 Jul 2026"; localized on non-English markets) andsold_date_at(best-effort ISO date "2026-07-02",Nonewhen the market's format isn't English) onclient.ebaycompleted/sold result cards. (SCR-122) - eBay item
is_ended—Itemnow exposesis_ended(Truewhen the listing has closed — sold or ended — any buying format, defaultFalse).end_time_utc/end_time_atbroaden from auction-only to the listing end: auction close or sold time for ended listings of any format; stillNonefor active fixed-price listings. (SCR-122)
- LinkedIn client (
client.linkedin) — LinkedIn's public, no-auth (logged-out) surface. Endpoints:jobs_search(),get_job(),company_jobs(),get_company(),get_school(),get_profile(),get_post(),get_article(),get_course(),geo_suggest(),health()under/v1/linkedin. Fully-typed Pydantic models (LinkedInJobsSearchResponse,LinkedInJobDetail,LinkedInProfileincl. nested experience/education,LinkedInCompany,LinkedInSchool,LinkedInPost,LinkedInLearningCourse,LinkedInGeoSuggestResponse). Sourced from the guest Jobs API and public SSR JSON-LD pages; deep logged-in data is auth-gated and out of scope. (SCR-119)
- Redfin client (
client.redfin) — Redfin (redfin.com, US) for-sale search, property detail, agent profiles, autocomplete, and markets. Endpoints:search(),get_property(),get_agent(),autocomplete(),list_markets(), mirroring the Immobiliare flat-client pattern, with fully-typed maximal-coverage Pydantic models (RedfinSearchResponse,RedfinPropertyincl. nestedaddress/price_history/tax_history/schools/amenities,RedfinAgent, etc.). Single market: redfin.com (US, USD, en-US). (SCR-116)
- LoopNet sub-client (
client.loopnet) — commercial-real-estate (CoStar) listings, brokers, and reference data across loopnet.com/.ca/.co.uk/.fr/.es (US/CA/UK/FR/ES). Endpoints:search.search()(for-lease / for-sale / auctions, all property types, filters, pagination),listings.get(),brokers.get(),reference.markets(),reference.property_types(), with fully-typed maximal-coverage Pydantic models (LoopnetSearchResponse,LoopnetListingDetailincl. offers/facts/brokers/media,LoopnetBrokerProfile,LoopnetListingCard, etc.). LoopNet is behind Akamai Bot Manager (browser-farm-only) — served via the ScrapeBadger farm. (SCR-102)
- Zillow sub-client (
client.zillow) — real-estate listings, property detail, and agent profiles from zillow.com (US + Canadian inventory). Five endpoints:search.search(),search.autocomplete(),properties.get_property(),agents.get_agent(),reference.list_markets(), with fully-typed maximal-coverage Pydantic models (ZillowSearchResponse,ZillowPropertyincl. nestedhome_facts/price_history/tax_history/schools/zestimate_history,ZillowAgent, etc.). (SCR-99)
- Add Leboncoin Scraper API client (France) — 10 endpoints: search, ad detail, similar ads, seller profile + listings, markets/regions/departments/categories/location search.
- Realtor sub-client (
client.realtor) — real-estate listings across realtor.com (US) and realtor.ca (Canada) behind a singlemarketparameter. Four endpoints:search.search(),search.autocomplete(),properties.get_property(),reference.list_markets(), with fully-typed Pydantic models (RealtorSearchResponse,RealtorPropertyDetail, etc.). (SCR-98)
client.twitter.tweets.advanced_search()/advanced_search_all()— aliases forsearch()/search_all()that match the/advanced_searchREST endpoint name, so the endpoint-named call no longer raisesAttributeError. (SCR-52)
- Auto-pagination no longer repeats the first page (
*_alliterators, e.g.client.twitter.tweets.search_all). The sharedpaginate()helper now stops when the backend returns the same cursor it was given, instead of re-fetching the page it just yielded — previously a non-advancing cursor from the API could cause a repeat-page loop. (SCR-52)
- Google Shopping offers by barcode —
client.google.shopping.offers(barcode, *, gl=None, hl="en")callsGET /v1/google/shopping/offers. Resolves a product barcode (GTIN-8/UPC-A/EAN-13/GTIN-14) to a product via Google web search, then returns its multi-seller Google Shopping prices (barcode,resolved_query,product_title,offers). Costs 14 credits; returns 422 for an invalid barcode and 404 if unresolvable.
- eBay auction data on the eBay client models:
SearchResult.current_bidandItem.current_bid— an auction's current high bid (mirrorsprice).Item.end_time_utc/Item.end_time_at— the absolute auction end time (Unix float / ISO-8601 Z).Item.buy_it_now_price— the Buy It Now price for fixed-price listings, or an auction that also offers Buy It Now (Nonefor pure auctions).bids(bid count) andtime_left(relative remaining, e.g."12h 16m") are now reliably populated for auction listings.
- eBay API client (
client.ebay) covering all 12 endpoints across 18 marketplaces:ebay.search.search()— active-listing keyword search (category, condition, buying_format, price, free-shipping filters)ebay.search.completed()— completed/sold listings (sold-price history)ebay.search.autocomplete()— keyword suggestionsebay.items.get_item()— full listing detail (images, shipping, item specifics, seller, returns)ebay.items.get_item_reviews()— catalog product reviews + rating histogram (optionalproduct_id)ebay.sellers.get_seller()/get_seller_items()/get_seller_feedback()ebay.categories.browse_category()— browse listings within a categoryebay.reference.list_categories()/list_markets()
- Frozen, forward-compatible Pydantic models for every eBay response field (exported with an
Ebay-prefix at the top level, e.g.EbayItem,EbaySearchResult,EbaySeller,EbayReview,EbayPrice).
-
Reddit response models trimmed to fields available via old.reddit.com HTML/RSS — after Reddit deprecated the unauthenticated
.jsonAPI, the ScrapeBadger backend switched to scrapingold.reddit.comHTML and RSS feeds. Fields that Reddit no longer exposes through that source have been removed from all models to keep the SDK types in sync with what the API actually returns. Consuming code that accessed removed fields will receiveAttributeErrorat runtime.RedditPost— removed:ups,downs,upvote_ratio,view_count,num_duplicates,edited,edited_at,is_video,is_locked,is_archived,is_pinned,is_robot_indexable,is_meta,is_crosspostable,send_replies,author_flair_text,author_flair_type,author_flair_template_id,link_flair_background_color,link_flair_text_color,link_flair_template_id,link_flair_type,link_flair_css_class,distinguished,thumbnail,thumbnail_width,thumbnail_height,post_hint,preview_images,media,gallery_data,crosspost_parent,suggested_sort,total_awards,awards,content_categories,removed_by_category,treatment_tags,subreddit_subscribers.RedditComment— removed:ups,downs,controversiality,edited,edited_at,gilded,is_locked,is_score_hidden,is_submitter,parent_id,post_title,send_replies,subreddit_type,total_awards,distinguished,author_flair_text,author_flair_type.RedditSubreddit— removed:subscribers,active_users,description_html,public_description_html,submit_text,submit_text_html,header_title,type,submission_type,is_quarantined,is_advertiser_friendly,advertiser_category,language,icon_url,header_url,banner_url,banner_background_color,primary_color,key_color,wiki_enabled,allow_images,allow_videos,allow_galleries,allow_polls,allow_discovery,spoilers_enabled,emojis_enabled,free_form_reports,accept_followers,restrict_posting,link_flair_enabled,link_flair_position,user_flair_enabled,user_flair_position,comment_score_hide_mins,should_archive_posts,allowed_media_in_comments.RedditUser— removed:id,fullname,description,icon_url,snoovatar_url,banner_url,profile_title,profile_url,awardee_karma,awarder_karma,has_verified_email,verified,accepts_followers,has_subscribed,is_employee,is_mod,is_suspended,is_nsfw,pref_show_snoovatar.RedditRule— removed:description_html,kind,violation_reason. -
Helper models deleted —
RedditPreviewImage,RedditMedia,RedditAward, andRedditUserSummaryare no longer exported (they were only used by removed fields). Remove any imports of these names from consuming code.
RedditPagination— new standalone pagination model (after,before,count,limit) exported fromscrapebadger.reddit.
- Reddit search & listing methods sent the wrong query-parameter names, causing a
422 'q' field requiredagainst the live API. The API expectsq(search query) andt(time filter); the SDK was sendingqueryandtime_filter. Fixed acrosssearch.posts,search.subreddits,search.users,search.domain_posts,subreddits.posts,users.posts,users.comments. The Python keyword arguments are unchanged (query=,time_filter=) — only the wire params are corrected. search.domain_posts()hit the wrong URL —/v1/reddit/search/domainwithdomainas a query param. The real route is/v1/reddit/domains/{domain}/posts(domain is a path segment). Fixed.
client.reddit.posts.get()andclient.reddit.posts.comments()were calling the wrong URL —/v1/reddit/posts/{subreddit}/{post_id}(two path segments) instead of the actual API route/v1/reddit/posts/{post_id}. Both methods 404'd against the live API in 0.8.0/0.8.1. Thesubredditpositional argument has been removed from both methods — callposts.get(post_id)andposts.comments(post_id, ...). This is a breaking change for anyone who worked around the bug, but the previous signature never worked against production.
client.reddit.subreddits.moderators(...)— Reddit gated the moderator listing behind authentication in 2024. There is no public path that yields the data. Removed the method along withRedditModeratorandSubredditModeratorResponsetypes rather than ship a permanently-broken endpoint.
client.reddit.search.subreddits(...)no longer crashes when Reddit returns banned/quarantined subreddits withnullvalues for required-typed fields (previously raised a backend 500 / ValidationError).
- Reddit Scraper API — new
client.reddit.*namespace covering 22 endpoints across search, posts, subreddits, users, and wiki:client.reddit.search.posts(q, ...)— global or subreddit-scoped post search with full Reddit syntax (title:, author:, subreddit:, flair:, AND/OR/NOT)client.reddit.search.subreddits(q, ...),client.reddit.search.users(q, ...)client.reddit.search.domain_posts(domain, ...)— posts linking to an external domainclient.reddit.posts.trending(...),client.reddit.posts.get(post_id)client.reddit.posts.comments(post_id, depth=...)— full nested comment trees with configurable depth (0–10)client.reddit.posts.duplicates(post_id, ...)— cross-post detectionclient.reddit.subreddits.get(name),.posts(...),.rules(...),.moderators(...),.wiki_pages(...),.wiki_page(...),.popular(...),.new(...)client.reddit.users.get(name),.posts(...),.comments(...),.moderated(...),.trophies(...)
- Comprehensive Reddit response models —
RedditPost(66 fields),RedditComment(34),RedditSubreddit(48),RedditUser(27) - Datetime parity — every datetime field ships both Unix timestamp (
*_utc) and ISO 8601 UTC string (*_at) - Frozen Pydantic models with
extra="ignore"for forward compatibility
client.google.shopping.product(product_id, ...)— Shopping product detail page fetch byproduct_id.client.google.shopping.click(title, source, q, product_id?)— Resolve the direct merchant URL for a Shopping product tile via Google's "I'm Feeling Lucky" redirect.client.google.search.light(q, ...)— Lightweightmode=fastSERP — organic results + related searches only, ~40% faster than the full SERP.client.google.search.search(..., mode="full" | "fast")— Newmodeparameter on the main search method.mode="fast"hits Google's litegbv=1endpoint.client.google.maps.posts(..., next_page_token=...)— Pagination support on business posts.
- Removed hardcoded per-endpoint credit numbers from docstrings and docs. Credit costs are configured per-endpoint by ScrapeBadger admins and returned live from
GET /public/pricing— no more stale "costs 2 credits" comments that go out of sync when pricing changes. client.google.products.detail(product_id, q=None, ...)—qis now optional. The backend accepts lookups byproduct_idalone; passqonly when you want the richer/async/oapvcontext blob.- CHANGELOG section heading for 0.6.0 renamed from "Google Scrapingdog parity" to "Google parity".
client.google.local— removed. The Local Pack is exposed via the SERP/v1/google/searchresponse'slocal_resultsfield rather than a dedicated endpoint. Breaking for anyone using the dedicated local client; migrate to readinglocal_resultsfromclient.google.search.search(...).
Three new Google product sub-clients and deeper Scholar / Trends / Search surface:
client.google.local— Local Pack business listings ranked for a SERP query (tbm=lcl). Driven byq + location/uule, returns ratings, reviews, addresses, phone numbers, and GPS coordinates. Complementary to the Maps API.client.google.shorts— Short-form vertical video results (YouTube Shorts, TikTok, Facebook Reels) via Google's Shorts SERP mode (udm=39).client.google.flights— One-way, round-trip, and multi-city flight search with passenger config, cabin class, stops filter, and max-price. Returnsbest_flights,other_flights,price_insights, and per-offer carbon emissions.client.google.scholar.profiles(mauthors, ...)— Author profile search by name withafter_author/before_authorpagination.client.google.scholar.author(author_id, ...)— Full author profile (articles, citation stats, co-authors).client.google.scholar.author_citation(author_id, ...)— Citations-per-year chart for a Scholar author.client.google.scholar.cite(q, ...)— MLA / APA / Chicago / Harvard / Vancouver citation formats plus export links (BibTeX / RIS / EndNote / RefWorks).client.google.trends.autocomplete(q, ...)— Categorized Knowledge Graph topic entities (mid,type, link) for a query prefix. Distinct from Google Search autocomplete.client.google.search.search(..., ai_overview=True)— Optional flag that chases Google's deferred AI Overviewpage_tokenwith a follow-up fetch and merges the result intoai_overview. Adds ~1s and 1 credit only when the SERP actually defers the overview.
The Google product roster is now 19 (was 16).
- Twitter Streams: Real-time tweet monitoring via WebSocket and webhooks
StreamClientwith full monitor CRUD:create_monitor,list_monitors,get_monitor,update_monitor,pause_monitor,resume_monitor,delete_monitor- WebSocket streaming via
connect()async context manager with auto-reconnect support verify_webhook_signature()utility for HMAC-SHA256 webhook verification- Delivery log and billing log listing:
list_delivery_logs,list_billing_logs - Full type-safe models:
StreamMonitor,StreamMonitorList,TweetEvent,ConnectedEvent,PingEvent,ErrorEvent,DeliveryLog,BillingLog WebSocketStreamErrorexception for stream connection failures
0.1.1 - 2024-12-27
- Fixed Python 3.10 compatibility for StrEnum
- Fixed all ruff linting issues
- Fixed mypy type checking errors
- Fixed GitHub badge URLs to correct organization
0.1.0 - 2024-12-27
- Initial release of the ScrapeBadger Python SDK
- Full async support with
httpx - Strongly-typed responses using Pydantic v2
- Twitter API client with 37+ endpoints:
- Tweets: get by ID, search, get replies, retweeters, favoriters
- Users: get by username/ID, followers, following, search
- Lists: get details, members, tweets, search
- Communities: get details, members, moderators, tweets
- Trends: get trends, place-specific trends, available locations
- Geo: search places, get place details
- Automatic pagination with async iterators
- Built-in retry logic with exponential backoff
- Comprehensive exception handling
- Full type hints for IDE support
- API key authentication
- No sensitive data logged