← back to Ventura Claw
Add 5 no-auth public-data connectors: MET, Cleveland, AIC, Wikipedia, Color API
4cd2509d847611b2e0f69a81a0c89afc90c519a2 · 2026-05-06 17:08:13 -0700 · SteveStudio2
All 5 are pre-connected — zero credentials required, just HTTPS calls.
Useful for DW content workflows (art reference, color tooling, encyclopedic
fact-checks). Live counts on health probe:
- MET Museum: 43,947 PD objects
- Cleveland Museum of Art: 68,738 CC0 artworks
- Art Institute of Chicago: 131,990 artworks (IIIF imagery)
- Wikipedia: english REST + search APIs
- Color API: identify + scheme actions
Each handler follows the archive.js pattern: meta + empty fields + always-
configured + health() probing a free endpoint + 2-3 read-only actions.
Registered in REAL map with WRITE_ACTIONS: Set([]) (read-only) and
READ_ACTIONS enumerated. Catalog entries added to connectors.json (62 total).
Files touched
M server/connectors.jsonA server/connectors/aic.jsA server/connectors/cleveland.jsA server/connectors/colorapi.jsM server/connectors/index.jsA server/connectors/met.jsA server/connectors/wikipedia.js
Diff
commit 4cd2509d847611b2e0f69a81a0c89afc90c519a2
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 17:08:13 2026 -0700
Add 5 no-auth public-data connectors: MET, Cleveland, AIC, Wikipedia, Color API
All 5 are pre-connected — zero credentials required, just HTTPS calls.
Useful for DW content workflows (art reference, color tooling, encyclopedic
fact-checks). Live counts on health probe:
- MET Museum: 43,947 PD objects
- Cleveland Museum of Art: 68,738 CC0 artworks
- Art Institute of Chicago: 131,990 artworks (IIIF imagery)
- Wikipedia: english REST + search APIs
- Color API: identify + scheme actions
Each handler follows the archive.js pattern: meta + empty fields + always-
configured + health() probing a free endpoint + 2-3 read-only actions.
Registered in REAL map with WRITE_ACTIONS: Set([]) (read-only) and
READ_ACTIONS enumerated. Catalog entries added to connectors.json (62 total).
---
server/connectors.json | 62 +++++++++++++++++++++++++++++++++++++++++-
server/connectors/aic.js | 43 +++++++++++++++++++++++++++++
server/connectors/cleveland.js | 46 +++++++++++++++++++++++++++++++
server/connectors/colorapi.js | 53 ++++++++++++++++++++++++++++++++++++
server/connectors/index.js | 19 ++++++++++++-
server/connectors/met.js | 44 ++++++++++++++++++++++++++++++
server/connectors/wikipedia.js | 61 +++++++++++++++++++++++++++++++++++++++++
7 files changed, 326 insertions(+), 2 deletions(-)
diff --git a/server/connectors.json b/server/connectors.json
index 005be68..cd8a8e2 100644
--- a/server/connectors.json
+++ b/server/connectors.json
@@ -691,5 +691,65 @@
"textToSpeech"
],
"sensitive": false
+ },
+ {
+ "id": "met",
+ "name": "MET Museum",
+ "category": "data",
+ "docs": "https://metmuseum.github.io/",
+ "auth": "none",
+ "triggers": 0,
+ "actions": 3,
+ "logo": null,
+ "tint": "#E4002B",
+ "sensitive": false
+ },
+ {
+ "id": "cleveland",
+ "name": "Cleveland Museum of Art",
+ "category": "data",
+ "docs": "https://openaccess-api.clevelandart.org/",
+ "auth": "none",
+ "triggers": 0,
+ "actions": 2,
+ "logo": null,
+ "tint": "#0A1F44",
+ "sensitive": false
+ },
+ {
+ "id": "aic",
+ "name": "Art Institute of Chicago",
+ "category": "data",
+ "docs": "https://api.artic.edu/docs/",
+ "auth": "none",
+ "triggers": 0,
+ "actions": 2,
+ "logo": null,
+ "tint": "#B22222",
+ "sensitive": false
+ },
+ {
+ "id": "wikipedia",
+ "name": "Wikipedia",
+ "category": "data",
+ "docs": "https://en.wikipedia.org/api/rest_v1/",
+ "auth": "none",
+ "triggers": 0,
+ "actions": 3,
+ "logo": "wikipedia",
+ "tint": "#000000",
+ "sensitive": false
+ },
+ {
+ "id": "colorapi",
+ "name": "Color API",
+ "category": "creative",
+ "docs": "https://www.thecolorapi.com/docs",
+ "auth": "none",
+ "triggers": 0,
+ "actions": 2,
+ "logo": null,
+ "tint": "#d4a04a",
+ "sensitive": false
}
-]
+]
\ No newline at end of file
diff --git a/server/connectors/aic.js b/server/connectors/aic.js
new file mode 100644
index 0000000..aa30d7e
--- /dev/null
+++ b/server/connectors/aic.js
@@ -0,0 +1,43 @@
+// Art Institute of Chicago — Public API. No key.
+// https://api.artic.edu/docs/
+const BASE = "https://api.artic.edu/api/v1";
+async function call(path) {
+ const r = await fetch(`${BASE}${path}`, {
+ headers: { "User-Agent": "VenturaClaw/1.0", Accept: "application/json", "AIC-User-Agent": "VenturaClaw (steve@venturaclaw.com)" },
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!r.ok) throw new Error(`aic ${r.status}`);
+ return r.json();
+}
+module.exports = {
+ meta: { id: "aic", name: "Art Institute of Chicago", category: "data", docsUrl: "https://api.artic.edu/docs/", auth: "none", realImpl: true },
+ fields: [],
+ configured() { return true; },
+ async health() {
+ try {
+ const d = await call("/artworks?limit=1");
+ return { ok: true, label: `${(d.pagination?.total ?? 0).toLocaleString()} artworks · public-domain images via IIIF` };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+ actions: {
+ async "search"({ q, limit }) {
+ if (!q) throw new Error("q required");
+ const qs = new URLSearchParams({ q, limit: String(Math.min(limit || 20, 100)), fields: "id,title,artist_display,date_display,image_id,is_public_domain,thumbnail" });
+ const d = await call(`/artworks/search?${qs}`);
+ return {
+ total: d.pagination?.total || 0,
+ artworks: (d.data || []).map(a => ({
+ id: a.id, title: a.title, artist: a.artist_display, date: a.date_display,
+ is_public_domain: a.is_public_domain,
+ thumbnail: a.thumbnail?.lqip,
+ image: a.image_id ? `https://www.artic.edu/iiif/2/${a.image_id}/full/843,/0/default.jpg` : null,
+ })),
+ };
+ },
+ async "artwork"({ id }) {
+ if (!id) throw new Error("id required");
+ const d = await call(`/artworks/${id}`);
+ return d.data;
+ },
+ },
+};
diff --git a/server/connectors/cleveland.js b/server/connectors/cleveland.js
new file mode 100644
index 0000000..2ab8e5d
--- /dev/null
+++ b/server/connectors/cleveland.js
@@ -0,0 +1,46 @@
+// Cleveland Museum of Art — Open Access. No API key.
+// https://openaccess-api.clevelandart.org/
+const BASE = "https://openaccess-api.clevelandart.org/api";
+async function call(path) {
+ const r = await fetch(`${BASE}${path}`, {
+ headers: { "User-Agent": "VenturaClaw/1.0", Accept: "application/json" },
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!r.ok) throw new Error(`cleveland ${r.status}`);
+ return r.json();
+}
+module.exports = {
+ meta: { id: "cleveland", name: "Cleveland Museum of Art", category: "data", docsUrl: "https://openaccess-api.clevelandart.org/", auth: "none", realImpl: true },
+ fields: [],
+ configured() { return true; },
+ async health() {
+ try {
+ const d = await call("/artworks?limit=1");
+ return { ok: true, label: `${(d.info?.total ?? 0).toLocaleString()} artworks · CC0 + open access` };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+ actions: {
+ async "search"({ q, hasImage, cc0only, limit }) {
+ const qs = new URLSearchParams();
+ if (q) qs.set("q", q);
+ if (hasImage) qs.set("has_image", "1");
+ if (cc0only) qs.set("cc0", "1");
+ qs.set("limit", String(Math.min(limit || 25, 100)));
+ const d = await call(`/artworks?${qs}`);
+ return {
+ total: d.info?.total || 0,
+ artworks: (d.data || []).map(a => ({
+ id: a.id, accession_number: a.accession_number,
+ title: a.title, creators: (a.creators || []).map(c => c.description).join(" · "),
+ date: a.creation_date, medium: a.technique, image: a.images?.web?.url || a.images?.print?.url,
+ url: a.url, license: a.share_license_status,
+ })),
+ };
+ },
+ async "artwork"({ id }) {
+ if (!id) throw new Error("id required");
+ const d = await call(`/artworks/${id}`);
+ return d.data;
+ },
+ },
+};
diff --git a/server/connectors/colorapi.js b/server/connectors/colorapi.js
new file mode 100644
index 0000000..77473d6
--- /dev/null
+++ b/server/connectors/colorapi.js
@@ -0,0 +1,53 @@
+// The Color API — no key. https://www.thecolorapi.com/docs
+const BASE = "https://www.thecolorapi.com";
+async function call(path) {
+ const r = await fetch(`${BASE}${path}`, {
+ headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
+ signal: AbortSignal.timeout(12_000),
+ });
+ if (!r.ok) throw new Error(`colorapi ${r.status}`);
+ return r.json();
+}
+function normHex(h) {
+ if (!h) return null;
+ let s = String(h).trim().replace(/^#/, "");
+ if (s.length === 3) s = s.split("").map(c => c + c).join("");
+ if (!/^[0-9a-fA-F]{6}$/.test(s)) return null;
+ return s.toLowerCase();
+}
+module.exports = {
+ meta: { id: "colorapi", name: "Color API", category: "creative", docsUrl: "https://www.thecolorapi.com/docs", auth: "none", realImpl: true },
+ fields: [],
+ configured() { return true; },
+ async health() {
+ try {
+ const d = await call("/id?hex=d4a04a&format=json");
+ return { ok: true, label: `live · sample: ${d.name?.value || "named"}` };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+ actions: {
+ async "identify"({ hex, rgb }) {
+ const h = normHex(hex);
+ if (!h && !rgb) throw new Error("hex or rgb required");
+ const qs = h ? `hex=${h}` : `rgb=${encodeURIComponent(rgb)}`;
+ const d = await call(`/id?${qs}&format=json`);
+ return {
+ hex: d.hex?.value, name: d.name?.value, rgb: d.rgb?.value,
+ hsl: d.hsl?.value, cmyk: d.cmyk?.value,
+ contrast_text: d.contrast?.value,
+ nearest_named: d.name?.exact_match_name === false ? d.name?.closest_named_hex : null,
+ };
+ },
+ async "scheme"({ hex, mode, count }) {
+ const h = normHex(hex);
+ if (!h) throw new Error("hex required");
+ const qs = new URLSearchParams({ hex: h, mode: mode || "analogic", count: String(Math.min(count || 5, 10)), format: "json" });
+ const d = await call(`/scheme?${qs}`);
+ return {
+ seed: d.seed?.hex?.value, mode: d.mode,
+ colors: (d.colors || []).map(c => ({ hex: c.hex?.value, name: c.name?.value, rgb: c.rgb?.value })),
+ scheme_image: d.image?.bare,
+ };
+ },
+ },
+};
diff --git a/server/connectors/index.js b/server/connectors/index.js
index 2a36560..6c699e8 100644
--- a/server/connectors/index.js
+++ b/server/connectors/index.js
@@ -15,8 +15,14 @@ const purelymail = require("./purelymail");
const archive = require("./archive");
const elevenlabs = require("./elevenlabs");
const shopify = require("./shopify");
+// Public no-auth APIs (no key required) — pre-connected for everyone.
+const met = require("./met");
+const cleveland = require("./cleveland");
+const aic = require("./aic");
+const wikipedia = require("./wikipedia");
+const colorapi = require("./colorapi");
-const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs, shopify };
+const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs, shopify, met, cleveland, aic, wikipedia, colorapi };
// 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.
@@ -38,6 +44,12 @@ const WRITE_ACTIONS = {
twilio: new Set(["sms.send"]),
elevenlabs: new Set(["textToSpeech"]),
shopify: new Set(["order.fulfill", "order.refund", "product.update"]),
+ // Public no-auth APIs are read-only — no writes.
+ met: new Set([]),
+ cleveland: new Set([]),
+ aic: new Set([]),
+ wikipedia: new Set([]),
+ colorapi: new Set([]),
};
// Read actions enumerated per connector — explicit allowlist for fail-safe behavior.
@@ -58,6 +70,11 @@ const READ_ACTIONS = {
twilio: new Set(["messages.list", "phone_numbers.list"]),
elevenlabs: new Set(["voicesList", "user", "modelsList"]),
shopify: new Set(["shop.info", "products.list", "products.count", "orders.list", "orders.recent", "collections.list"]),
+ met: new Set(["search", "object", "departments"]),
+ cleveland: new Set(["search", "artwork"]),
+ aic: new Set(["search", "artwork"]),
+ wikipedia: new Set(["summary", "search", "onthisday"]),
+ colorapi: new Set(["identify", "scheme"]),
};
function isWrite(id, action) {
diff --git a/server/connectors/met.js b/server/connectors/met.js
new file mode 100644
index 0000000..c15d942
--- /dev/null
+++ b/server/connectors/met.js
@@ -0,0 +1,44 @@
+// Metropolitan Museum of Art — open access collection.
+// No API key. https://metmuseum.github.io/
+const BASE = "https://collectionapi.metmuseum.org/public/collection/v1";
+async function call(path) {
+ const r = await fetch(`${BASE}${path}`, {
+ headers: { "User-Agent": "VenturaClaw/1.0 (steve@venturaclaw.com)", Accept: "application/json" },
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!r.ok) throw new Error(`met ${r.status}`);
+ return r.json();
+}
+module.exports = {
+ meta: { id: "met", name: "MET Museum", category: "data", docsUrl: "https://metmuseum.github.io/", auth: "none", realImpl: true },
+ fields: [],
+ configured() { return true; },
+ async health() {
+ try {
+ const d = await call("/objects?metadataDate=2026-01-01");
+ return { ok: true, label: `${(d.total ?? 0).toLocaleString()} objects · public domain catalog` };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+ actions: {
+ async "search"({ q, hasImages, isPublicDomain, departmentId }) {
+ if (!q) throw new Error("q required");
+ const qs = new URLSearchParams({ q });
+ if (hasImages) qs.set("hasImages", "true");
+ if (isPublicDomain) qs.set("isPublicDomain", "true");
+ if (departmentId) qs.set("departmentId", String(departmentId));
+ const d = await call(`/search?${qs}`);
+ return { total: d.total, objectIDs: (d.objectIDs || []).slice(0, 100) };
+ },
+ async "object"({ objectID }) {
+ if (!objectID) throw new Error("objectID required");
+ const d = await call(`/objects/${objectID}`);
+ return {
+ objectID: d.objectID, title: d.title, artist: d.artistDisplayName, date: d.objectDate,
+ medium: d.medium, classification: d.classification, department: d.department,
+ isPublicDomain: d.isPublicDomain, primaryImage: d.primaryImage, primaryImageSmall: d.primaryImageSmall,
+ objectURL: d.objectURL, creditLine: d.creditLine, dimensions: d.dimensions,
+ };
+ },
+ async "departments"() { return await call("/departments"); },
+ },
+};
diff --git a/server/connectors/wikipedia.js b/server/connectors/wikipedia.js
new file mode 100644
index 0000000..7bbe050
--- /dev/null
+++ b/server/connectors/wikipedia.js
@@ -0,0 +1,61 @@
+// Wikipedia / Wikimedia REST API. No key.
+// https://en.wikipedia.org/api/rest_v1/
+const BASE = "https://en.wikipedia.org/api/rest_v1";
+async function call(path) {
+ const r = await fetch(`${BASE}${path}`, {
+ headers: { "User-Agent": "VenturaClaw/1.0 (steve@venturaclaw.com)", Accept: "application/json" },
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!r.ok) throw new Error(`wikipedia ${r.status}`);
+ return r.json();
+}
+module.exports = {
+ meta: { id: "wikipedia", name: "Wikipedia", category: "data", docsUrl: "https://en.wikipedia.org/api/rest_v1/", auth: "none", realImpl: true },
+ fields: [],
+ configured() { return true; },
+ async health() {
+ try {
+ const d = await call("/page/summary/Designer_Wallcoverings");
+ return { ok: true, label: "live · " + (d.title || "english wikipedia") };
+ } catch (e) {
+ // Fallback to a guaranteed page
+ try {
+ await call("/page/summary/Earth");
+ return { ok: true, label: "live · english wikipedia" };
+ } catch (e2) { return { ok: false, reason: e2.message }; }
+ }
+ },
+ actions: {
+ async "summary"({ title }) {
+ if (!title) throw new Error("title required");
+ const d = await call(`/page/summary/${encodeURIComponent(title.replace(/\s+/g, "_"))}`);
+ return {
+ title: d.title, description: d.description, extract: d.extract,
+ thumbnail: d.thumbnail?.source, url: d.content_urls?.desktop?.page,
+ wikibase_item: d.wikibase_item, type: d.type, lang: d.lang,
+ };
+ },
+ async "search"({ q, limit }) {
+ if (!q) throw new Error("q required");
+ const qs = new URLSearchParams({ q, limit: String(Math.min(limit || 10, 50)) });
+ const r = await fetch(`https://en.wikipedia.org/w/rest.php/v1/search/page?${qs}`, {
+ headers: { "User-Agent": "VenturaClaw/1.0", Accept: "application/json" },
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!r.ok) throw new Error(`wikipedia search ${r.status}`);
+ const d = await r.json();
+ return {
+ pages: (d.pages || []).map(p => ({
+ id: p.id, title: p.title, excerpt: p.excerpt?.replace(/<[^>]+>/g, ""), thumbnail: p.thumbnail?.url,
+ url: `https://en.wikipedia.org/wiki/${encodeURIComponent(p.key)}`,
+ })),
+ };
+ },
+ async "onthisday"({ type, month, day }) {
+ const t = type || "events";
+ const m = String(month || (new Date().getMonth() + 1)).padStart(2, "0");
+ const d = String(day || new Date().getDate()).padStart(2, "0");
+ return await call(`/feed/onthisday/${t}/${m}/${d}`);
+ },
+ },
+};
← a6a5067 ENV_KEY_TO_CONNECTOR: align field names with what the handle
·
back to Ventura Claw
·
Add 5 no-auth SMB / civic public-data connectors a5630c3 →