← back to Marketing Command Center
channels: add read-only GET /tiktok-health token-validity probe (creator_info; token never leaves server)
a89c005143416f52a284724488fd1abaf49a668a · 2026-08-12 16:47:34 -0700 · Steve
Files touched
M modules/channels/index.jsM package.json
Diff
commit a89c005143416f52a284724488fd1abaf49a668a
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 16:47:34 2026 -0700
channels: add read-only GET /tiktok-health token-validity probe (creator_info; token never leaves server)
---
modules/channels/index.js | 32 ++++++++++++++++++++++++++++++++
package.json | 2 +-
2 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/modules/channels/index.js b/modules/channels/index.js
index ba848d1..c9ca981 100644
--- a/modules/channels/index.js
+++ b/modules/channels/index.js
@@ -725,6 +725,38 @@ module.exports = {
const connected = Object.values(st).filter(p => p.connected).length;
res.json({ platforms: st, connectedCount: connected, total: Object.keys(st).length, outbox: readOutbox().length });
});
+
+ // Read-only TikTok token VALIDITY probe. The /status 'connected' flag for TikTok
+ // is only a presence check (env var set), which can mask an EXPIRED token — so a
+ // handoff might look ready yet fail at publish time. This calls TikTok's
+ // creator_info/query (a read; it does NOT post) with the SAME token postTikTok()
+ // uses, and reports genuine validity. The token never leaves the server — only a
+ // last-4 digest is returned.
+ router.get('/tiktok-health', async (_req, res) => {
+ const token = env('TIKTOK_ACCESS_TOKEN') || (readTokens().tiktok || {}).access_token || '';
+ if (!token) return res.json({ ok: true, valid: false, reason: 'no TikTok token configured' });
+ try {
+ const r = await fetch('https://open.tiktokapis.com/v2/post/publish/creator_info/query/', {
+ method: 'POST',
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json; charset=UTF-8' },
+ body: '{}',
+ });
+ const j = await r.json().catch(() => ({}));
+ const code = j.error && j.error.code;
+ const valid = r.ok && code === 'ok';
+ res.json({
+ ok: true, valid, http: r.status,
+ error_code: code || null,
+ error_message: (j.error && j.error.message) || null,
+ creator_username: (j.data && j.data.creator_username) || null,
+ privacy_options: (j.data && j.data.privacy_level_options) || null,
+ last4: token.slice(-4),
+ caveat: valid ? 'Direct posts are SELF_ONLY (private) until TikTok approves the app audit.' : null,
+ });
+ } catch (e) {
+ res.json({ ok: true, valid: false, error_message: String(e.message || e).slice(0, 140) });
+ }
+ });
// Localize expiring IG/fbcdn media to durable local copies before serving
// (see lib/media-cache): cached→local path synchronously, background-fetch
// any missing so the next load self-heals. Never blocks the response.
diff --git a/package.json b/package.json
index 3b5054e..addd357 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "marketing-command-center",
- "version": "1.10.1",
+ "version": "1.10.2",
"description": "DW Marketing Command Center — Constant Contact, marketing calendar, suggested copy, on-demand layouts",
"main": "server.js",
"scripts": {
← 941504a auto-data-snapshot: 2026-08-12T16:33:05 (2 data files) — dat
·
back to Marketing Command Center
·
channels: consolidated /health probe (fb/ig/tiktok/linkedin/ f874f5b →