Python and Node.js SDKs
The official clients wrap the REST API with a typed method for every engine, bulk jobs, monitors, webhook verification, typed errors and automatic retries. They use the same API key and credits as raw HTTP calls, and failed calls are still free.
The PyPI and npm releases are rolling out now. If pip install gridlode or npm install gridlode can't find the package yet, call the REST API directly with the examples in the docs; every SDK method maps one-to-one to an endpoint.
| Python | Node.js | |
|---|---|---|
| Package | gridlode on PyPI | gridlode on npm |
| Requires | Python 3.9+ (depends only on httpx) | Node 18+ (no dependencies, uses fetch) |
| Clients | Gridlode, AsyncGridlode | Gridlode (ESM and CommonJS, TypeScript types included) |
Install
pip install gridlodeCreate a key in the dashboard and set it as the GRIDLODE_API_KEY environment variable, or pass it to the client. See Authentication.
Quick start
from gridlode import Gridlode
client = Gridlode() # reads GRIDLODE_API_KEY, or Gridlode(api_key="sk_live_...")
dns = client.domain.dns(domain="gridlode.com", types=["A", "MX"])
print(dns.data["records"])
print(dns.credits_charged, dns.balance)
page = client.url.markdown(url="https://example.com")
serp = client.serp.google(q="best crm for startups", gl="us")
places = client.places.google_maps(q="coffee", location="Austin, TX", limit=40)
reviews = client.reviews.trustpilot(domain="gridlode.com", limit=20)
# Any engine by slug or route, including engines newer than your SDK version
whois = client.run("domain.whois", domain="example.com", max_age=86400)Every engine is a method named after its slug: domain.dns is client.domain.dns() and serp.bing is client.serp.bing(). Python uses snake_case method names and keyword arguments; Node.js uses camelCase method names and a parameters object. Parameter names are the same as in the API reference, and leaving one out uses the API default.
The namespaces are ai, apps, classifieds, code, contacts, domain, ecommerce, jobs, places, realestate, reviews, serp, social, travel, url and video. When a route starts differently from the slug, that prefix works too: client.search is client.serp (/v1/search/…), client.youtube is client.video and client.github is client.code.
Engines marked may be disabled are under legal review and off by default: calls fail with EngineDisabled, without charge, until the engine is enabled for your account. The AI engines (ai.extract, ai.review_insights) are billed by actual usage and never cost more than their max_credits parameter (150 by default).
Engine methods
| Engine | Python | Node.js |
|---|---|---|
| AI Data Extraction | ai.extract() | ai.extract() |
| AI Review Insights | ai.review_insights() | ai.reviewInsights() |
| App Store App Details | apps.app_store_app() | apps.appStoreApp() |
| App Store Search | apps.app_store_search() | apps.appStoreSearch() |
| Google Play App Details | apps.google_play_app() | apps.googlePlayApp() |
| Google Play Search | apps.google_play_search() | apps.googlePlaySearch() |
| AutoScout24 Car Listingsmay be disabled | classifieds.autoscout24() | classifieds.autoscout24() |
| Craigslist Listings Searchmay be disabled | classifieds.craigslist() | classifieds.craigslist() |
| mobile.de Car Listingsmay be disabled | classifieds.mobile_de() | classifieds.mobileDe() |
| OLX Listings Searchmay be disabled | classifieds.olx() | classifieds.olx() |
| GitHub Repository | code.github_repo() | code.githubRepo() |
| GitHub Repository Search | code.github_search() | code.githubSearch() |
| GitHub Trending | code.github_trending() | code.githubTrending() |
| GitHub User & Organization | code.github_user() | code.githubUser() |
| Company Enrichment | contacts.company_profile() | contacts.companyProfile() |
| Company Website Finder | contacts.company_website() | contacts.companyWebsite() |
| Email Checker | contacts.email_check() | contacts.emailCheck() |
| Email Finder | contacts.email_finder() | contacts.emailFinder() |
| Phone Validator | contacts.phone() | contacts.phone() |
| Emails & Contacts from Website | contacts.website() | contacts.website() |
| Domain Authority | domain.authority() | domain.authority() |
| Domain Availability | domain.availability() | domain.availability() |
| Referring Domains (Backlinks) | domain.backlinks() | domain.backlinks() |
| DNS Lookup | domain.dns() | domain.dns() |
| Email Security Audit | domain.email_security() | domain.emailSecurity() |
| IP Lookup (ASN, Network, Hosting) | domain.ip_info() | domain.ipInfo() |
| RDAP Lookup | domain.rdap() | domain.rdap() |
| Reverse Analytics & AdSense ID Lookup | domain.reverse_ids() | domain.reverseIds() |
| Similar Sites | domain.similar_sites() | domain.similarSites() |
| SSL Certificate Check | domain.ssl() | domain.ssl() |
| Subdomain Finder | domain.subdomains() | domain.subdomains() |
| Technology Lookup | domain.tech() | domain.tech() |
| WHOIS Lookup | domain.whois() | domain.whois() |
| Amazon Productmay be disabled | ecommerce.amazon_product() | ecommerce.amazonProduct() |
| Amazon Search Resultsmay be disabled | ecommerce.amazon_search() | ecommerce.amazonSearch() |
| eBay Item Detailsmay be disabled | ecommerce.ebay_item() | ecommerce.ebayItem() |
| eBay Search Resultsmay be disabled | ecommerce.ebay_search() | ecommerce.ebaySearch() |
| Shopify Store Products | ecommerce.shopify_store() | ecommerce.shopifyStore() |
| Walmart Productmay be disabled | ecommerce.walmart_product() | ecommerce.walmartProduct() |
| Walmart Search Resultsmay be disabled | ecommerce.walmart_search() | ecommerce.walmartSearch() |
| Ashby Job Board | jobs.ashby() | jobs.ashby() |
| Company Careers Page Jobs | jobs.company_site() | jobs.companySite() |
| Greenhouse Job Board | jobs.greenhouse() | jobs.greenhouse() |
| Lever Job Board | jobs.lever() | jobs.lever() |
| Angi Pro Searchmay be disabled | places.angi() | places.angi() |
| BBB Business Search & Profiles | places.bbb() | places.bbb() |
| Bing Maps Places Search | places.bing_maps() | places.bingMaps() |
| Google Maps Places Search | places.google_maps() | places.googleMaps() |
| Google Maps Place Photos | places.google_maps_photos() | places.googleMapsPhotos() |
| Google Maps Place Details | places.google_maps_place() | places.googleMapsPlace() |
| Google Maps Rank Grid | places.google_maps_rank() | places.googleMapsRank() |
| Yellow Pages Business Search | places.yellowpages() | places.yellowpages() |
| Redfin Property Detailsmay be disabled | realestate.redfin() | realestate.redfin() |
| Zillow Property Detailsmay be disabled | realestate.zillow_property() | realestate.zillowProperty() |
| Zillow Listings Searchmay be disabled | realestate.zillow_search() | realestate.zillowSearch() |
| App Store Reviews | reviews.app_store() | reviews.appStore() |
| Booking.com Hotel Reviewsmay be disabled | reviews.booking() | reviews.booking() |
| Google Maps Reviews | reviews.google_maps() | reviews.googleMaps() |
| Google Play Reviews | reviews.google_play() | reviews.googlePlay() |
| Tripadvisor Reviews & Business Infomay be disabled | reviews.tripadvisor() | reviews.tripadvisor() |
| Trustpilot Reviews | reviews.trustpilot() | reviews.trustpilot() |
| Baidu Search | serp.baidu() | serp.baidu() |
| Bing Search | serp.bing() | serp.bing() |
| Bing Images Search | serp.bing_images() | serp.bingImages() |
| Bing News Search | serp.bing_news() | serp.bingNews() |
| Bing Videos Search | serp.bing_videos() | serp.bingVideos() |
| Brave Search | serp.brave() | serp.brave() |
| Custom Search API Replacement | serp.cse() | serp.cse() |
| DuckDuckGo Search | serp.duckduckgo() | serp.duckduckgo() |
| Google Search | serp.google() | serp.google() |
| Google AI Mode | serp.google_ai_mode() | serp.googleAiMode() |
| Google Autocomplete | serp.google_autocomplete() | serp.googleAutocomplete() |
| Google Books | serp.google_books() | serp.googleBooks() |
| Google Financemay be disabled | serp.google_finance() | serp.googleFinance() |
| Google Images Search | serp.google_images() | serp.googleImages() |
| Google Newsmay be disabled | serp.google_news() | serp.googleNews() |
| Google Patent Details | serp.google_patent() | serp.googlePatent() |
| Google Patents Search | serp.google_patents() | serp.googlePatents() |
| Google Scholarmay be disabled | serp.google_scholar() | serp.googleScholar() |
| Google Trends | serp.google_trends() | serp.googleTrends() |
| Google Trends Trending Now | serp.google_trends_trending() | serp.googleTrendsTrending() |
| Naver Search | serp.naver() | serp.naver() |
| Yahoo Search | serp.yahoo() | serp.yahoo() |
| Yandex Search | serp.yandex() | serp.yandex() |
| Facebook Pagemay be disabled | social.facebook_page() | social.facebookPage() |
| Google Ads Transparency Centermay be disabled | social.google_ads() | social.googleAds() |
| Instagram Profilemay be disabled | social.instagram_profile() | social.instagramProfile() |
| Meta Ad Librarymay be disabled | social.meta_ads() | social.metaAds() |
| Pinterest Profile & Board | social.pinterest_profile() | social.pinterestProfile() |
| TikTok Ad Librarymay be disabled | social.tiktok_ads() | social.tiktokAds() |
| TikTok Profile | social.tiktok_profile() | social.tiktokProfile() |
| TikTok Video | social.tiktok_video() | social.tiktokVideo() |
| Airbnb Listing Details & Reviewsmay be disabled | travel.airbnb_listing() | travel.airbnbListing() |
| Airbnb Stays Searchmay be disabled | travel.airbnb_search() | travel.airbnbSearch() |
| Google Flights Searchmay be disabled | travel.google_flights() | travel.googleFlights() |
| Google Hotels Searchmay be disabled | travel.google_hotels() | travel.googleHotels() |
| Broken Link Checker | url.broken_links() | url.brokenLinks() |
| Website Crawler (Markdown for RAG) | url.crawl() | url.crawl() |
| GEO / AI-Readiness Audit | url.geo_audit() | url.geoAudit() |
| HTTP Headers & Security Check | url.headers() | url.headers() |
| Page HTML | url.html() | url.html() |
| Keyword Density | url.keyword_density() | url.keywordDensity() |
| Page Links Extractor | url.links() | url.links() |
| URL to Markdown | url.markdown() | url.markdown() |
| Page Metadata & SEO Tags | url.metadata() | url.metadata() |
| PageSpeed & Core Web Vitals Test | url.pagespeed() | url.pagespeed() |
| Article Extractor & Readability | url.readability() | url.readability() |
| Screenshot | url.screenshot() | url.screenshot() |
| Sitemap Parser | url.sitemap() | url.sitemap() |
| YouTube Channel | video.youtube_channel() | video.youtubeChannel() |
| YouTube Comments | video.youtube_comments() | video.youtubeComments() |
| YouTube Search | video.youtube_search() | video.youtubeSearch() |
| YouTube Transcript | video.youtube_transcript() | video.youtubeTranscript() |
| YouTube Video Details | video.youtube_video() | video.youtubeVideo() |
The response
Engine methods return the standard response envelope. In Python it is a dict with shortcuts: res.data, res.request_id, res.credits_charged, res.balance, res.cached and res.parameters. In Node.js it is a typed object (EngineResponse<T>) where you read res.data, res.credits and res.cache directly.
Pass max_age (Python) or { maxAge } in the options argument (Node.js), in seconds, to accept a cached answer at a discount. See Credits and billing.
client.account() # {"account_id": ..., "credits": {"available": 950.0, "held": 0.0}}
client.engines() # every engine with route, credits, unit and params_schemaBulk jobs
Run one engine over up to 10,000 inputs in the background. wait polls with a growing interval until the job is done, failed or cancelled; iter_results / iterResults yields one item per input with its params, status, data, error_code and credits_charged. See Bulk jobs and webhooks for limits and billing.
job = client.tasks.create(
"domain.tech",
inputs=["shopify.com", "wordpress.org", "vercel.com"],
field="domain",
name="Competitor stacks",
webhook_url="https://example.com/hooks/gridlode", # optional
)
secret = job.get("webhook_secret") # returned once; store it
task = client.tasks.wait(job, timeout=900) # polls with backoff until done, failed or cancelled
for item in client.tasks.iter_results(task):
if item["status"] == "success":
print(item["params"]["domain"], len(item["data"]["technologies"]))
else:
print(item["params"]["domain"], "failed:", item["error_code"])
csv_text = client.tasks.results(task, format="csv")Send full parameter objects with items instead of inputs, and use tasks.get, tasks.list and tasks.cancel to manage jobs.
Monitors
A monitor re-runs one engine on a schedule (every 60 minutes or more), keeps a snapshot of the values you watch and records events: change when they differ, alert when a condition starts to hold, recovered when it clears, and error. Events are sent to a signed webhook and, unless you turn it off with notify_email, by email. Each run is billed like a normal request, and create returns estimated_credits_per_month.
client.monitors.templates() # domain_expiry, ssl_expiry, dns_change, email_security, page_change, serp_rank, app_store_reviews, google_play_reviews, new_reviews
# From a template: alert 21 days before the certificate expires
ssl = client.monitors.create(
template="ssl_expiry", target="gridlode.com", options={"days": 21},
webhook_url="https://example.com/hooks/gridlode",
)
secret = ssl.get("webhook_secret") # returned once; store it
# From any engine: what to run, which values to compare, when to alert
rdap = client.monitors.create(
engine="domain.rdap",
params={"domain": "example.com"},
watch=["dates.expires", "registrar.name", "nameservers"],
conditions=[{"path": "dates.expires", "op": "days_until_lt", "value": 30, "label": "Expires within 30 days"}],
interval_minutes=1440,
)
client.monitors.run(rdap) # check now
client.monitors.update(rdap, status="paused") # name, status, interval_minutes, notify_email, webhook_url
client.monitors.events(rdap, limit=20) # or client.monitors.events() for every monitor
client.monitors.history(rdap) # snapshots of the watched values
client.monitors.delete(rdap)Watch expressions pick values out of the engine's result: a.b.c (a dotted path, list indexes allowed), a[].b (a value across a list), @hash(path), @rank(array, field, needle), @ids(array, field) and @count(path). Condition operators are lt, lte, gt, gte, eq, ne, days_until_lt, contains, not_contains, is_true and is_false.
Verify webhooks
Jobs send task.completed, task.failed or task.cancelled with {"event":...,"task":{...}}. Monitors send monitor.change, monitor.alert, monitor.recovered or monitor.error with the monitor, a summary and the changes. Every delivery is signed with that job's or monitor's webhook_secret in the X-Gridlode-Signature header (t=timestamp,v1=hex). Verify the raw request body, not re-serialized JSON. Signatures older than five minutes are rejected by default.
from flask import Flask, abort, request
from gridlode import webhooks, WebhookVerificationError
app = Flask(__name__)
@app.post("/hooks/gridlode")
def gridlode_hook():
try:
event = webhooks.construct_event(
request.get_data(), # the raw body, not parsed JSON
request.headers.get("X-Gridlode-Signature"),
WEBHOOK_SECRET, # whsec_... from tasks or monitors.create()
)
except WebhookVerificationError:
abort(400)
if event["event"] == "task.completed":
download_results(event["task"]["id"])
elif event["event"] == "monitor.alert":
notify_on_call(event["monitor"]["name"], event["summary"])
return "", 204Use webhooks.verify(payload, header, secret) to get true or false instead of an exception, and webhooks.sign(payload, secret) to build a valid header when testing your endpoint locally.
Errors
Failed calls raise (Python) or reject with (Node.js) a subclass of APIError that carries the HTTP status, the error type, code, message, details and the request_id. All errors share the base class GridlodeError. See Errors for every code.
| Class | HTTP | When |
|---|---|---|
InvalidRequest | 400 | A parameter is missing or invalid. details lists the fields. |
AuthenticationError | 401 | Missing, malformed or revoked key. |
InsufficientCredits | 402 | Balance too low or monthly spend cap reached. |
PermissionDenied | 403 | Blocked by robots.txt, or account suspended. |
NotFound | 404 | Unknown endpoint, task or monitor. |
Conflict | 409 | Wrong state, for example cancelling a finished job. |
TargetError | 424 | The target site or registry failed, refused or timed out. |
RateLimited | 429 | Too many requests. retry_after / retryAfter holds Retry-After in seconds. |
ServerError | 5xx | Temporary problem on our side. You were not charged. |
EngineDisabled | 503 | The engine is switched off for your account, for example one under legal review. Not retried. |
APIConnectionError, APITimeoutError | none | The API could not be reached, or did not answer in time. |
from gridlode import APIError, InsufficientCredits, RateLimited, TargetError
try:
meta = client.url.metadata(url="https://example.com")
except TargetError as err: # 424: the site failed or refused; not charged
print("site failed:", err.code)
except InsufficientCredits: # 402
print("top up your balance")
except RateLimited as err: # 429 after the automatic retries
print("slow down, retry in", err.retry_after, "s")
except APIError as err: # anything else from the API
print(err.status_code, err.code, err.message, err.request_id)Retries and timeouts
Engine calls, the search endpoint, job creation, monitors and the MCP server share your key's rate limit. Both clients retry HTTP 429 (waiting for Retry-After, up to 60 seconds), 500, 502, 503, 504 and connection failures, by default twice, with jittered exponential backoff. A request that may already have reached the server, such as one that timed out while waiting for the answer, is retried only for GET, so an engine call, job or monitor is never created twice. Target errors (424) and disabled engines are not retried automatically; retry target errors yourself if the site is likely to recover. See Rate limits.
client = Gridlode(timeout=120, max_retries=4) # seconds
fast = client.with_options(timeout=10, max_retries=0) # shares the connection poolAsync Python
import asyncio
from gridlode import AsyncGridlode
async def main():
async with AsyncGridlode() as client:
domains = ["stripe.com", "shopify.com", "vercel.com"]
results = await asyncio.gather(*(client.domain.rdap(domain=d) for d in domains))
for d, r in zip(domains, results):
print(d, r.data.get("registrar"))
asyncio.run(main())Configuration
| Python | Node.js | Environment variable | Default |
|---|---|---|---|
api_key | apiKey | GRIDLODE_API_KEY | required |
base_url | baseUrl | GRIDLODE_BASE_URL | https://api.gridlode.com |
timeout | timeout | 90 s (Python) / 90000 ms (Node.js) | |
max_retries | maxRetries | 2 | |
http_client | fetch | a new httpx client / global fetch |
No SDK for your language? Every engine is a plain HTTPS call, and the OpenAPI spec can generate a client. For no-code tools, see Integrations; for AI agents, the MCP server.