Skip to content

Feed core — API reference

The complete public surface of @ghostwritr/feed. All reads are keyless: the siteId is the only key. For task-level guidance, start at the overview.

import {
fetchArticles,
fetchArticle,
toArticle,
signWebhook,
verifyWebhook,
WEBHOOK_ID_HEADER,
WEBHOOK_TIMESTAMP_HEADER,
WEBHOOK_SIGNATURE_HEADER,
DEFAULT_STATIC_BASE_URL,
GhostwritrError,
} from "@ghostwritr/feed";
import type {
FeedFetchOptions,
Article,
ArticleImage,
Author,
ArticleOrder,
GhostwritrErrorCode,
FeedUpdatedPayload,
} from "@ghostwritr/feed";
fetchArticles(opts: FeedFetchOptions): Promise<Article[]>

Every published article, newest first; de-duped by id. Returns [] for an empty feed; throws NOT_FOUND when the manifest is absent. See Fetchers.

fetchArticle(opts: FeedFetchOptions & { slug: string }): Promise<Article | null>

One article by slug via its by-slug object, or null on a 404. See Fetchers.

toArticle(raw: unknown): Article

Validate and map a single raw feed item. Throws INVALID_RESPONSE on a missing/mistyped guaranteed field or an unparseable timestamp. See Fetchers.

signWebhook(
secret: string,
params: { id: string; timestamp: number; rawBody: string },
): Promise<string>

Builds the Webhook-Signature value (v1=<hex>) for a delivery — the HMAC-SHA256 of `${id}.${timestamp}.${rawBody}`. Mostly used to build a fixture in tests. See Webhook helpers.

verifyWebhook(
secret: string,
rawBody: string,
headers: Headers | Record<string, string | null | undefined>,
opts?: { toleranceSeconds?: number; now?: number },
): Promise<boolean>

Verify an inbound webhook. Reads Webhook-Id / Webhook-Timestamp / Webhook-Signature from the headers object itself, rejects a timestamp outside toleranceSeconds (default 300 → replay defense), and constant-time compares the v1 HMAC. Returns false on any missing/stale/mismatched input. See Webhook helpers.

const WEBHOOK_ID_HEADER = "Webhook-Id";

The per-delivery id header — also the idempotency key; dedupe retries on it.

const WEBHOOK_TIMESTAMP_HEADER = "Webhook-Timestamp";

The delivery’s unix-seconds timestamp header — signed into the HMAC and checked against the replay-tolerance window.

const WEBHOOK_SIGNATURE_HEADER = "Webhook-Signature";

The signature header, carrying v1=<hex> where <hex> is the HMAC-SHA256, keyed by your secret, of `${id}.${timestamp}.${rawBody}`.

const DEFAULT_STATIC_BASE_URL = "https://feeds.ghostwritr.io";

The default static-feed origin. Override per-call with opts.staticBaseUrl.

interface FeedFetchOptions {
siteId: string; // the unguessable read key
staticBaseUrl?: string; // default DEFAULT_STATIC_BASE_URL
fetch?: typeof fetch; // caching / retry / test seam; default global fetch
}
interface Article {
id: string;
title: string; // your <h1>
slug: string; // your route param
markdown: string; // the body (Markdown, not MDX)
seoTitle: string; // falls back to title
seoDescription: string | null;
canonicalUrl: string | null;
formatType: string; // schema.org type hint, e.g. "Article"
inLanguage: string; // BCP-47, e.g. "en"
tags: string[]; // possibly empty
image: ArticleImage | null;
author: Author; // always present
publishedAt: string; // ISO-8601
createdAt: string; // ISO-8601
updatedAt: string; // ISO-8601
}

A published article is a complete article: markdown, seoTitle, publishedAt, and author are guaranteed (with server-side fallbacks). See the article shape.

interface ArticleImage {
url: string; // ready-to-use responsive URL (default/large width)
alt: string; // may be empty for a decorative cover
width: number | null; // set <img width/height> to prevent layout shift
height: number | null;
srcset: string | null; // for <img srcset>
}
interface Author {
name: string;
slug: string;
bio: string | null;
avatarUrl: string | null;
jobTitle: string | null;
sameAs: string[]; // profile/social URLs
kind: "real" | "persona"; // "real" → Person JSON-LD; "persona" → Organization
}

author.kind drives JSON-LD: "real" emits a schema.org Person, "persona" emits an Organization. See SEO & JSON-LD.

type ArticleOrder = "published-desc" | "feed";

Ordering hint for an adapter’s article list. "published-desc" (default) is newest-first; "feed" is the static feed’s native order (also newest-first by updatedAt).

type GhostwritrErrorCode =
| "CONFIG"
| "NETWORK_ERROR"
| "UNAUTHORIZED"
| "FORBIDDEN"
| "NOT_FOUND"
| "RATE_LIMITED"
| "SERVER_ERROR"
| "INVALID_RESPONSE"
| (string & {}); // open to future codes

See Error handling.

interface FeedUpdatedPayload {
event: "feed.updated";
siteId: string;
buildId: string;
}

The webhook body POSTed when a site’s feed rebuilds. See Webhook helpers.

class GhostwritrError extends Error {
readonly status: number; // HTTP status, or 0 for client-side errors
readonly code: GhostwritrErrorCode | null;
readonly details?: unknown;
}

The single error type thrown by every function. See Error handling.