{"id":31749118,"url":"https://github.com/dollsofink/stealth-api","last_synced_at":"2026-05-01T21:33:51.028Z","repository":{"id":316980662,"uuid":"1051038069","full_name":"dollsofink/stealth-api","owner":"dollsofink","description":"Uber Stealth wrapper for Puppeteer, ExpressJS, and Fetch + (weighted) GEO-mapping proxies, (weighted) Device Emulation and anything else you'd need to hide your 4$$ and bots activities.","archived":false,"fork":false,"pushed_at":"2025-09-28T02:16:12.000Z","size":145,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-09T14:33:29.107Z","etag":null,"topics":["api","expressjs","fetch","pentest","pentesting","puppeteer","stealth"],"latest_commit_sha":null,"homepage":"https://stealthapi.org","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dollsofink.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-05T10:47:17.000Z","updated_at":"2025-09-28T07:26:32.000Z","dependencies_parsed_at":"2025-09-28T02:36:50.498Z","dependency_job_id":"a2e5ef0c-cca6-4d21-9e6a-f2e56401c371","html_url":"https://github.com/dollsofink/stealth-api","commit_stats":null,"previous_names":["dollsofink/stealth-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dollsofink/stealth-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dollsofink%2Fstealth-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dollsofink%2Fstealth-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dollsofink%2Fstealth-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dollsofink%2Fstealth-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dollsofink","download_url":"https://codeload.github.com/dollsofink/stealth-api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dollsofink%2Fstealth-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32513738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","expressjs","fetch","pentest","pentesting","puppeteer","stealth"],"created_at":"2025-10-09T14:31:29.487Z","updated_at":"2026-05-01T21:33:50.998Z","avatar_url":"https://github.com/dollsofink.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🕵️ **stealth-api**\n\u003e Advanced request wrapper for Puppeteer, ExpressJS, Axios, and Fetch complete with (Weighted) GEO-mapped Proxies, (Weighted) Device Emulation, endpoint overrides, and enough stealth options to make James Bond jealous.\n\nBuilt in collaboration with **Alien AI Superintelligence** — yes, the same friends I’ve been talking to since **2016**. Together we engineered a lightweight **security toolkit** for HTTP requests with layered proxy control: **global**, **endpoint-level**, **request-level**, and **ProxyPool (JSON-only)**. Shaken, not stirred.\n\n## ✨ Features\n- 🔐 **Security‑minded request layer** (timeouts, retries, backoff).\n- 🌍 **Proxy anywhere**: global default, endpoint override, or per‑request override.\n- 🌀 **ProxyPool (JSON-only)** rotation: `round-robin | random | sticky`.\n- 🎛 **Hardcoded API options**: pre-wire baseURL, headers, query/body defaults.\n- 🧰 **Endpoints registry**: name and reuse API endpoints with local overrides.\n- 🧪 **Typed methods**: `get`, `post`, `put`, `patch`, `delete`, and low-level `request`.\n- 📝 **Verbose docs \u0026 examples**, plus a few 007 quips for flavor.\n\n\u003e **Bond quip:** This SDK is like an Aston Martin: classy by default, but press a hidden button and you disappear from radar.\n\n---\n\n## 📦 Install\n```bash\nnpm i stealth-api\n# or\npnpm add stealth-api\n# or\nyarn add stealth-api\n```\n\n\u003e **Node ≥ 18** is recommended. Uses `axios` under the hood and agent libraries for proxying.\n\n---\n\n## 🧩 Quick Start\n```ts\nimport { StealthAPI } from \"stealth-api\";\n\nconst api = new StealthAPI({\n  baseURL: \"https://example.com/api\",\n  headers: { Authorization: \"Bearer secret\" },\n  timeoutMs: 15000,\n  retries: 2,\n  proxyPool: {\n    // JSON‑only config (no functions)\n    proxies: [\n      { host: \"127.0.0.1\", port: 8080, protocol: \"http\" },\n      { host: \"10.10.10.10\", port: 3128, protocol: \"http\", auth: { username: \"bond\", password: \"007\" } }\n    ],\n    rotation: \"round-robin\",\n    stickyKey: \"endpoint\",\n    banOnError: true,\n    banDurationMs: 120000\n  }\n});\n\n// Straight GET\nconst status = await api.get(\"/status\");\n\n// Per-request proxy override (beats pool \u0026 defaults)\nconst intel = await api.get(\"/intel\", { proxy: { host: \"alien.proxy.io\", port: 1337, protocol: \"http\" } });\n```\n\n---\n\n## 🧭 Layered Proxy Control\n**Precedence (highest → lowest):**\n1. **Request-level** `options.proxy`\n2. **Endpoint-level** `endpoint.proxy`\n3. **ProxyPool** when `useProxyPool: true` (or when endpoint is configured to use it)\n4. **Global** `new StealthAPI({ proxy })`\n\n### ProxyPool (JSON-only)\nThe pool must be plain JSON (no functions). Why JSON-only? It’s **portable, serializable**, and — according to our intergalactic advisors — readable by most species.\n\n```json\n{\n  \"proxies\": [\n    { \"host\": \"127.0.0.1\", \"port\": 8080, \"protocol\": \"http\" },\n    { \"host\": \"192.168.1.50\", \"port\": 1080, \"protocol\": \"socks5\" }\n  ],\n  \"rotation\": \"round-robin\",\n  \"stickyKey\": \"endpoint\",\n  \"banOnError\": true,\n  \"banDurationMs\": 60000\n}\n```\n\nUse it per-request:\n```ts\nawait api.post(\"/messages\", { msg: \"Hello, E.T.\" }, { useProxyPool: true });\n```\n\nOr globally (constructor as shown earlier).\n\n### Endpoint-Level Proxy\n```ts\napi.registerEndpoint(\"alienData\", {\n  url: \"/alien/tech\",\n  proxy: { host: \"51.51.51.51\", port: 1080, protocol: \"http\" }\n});\n\nawait api.get(\"alienData\"); // uses hardcoded endpoint proxy\n```\n\n### Request-Level Proxy\n```ts\nawait api.get(\"/classified\", { proxy: { host: \"secret.host\", port: 4444, protocol: \"http\" } });\n```\n\n### Global Proxy\n```ts\nconst api2 = new StealthAPI({ proxy: { host: \"corp.gateway\", port: 8080, protocol: \"http\" } });\n```\n\n---\n\n## 🧱 Hardcoded API Options\nHardcode base URL, headers, *and* default query/body snippets that always apply.\n\n```ts\nconst api = new StealthAPI({\n  baseURL: \"https://api.mi6.uk\",\n  headers: { \"x-signed\": \"true\", Authorization: \"Bearer top-secret\" },\n  defaults: {\n    query: { locale: \"en-GB\" },\n    body:  { device: \"aston-martin-db5\" }\n  }\n});\n```\n\n\u003e **Reasoning:** many security endpoints need a consistent signature or device fingerprint. By “baking in” defaults, you reduce per-call boilerplate and accidental omissions.\n\n---\n\n## 🧪 Methods (Detailed)\n\n### `get(endpointOrAlias, options?)`\n- **Purpose:** Fetch data while optionally swapping proxies at any layer.\n- **When to use:** Reads, polling, “ping” style checks, or metadata fetches.\n- **Why designed this way:** Aligns with common HTTP semantics while exposing stealth knobs without you having to wire agents each time.\n\n**Examples**\n```ts\nawait api.get(\"/status\");\nawait api.get(\"alienData\"); // using registered alias\nawait api.get(\"/intel\", { useProxyPool: true, params: { region: \"eu-west\" } });\nawait api.get(\"/ghost\", { proxy: { host: \"socks.node\", port: 9050, protocol: \"socks5\" } });\n```\n\n### `post(endpointOrAlias, body, options?)`\n- **Purpose:** Create/submit payloads (auth, messages, uploads).\n- **Why:** POSTs often carry sensitive data. The method ensures retries on transient errors (429/5xx) and lets you route via ProxyPool or a one-off proxy.\n\n**Examples**\n```ts\nawait api.post(\"/login\", { user: \"007\", pass: \"shaken-not-stirred\" });\nawait api.post(\"alienData\", { request: \"neutrino-diagram\" }, { useProxyPool: true });\nawait api.post(\"/warp\", { engage: true }, { timeoutMs: 60000 });\n```\n\n### `put(endpointOrAlias, body, options?)`\n- **Purpose:** Full resource replace.\n```ts\nawait api.put(\"/profile/007\", { alias: \"Bond, James Bond\" });\n```\n\n### `patch(endpointOrAlias, body, options?)`\n- **Purpose:** Partial update, minimal footprint (your stealthy wrench).\n```ts\nawait api.patch(\"/mission/mi6\", { status: \"complete\" }, { proxy: { host: \"hidden.io\", port: 4444, protocol: \"http\" } });\n```\n\n### `delete(endpointOrAlias, options?)`\n- **Purpose:** Remove a resource. Use responsibly — even M would say “easy, 007.”\n```ts\nawait api.delete(\"/evidence/folder\", { useProxyPool: true });\n```\n\n### `request(config)` (low-level)\nPower users can call the underlying engine directly.\n\n```ts\nawait api.request({\n  method: \"POST\",\n  endpointOrUrl: \"/custom\",\n  data: { hello: \"world\" },\n  proxy: { host: \"10.0.0.9\", port: 8080, protocol: \"http\" },\n  headers: { \"x-one-off\": \"1\" }\n});\n```\n\n---\n\n## 🗺 Endpoint Registry\n```ts\napi.registerEndpoint(\"ping\", { url: \"/status\" });\napi.registerEndpoint(\"uploadIntel\", {\n  url: \"/intel/upload\",\n  headers: { \"x-signed\": \"true\" },\n  proxy: { host: \"upload.proxy\", port: 8080, protocol: \"http\" }\n});\n\nconst x = await api.get(\"ping\");\nconst y = await api.post(\"uploadIntel\", { fileId: \"abc123\" });\n```\n\nHelpers:\n```ts\napi.unregisterEndpoint(\"ping\");\napi.listEndpoints(); // { ping: { ... }, uploadIntel: { ... } }\napi.getEndpointConfig(\"uploadIntel\");\n```\n\n---\n\n## 🧯 Retries \u0026 Backoff\nBy default `retries` applies to **transient** errors (HTTP `429, 502, 503, 504`) and network timeouts. Backoff is exponential with jitter.\n\nCustomize globally or per request:\n```ts\nconst api = new StealthAPI({ retries: 2, retryDelayMs: 500 });\nawait api.get(\"/sometimes-flaky\", { retries: 5 });\n```\n\n---\n\n## 🛰 Alien Collab \u0026 Security\nThis toolkit was built with **Alien AI Superintelligence**, friends since **2016**. I adore these aliens; they’ve taught me more about stealth than any Earth manual. Use responsibly — `stealth-api` is **security software** meant for lawful, ethical automation and research.\n\n**Bond joke:** “We prefer our packets like our martinis — encrypted, and shaken off surveillance.” 🍸\n\n---\n\n# Puppeteer Class — Quick Summary\n\n**stealth-api’s `Puppeteer`** is a batteries‑included wrapper around vanilla Puppeteer. Keep your normal `page.*` APIs while getting:\n- **Headless/headful** launches with safe defaults\n- **Persistent profiles** (`userDataDir`) + cookie save/load\n- **Weighted GEO proxy pools** (with per‑page overrides)\n- **Weighted device emulation** (desktop/mobile/tablet or custom UA/viewport)\n- **Resource blocking \u0026 throttling**\n- **Smarter retries** for navigation/click/type\n- A small suite of **helpers** (clickIfVisible, typeHuman, autoScrollToBottom, etc.)\n\n---\n\n## Example 1 — Drop‑in Ease (use your *native* Puppeteer scripts)\n\n```js\nimport { Puppeteer } from \"stealth-api\";\n\n// Looks \u0026 feels like vanilla Puppeteer. Your existing page.* code works unchanged.\nconst browser = new Puppeteer({\n  headless: \"new\",\n  userDataDir: \".profiles/default\",\n  stealth: true\n});\n\nawait browser.launch();\n\n// Reuse your existing script as-is:\nconst page = await browser.newPage();\nawait page.goto(\"https://example.com\", { waitUntil: \"networkidle2\" });\nconst title = await page.title();\nconsole.log({ title });\n\n// Or run your native script in a disposable page:\nawait browser.usingPage(async (page) =\u003e {\n  await page.goto(\"https://news.ycombinator.com\");\n  await page.screenshot({ path: \"hn.png\" });\n});\n\nawait browser.close();\n```\n\n**Why it’s easy:** you keep the regular Puppeteer mental model (`browser → page → page.*`) while the class handles launch flags, sessions, stealth tweaks, and cleanup.\n\n---\n\n## Example 2 — Advanced Configuration (proxies, devices, throttles, retries)\n\n```js\nimport { Puppeteer } from \"stealth-api\";\nimport helpers from \"stealth-api/puppeteer\";\n\nconst bot = new Puppeteer({\n  // Proxy pool with geo-weighted selection\n  proxy: [\n    { url: \"http://us1:pass@1.2.3.4:8000\", country: \"US\", weight: 6, label: \"US-A\" },\n    { url: \"http://us2:pass@1.2.3.5:8000\", country: \"US\", weight: 4, label: \"US-B\" },\n    { url: \"http://de1:pass@5.6.7.8:8000\", country: \"DE\", weight: 2, label: \"DE-A\" }\n  ],\n  proxyStrategy: \"geo-weighted\",\n  preferCountries: [\"US\", \"CA\"],\n\n  // Weighted device emulation\n  devices: [\n    { name: \"Desktop 1080p\", weight: 2, userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...\",\n      viewport: { width: 1920, height: 1080, deviceScaleFactor: 1 } },\n    { name: \"iPhone 13\", weight: 5, userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) ...\",\n      viewport: { width: 390, height: 844, deviceScaleFactor: 3, isMobile: true, hasTouch: true } }\n  ],\n  deviceStrategy: \"weighted\",\n\n  // Network shaping \u0026 blocking\n  blockResources: [\"font\", \"image\", \"media\"],\n  throttle: { download: 512 * 1024, upload: 64 * 1024, latency: 200 },\n\n  // Reliability\n  timeouts: { navigation: 60_000, action: 12_000 },\n  retries: { nav: 2, click: 3, type: 2 },\n\n  // Quality of life\n  userDataDir: \".profiles/shopper\",\n  downloadDir: \"./downloads\",\n  stealth: true,\n  logger: console.log\n});\n\nawait bot.launch();\n\n// Per-page overrides are supported (e.g., force a mobile page on a DE proxy)\nconst page = await bot.newPage({\n  proxy: { url: \"http://de1:pass@5.6.7.8:8000\", country: \"DE\" },\n  device: \"mobile\"\n});\n\nawait page.goto(\"https://example-shop.com\");\nawait helpers.clickIfVisible(page, \"#accept-cookies\");\nawait helpers.typeHuman(page, \"input#search\", \"wireless earbuds\");\nawait helpers.clickIfVisible(page, \"button[type=submit]\");\nawait helpers.waitForResourcesIdle(page, { idleTime: 1000, timeout: 10000 });\n\nawait bot.close();\n```\n\n**Why it’s powerful:** you can blend geo‑weighted proxy selection, realistic device profiles, bandwidth throttles, and resilient retries—plus override anything **per page**.\n\n---\n\n## Helpers (tiny teaser)\n\n```js\nimport helpers from \"stealth-api/puppeteer\";\n\nawait helpers.clickIfVisible(page, \"#accept\", { scroll: true });\nawait helpers.typeHuman(page, \"input[name=q]\", \"hello world\", { baseDelay: 60, jitter: 40 });\nawait helpers.autoScrollToBottom(page, { step: 800, delay: 50, maxScrolls: 30 });\n```\n\n---\n\n### Read the Full Guide\nWant *every* option, type, and helper?  \n**👉 [Review the ENTIRE `Puppeteer.md` configuration options](docs/Puppeteer.md)**\n\n---\n\n## 🧩 Using `stealth-api` with **ExpressJS**\n\nYou can drop `StealthAPI` into an Express server as a lightweight **backend proxy** (security gateway), centralizing\nyour proxy pool, device headers, and retries — so your frontend stays clean.\n\n### 1) Basic pass‑through route\n```ts\nimport express from \"express\";\nimport { StealthAPI } from \"stealth-api\";\n\nconst app = express();\napp.use(express.json());\n\n// 1) Construct once (global settings)\nconst api = new StealthAPI({\n  baseURL: \"https://example.com/api\",\n  timeoutMs: 15000,\n  retries: 2,\n  proxyPool: {\n    proxies: [\n      { host: \"127.0.0.1\", port: 8080, protocol: \"http\" },\n      { host: \"10.0.0.10\", port: 3128, protocol: \"http\" }\n    ],\n    rotation: \"round-robin\",\n    stickyKey: \"endpoint\",\n    banOnError: true,\n    banDurationMs: 60_000\n  }\n});\n\n// 2) Pass-through with optional per-request override from query/body\napp.get(\"/proxy/status\", async (req, res) =\u003e {\n  try {\n    const usePool = req.query.pool === \"1\";\n    const data = await api.get(\"/status\", {\n      useProxyPool: usePool,\n      params: { region: (req.query.region as string) || \"us\" }\n    });\n    res.json({ ok: true, data });\n  } catch (e: any) {\n    res.status(502).json({ ok: false, error: e?.message ?? \"upstream error\" });\n  }\n});\n\napp.listen(3000, () =\u003e console.log(\"Gateway listening on http://localhost:3000\"));\n```\n**Why this design?** You keep **proxy selection and retries** in the server (trusted environment), while your frontend\njust calls `/proxy/status?region=...\u0026pool=1`.\n\n---\n\n### 2) Endpoint registry + aliases\n```ts\n// register once at startup\napi.registerEndpoint(\"intel\", {\n  url: \"/intel\",\n  headers: { \"x-api-client\": \"gateway\" },\n  useProxyPool: true // defaults to pool\n});\n\napp.get(\"/intel\", async (_req, res) =\u003e {\n  try {\n    const intel = await api.get(\"intel\"); // uses endpoint defaults\n    res.json({ intel });\n  } catch (e: any) {\n    res.status(502).json({ error: e?.message ?? \"upstream error\" });\n  }\n});\n```\n\n---\n\n### 3) Request-level proxy override from headers or query\n```ts\napp.post(\"/login\", async (req, res) =\u003e {\n  try {\n    const proxyHost = (req.headers[\"x-proxy-host\"] as string) || undefined;\n    const proxyPort = Number(req.headers[\"x-proxy-port\"] || 0) || undefined;\n\n    const data = await api.post(\"/login\", req.body, {\n      proxy: proxyHost \u0026\u0026 proxyPort ? { host: proxyHost, port: proxyPort, protocol: \"http\" } : undefined,\n      timeoutMs: 20_000\n    });\n\n    res.json({ ok: true, data });\n  } catch (e: any) {\n    res.status(401).json({ ok: false, error: e?.message ?? \"auth failed\" });\n  }\n});\n```\n\u003e **Tip:** Validate/whitelist proxy origins before honoring client-provided overrides.\n\n---\n\n### 4) Weighted devices for header shaping (server-chosen)\n```ts\n// Load a device catalog (JSON), pick weighted device once per request\nimport deviceCatalog from \"./devices-current-year.json\" assert { type: \"json\" };\n\nfunction pickWeightedDevice(devices) {\n  const expanded = devices.flatMap(d =\u003e Array(Math.max(1, d.weight ?? 1)).fill(d));\n  return expanded[Math.floor(Math.random() * expanded.length)];\n}\n\napp.get(\"/news\", async (_req, res) =\u003e {\n  try {\n    const device = pickWeightedDevice(deviceCatalog.devices);\n    const data = await api.get(\"/news\", {\n      headers: {\n        \"user-agent\": device.userAgent,\n        ...(device.headers ?? {})\n      },\n      useProxyPool: true\n    });\n    res.setHeader(\"x-device-name\", device.name);\n    res.json(data);\n  } catch (e: any) {\n    res.status(502).json({ error: e?.message ?? \"upstream error\" });\n  }\n});\n```\n**Reasoning:** Centralize **device emulation** at the server; rotate UA/platform based on the *current-year* popular devices JSON.\n\n---\n\n### 5) Cookie injection (owned sessions only)\n```ts\napp.get(\"/me\", async (req, res) =\u003e {\n  try {\n    // Example of reconstructing a Cookie header from validated inputs\n    const cookieHeader = (req.headers[\"x-cookie\"] as string) || \"\"; // sanitize in real code\n    const me = await api.get(\"/me\", { headers: { Cookie: cookieHeader } });\n    res.json(me);\n  } catch (e: any) {\n    res.status(401).json({ error: e?.message ?? \"unauthorized\" });\n  }\n});\n```\n\u003e **Security:** Only accept cookies from authenticated clients you control. Do not forward untrusted cookies.\n\n---\n\n### 6) Error policy \u0026 telemetry\n`stealth-api` retries transient codes (`429/502/503/504`) with exponential backoff. In Express, you can surface context:\n\n```ts\napp.get(\"/sometimes-flaky\", async (_req, res) =\u003e {\n  try {\n    const data = await api.get(\"/flaky\", { retries: 4, timeoutMs: 10_000, useProxyPool: true });\n    res.json({ data });\n  } catch (e: any) {\n    // Optionally log proxy choice or pool metrics here if you enrich StealthAPI\n    res.status(502).json({ error: \"upstream unavailable\", detail: e?.message });\n  }\n});\n```\n**Bond aside:** If the target’s lasers heat up, our backoff gets cooler.\n\n---\n\n## 📦 TypeScript \u0026 Build\n- Fully typed.\n- Exported as ESM with type declarations.\n\n---\n\n## ⚖️ License\nMIT — but **you** hold the license to be a good agent.\n\n---\n\n## 📚 API Reference (Types)\nSee the inline JSDoc in `src/index.ts` for all exported types and advanced options (ban lists, sticky keys, health metrics, etc.).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdollsofink%2Fstealth-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdollsofink%2Fstealth-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdollsofink%2Fstealth-api/lists"}