nexoflow-sdk
v0.3.8
Published
Official SDK for the NexoFlow Developer API - fetch AI-generated blog posts and content with full TypeScript support. Zero dependencies, built-in retries, works in any server runtime.
Maintainers
Readme
nexoflow-sdk
The official TypeScript SDK for the NexoFlow Developer API.
What is NexoFlow?
NexoFlow is an AI-powered content automation platform. It lets you generate, schedule, and publish blog posts and social media content - all from one dashboard. NexoFlow handles AI writing, image generation, and multi-channel publishing (WordPress or any JavaScript framework via the Developer API), then delivers your content so you can display it on any website.
nexoflow-sdk gives you a clean, type-safe way to fetch that content from any JavaScript or TypeScript backend. Zero dependencies, full TypeScript support, built-in retries, and works everywhere Node.js runs.
Why use the SDK?
- Zero dependencies - nothing to audit, nothing to break.
- Full TypeScript support - every response is typed, autocomplete works out of the box.
- Built-in retries & timeouts - transient failures are handled automatically with exponential backoff.
- Async iterators - paginate through thousands of posts with a simple
for awaitloop. - Framework-agnostic - works in Next.js, Nuxt, SvelteKit, Astro, Remix, Express, Fastify, Cloudflare Workers, Deno, Bun, and any server runtime.
- ISR-ready - pass
revalidateand the SDK sets the right cache headers for Next.js Incremental Static Regeneration.
Install
npm install nexoflow-sdk# or with yarn / pnpm
yarn add nexoflow-sdk
pnpm add nexoflow-sdkQuick Start
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
// List recent posts
const { data } = await nf.posts.list({ limit: 10 })
console.log(data.posts) // PostListItem[]
console.log(data.pagination) // { page, limit, total, hasMore }
// Get a single post by slug
const { data: post } = await nf.posts.get("my-first-post")
console.log(post.title)
console.log(post.content) // full HTML content
// Iterate over ALL posts (auto-paginates)
for await (const post of nf.posts.iter()) {
console.log(post.title)
}Related posts
List responses include relatedPosts and relatedPostSlugs (the same ordered slug array). When you fetch a single post with posts.get(slug), the API also returns relatedArticles: one card per slug with title, meta title, featured image URL, and published date. Unpublished or missing neighbors still appear in order with null fields.
Server-Side Usage Only
Your NexoFlow API key (pk_live_*) is a secret. Treat it like a database password.
Do NOT:
- Import
nexoflow-sdkin browser-side code or React client components - Hard-code the key in source files that ship to the browser
- Use the key in inline browser scripts, Vite client bundles, or Angular services
Do:
- Store the key in an environment variable (e.g.
NEXOFLOW_API_KEY) - Only call the SDK from server-side code (Server Components, API routes, server loaders, etc.)
If the SDK detects it is running in a browser (window is defined), it prints a console warning. Execution is not blocked, but the warning indicates a security misconfiguration.
Configuration
const nf = new NexoFlow({
// Required
apiKey: process.env.NEXOFLOW_API_KEY!,
// Optional
baseUrl: "https://nexoflow.net", // default
timeout: 15_000, // 15 seconds (default)
revalidate: 60, // Next.js ISR hint in seconds (default)
// Custom fetch (useful for polyfills, edge runtimes, tests)
fetch: customFetchFn,
// Retry transient failures (5xx, network errors)
retry: {
attempts: 3, // default
delay: 500, // base delay in ms, doubles each retry (default)
},
// Debug logging (request/response details)
debug: true,
// Lifecycle hooks
hooks: {
beforeRequest: ({ url, init }) => {
console.log("Requesting:", url)
},
afterResponse: (res) => {
console.log("Status:", res.status)
},
},
})| Option | Type | Default | Description |
| ------------ | ------------------ | ---------------------- | ------------------------------------------------- |
| apiKey | string | required | Your project API key (pk_live_*) |
| baseUrl | string | https://nexoflow.net | NexoFlow instance URL |
| revalidate | number \| false | 60 | Default ISR cache time (seconds) for Next.js |
| timeout | number | 15000 | Request timeout (ms) |
| fetch | typeof fetch | globalThis.fetch | Custom fetch implementation |
| retry | object | { attempts: 3, delay: 500 } | Retry policy for transient failures |
| debug | boolean | false | Log request/response details to console |
| hooks | object | undefined | beforeRequest / afterResponse lifecycle hooks |
Response Format
Every SDK method returns a consistent envelope:
{
data: T, // the API response payload
rateLimit?: { // extracted from response headers (when available)
limit?: number,
remaining?: number,
reset?: number,
}
}const { data, rateLimit } = await nf.posts.list({ limit: 5 })
console.log(data.posts)
console.log(data.pagination)
console.log(rateLimit?.remaining)Posts
nf.posts.list(params?) // List posts (paginated)
nf.posts.get(slug, opts?) // Get single post with full content
nf.posts.all(params?) // Get ALL posts (auto-paginated)
nf.posts.slugs() // Get all slugs (for static generation)
nf.posts.iter(params?) // Async iterator over all postsList params: page, limit, tag, category, sort ("newest" | "oldest")
Get options: format ("html" | "markdown"), styled (boolean)
SEO / robots meta
Both PostListItem (list responses) and Post (single-post response) include:
| Field | Type | Description |
|-------|------|-------------|
| noIndex | boolean | When true, tell search engines not to index this post |
| noFollow | boolean | When true, tell search engines not to follow links on this post |
| readingTimeMinutes | number | Estimated reading time (min 1). Render as "X min read". |
| path | string | Resolved URL path built from the project's permalink structure (e.g. /2024/03/my-post). Use directly as an href. |
Use them when configuring robots meta tags or framework metadata:
// Next.js App Router — app/blog/[slug]/page.tsx
import type { Metadata } from "next"
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const { data: post } = await nf.posts.get(slug)
return {
title: post.metaTitle || post.title,
description: post.metaDescription ?? undefined,
robots: {
index: !post.noIndex,
follow: !post.noFollow,
},
}
}You can also check the flag in a list response to skip rendering a post or to add robots meta manually:
for (const post of data.posts) {
if (post.noIndex) continue // skip noindex posts from sitemap, etc.
}Site feature flags & permalink structure
Use nf.siteSettings.get() to read the project's global settings once at build time:
const { data: siteSettings } = await nf.siteSettings.get()
// permalinkStructure is informational — each post already exposes a resolved `path`
console.log(siteSettings.permalinkStructure) // e.g. "/%year%/%month%/%slug%"
const {
enableReadingTime, // show "X min read" labels?
enableRelatedPosts, // render related articles section?
enableSocialSharing, // render share buttons?
enableSchemaMarkup, // inject JSON-LD BlogPosting schema?
} = siteSettings.featuresReading time
Every post includes readingTimeMinutes. Gate the render with the feature flag:
{post.siteFeatures.enableReadingTime && (
<span>{post.readingTimeMinutes} min read</span>
)}Related posts
The relatedArticles array on single-post responses already contains resolved cards. Use the toggle to decide whether to render the section:
{post.siteFeatures.enableRelatedPosts && post.relatedArticles.length > 0 && (
<RelatedPostsSection articles={post.relatedArticles} />
)}Social sharing
The enableSocialSharing flag tells you whether to render share buttons. Build them with the post's URL (using path) and title:
{post.siteFeatures.enableSocialSharing && (
<ShareButtons
url={`https://yoursite.com${post.path}`}
title={post.title}
/>
)}JSON-LD schema markup
When enableSchemaMarkup is true, inject a BlogPosting JSON-LD block in your page head (for example via your framework’s metadata or head API).
// Next.js App Router — build jsonLd then pass to your head/layout helper
if (post.siteFeatures.enableSchemaMarkup) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.metaTitle || post.title,
description: post.metaDescription ?? undefined,
image: post.featuredImageUrl ?? undefined,
datePublished: post.publishedAt ?? undefined,
dateModified: post.updatedAt,
author: post.authorData
? { "@type": "Person", name: post.authorData.name }
: undefined,
}
// e.g. serialize with JSON.stringify(jsonLd) and emit as application/ld+json
}Examples
Blog index with pagination:
const { data } = await nf.posts.list({ limit: 12, category: "engineering" })
for (const post of data.posts) {
console.log(`${post.title} - ${post.excerpt}`)
}
if (data.pagination.hasMore) {
const { data: page2 } = await nf.posts.list({ limit: 12, page: 2 })
}Static site generation (get all slugs):
const slugs = await nf.posts.slugs()
// [{ slug: "intro-to-nextjs" }, { slug: "deploy-to-vercel" }, ...]Async iterator - process every post without manual pagination:
for await (const post of nf.posts.iter({ category: "news" })) {
console.log(post.title)
}Things to Do
For location-based content pages with structured attraction data.
nf.thingsToDo.list(params?) // List pages (paginated)
nf.thingsToDo.get(slug, opts?) // Get single page with attractions
nf.thingsToDo.all(params?) // Get ALL pages (auto-paginated)
nf.thingsToDo.slugs() // Get all slugs
nf.thingsToDo.iter(params?) // Async iterator over all pagesList params: page, limit, city, state, sort
Get options: format ("json" | "html"). With "html", the response includes page.contentHtml plus top-level contentStyles and layoutHints for styling and layout guidance.
SEO: Each list item and full page includes noIndex (boolean). When it is true, tell crawlers not to index the URL-for example in Next.js App Router:
// app/things-to-do/[slug]/page.tsx
import type { Metadata } from "next"
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const { data } = await nf.thingsToDo.get(slug)
const page = data.page
return {
title: page.metaTitle || page.pageTitle,
description: page.metaDescription ?? undefined,
...(page.noIndex ? { robots: { index: false, follow: false } } : {}),
}
}Maps / embeds
Each attraction includes:
mapEmbedSrc- HTTPS URL only. Use this for<iframe src={attraction.mapEmbedSrc} />(React, Vue, Svelte, etc.). This avoids passing full HTML intosrc, which browsers treat as a URL string and breaks the embed.mapEmbed- Full<iframe …></iframe>HTML from NexoFlow. Render withdangerouslySetInnerHTML(or your framework’s equivalent), not as an iframesrc.mapLink- Opens the location in Google Maps in a new tab.
mapEmbedSrc may be null if only a short link was stored and the API could not derive a synchronous embed URL; fall back to mapEmbed or mapLink in that case.
Error Handling
import { NexoFlow, NexoFlowError } from "nexoflow-sdk"
try {
const { data } = await nf.posts.get("nonexistent")
} catch (err) {
if (err instanceof NexoFlowError) {
console.log(err.status) // 404
console.log(err.code) // "HTTP_404"
console.log(err.message) // "Post not found."
console.log(err.requestId) // server request ID (if available)
console.log(err.isNotFound) // true
console.log(err.isUnauthorized) // false
console.log(err.isRateLimited) // false
}
}Framework Examples
Next.js (App Router)
Use the SDK in Server Components, Route Handlers, or generateStaticParams. Never import it in client components.
// app/blog/page.tsx - Server Component
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({
apiKey: process.env.NEXOFLOW_API_KEY!,
revalidate: 60,
})
export default async function BlogPage() {
const { data } = await nf.posts.list({ limit: 12 })
return (
<main>
{data.posts.map((post) => (
<article key={post.slug}>
{post.featuredImageUrl && (
<img src={post.featuredImageUrl} alt={post.title} />
)}
<a href={`/blog/${post.slug}`}><h2>{post.title}</h2></a>
<p>{post.excerpt}</p>
</article>
))}
{data.pagination.hasMore && (
<a href={`/blog?page=${data.pagination.page + 1}`}>Next page</a>
)}
</main>
)
}// app/blog/[slug]/page.tsx - Single post with static generation
import { NexoFlow, NexoFlowError } from "nexoflow-sdk"
import { notFound } from "next/navigation"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
export async function generateStaticParams() {
return await nf.posts.slugs()
}
export default async function PostPage({ params }: { params: { slug: string } }) {
try {
const { data: post } = await nf.posts.get(params.slug, { styled: true })
return (
<article>
<h1>{post.title}</h1>
{post.contentStyles && (
<style dangerouslySetInnerHTML={{ __html: post.contentStyles }} />
)}
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
} catch (err) {
if (err instanceof NexoFlowError && err.isNotFound) notFound()
throw err
}
}Nuxt 3
Use in server routes - the API key stays on the server:
// server/api/posts.ts
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
export default defineEventHandler(async () => {
const { data } = await nf.posts.list({ limit: 20 })
return data
})In pages/blog.vue, use a Nuxt script setup block to call useFetch("/api/posts"), then render:
<template>
<article v-for="post in blogData.posts" :key="post.slug">
<NuxtLink :to="`/blog/${post.slug}`">
<h2>{{ post.title }}</h2>
</NuxtLink>
<p>{{ post.excerpt }}</p>
</article>
</template>SvelteKit
Use in load functions (+page.server.ts):
// src/routes/blog/+page.server.ts
import { NexoFlow } from "nexoflow-sdk"
import { NEXOFLOW_API_KEY } from "$env/static/private"
const nf = new NexoFlow({ apiKey: NEXOFLOW_API_KEY })
export async function load() {
const { data } = await nf.posts.list({ limit: 20 })
return { posts: data.posts, pagination: data.pagination }
}Astro
Use in frontmatter (runs at build time or SSR):
---
// src/pages/blog.astro
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: import.meta.env.NEXOFLOW_API_KEY })
const { data } = await nf.posts.list({ limit: 20 })
---
<html>
<body>
{data.posts.map((post) => (
<article>
<a href={`/blog/${post.slug}`}><h2>{post.title}</h2></a>
<p>{post.excerpt}</p>
</article>
))}
</body>
</html>Remix
Use in loader functions:
// app/routes/blog.tsx
import { json } from "@remix-run/node"
import { useLoaderData, Link } from "@remix-run/react"
import { NexoFlow } from "nexoflow-sdk"
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
export async function loader() {
const { data } = await nf.posts.list({ limit: 20 })
return json(data)
}
export default function Blog() {
const { posts } = useLoaderData<typeof loader>()
return (
<main>
{posts.map((post) => (
<article key={post.slug}>
<Link to={`/blog/${post.slug}`}><h2>{post.title}</h2></Link>
<p>{post.excerpt}</p>
</article>
))}
</main>
)
}Express / Fastify
import express from "express"
import { NexoFlow } from "nexoflow-sdk"
const app = express()
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })
app.get("/api/posts", async (req, res) => {
const { data } = await nf.posts.list({ limit: 20 })
res.json(data)
})
app.listen(3000)Edge Runtimes (Cloudflare Workers, Vercel Edge)
The SDK uses globalThis.fetch and AbortSignal.timeout - both available natively in edge runtimes:
export default {
async fetch(request: Request, env: Env) {
const nf = new NexoFlow({ apiKey: env.NEXOFLOW_API_KEY })
const { data } = await nf.posts.list({ limit: 5 })
return Response.json(data)
},
}TypeScript
Every response is fully typed. Import individual types as needed:
import type {
Post,
PostListItem,
RelatedArticle,
SiteFeatures,
SiteSettingsResponse,
Category,
Author,
Tag,
Pagination,
ApiResponse,
RateLimitInfo,
} from "nexoflow-sdk"Requirements
- Node.js 18+ (uses native
fetchandAbortSignal.timeout) - Works with Next.js, Nuxt, SvelteKit, Astro, Remix, Express, Fastify, Angular (server-side), Cloudflare Workers, Deno, and Bun
Links
- NexoFlow Dashboard - create your project and get an API key
- GitHub - source code, issues, contributions
- npm - package registry
License
MIT
