Build a native reader with TypeScript
Use the public client during a server render, in a route, or from any JavaScript runtime with fetch.
Install the SDK
pnpm add @prosewire/sdkThe package declares Node.js 24 or newer, ships ESM JavaScript plus bundled
TypeScript declarations, and uses fetch for HTTP requests.
Create a client
import { createPublicClient } from "@prosewire/sdk";
const content = createPublicClient({
baseUrl: "https://publish.example.com",
blog: "fieldnotes",
});List and retrieve posts
const posts = await content.listPosts({ page: 1, pageSize: 10 });
const article = await content.getPost("shipping-with-confidence");
const resolution = await content.resolvePost("an-older-slug");
const html = await content.getRendered("shipping-with-confidence");Public queries only return published content whose publication time has arrived. Drafts and future scheduled posts stay private.
What the client returns
- Post content and excerpt
- Canonical slug and publication timestamps
- Authors and categories
- Locale and search metadata
- Reading time and page metadata
listPosts accepts search, category, page, and pageSize; pageSize is capped at 100. The older limit option remains a deprecated alias. A non-success HTTP response rejects with an error that includes the response status.
listAllPosts follows pagination for static builds. resolvePost distinguishes a canonical post, an old slug that should redirect, and a missing post. listRedirects returns the redirect manifest used by framework integrations.
Use the management API
Create a publication-scoped API key in Integrate → Scoped API keys. Keep it in a server-only environment variable and grant only the scopes the application needs.
PROSEWIRE_API_URL=https://your-prosewire-deployment
PROSEWIRE_API_KEY=pw_live_...import { createClient } from "@prosewire/sdk";
const management = createClient({
baseUrl: process.env.PROSEWIRE_API_URL!,
apiKey: process.env.PROSEWIRE_API_KEY!,
});
const drafts = await management.posts.list({ status: "draft" });Never expose a management key in browser JavaScript. Public content clients do not need authentication.
The optional blog value on posts.list() asserts that the key belongs to the
given publication slug or UUID. A mismatch is rejected rather than selecting a
different publication.
The Promise client exposes these management operations:
await management.health();
await management.blogs.list();
await management.posts.list({ page: 1, pageSize: 20 });
await management.posts.get({ params: { id: postId } });
const revisions = await management.posts.revisions({ params: { id: postId } });
await management.posts.create(input);
await management.posts.update({ params: { id: postId }, body: patch });
await management.posts.restore({
params: { id: postId, revisionId: revisions[0].id },
});
await management.posts.archive({ params: { id: postId } });Managed media uses a signed direct upload. Keep this flow in trusted server code because it starts with a write-scoped management key:
import { readFile } from "node:fs/promises";
const bytes = await readFile("cover.webp");
const reservation = await management.media.startUpload({
blogId,
filename: "cover.webp",
mimeType: "image/webp",
byteSize: bytes.byteLength,
});
const uploaded = await fetch(reservation.upload.url, {
method: reservation.upload.method,
headers: reservation.upload.headers,
body: bytes,
});
if (!uploaded.ok) throw new Error(`Upload failed: ${uploaded.status}`);
const asset = await management.media.completeUpload({
params: { id: reservation.asset.id },
});Use media.list() and media.get() to inspect quota, variants, and references. media.delete() permanently removes stored objects and is rejected while a current post references the asset.
createEffectClient exposes the same operations as Effects. Create, update, and media inputs are validated by the shared contract; see Use raw JSON or rendered HTML for limits and HTTP behavior.