jackwener/OpenCLI · error · CommandExecutionError

Substack publication search failed: HTTP ${resp.status}

Error message

Substack publication search failed: HTTP ${resp.status}

What it means

searchPublications calls the Substack profile/publication search API (substack.com/api/v1/profile/search). Any non-ok HTTP response throws a CommandExecutionError carrying the status code.

Source

Thrown at clis/substack/search.js:44

        throw new CommandExecutionError(`Substack post search failed: HTTP ${resp.status}`);
    const data = await resp.json();
    const results = Array.isArray(data?.results) ? data.results : [];
    return results.slice(0, limit).map((item, index) => ({
        rank: index + 1,
        title: trim(item?.title),
        author: trim(item?.publishedBylines?.[0]?.name),
        date: trim(item?.post_date).split('T')[0] || trim(item?.post_date),
        description: trim(item?.description || item?.subtitle || item?.truncated_body_text).slice(0, 150),
        url: trim(item?.canonical_url),
    }));
}
async function searchPublications(keyword, limit) {
    const url = new URL('https://substack.com/api/v1/profile/search');
    url.searchParams.set('query', keyword);
    url.searchParams.set('page', '0');
    const resp = await fetch(url, { headers: headers() });
    if (!resp.ok)
        throw new CommandExecutionError(`Substack publication search failed: HTTP ${resp.status}`);
    const data = await resp.json();
    const results = Array.isArray(data?.results) ? data.results : [];
    return results.slice(0, limit).map((item, index) => {
        const publication = item?.primaryPublication || item?.publicationUsers?.[0]?.publication || {};
        return {
            rank: index + 1,
            title: trim(publication?.name || item?.name),
            author: trim(item?.name),
            date: '',
            description: trim(publication?.hero_text || item?.bio).slice(0, 150),
            url: publicationBaseUrl(publication),
        };
    });
}
cli({
    site: 'substack',
    name: 'search',
    access: 'read',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry with backoff for transient 429/5xx statuses
  2. Run within a browser session so request headers include valid cookies
  3. Reduce lookup frequency or batch size
  4. Confirm the endpoint path is still valid after Substack API updates

Example fix

// before
const pubs = await searchPublications('tech', 10); // throws on 403
// after
let pubs;
try { pubs = await searchPublications('tech', 10); }
catch (e) { console.error('publication search:', e.message); pubs = []; }
Defensive patterns

Strategy: retry

Validate before calling

// ensure a browser session exists so headers() carries valid cookies
if (!session || !session.cookies) throw new Error('run within a browser session for substack publication search');

Type guard

null

Try / catch

try { pubs = await searchPublications(q, 10); } catch (e) {
  const m = /HTTP (\d+)/.exec(e.message);
  if (m && ['429','502','503'].includes(m[1])) { await sleep(5000); pubs = await searchPublications(q, 10); } else throw e;
}

Prevention

When it happens

Trigger: Any searchPublications call where the profile search endpoint returns non-2xx: rate limiting, missing session cookies, 403 from bot protection, or 5xx during Substack incidents.

Common situations: Bulk publication lookups in a loop, unauthenticated calls from datacenter IPs, Substack API surface changes, transient 502/503 during platform deploys.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/99ef0f2dc07cbbe5. Report an issue: GitHub.