[object Object]

← back to Ventura Claw

Add 5 no-auth SMB / civic public-data connectors

a5630c32573351c8244e9e2cefc2555c6e7642c3 · 2026-05-06 17:12:55 -0700 · SteveStudio2

Federal:
  sec         — SEC EDGAR (10-K filings, financial data, ticker lookup)
  usaspending — federal contracts + grants search by keyword/recipient/year
Local-state:
  nws         — National Weather Service (forecasts + active alerts by lat/lon)
  lacity      — LA City Open Data (Socrata) — business tax registry, permits,
                crime, code violations; SoQL query interface
  cadata      — California Open Data (CKAN) — package search + datastore query

All zero-auth — pre-connected for everyone. Health probes verified live on
Mac dev + Kamatera prod.

(USPTO was attempted but TSDR + PEDS recently auth-walled their public
endpoints; dropped from this batch.)

Files touched

Diff

commit a5630c32573351c8244e9e2cefc2555c6e7642c3
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 6 17:12:55 2026 -0700

    Add 5 no-auth SMB / civic public-data connectors
    
    Federal:
      sec         — SEC EDGAR (10-K filings, financial data, ticker lookup)
      usaspending — federal contracts + grants search by keyword/recipient/year
    Local-state:
      nws         — National Weather Service (forecasts + active alerts by lat/lon)
      lacity      — LA City Open Data (Socrata) — business tax registry, permits,
                    crime, code violations; SoQL query interface
      cadata      — California Open Data (CKAN) — package search + datastore query
    
    All zero-auth — pre-connected for everyone. Health probes verified live on
    Mac dev + Kamatera prod.
    
    (USPTO was attempted but TSDR + PEDS recently auth-walled their public
    endpoints; dropped from this batch.)
---
 server/connectors.json           | 60 ++++++++++++++++++++++++++++++++++++++
 server/connectors/cadata.js      | 63 ++++++++++++++++++++++++++++++++++++++++
 server/connectors/index.js       | 18 +++++++++++-
 server/connectors/lacity.js      | 55 +++++++++++++++++++++++++++++++++++
 server/connectors/nws.js         | 54 ++++++++++++++++++++++++++++++++++
 server/connectors/sec.js         | 58 ++++++++++++++++++++++++++++++++++++
 server/connectors/usaspending.js | 46 +++++++++++++++++++++++++++++
 7 files changed, 353 insertions(+), 1 deletion(-)

diff --git a/server/connectors.json b/server/connectors.json
index cd8a8e2..04bd93a 100644
--- a/server/connectors.json
+++ b/server/connectors.json
@@ -751,5 +751,65 @@
     "logo": null,
     "tint": "#d4a04a",
     "sensitive": false
+  },
+  {
+    "id": "sec",
+    "name": "SEC EDGAR",
+    "category": "data",
+    "docs": "https://www.sec.gov/edgar/sec-api-documentation",
+    "auth": "none",
+    "triggers": 0,
+    "actions": 3,
+    "logo": null,
+    "tint": "#003366",
+    "sensitive": false
+  },
+  {
+    "id": "usaspending",
+    "name": "USAspending",
+    "category": "data",
+    "docs": "https://api.usaspending.gov/",
+    "auth": "none",
+    "triggers": 0,
+    "actions": 2,
+    "logo": null,
+    "tint": "#1B3A6B",
+    "sensitive": false
+  },
+  {
+    "id": "nws",
+    "name": "NWS Weather",
+    "category": "data",
+    "docs": "https://www.weather.gov/documentation/services-web-api",
+    "auth": "none",
+    "triggers": 0,
+    "actions": 2,
+    "logo": null,
+    "tint": "#0078D4",
+    "sensitive": false
+  },
+  {
+    "id": "lacity",
+    "name": "LA City Open Data",
+    "category": "data",
+    "docs": "https://data.lacity.org/browse",
+    "auth": "none",
+    "triggers": 0,
+    "actions": 3,
+    "logo": null,
+    "tint": "#FFC72C",
+    "sensitive": false
+  },
+  {
+    "id": "cadata",
+    "name": "California Open Data",
+    "category": "data",
+    "docs": "https://data.ca.gov/dataset",
+    "auth": "none",
+    "triggers": 0,
+    "actions": 2,
+    "logo": null,
+    "tint": "#F1B82D",
+    "sensitive": false
   }
 ]
\ No newline at end of file
diff --git a/server/connectors/cadata.js b/server/connectors/cadata.js
new file mode 100644
index 0000000..ca5d068
--- /dev/null
+++ b/server/connectors/cadata.js
@@ -0,0 +1,63 @@
+// California Open Data (Socrata, hosted on data.ca.gov). No auth needed.
+// Catalog: https://data.ca.gov/dataset
+// Useful datasets:
+//   - CA SOS Business Filings:     8ek2-eknp (SOS active business search)
+//   - CalEnviroScreen 4.0:         <varies>
+//   - State Contracts Inventory:   ng9z-2spp
+//   - Active Civil Cases:          c9b6-2v9a
+const BASE = "https://data.ca.gov/api/3/action/datastore_search";
+async function call(qs) {
+  const r = await fetch(`${BASE}?${qs}`, {
+    headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
+    signal: AbortSignal.timeout(20_000),
+  });
+  if (!r.ok) throw new Error(`cadata ${r.status}`);
+  return r.json();
+}
+async function packageList() {
+  const r = await fetch("https://data.ca.gov/api/3/action/package_list?limit=10", {
+    headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
+    signal: AbortSignal.timeout(15_000),
+  });
+  if (!r.ok) throw new Error(`cadata package_list ${r.status}`);
+  return r.json();
+}
+module.exports = {
+  meta: { id: "cadata", name: "California Open Data", category: "data", docsUrl: "https://data.ca.gov/dataset", auth: "none", realImpl: true },
+  fields: [],
+  configured() { return true; },
+  async health() {
+    try {
+      const d = await packageList();
+      return { ok: true, label: `live · CKAN datastore · ${(d.result || []).length}+ packages sampled` };
+    } catch (e) { return { ok: false, reason: e.message }; }
+  },
+  actions: {
+    async "search_packages"({ q, rows }) {
+      if (!q) throw new Error("q required");
+      const qs = new URLSearchParams({ q, rows: String(Math.min(rows || 20, 100)) });
+      const r = await fetch(`https://data.ca.gov/api/3/action/package_search?${qs}`, {
+        headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
+        signal: AbortSignal.timeout(15_000),
+      });
+      if (!r.ok) throw new Error(`cadata search ${r.status}`);
+      const d = await r.json();
+      return {
+        count: d.result?.count || 0,
+        packages: (d.result?.results || []).map(p => ({
+          id: p.id, name: p.name, title: p.title, organization: p.organization?.title,
+          notes: p.notes?.slice(0, 240),
+          resources: (p.resources || []).map(r => ({ id: r.id, name: r.name, format: r.format, url: r.url })).slice(0, 5),
+        })),
+      };
+    },
+    async "datastore_query"({ resource_id, q, limit, offset }) {
+      if (!resource_id) throw new Error("resource_id required");
+      const qs = new URLSearchParams({ resource_id, limit: String(Math.min(limit || 50, 500)) });
+      if (offset) qs.set("offset", String(offset));
+      if (q) qs.set("q", q);
+      const d = await call(qs.toString());
+      return { total: d.result?.total, records: d.result?.records || [] };
+    },
+  },
+};
diff --git a/server/connectors/index.js b/server/connectors/index.js
index 6c699e8..c4af468 100644
--- a/server/connectors/index.js
+++ b/server/connectors/index.js
@@ -21,8 +21,14 @@ const cleveland  = require("./cleveland");
 const aic        = require("./aic");
 const wikipedia  = require("./wikipedia");
 const colorapi   = require("./colorapi");
+// SMB / civic — federal + state + local public-data APIs.
+const sec         = require("./sec");
+const usaspending = require("./usaspending");
+const nws         = require("./nws");
+const lacity      = require("./lacity");
+const cadata      = require("./cadata");
 
-const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs, shopify, met, cleveland, aic, wikipedia, colorapi };
+const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs, shopify, met, cleveland, aic, wikipedia, colorapi, sec, usaspending, nws, lacity, cadata };
 
 // Phase 8 — central sensitivity registry. Source of truth for "this action mutates state."
 // Anything in WRITE_ACTIONS[id] requires the approval gate. Anything NOT listed is treated as read-only.
@@ -50,6 +56,11 @@ const WRITE_ACTIONS = {
   aic:        new Set([]),
   wikipedia:  new Set([]),
   colorapi:   new Set([]),
+  sec:        new Set([]),
+  usaspending:new Set([]),
+  nws:        new Set([]),
+  lacity:     new Set([]),
+  cadata:     new Set([]),
 };
 
 // Read actions enumerated per connector — explicit allowlist for fail-safe behavior.
@@ -75,6 +86,11 @@ const READ_ACTIONS = {
   aic:        new Set(["search", "artwork"]),
   wikipedia:  new Set(["summary", "search", "onthisday"]),
   colorapi:   new Set(["identify", "scheme"]),
+  sec:        new Set(["ticker_lookup", "submissions", "company_concept"]),
+  usaspending:new Set(["search_awards", "recipient_summary"]),
+  nws:        new Set(["forecast", "alerts"]),
+  lacity:     new Set(["datasets", "query", "business_search"]),
+  cadata:     new Set(["search_packages", "datastore_query"]),
 };
 
 function isWrite(id, action) {
diff --git a/server/connectors/lacity.js b/server/connectors/lacity.js
new file mode 100644
index 0000000..f154ebe
--- /dev/null
+++ b/server/connectors/lacity.js
@@ -0,0 +1,55 @@
+// LA City Open Data (Socrata). Read-only, no auth required for ≤1000 req/hr.
+// Catalog: https://data.lacity.org/browse
+// Useful datasets:
+//   - Active Business Tax Registration: 6rrh-rzua
+//   - Crime Data 2020-Present:           2nrs-mtv8
+//   - Building & Safety Permits:         pi9x-tg5x
+//   - Code Violations:                   855j-wf32
+const BASE = "https://data.lacity.org/resource";
+async function call(dataset, qs) {
+  const url = `${BASE}/${dataset}.json` + (qs ? `?${qs}` : "");
+  const r = await fetch(url, {
+    headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0", "X-App-Token": "" },
+    signal: AbortSignal.timeout(20_000),
+  });
+  if (!r.ok) throw new Error(`lacity ${r.status}`);
+  return r.json();
+}
+const KNOWN = {
+  business_tax_registry: "6rrh-rzua",
+  building_permits: "pi9x-tg5x",
+  crime_data: "2nrs-mtv8",
+  code_violations: "855j-wf32",
+};
+module.exports = {
+  meta: { id: "lacity", name: "LA City Open Data", category: "data", docsUrl: "https://data.lacity.org/browse", auth: "none", realImpl: true },
+  fields: [],
+  configured() { return true; },
+  async health() {
+    try {
+      const d = await call(KNOWN.business_tax_registry, "$limit=1");
+      return { ok: true, label: `live · ${Object.keys(KNOWN).length} curated datasets (Socrata SoQL)` };
+    } catch (e) { return { ok: false, reason: e.message }; }
+  },
+  actions: {
+    async "datasets"() { return { datasets: KNOWN }; },
+    async "query"({ dataset, where, select, order, limit, q }) {
+      const id = KNOWN[dataset] || dataset;
+      if (!id || !/^[a-z0-9-]+$/i.test(id)) throw new Error("dataset id required");
+      const qs = new URLSearchParams();
+      if (where) qs.set("$where", where);
+      if (select) qs.set("$select", select);
+      if (order) qs.set("$order", order);
+      if (q) qs.set("$q", q);
+      qs.set("$limit", String(Math.min(limit || 50, 1000)));
+      return { rows: await call(id, qs.toString()) };
+    },
+    async "business_search"({ name, address, limit }) {
+      const where = [];
+      if (name) where.push(`upper(business_name) like upper('%${String(name).replace(/'/g, "''")}%')`);
+      if (address) where.push(`upper(street_address) like upper('%${String(address).replace(/'/g, "''")}%')`);
+      const qs = new URLSearchParams({ $where: where.join(" AND ") || "1=1", $limit: String(Math.min(limit || 25, 200)) });
+      return { rows: await call(KNOWN.business_tax_registry, qs.toString()) };
+    },
+  },
+};
diff --git a/server/connectors/nws.js b/server/connectors/nws.js
new file mode 100644
index 0000000..4e3fa06
--- /dev/null
+++ b/server/connectors/nws.js
@@ -0,0 +1,54 @@
+// US National Weather Service — public, no key. UA required.
+// https://www.weather.gov/documentation/services-web-api
+const BASE = "https://api.weather.gov";
+const UA = "VenturaClaw/1.0 (steve@venturaclaw.com)";
+async function call(path) {
+  const r = await fetch(`${BASE}${path}`, {
+    headers: { "User-Agent": UA, Accept: "application/geo+json" },
+    signal: AbortSignal.timeout(15_000),
+  });
+  if (!r.ok) throw new Error(`nws ${r.status}`);
+  return r.json();
+}
+module.exports = {
+  meta: { id: "nws", name: "NWS Weather", category: "data", docsUrl: "https://www.weather.gov/documentation/services-web-api", auth: "none", realImpl: true },
+  fields: [],
+  configured() { return true; },
+  async health() {
+    try {
+      // Pull a known LA gridpoint (Steve's home territory)
+      const d = await call("/points/34.0522,-118.2437");
+      return { ok: true, label: `live · LA gridpoint office=${d.properties?.gridId || "?"}` };
+    } catch (e) { return { ok: false, reason: e.message }; }
+  },
+  actions: {
+    async "forecast"({ lat, lon, hourly }) {
+      if (lat == null || lon == null) throw new Error("lat + lon required");
+      const pts = await call(`/points/${lat},${lon}`);
+      const url = hourly ? pts.properties?.forecastHourly : pts.properties?.forecast;
+      if (!url) throw new Error("no forecast url for that location");
+      const f = await fetch(url, { headers: { "User-Agent": UA }, signal: AbortSignal.timeout(15_000) }).then(r => r.json());
+      return {
+        location: pts.properties?.relativeLocation?.properties,
+        periods: (f.properties?.periods || []).slice(0, hourly ? 24 : 7).map(p => ({
+          name: p.name, startTime: p.startTime, temperature: p.temperature, temperatureUnit: p.temperatureUnit,
+          windSpeed: p.windSpeed, windDirection: p.windDirection, shortForecast: p.shortForecast, detailedForecast: p.detailedForecast,
+        })),
+      };
+    },
+    async "alerts"({ area, point }) {
+      const qs = new URLSearchParams();
+      if (area) qs.set("area", area); // 2-letter state code
+      if (point) qs.set("point", point); // "lat,lon"
+      const d = await call(`/alerts/active?${qs}`);
+      return {
+        count: (d.features || []).length,
+        alerts: (d.features || []).slice(0, 25).map(f => ({
+          headline: f.properties?.headline, severity: f.properties?.severity,
+          urgency: f.properties?.urgency, event: f.properties?.event, areaDesc: f.properties?.areaDesc,
+          effective: f.properties?.effective, expires: f.properties?.expires,
+        })),
+      };
+    },
+  },
+};
diff --git a/server/connectors/sec.js b/server/connectors/sec.js
new file mode 100644
index 0000000..c6cf302
--- /dev/null
+++ b/server/connectors/sec.js
@@ -0,0 +1,58 @@
+// SEC EDGAR — public-company filings & financial data. No key.
+// https://www.sec.gov/edgar/sec-api-documentation
+// SEC requires a descriptive User-Agent on every request.
+const UA = "VenturaClaw/1.0 (steve@venturaclaw.com)";
+async function call(url) {
+  const r = await fetch(url, {
+    headers: { "User-Agent": UA, Accept: "application/json" },
+    signal: AbortSignal.timeout(20_000),
+  });
+  if (!r.ok) throw new Error(`sec ${r.status}`);
+  return r.json();
+}
+function pad10(cik) { return String(cik).replace(/^0+/, "").padStart(10, "0"); }
+module.exports = {
+  meta: { id: "sec", name: "SEC EDGAR", category: "data", docsUrl: "https://www.sec.gov/edgar/sec-api-documentation", auth: "none", realImpl: true },
+  fields: [],
+  configured() { return true; },
+  async health() {
+    try {
+      const r = await fetch("https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=0000320193&type=10-K&dateb=&owner=include&count=1", {
+        headers: { "User-Agent": UA }, signal: AbortSignal.timeout(15_000),
+      });
+      return { ok: r.ok, label: r.ok ? "live · public-company filings" : `http ${r.status}` };
+    } catch (e) { return { ok: false, reason: e.message }; }
+  },
+  actions: {
+    // Look up a company by ticker, returns CIK + name
+    async "ticker_lookup"({ ticker }) {
+      if (!ticker) throw new Error("ticker required");
+      const d = await call("https://www.sec.gov/files/company_tickers.json");
+      const t = String(ticker).toUpperCase();
+      for (const v of Object.values(d || {})) {
+        if (v.ticker === t) return { cik: pad10(v.cik_str), ticker: v.ticker, name: v.title };
+      }
+      return { error: "not_found" };
+    },
+    async "submissions"({ cik }) {
+      if (!cik) throw new Error("cik required");
+      const d = await call(`https://data.sec.gov/submissions/CIK${pad10(cik)}.json`);
+      return {
+        cik: d.cik, name: d.name, sic: d.sic, sicDescription: d.sicDescription,
+        tickers: d.tickers, exchanges: d.exchanges,
+        recent_filings: (d.filings?.recent?.form || []).slice(0, 20).map((form, i) => ({
+          form,
+          filingDate: d.filings.recent.filingDate[i],
+          accessionNumber: d.filings.recent.accessionNumber[i],
+          primaryDocument: d.filings.recent.primaryDocument[i],
+        })),
+      };
+    },
+    async "company_concept"({ cik, taxonomy, tag }) {
+      if (!cik || !tag) throw new Error("cik + tag required");
+      const t = taxonomy || "us-gaap";
+      const d = await call(`https://data.sec.gov/api/xbrl/companyconcept/CIK${pad10(cik)}/${t}/${tag}.json`);
+      return d;
+    },
+  },
+};
diff --git a/server/connectors/usaspending.js b/server/connectors/usaspending.js
new file mode 100644
index 0000000..e472583
--- /dev/null
+++ b/server/connectors/usaspending.js
@@ -0,0 +1,46 @@
+// USAspending.gov — federal awards / contracts / grants. No key.
+// https://api.usaspending.gov/
+const BASE = "https://api.usaspending.gov/api/v2";
+async function call(method, path, body) {
+  const r = await fetch(`${BASE}${path}`, {
+    method, headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
+    body: body ? JSON.stringify(body) : undefined,
+    signal: AbortSignal.timeout(25_000),
+  });
+  if (!r.ok) throw new Error(`usaspending ${r.status}: ${await r.text().then(t => t.slice(0, 180)).catch(()=>"")}`);
+  return r.json();
+}
+module.exports = {
+  meta: { id: "usaspending", name: "USAspending", category: "data", docsUrl: "https://api.usaspending.gov/", auth: "none", realImpl: true },
+  fields: [],
+  configured() { return true; },
+  async health() {
+    // Use a small, known-stable POST endpoint that doesn't require complex filters
+    try {
+      const d = await call("POST", "/search/spending_by_award_count/", {
+        filters: { award_type_codes: ["A", "B", "C", "D"] },
+      });
+      return { ok: true, label: "live · federal awards/contracts/grants data" };
+    } catch (e) { return { ok: false, reason: e.message }; }
+  },
+  actions: {
+    async "search_awards"({ keyword, recipient, award_type, fiscal_year, limit }) {
+      const filters = {};
+      if (keyword) filters.keywords = [keyword];
+      if (recipient) filters.recipient_search_text = [recipient];
+      if (award_type) filters.award_type_codes = Array.isArray(award_type) ? award_type : [award_type];
+      if (fiscal_year) filters.time_period = [{ start_date: `${fiscal_year}-10-01`, end_date: `${fiscal_year + 1}-09-30` }];
+      const d = await call("POST", "/search/spending_by_award/", {
+        filters,
+        fields: ["Award ID", "Recipient Name", "Award Amount", "Award Type", "Awarding Agency", "Description", "Start Date", "End Date"],
+        page: 1, limit: Math.min(limit || 25, 100),
+      });
+      return { total: d.results?.length || 0, awards: d.results || [] };
+    },
+    async "recipient_summary"({ duns_or_uei }) {
+      if (!duns_or_uei) throw new Error("duns_or_uei required");
+      const d = await call("GET", `/recipient/duns/${encodeURIComponent(duns_or_uei)}/`);
+      return d;
+    },
+  },
+};

← 4cd2509 Add 5 no-auth public-data connectors: MET, Cleveland, AIC, W  ·  back to Ventura Claw  ·  /services hero: tighter measurable hook — '47-second domain 5a1709c →