[object Object]

← back to Pattern Vault

etsy: auto-resolve digital-pattern taxonomy_id at wire-up (getSellerTaxonomyNodes) — removes the manual taxonomy step

1909be7c89865b8d74a889b6a8756cdb358c70d3 · 2026-07-04 09:55:37 -0700 · Steve Abrams

Files touched

Diff

commit 1909be7c89865b8d74a889b6a8756cdb358c70d3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 4 09:55:37 2026 -0700

    etsy: auto-resolve digital-pattern taxonomy_id at wire-up (getSellerTaxonomyNodes) — removes the manual taxonomy step
---
 scripts/etsy-list.mjs | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/scripts/etsy-list.mjs b/scripts/etsy-list.mjs
index 113546c..3b31039 100644
--- a/scripts/etsy-list.mjs
+++ b/scripts/etsy-list.mjs
@@ -127,12 +127,36 @@ async function form(body) {
   return p;
 }
 
-if (LIVE && missing.length === 0) {
+// Resolve the digital-pattern taxonomy_id from the live seller taxonomy (x-api-key only, no OAuth).
+// Prefers a "Digital Prints"/"Digital Patterns" node, else the closest "Patterns" under Craft Supplies.
+async function resolveTaxonomy(apiKey) {
+  const res = await fetch('https://openapi.etsy.com/v3/application/seller-taxonomy/nodes', { headers: { 'x-api-key': apiKey } });
+  if (!res.ok) throw new Error('seller-taxonomy: ' + res.status);
+  const { results } = await res.json();
+  const flat = [];
+  const walk = (n, trail) => { const p = [...trail, n.name]; flat.push({ id: n.id, path: p.join(' > ') }); (n.children || []).forEach((c) => walk(c, p)); };
+  (results || []).forEach((n) => walk(n, []));
+  const score = (p) => (/digital prints/i.test(p) ? 3 : /digital.*pattern|pattern.*digital/i.test(p) ? 2 : /pattern/i.test(p) ? 1 : 0);
+  const best = flat.filter((x) => score(x.path) > 0).sort((a, b) => score(b.path) - score(a.path))[0];
+  return best || null;
+}
+
+if (LIVE && missing.filter((m) => m !== 'taxonomyId').length === 0 && !CFG.taxonomyId) {
+  try {
+    const t = await resolveTaxonomy(CFG.apiKey);
+    if (t) { CFG.taxonomyId = String(t.id); console.log(`[etsy] resolved taxonomy_id ${t.id} — "${t.path}"`); }
+    else console.log('[etsy] could not auto-resolve taxonomy — set ETSY_TAXONOMY_ID manually.');
+  } catch (e) { console.log('[etsy] taxonomy resolve failed:', e.message); }
+}
+
+if (LIVE && CFG.apiKey && CFG.token && CFG.shopId && CFG.taxonomyId) {
+  const TAX = Number(CFG.taxonomyId);
   const base = `https://openapi.etsy.com/v3/application/shops/${CFG.shopId}/listings`;
   const headers = { 'x-api-key': CFG.apiKey, Authorization: `Bearer ${CFG.token}`, 'Content-Type': 'application/x-www-form-urlencoded' };
   let ok = 0, fail = 0;
   for (const item of plan) {
     try {
+      item.body.taxonomy_id = TAX; // inject resolved id (build-time value may be a placeholder)
       const res = await fetch(base, { method: 'POST', headers, body: await form(item.body) });
       const j = await res.json();
       if (!res.ok) { console.error(`[etsy] FAIL "${item.body.title}": ${JSON.stringify(j)}`); fail++; continue; }

← d0febc9 etsy: dry-run listing pipeline from kit CSV — 38 type=downlo  ·  back to Pattern Vault  ·  etsy: split compound tags into <=20-char atoms instead of dr 71cadbe →