Skip to content

t1k:marketing:ua:app-metadata

FieldValue
Moduleua
Version1.8.4
Effortlow
Tools—
/t1k:marketing:ua:app-metadata

Given an app identifier, return { name, iconUrl, storeUrl, platform }.

Origin: adapted from ClaudeAssistant store-review (app-lookup.service.ts). First consumer: marketing-hub ROAS per-app table.

  • Display an app icon / logo next to ROAS or revenue rows
  • Resolve a package name or bundle ID to a human-readable app name
  • Populate store URL + platform for a new app record
  • Bulk back-fill icon URLs for existing rows with null icons
dots present → reverse-DNS → try Android first, iOS bundleId fallback
purely numeric → iOS App Store ID (id=)
Terminal window
npm install google-play-scraper@^10.1.2
import gplay from 'google-play-scraper';
async function lookupAndroid(packageName: string) {
const app = await gplay.app({ appId: packageName });
return { platform: 'android' as const,
name: app.title, iconUrl: app.icon, storeUrl: app.url };
}

Throws Error('app not found') when package is absent — catch and treat as notFound.

async function lookupIos(idOrBundle: string, field: 'id' | 'bundleId') {
const url = `https://itunes.apple.com/lookup?${field}=${encodeURIComponent(idOrBundle)}`;
const json = await fetch(url).then(r => r.json()) as { resultCount: number; results: any[] };
if (json.resultCount === 0) return null;
const r = json.results[0];
return { platform: 'ios' as const,
name: r.trackName,
iconUrl: r.artworkUrl512 ?? r.artworkUrl100 ?? '',
storeUrl: r.trackViewUrl };
}

resultCount === 0 is the canonical “not found” — never throw on it.

export async function resolveAppMetadata(id: string) {
if (/^\d+$/.test(id)) return lookupIos(id, 'id');
try { return await lookupAndroid(id); }
catch { return lookupIos(id, 'bundleId'); }
}

Store results in app_metadata_cache (columns: package_name, name, icon_url, store_url, platform, resolved_at, not_found). Refresh TTL ≈ 30 days. Mark not_found = TRUE to avoid re-hitting the API. Batch: 500 ms inter-lookup delay. See references/implementation.md for schema, batch wrapper, and artworkUrl size priority.

These are external vendor calls — run only in a scheduler / cron / job context. Dashboard / read endpoints query app_metadata_cache only; a cache miss returns empty (never triggers a live fetch). See project CLAUDE.md § “Live-API Isolation”.

  • google-play-scraper throws on a missing package — catch per-item, never abort the batch.
  • resultCount === 0 from iTunes ≠ HTTP error — it is a successful “not found”.
  • Purely numeric identifiers are always iOS; passing them to gplay.app() throws.
  • artworkUrl512 may be absent for older apps; fall back to artworkUrl100.
  • not_found cache flag prevents infinite re-fetching of removed/region-restricted apps.