← back to Ventura Claw
server/connectors/etsy.js
54 lines
// Etsy — REAL. v3 API, OAuth2 access token + x-api-key header.
const API = "https://api.etsy.com/v3/application";
function key(c) { const k = c?.ETSY_KEYSTRING || process.env.ETSY_KEYSTRING; if (!k) throw new Error("ETSY_KEYSTRING not set"); return k; }
function token(c) { const t = c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN; if (!t) throw new Error("ETSY_ACCESS_TOKEN not set"); return t; }
async function call(method, path, body, c) {
const r = await fetch(`${API}${path}`, {
method,
headers: { "Authorization": `Bearer ${token(c)}`, "x-api-key": key(c), "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined
});
const d = await r.json();
if (!r.ok) throw new Error(`etsy ${path} ${r.status}: ${d.error_description || d.error || JSON.stringify(d).slice(0,200)}`);
return d;
}
module.exports = {
meta: { id: "etsy", name: "Etsy", category: "commerce", docsUrl: "https://developers.etsy.com/documentation/", auth: "oauth2", realImpl: true },
fields: [
{ key: "ETSY_KEYSTRING", label: "API Keystring (x-api-key)", type: "password", required: true, hint: "developers.etsy.com app settings" },
{ key: "ETSY_ACCESS_TOKEN", label: "OAuth Access Token", type: "password", required: true, hint: "user-token from OAuth flow (user_id.token)" },
{ key: "ETSY_SHOP_ID", label: "Default Shop ID", type: "text", required: false, hint: "numeric shop_id (optional)" }
],
configured(c) { return !!((c?.ETSY_KEYSTRING || process.env.ETSY_KEYSTRING) && (c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN)); },
async health(c) {
if (!this.configured(c)) return { ok: false, reason: "no_token" };
try {
// Token format is "<user_id>.<token>" — first part is user_id
const tokParts = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".");
const userId = tokParts[0];
const d = await call("GET", `/users/${userId}`, null, c);
return { ok: true, user_id: d.user_id, login_name: d.login_name, primary_email: d.primary_email };
} catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "me"(_, c) {
const userId = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".")[0];
return call("GET", `/users/${userId}`, null, c);
},
async "shops.findByUser"(_, c) {
const userId = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".")[0];
return call("GET", `/users/${userId}/shops`, null, c);
},
async "listings.active"({ shop_id, limit }, c) {
const id = shop_id || c?.ETSY_SHOP_ID || process.env.ETSY_SHOP_ID;
if (!id) throw new Error("shop_id required");
return call("GET", `/shops/${id}/listings/active?limit=${limit||25}`, null, c);
},
async "receipts.list"({ shop_id, limit }, c) {
const id = shop_id || c?.ETSY_SHOP_ID || process.env.ETSY_SHOP_ID;
if (!id) throw new Error("shop_id required");
return call("GET", `/shops/${id}/receipts?limit=${limit||25}`, null, c);
}
}
};