[object Object]

← back to CelebritySignatures

Repoint TM register fetcher to USPTO Open Data Portal (trtdxfap); document WAF + ID.me-key reality, add --file/--odp/--url ingest modes

9a722d34a5b4734a6ab2c38edc398e02259824cd · 2026-08-06 09:34:58 -0700 · Steve

Legacy bulkdata.uspto.gov is retired; ODP is WAF-gated + BSD API needs an ID.me
key, so headless fetch is blocked. Fetcher now supports ingesting a browser/
openclaw-downloaded file (--file, $0 no key), the keyed BSD API (--odp), or a
direct URL. Store stays empty (only-legally-clear) until a real file is ingested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 9a722d34a5b4734a6ab2c38edc398e02259824cd
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 09:34:58 2026 -0700

    Repoint TM register fetcher to USPTO Open Data Portal (trtdxfap); document WAF + ID.me-key reality, add --file/--odp/--url ingest modes
    
    Legacy bulkdata.uspto.gov is retired; ODP is WAF-gated + BSD API needs an ID.me
    key, so headless fetch is blocked. Fetcher now supports ingesting a browser/
    openclaw-downloaded file (--file, $0 no key), the keyed BSD API (--odp), or a
    direct URL. Store stays empty (only-legally-clear) until a real file is ingested.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/fetch-tm-register.mjs | 96 ++++++++++++++++++++++++++++++-------------
 1 file changed, 68 insertions(+), 28 deletions(-)

diff --git a/scripts/fetch-tm-register.mjs b/scripts/fetch-tm-register.mjs
index 6a86dfb..b6f232f 100644
--- a/scripts/fetch-tm-register.mjs
+++ b/scripts/fetch-tm-register.mjs
@@ -6,52 +6,92 @@
 // ID.me-linked key and look up by serial#, not by mark name. The free, $0, no-rate-
 // limit path is the published bulk register, which we index locally once.
 //
-// SOURCE: USPTO Trademark Daily XML — https://bulkdata.uspto.gov/data/trademark/dailyxml/applications/
-// Each daily zip (apcYYMMDD.zip) holds <case-file> records with the mark text,
-// status, and Nice classification. The annual full-register file is the complete
-// set (tens of GB) — pull it once for a true full screen.
+// SOURCE (2026): the legacy host bulkdata.uspto.gov is RETIRED. Trademark bulk
+// data now lives on the USPTO Open Data Portal:
+//   dataset page : https://data.uspto.gov/bulkdata/datasets/trtdxfap
+//   product      : "Trademark Daily XML File (TDXF) applications" (trtdxfap)
+// Each file is XML with <case-file> records (mark text, status, Nice class).
 //
-// This script does NOT auto-download gigabytes. Usage:
-//   node scripts/fetch-tm-register.mjs --url <zip-or-xml-url>   # fetch ONE file
-//   node scripts/fetch-tm-register.mjs --list                  # print the daily-xml index URL + guidance
-// Files land in tmp_tm_cache/ ; then run build-tm-index.mjs.
-import { mkdir, writeFile } from 'node:fs/promises';
+// REALITY (verified 2026-08-06): the ODP is behind an AWS WAF anti-bot challenge
+// AND its Bulk-Search/Download (BSD) API requires an ID.me-linked USPTO API key
+// (X-API-KEY). So a plain headless fetch is BLOCKED. Three real ways in:
+//   (A) --file <path>   : ingest a file YOU downloaded in a real browser (or via
+//                         the openclaw/portal-driver real-Chrome flow). $0, no key.
+//   (B) --odp <fileName>: hit the BSD download API with USPTO_ODP_API_KEY (env/.env)
+//                         — needs Steve's ID.me-linked key. $0 once keyed.
+//   (C) --url <httpsUrl>: fetch a direct file URL (if you have an unwalled one).
+// Files land in tmp_tm_cache/ ; then run build-tm-index.mjs + build-tm-clearance.mjs.
+import { mkdir, writeFile, copyFile } from 'node:fs/promises';
 import { fileURLToPath } from 'node:url';
-import { join } from 'node:path';
+import { join, basename } from 'node:path';
+import { readFileSync } from 'node:fs';
 
 const ROOT = fileURLToPath(new URL('..', import.meta.url));
 const CACHE = join(ROOT, 'tmp_tm_cache');
-const DAILY = 'https://bulkdata.uspto.gov/data/trademark/dailyxml/applications/';
+const DATASET = 'https://data.uspto.gov/bulkdata/datasets/trtdxfap';
+const BSD_DOWNLOAD = 'https://data.uspto.gov/api/v1/datasets/products/files'; // BSD download base (key-gated)
 
 const args = process.argv.slice(2);
 const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : null; };
+function envVal(name) {
+  if (process.env[name]) return process.env[name];
+  try { const m = readFileSync(join(ROOT, '.env'), 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
+  return null;
+}
 
 async function main() {
   await mkdir(CACHE, { recursive: true });
   if (args.includes('--list') || args.length === 0) {
-    console.log('USPTO bulk trademark register (free, $0):');
-    console.log('  Daily XML index : ' + DAILY);
-    console.log('  Full register   : the annual application file on that server (large).');
-    console.log('');
-    console.log('Fetch one file:  node scripts/fetch-tm-register.mjs --url ' + DAILY + 'apc250102.zip');
-    console.log('Then build:      node scripts/build-tm-index.mjs');
-    console.log('');
-    console.log('Until a real file is fetched, the index stays a SEED SAMPLE (complete:false)');
-    console.log('and the storefront treats it as a partial screen (disclosed in the UI).');
+    console.log('USPTO trademark bulk register — Open Data Portal (data.uspto.gov):');
+    console.log('  Dataset : ' + DATASET + '  (product trtdxfap)');
+    console.log('  NOTE: ODP is behind an AWS WAF + the BSD API needs an ID.me-linked key.');
+    console.log('        A plain headless fetch is BLOCKED. Choose one:');
+    console.log('   (A) node scripts/fetch-tm-register.mjs --file /path/to/downloaded-trtdxf.zip   # $0, no key');
+    console.log('   (B) USPTO_ODP_API_KEY=… node scripts/fetch-tm-register.mjs --odp <fileName>    # $0, needs key');
+    console.log('   (C) node scripts/fetch-tm-register.mjs --url <direct-https-url>');
+    console.log('Then: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
+    console.log('Until a REAL file is ingested the index stays a SEED SAMPLE (complete:false) and');
+    console.log('NOTHING is sellable — "only legally clear" holds by default.');
     return;
   }
-  const target = get('--url');
-  if (!target || !/^https:\/\/bulkdata\.uspto\.gov\//.test(target)) {
-    console.error('Refusing: --url must be an https://bulkdata.uspto.gov/... file.');
-    process.exit(2);
+
+  // (A) ingest an already-downloaded file (browser / openclaw)
+  const localFile = get('--file');
+  if (localFile) {
+    const out = join(CACHE, basename(localFile));
+    await copyFile(localFile, out);
+    console.log('Ingested local register file -> ' + out + '  (cost: $0)');
+    console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
+    return;
   }
-  const out = join(CACHE, target.split('/').pop());
-  console.log('Cost: $0 (public USPTO bulk data). Fetching', target, '...');
+
+  // (B) BSD download API with an ID.me-linked key
+  const odpFile = get('--odp');
+  if (odpFile) {
+    const key = envVal('USPTO_ODP_API_KEY');
+    if (!key) { console.error('Missing USPTO_ODP_API_KEY (ID.me-linked). See --list option B.'); process.exit(2); }
+    const target = `${BSD_DOWNLOAD}/${encodeURIComponent(odpFile)}/download`;
+    console.log('Cost: $0 (keyed ODP). Fetching', target, '...');
+    const r = await fetch(target, { headers: { 'X-API-KEY': key } });
+    if (!r.ok) { console.error('ODP fetch failed:', r.status, r.statusText, '(WAF or bad key/filename?)'); process.exit(1); }
+    const buf = Buffer.from(await r.arrayBuffer());
+    const out = join(CACHE, basename(odpFile));
+    await writeFile(out, buf);
+    console.log(`Saved ${(buf.length / 1e6).toFixed(1)} MB -> ${out}`);
+    console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
+    return;
+  }
+
+  // (C) direct URL
+  const target = get('--url');
+  if (!target || !/^https:\/\//.test(target)) { console.error('Refusing: --url must be an https URL.'); process.exit(2); }
+  console.log('Cost: $0. Fetching', target, '...');
   const r = await fetch(target);
-  if (!r.ok) { console.error('fetch failed:', r.status, r.statusText); process.exit(1); }
+  if (!r.ok) { console.error('fetch failed:', r.status, r.statusText, '(the ODP host is WAF-gated; use --file or --odp instead)'); process.exit(1); }
   const buf = Buffer.from(await r.arrayBuffer());
+  const out = join(CACHE, basename(new URL(target).pathname) || 'register.xml');
   await writeFile(out, buf);
   console.log(`Saved ${(buf.length / 1e6).toFixed(1)} MB -> ${out}`);
-  console.log('Next: node scripts/build-tm-index.mjs');
+  console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
 }
 main().catch(e => { console.error(e); process.exit(1); });

← a1b47a6 Add /wear: left-chest signature apparel with USPTO trademark  ·  back to CelebritySignatures  ·  Add WEAR-READY-FOR-KEY finish note (paste key -> full-regist 4986121 →