opencode-skills-collection 3.0.37 → 3.0.38

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 (73) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +13 -1
  2. package/bundled-skills/2slides-ppt-generator/SKILL.md +786 -0
  3. package/bundled-skills/2slides-ppt-generator/references/api-reference.md +499 -0
  4. package/bundled-skills/2slides-ppt-generator/references/mcp-integration.md +282 -0
  5. package/bundled-skills/2slides-ppt-generator/references/pricing.md +195 -0
  6. package/bundled-skills/2slides-ppt-generator/scripts/api_constants.py +87 -0
  7. package/bundled-skills/2slides-ppt-generator/scripts/create_pdf_slides.py +159 -0
  8. package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +157 -0
  9. package/bundled-skills/2slides-ppt-generator/scripts/generate_narration.py +197 -0
  10. package/bundled-skills/2slides-ppt-generator/scripts/generate_slides.py +247 -0
  11. package/bundled-skills/2slides-ppt-generator/scripts/get_job_status.py +106 -0
  12. package/bundled-skills/2slides-ppt-generator/scripts/search_themes.py +137 -0
  13. package/bundled-skills/anti-sycophancy/README.md +86 -0
  14. package/bundled-skills/anti-sycophancy/SKILL.md +40 -0
  15. package/bundled-skills/antigravity-agent-manager/SKILL.md +112 -0
  16. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  17. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  18. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  19. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  20. package/bundled-skills/docs/sources/sources.md +1 -0
  21. package/bundled-skills/docs/users/bundles.md +1 -1
  22. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  23. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  24. package/bundled-skills/docs/users/getting-started.md +1 -1
  25. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  26. package/bundled-skills/docs/users/usage.md +4 -4
  27. package/bundled-skills/docs/users/visual-guide.md +4 -4
  28. package/bundled-skills/event-staffing-compliance/SKILL.md +91 -0
  29. package/bundled-skills/event-staffing-ordering/SKILL.md +119 -0
  30. package/bundled-skills/examprep-ai/SKILL.md +446 -0
  31. package/bundled-skills/hasdata/SKILL.md +107 -0
  32. package/bundled-skills/hasdata/references/code-recipes.md +150 -0
  33. package/bundled-skills/hasdata/references/ecommerce.md +116 -0
  34. package/bundled-skills/hasdata/references/jobs.md +111 -0
  35. package/bundled-skills/hasdata/references/local-business.md +145 -0
  36. package/bundled-skills/hasdata/references/real-estate.md +84 -0
  37. package/bundled-skills/hasdata/references/scraper-jobs.md +252 -0
  38. package/bundled-skills/hasdata/references/search.md +154 -0
  39. package/bundled-skills/hasdata/references/travel.md +202 -0
  40. package/bundled-skills/hasdata/references/web-scraping.md +159 -0
  41. package/bundled-skills/hasdata/references/youtube.md +186 -0
  42. package/bundled-skills/hasdata-cli/SKILL.md +169 -0
  43. package/bundled-skills/hasdata-cli/references/all-commands.md +107 -0
  44. package/bundled-skills/hasdata-cli/references/ecommerce.md +106 -0
  45. package/bundled-skills/hasdata-cli/references/enrichment.md +227 -0
  46. package/bundled-skills/hasdata-cli/references/jobs.md +84 -0
  47. package/bundled-skills/hasdata-cli/references/local-business.md +123 -0
  48. package/bundled-skills/hasdata-cli/references/real-estate.md +126 -0
  49. package/bundled-skills/hasdata-cli/references/search.md +122 -0
  50. package/bundled-skills/hasdata-cli/references/travel.md +102 -0
  51. package/bundled-skills/hasdata-cli/references/web-scraping.md +181 -0
  52. package/bundled-skills/hasdata-cli/references/youtube.md +145 -0
  53. package/bundled-skills/linkedin-content-generator/SKILL.md +492 -0
  54. package/bundled-skills/linkedin-content-generator/scripts/generate_calendar.py +82 -0
  55. package/bundled-skills/linkedin-content-generator/scripts/generate_carousel.py +69 -0
  56. package/bundled-skills/linkedin-content-generator/scripts/generate_newsletter.py +64 -0
  57. package/bundled-skills/linkedin-content-generator/scripts/generate_post.py +77 -0
  58. package/bundled-skills/linkedin-content-generator/scripts/memory.md +49 -0
  59. package/bundled-skills/linkedin-content-generator/scripts/memory_manager.py +134 -0
  60. package/bundled-skills/linkedin-content-generator/scripts/utils.py +96 -0
  61. package/bundled-skills/permission-manager/README.md +22 -0
  62. package/bundled-skills/permission-manager/SKILL.md +54 -0
  63. package/bundled-skills/skill-suggester/README.md +14 -0
  64. package/bundled-skills/skill-suggester/SKILL.md +69 -0
  65. package/bundled-skills/smart-git-automation/README.md +31 -0
  66. package/bundled-skills/smart-git-automation/SKILL.md +96 -0
  67. package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +3 -1
  68. package/bundled-skills/vercel-optimize/lib/render-report.mjs +2 -2
  69. package/bundled-skills/vercel-optimize/lib/util.mjs +7 -0
  70. package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +2 -7
  71. package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +2 -1
  72. package/package.json +1 -1
  73. package/skills_index.json +268 -0
@@ -0,0 +1,150 @@
1
+ # Code recipes — wiring HasData into your code
2
+
3
+ ## Ground rules
4
+
5
+ - **Base URL:** `https://api.hasdata.com`. Header `x-api-key` on every request.
6
+ - **Methods:** Scraper APIs are `GET`; Web Scraping is `POST`; Scraper Jobs use `POST` (submit) + `GET` (status/results) + `DELETE` (stop).
7
+ - **Key handling:** read from env (`HASDATA_API_KEY`). Never hardcode, never log.
8
+ - **Timeouts:** **client timeout ≥ 300 s.** HasData's deadline is 300 s; shorter clients get phantom failures while still being billed.
9
+ - **Retries:** `429` and `5xx` only with exponential backoff + jitter. Never retry `4xx`.
10
+ - **Concurrency:** cap at plan limit. Free tier = 1.
11
+ - **Success signal:** sync APIs require `body.requestMetadata.status === "ok"`. HTTP 200 alone isn't enough.
12
+
13
+ ## Status codes
14
+
15
+ | Code | Meaning | Action |
16
+ |---|---|---|
17
+ | 200 + `status:"ok"` | OK | Use body |
18
+ | 401 | Bad/missing key | Fix — don't retry |
19
+ | 403 | Quota exhausted | Don't retry |
20
+ | 429 | Concurrency cap | Backoff + retry |
21
+ | 500 | Server error | Retry |
22
+
23
+ ## Python — minimal client
24
+
25
+ ```python
26
+ import os, requests
27
+
28
+ class HasData:
29
+ BASE = "https://api.hasdata.com"
30
+
31
+ def __init__(self, api_key=None, timeout=300):
32
+ self.s = requests.Session()
33
+ self.s.headers["x-api-key"] = api_key or os.environ["HASDATA_API_KEY"]
34
+ self.timeout = timeout
35
+
36
+ def get(self, path, **params):
37
+ r = self.s.get(f"{self.BASE}{path}", params=params, timeout=self.timeout)
38
+ r.raise_for_status()
39
+ body = r.json()
40
+ if body.get("requestMetadata", {}).get("status") != "ok":
41
+ raise RuntimeError(f"hasdata not-ok: {body.get('requestMetadata')}")
42
+ return body
43
+
44
+ def post(self, path, body):
45
+ r = self.s.post(f"{self.BASE}{path}", json=body, timeout=self.timeout)
46
+ r.raise_for_status()
47
+ return r.json()
48
+
49
+ hd = HasData()
50
+ serp = hd.get("/scrape/google/serp", q="coffee", num=20)["organicResults"]
51
+ md = hd.post("/scrape/web", {"url": "https://example.com", "outputFormat": ["markdown"]})["markdown"]
52
+ ```
53
+
54
+ ## Python — retry + bounded concurrency
55
+
56
+ ```python
57
+ import time, random
58
+ from concurrent.futures import ThreadPoolExecutor, as_completed
59
+ from requests import HTTPError
60
+
61
+ def with_retry(fn, attempts=5, base=1.0, cap=60.0):
62
+ for i in range(attempts):
63
+ try:
64
+ return fn()
65
+ except HTTPError as e:
66
+ code = e.response.status_code
67
+ if code == 429 or 500 <= code < 600:
68
+ time.sleep(min(cap, base * 2 ** i) + random.random())
69
+ continue
70
+ raise
71
+ raise RuntimeError("retry exhausted")
72
+
73
+ def scrape_many(urls, workers=5):
74
+ out = {}
75
+ with ThreadPoolExecutor(max_workers=workers) as ex:
76
+ futs = {ex.submit(lambda u=u: hd.post("/scrape/web", {"url": u, "outputFormat": ["markdown"]})): u
77
+ for u in urls}
78
+ for f in as_completed(futs):
79
+ try:
80
+ out[futs[f]] = f.result().get("markdown")
81
+ except Exception as e:
82
+ out[futs[f]] = e
83
+ return out
84
+ ```
85
+
86
+ Cap `workers` at your plan's concurrency — anything higher just generates `429`s.
87
+
88
+ ## TypeScript — minimal client
89
+
90
+ ```typescript
91
+ const BASE = "https://api.hasdata.com";
92
+ const KEY = process.env.HASDATA_API_KEY!;
93
+
94
+ async function get<T = any>(path: string, params: Record<string, string | number> = {}): Promise<T> {
95
+ const qs = new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)]));
96
+ const r = await fetch(`${BASE}${path}?${qs}`, {
97
+ headers: { "x-api-key": KEY },
98
+ signal: AbortSignal.timeout(300_000),
99
+ });
100
+ if (!r.ok) throw new Error(`HasData ${r.status} ${await r.text()}`);
101
+ const body = await r.json() as any;
102
+ if (body?.requestMetadata?.status && body.requestMetadata.status !== "ok") {
103
+ throw new Error(`HasData not-ok: ${JSON.stringify(body.requestMetadata)}`);
104
+ }
105
+ return body as T;
106
+ }
107
+
108
+ async function post<T = any>(path: string, body: unknown): Promise<T> {
109
+ const r = await fetch(`${BASE}${path}`, {
110
+ method: "POST",
111
+ headers: { "x-api-key": KEY, "Content-Type": "application/json" },
112
+ body: JSON.stringify(body),
113
+ signal: AbortSignal.timeout(300_000),
114
+ });
115
+ if (!r.ok) throw new Error(`HasData ${r.status} ${await r.text()}`);
116
+ return r.json() as Promise<T>;
117
+ }
118
+
119
+ // Bounded concurrency, no deps
120
+ async function pool<T, R>(items: T[], n: number, fn: (x: T) => Promise<R>) {
121
+ const out: R[] = []; let i = 0;
122
+ await Promise.all(Array.from({ length: n }, async () => {
123
+ while (i < items.length) { const k = i++; out[k] = await fn(items[k]); }
124
+ }));
125
+ return out;
126
+ }
127
+ ```
128
+
129
+ ## Pagination cheat sheet
130
+
131
+ | Endpoint family | Pagination |
132
+ |---|---|
133
+ | Google SERP / Light SERP / Bing | `start` + `num` (max 100) |
134
+ | Google Maps Search | `start` (steps of 20) |
135
+ | Yelp Search | `start` (steps of 10) |
136
+ | Google Maps Reviews / Glassdoor / Airbnb | `nextPageToken` |
137
+ | Indeed / YellowPages / Amazon Search | `start` or `page` |
138
+ | Shopify Products | `page` (with `limit` ≤ 250) |
139
+ | Scraper-Job results | `page` + `limit` (max 100) until `meta.currentPage >= meta.lastPage` |
140
+
141
+ ## Pre-ship checklist
142
+
143
+ - [ ] Key from env, never logged.
144
+ - [ ] All HTTP timeouts ≥ 300 s.
145
+ - [ ] `requestMetadata.status === "ok"` checked on every sync response.
146
+ - [ ] Backoff on 429 + 5xx; never on 4xx.
147
+ - [ ] Concurrency capped at plan limit.
148
+ - [ ] Job `id` (from submit response) persisted to durable storage immediately.
149
+ - [ ] Webhooks paired with polling fallback.
150
+ - [ ] Result files downloaded immediately on `scraper.job.finished`.
@@ -0,0 +1,116 @@
1
+ # E-commerce APIs — Amazon & Shopify
2
+
3
+ | Endpoint | Returns |
4
+ |---|---|
5
+ | `/scrape/amazon/product` | Single product (price, ratings, variants, other sellers, A+) |
6
+ | `/scrape/amazon/search` | Search results (sponsored + organic) |
7
+ | `/scrape/amazon/seller` | Seller profile |
8
+ | `/scrape/amazon/seller-products` | Seller catalog |
9
+ | `/scrape/shopify/products` | Products from any Shopify store |
10
+ | `/scrape/shopify/collections` | Collections from any Shopify store |
11
+
12
+ All synchronous `GET`.
13
+
14
+ ## Amazon Product
15
+
16
+ ```python
17
+ import requests
18
+
19
+ resp = requests.get(
20
+ "https://api.hasdata.com/scrape/amazon/product",
21
+ headers={"x-api-key": API_KEY},
22
+ params={"asin": "B0DHJ7SBDR", "domain": "www.amazon.com", "otherSellers": "true"},
23
+ timeout=300,
24
+ )
25
+ ```
26
+
27
+ | Param | Notes |
28
+ |---|---|
29
+ | `asin` | **Required.**. |
30
+ | `domain` | `www.amazon.com` (default), `.co.uk`, `.de`, `.co.jp`, … |
31
+ | `language` | Locale per domain. |
32
+ | `deliveryZip` | Affects shipping/availability fields. |
33
+ | `shippingLocation` | 2-letter country code. |
34
+ | `otherSellers` | `true` (default) to include other-seller block. |
35
+
36
+ Response: top-level `requestMetadata` + `product`. The `product` object's keys (verified live): `asin`, `url`, `title`, `brand`, `isAvailable`, `primaryFeatures`, `features`, `featureBullets`, `description`, `badges`, `breadcrumbs`, `whatIsInTheBox`, `variants`, `totalImages`, `primaryImage`, `images`, `descriptionImages`, `totalVideos`, `primaryVideo`, `videos`, `specification`, `reviewsInfo` (rating + count + sample reviews live here, not at the root). Pricing fields are surfaced via `variants` and `specification`.
37
+
38
+ ## Amazon Search
39
+
40
+ ```python
41
+ params = {"q": "mechanical keyboard", "domain": "www.amazon.com", "page": 1}
42
+ ```
43
+
44
+ Params: `q` (required), `domain`, `language`, `page`, `deliveryZip`, `shippingLocation`, `sortBy`.
45
+
46
+ ## Amazon Seller / Seller Products
47
+
48
+ ```python
49
+ profile = requests.get(
50
+ "https://api.hasdata.com/scrape/amazon/seller",
51
+ headers={"x-api-key": API_KEY},
52
+ params={"sellerId": "A1MNOPQR", "domain": "www.amazon.com"},
53
+ timeout=300,
54
+ ).json()
55
+
56
+ catalog = requests.get(
57
+ "https://api.hasdata.com/scrape/amazon/seller-products",
58
+ headers={"x-api-key": API_KEY},
59
+ params={"sellerId": "A1MNOPQR", "page": 1},
60
+ timeout=300,
61
+ ).json()
62
+ ```
63
+
64
+ Use cases: counterfeit detection, MAP enforcement, competitor catalog mirroring.
65
+
66
+ ## Shopify Products
67
+
68
+ Works on **any** Shopify storefront with no authentication.
69
+
70
+ ```python
71
+ def shopify_all(store_url):
72
+ page, out = 1, []
73
+ while True:
74
+ batch = requests.get(
75
+ "https://api.hasdata.com/scrape/shopify/products",
76
+ headers={"x-api-key": API_KEY},
77
+ params={"url": store_url, "page": page, "limit": 250},
78
+ timeout=300,
79
+ ).json().get("products", [])
80
+ if not batch:
81
+ return out
82
+ out.extend(batch)
83
+ page += 1
84
+ ```
85
+
86
+ | Param | Notes |
87
+ |---|---|
88
+ | `url` | **Required.** Storefront URL. |
89
+ | `limit` | 1–250, default `1`. **Bump to 250** for catalog work. |
90
+ | `page` | 1-indexed. |
91
+ | `collection` | Collection handle filter. |
92
+
93
+ `/scrape/shopify/collections` has the same shape and returns the collection list.
94
+
95
+ ## Patterns
96
+
97
+ ### Cross-merchant price comparison
98
+
99
+ ```python
100
+ a = requests.get("https://api.hasdata.com/scrape/amazon/search",
101
+ headers={"x-api-key": API_KEY},
102
+ params={"q": query}, timeout=300).json()
103
+ g = requests.get("https://api.hasdata.com/scrape/google/shopping",
104
+ headers={"x-api-key": API_KEY},
105
+ params={"q": query, "gl": "us"}, timeout=300).json()
106
+ ```
107
+
108
+ ### Reviews & bestsellers go through Scraper Jobs
109
+
110
+ The Product API only includes a sample of reviews. For all reviews use the `amazon-product-reviews` Scraper Job. For bestseller ranks use `amazon-bestsellers` — there's no synchronous API. See `scraper-jobs.md`.
111
+
112
+ ## Gotchas
113
+
114
+ - **Same ASIN ≠ same product across `domain`s.** `.com` vs `.co.uk` can differ.
115
+ - **`deliveryZip` changes availability.** Pass it when stock matters; omit for spec-only scrapes.
116
+ - **Shopify `limit` defaults to 1** — always set 250 for catalog crawls.
@@ -0,0 +1,111 @@
1
+ # Jobs APIs — Indeed & Glassdoor
2
+
3
+ | Endpoint | Returns |
4
+ |---|---|
5
+ | `/scrape/indeed/listing` | Indeed search results |
6
+ | `/scrape/indeed/job` | Single Indeed job detail |
7
+ | `/scrape/glassdoor/listing` | Glassdoor search results |
8
+ | `/scrape/glassdoor/job` | Single Glassdoor job (incl. salary band, company snippet) |
9
+
10
+ All synchronous `GET`.
11
+
12
+ ## Indeed Listing
13
+
14
+ ```python
15
+ import requests
16
+
17
+ resp = requests.get(
18
+ "https://api.hasdata.com/scrape/indeed/listing",
19
+ headers={"x-api-key": API_KEY},
20
+ params={
21
+ "keyword": "software engineer",
22
+ "location": "New York, NY",
23
+ "sort": "date",
24
+ "domain": "www.indeed.com",
25
+ "start": 0,
26
+ },
27
+ timeout=300,
28
+ )
29
+ ```
30
+
31
+ | Param | Notes |
32
+ |---|---|
33
+ | `keyword` | **Required.** |
34
+ | `location` | **Required.** |
35
+ | `sort` | `date`, `relevance` (default). |
36
+ | `domain` | Country site — `www.indeed.com`, `uk.indeed.com`, `de.indeed.com`. |
37
+ | `start` | Offset, **steps of 10**. |
38
+
39
+ Response: `jobs` array with `title`, `company`, `location`, `salary`, `description`, `postedAt`, `link`, `jobKey`. Salary is free-form string — parse with regex.
40
+
41
+ ## Indeed Job
42
+
43
+ Pass `jobKey` from listing → returns full description, requirements, benefits, company URL.
44
+
45
+ ## Glassdoor Listing & Job
46
+
47
+ ```python
48
+ params = {"keyword": "software engineer", "location": "New York, NY", "sort": "recent"}
49
+ # pagination: pass back nextPageToken
50
+ ```
51
+
52
+ | Param | Notes |
53
+ |---|---|
54
+ | `keyword`, `location` | **Required.** |
55
+ | `sort` | `recent` (default), `relevant`. |
56
+ | `domain` | Country site. |
57
+ | `nextPageToken` | Cursor pagination. |
58
+
59
+ ## Patterns
60
+
61
+ ### Salary band
62
+
63
+ ```python
64
+ import re, statistics
65
+
66
+ def salary_band(role, location):
67
+ page = requests.get(
68
+ "https://api.hasdata.com/scrape/indeed/listing",
69
+ headers={"x-api-key": API_KEY},
70
+ params={"keyword": role, "location": location}, timeout=300,
71
+ ).json()
72
+ nums = [int(m.replace(",", ""))
73
+ for j in page.get("jobs", [])
74
+ for m in re.findall(r"\$([\d,]+)", j.get("salary") or "")]
75
+ if not nums: return None
76
+ return {"n": len(nums), "median": statistics.median(nums)}
77
+ ```
78
+
79
+ ### Hiring velocity by company
80
+
81
+ ```python
82
+ from collections import Counter
83
+
84
+ page = indeed_listing(role, loc, sort="date")
85
+ Counter(j.get("company") for j in page.get("jobs", []))
86
+ ```
87
+
88
+ Run weekly; sustained increases often precede earnings/PR signals.
89
+
90
+ ### Pagination differs
91
+
92
+ ```python
93
+ # Indeed: numeric start
94
+ for p in range(10):
95
+ page = indeed_listing(kw, loc, start=p * 10)
96
+
97
+ # Glassdoor: cursor token
98
+ out, token = [], None
99
+ while True:
100
+ page = glassdoor_listing(kw, loc, next_token=token)
101
+ out.extend(page.get("jobs", []))
102
+ token = page.get("nextPageToken")
103
+ if not token: break
104
+ ```
105
+
106
+ ## Gotchas
107
+
108
+ - **Salary is free-form string.** Always regex-parse.
109
+ - **Indeed = numeric start (10), Glassdoor = token.** Don't mix.
110
+ - **`domain` matters for non-US.** `uk.indeed.com`, `ca.indeed.com`, etc.
111
+ - **Prefer the API + pagination for bulk.** Reach for the matching Scraper Job only when you want webhook-driven fan-out across many keyword × location pairs without managing the polling loop yourself.
@@ -0,0 +1,145 @@
1
+ # Local Business APIs — Google Maps, Yelp, YellowPages
2
+
3
+ | Endpoint | Returns |
4
+ |---|---|
5
+ | `/scrape/google-maps/search` | Search results in a viewport |
6
+ | `/scrape/google-maps/place` | Single place details |
7
+ | `/scrape/google-maps/reviews` | Reviews for a place, paginated |
8
+ | `/scrape/google-maps/photos` | Photo gallery |
9
+ | `/scrape/google-maps/posts` | Owner-published posts (offers, events, announcements) |
10
+ | `/scrape/google-maps/contributor-reviews` | All reviews by a Google reviewer |
11
+ | `/scrape/yelp/search` | Yelp search |
12
+ | `/scrape/yelp/place` | Yelp business detail |
13
+ | `/scrape/yellowpages/search` | YellowPages search |
14
+ | `/scrape/yellowpages/place` | YellowPages business detail |
15
+
16
+ All synchronous `GET`.
17
+
18
+ ## Google Maps Search
19
+
20
+ ```python
21
+ import requests
22
+
23
+ resp = requests.get(
24
+ "https://api.hasdata.com/scrape/google-maps/search",
25
+ headers={"x-api-key": API_KEY},
26
+ params={"q": "Pizza", "ll": "@40.7455,-74.0083,14z"},
27
+ timeout=300,
28
+ )
29
+ ```
30
+
31
+ | Param | Notes |
32
+ |---|---|
33
+ | `q` | **Required.** Free-form query. |
34
+ | `ll` | `@LAT,LNG,ZOOMz` viewport — **lat/lng + zoom, not a city name**. Required for tight pagination. |
35
+ | `domain`, `gl`, `hl` | Standard. |
36
+ | `start` | Pagination offset, **steps of 20**. |
37
+
38
+ Response: `localResults` — each entry has `position`, `title`, `placeId`, `dataId`, `kgmid`, `thumbnail`, `phone`, `address`, `website`, `description`, `workingHours` (object with `timezone` + `days[]`), `openState`, `rating`, `reviews`, `type` + `types[]` (categories), `price`, `priceDescription`, `gpsCoordinates`, `serviceOptions[]`, `extensions` (offerings, accessibility, payments, …), `menu`. Feed `placeId`/`dataId` into `/place` and `/reviews`.
39
+
40
+ ## Google Maps Place
41
+
42
+ ```python
43
+ params = {"placeId": "ChIJFU2bda4SM4cRKSCRyb6pOB8"}
44
+ ```
45
+
46
+ Returns full place detail — coordinates, hours by day, phone, website, popular times, attributes (delivery, dine-in), photo summary.
47
+
48
+ ## Google Maps Reviews
49
+
50
+ ```python
51
+ def reviews(place_id=None, data_id=None, sort_by="newestFirst", token=None):
52
+ params = {}
53
+ if place_id: params["placeId"] = place_id
54
+ if data_id: params["dataId"] = data_id
55
+ if sort_by: params["sortBy"] = sort_by
56
+ if token: params["nextPageToken"] = token
57
+ return requests.get(
58
+ "https://api.hasdata.com/scrape/google-maps/reviews",
59
+ headers={"x-api-key": API_KEY},
60
+ params=params, timeout=300,
61
+ ).json()
62
+ ```
63
+
64
+ | Param | Notes |
65
+ |---|---|
66
+ | `placeId` / `dataId` | Pass one. `dataId` is the hex pair from Maps results. |
67
+ | `sortBy` | `newestFirst`, `highestRating`, `lowestRating`, `mostRelevant`. |
68
+ | `topicId` | Filter by review topic. |
69
+ | `nextPageToken` | Cursor pagination. |
70
+
71
+ ## Google Maps Posts
72
+
73
+ ```python
74
+ resp = requests.get(
75
+ "https://api.hasdata.com/scrape/google-maps/posts",
76
+ headers={"x-api-key": API_KEY},
77
+ params={"placeId": "ChIJ..."}, # or dataId="0x...:0x..."
78
+ timeout=300,
79
+ )
80
+ for p in resp.json().get("posts", []):
81
+ print(p["postedAt"], p["description"][:120], p.get("cta", {}).get("url"))
82
+ ```
83
+
84
+ Either `placeId` **or** `dataId` is required. Optional: `hl` (UI language), `nextPageToken` (cursor pagination). 10 credits/call.
85
+
86
+ Per-post fields (verified live): `postId`, `locationId`, `title`, `description`, `image`, `cta` (`label` + `url`), `createdAt` (ISO), `postedAt` (human-readable), `shareUrl`, `postUrl`. Response top-level: `posts`, `pagination`, `source`, `requestMetadata`.
87
+
88
+ Posts surface current offers, holiday hours, events, and product launches the business is actively promoting. Cheaper signal than the homepage scrape, and `cta.url` is the canonical landing page.
89
+
90
+ ## Yelp & YellowPages
91
+
92
+ ```python
93
+ # Yelp
94
+ params = {"keyword": "McDonald's", "location": "New York, NY", "start": 0} # steps of 10
95
+ # YellowPages
96
+ params = {"keyword": "Plumbers", "location": "New York, NY", "page": 1}
97
+ ```
98
+
99
+ YellowPages is US-only — EU/APAC searches return nothing useful.
100
+
101
+ ## Patterns
102
+
103
+ ### Lead-gen with emails (Maps + Web Scraping)
104
+
105
+ Maps results have website + phone but **not email**. Combine with the Web Scraping API's `extractEmails` only for public business contact pages, legitimate outreach, and workflows that honor opt-out, privacy-law, rate, and terms-of-service constraints:
106
+
107
+ ```python
108
+ leads = []
109
+ for biz in maps_results.get("localResults", []):
110
+ site = biz.get("website")
111
+ if not site: continue
112
+ page = requests.post(
113
+ "https://api.hasdata.com/scrape/web",
114
+ headers={"x-api-key": API_KEY},
115
+ json={"url": site, "extractEmails": True},
116
+ timeout=300,
117
+ ).json()
118
+ leads.append({
119
+ "name": biz["title"],
120
+ "phone": biz.get("phone"),
121
+ "website": site,
122
+ "emails": page.get("extractedEmails") or [],
123
+ })
124
+ ```
125
+
126
+ For higher volume, switch to the `contacts` Scraper Job (see `scraper-jobs.md`) only when you have a legitimate purpose, a compliant outreach process, and rate/opt-out controls.
127
+
128
+ ### New-business discovery
129
+
130
+ Filter Maps by review count `< 5` — usually means recently opened.
131
+
132
+ ```python
133
+ new = [b for b in localResults if (b.get("reviews") or 0) < 5]
134
+ ```
135
+
136
+ ### Multi-location chain mapping
137
+
138
+ Search the brand name; every `localResults` entry is a branch.
139
+
140
+ ## Gotchas
141
+
142
+ - **`ll` is a viewport, not a city.** `@lat,lng,zoom`. Pasting "Brooklyn" fails.
143
+ - **Pagination steps differ.** Maps `start` = +20, Yelp `start` = +10, Maps Reviews uses `nextPageToken`.
144
+ - **`placeId` vs `dataId`** — Place prefers `placeId`; Reviews accepts either.
145
+ - **YellowPages is US-only.**
@@ -0,0 +1,84 @@
1
+ # Real Estate APIs — Zillow, Redfin
2
+
3
+ | Endpoint | Returns |
4
+ |---|---|
5
+ | `/scrape/zillow/listing` | Search results by area + filters |
6
+ | `/scrape/zillow/property` | Single home (history, agent, schools, taxes) |
7
+ | `/scrape/redfin/listing` | Redfin search results |
8
+ | `/scrape/redfin/property` | Single Redfin home |
9
+
10
+ All synchronous `GET`. 5 credits each.
11
+
12
+ For short-term rentals (Airbnb), hotels (Booking), and flights, see `travel.md`.
13
+
14
+ ## Zillow Listing
15
+
16
+ Filter params use **bracketed** keys (`price[min]`, `beds[max]`).
17
+
18
+ ```python
19
+ import requests
20
+
21
+ def zillow_search(keyword, listing_type="forSale", **filters):
22
+ r = requests.get(
23
+ "https://api.hasdata.com/scrape/zillow/listing",
24
+ headers={"x-api-key": API_KEY},
25
+ params={"keyword": keyword, "type": listing_type, **filters},
26
+ timeout=300,
27
+ )
28
+ return r.json()
29
+
30
+ zillow_search("Brooklyn, NY", price={"min": 800000, "max": 2000000})
31
+ zillow_search("33321", "sold", daysOnZillow="6m") # recent comps
32
+ ```
33
+
34
+ `requests` + `axios` serialize nested dicts as `price[min]=…&price[max]=…` automatically. With raw `URLSearchParams`, build the bracketed keys yourself.
35
+
36
+ | Param | Notes |
37
+ |---|---|
38
+ | `keyword` | **Required.** Area string ("New York, NY", zip, neighborhood). |
39
+ | `type` | **Required.** `forSale`, `forRent`, `sold`. |
40
+ | `price[min/max]`, `beds[min/max]`, `baths[min/max]`, `sqft[min/max]` | Range filters. |
41
+ | `daysOnZillow` | `24h`, `7d`, `14d`, `30d`, `90d`, `6m`, `12m`. |
42
+ | `page` | Pagination. |
43
+
44
+ Response: `requestMetadata`, `searchInformation`, **`properties`** (the listings array — not `listings`), `pagination`.
45
+
46
+ ## Zillow Property
47
+
48
+ ```python
49
+ requests.get(
50
+ "https://api.hasdata.com/scrape/zillow/property",
51
+ headers={"x-api-key": API_KEY},
52
+ params={"url": url, "extractAgentEmails": "true"},
53
+ timeout=300,
54
+ )
55
+ ```
56
+
57
+ Takes a full Zillow URL (not zpid). Returns address, lot/sqft/beds/baths, price + tax history, schools, agent block, photos. Agent emails are best-effort.
58
+
59
+ ## Redfin
60
+
61
+ ```python
62
+ # Listing
63
+ params = {"keyword": "33321", "type": "forSale", "page": 1}
64
+ # Property
65
+ params = {"url": "https://www.redfin.com/FL/Tamarac/9...html"}
66
+ ```
67
+
68
+ Same bracketed `price[min]`, `beds[min]`, etc. as Zillow. Zip codes work best for `keyword`.
69
+
70
+ ## Patterns
71
+
72
+ ### Sold comps for ROI
73
+
74
+ ```python
75
+ sold = zillow_search(zip_code, "sold", daysOnZillow="6m").get("properties", [])
76
+ ppsf = [(l["price"] / l["livingArea"]) for l in sold if l.get("livingArea")]
77
+ ```
78
+
79
+ ## Gotchas
80
+
81
+ - **Bracketed query keys** — work with `requests`/`axios`, not raw `URLSearchParams`.
82
+ - **`type=sold` + `daysOnZillow` = comps recipe.** Without `daysOnZillow`, history is unbounded.
83
+ - **Property endpoints take URLs**, not IDs.
84
+ - **Agent emails are best-effort.**