One API to post everywhere. 8 platforms, zero headaches.
The official Node.js SDK for the PostZen API — schedule and publish social media posts across X/Twitter, Instagram, TikTok, LinkedIn, Facebook, YouTube, Threads, and Pinterest with a single integration.
npm install @postzen/nodeRequires Node 18+. Ships CommonJS and ESM builds with TypeScript declarations, and has zero runtime dependencies (it uses the built-in fetch).
Create an API key on the API keys page and expose it as POSTZEN_API_KEY:
export POSTZEN_API_KEY="pzn_live_..."Then create and publish a post:
import PostZen from '@postzen/node';
const postzen = new PostZen();
const { data } = await postzen.posts.createPost({
body: {
title: 'Launch post',
content: 'We shipped the new release.',
publishNow: true,
platforms: [
{
platform: 'twitter',
accountId: 'account_id',
},
],
},
});
if (data && 'post' in data) {
console.log(`Created post ${data.post._id}`);
}The client authenticates with a bearer API key, resolved in this order:
- The
apiKeyoption passed to the constructor. - The
POSTZEN_API_KEYenvironment variable.
If neither is set, the constructor throws a PostZenApiError with code missing_api_key.
import PostZen from '@postzen/node';
// Explicit key
const postzen = new PostZen({ apiKey: 'pzn_live_...' });
// Or rely on POSTZEN_API_KEY in the environment
const fromEnv = new PostZen();import PostZen from '@postzen/node';
const postzen = new PostZen({
apiKey: 'pzn_live_...',
baseURL: 'https://api.postzen.dev',
timeout: 60000,
defaultHeaders: {
'X-Request-Source': 'my-app',
},
});| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
process.env.POSTZEN_API_KEY |
PostZen API key used as a bearer token. |
baseURL |
string | null |
https://api.postzen.dev |
Override the API base URL. |
timeout |
number |
60000 |
Request timeout in milliseconds. The client aborts timed-out requests. |
defaultHeaders |
Record<string, string> |
{} |
Headers sent with every API request. |
const { data } = await postzen.profiles.createProfile({
body: {
name: 'Marketing Team',
description: 'Profile for marketing campaigns',
color: '#4caf50',
},
});
console.log(data.profile._id);const profileId = 'profile_id';
const { data: start } = await postzen.connect.createConnectUrl({
path: {
platform: 'linkedin',
},
query: {
profileId,
redirectUrl: 'https://yourapp.example/connected',
},
});
console.log(start.authUrl);
await postzen.connect.completeConnect({
path: {
platform: 'linkedin',
},
body: {
code: 'oauth_code_from_redirect',
state: start.state,
profileId,
},
});const file = await fetch('https://example.com/image.png').then((response) => response.blob());
const { data: presign } = await postzen.media.createMediaPresign({
body: {
filename: 'image.png',
contentType: 'image/png',
size: file.size,
},
});
await fetch(presign.uploadUrl, {
method: 'PUT',
headers: {
'Content-Type': 'image/png',
},
body: file,
});
await postzen.posts.createPost({
body: {
title: 'Post with media',
content: 'Uploaded through a PostZen presigned URL.',
publishNow: true,
mediaItems: [
{
url: presign.publicUrl,
title: 'image.png',
},
],
platforms: [
{
platform: 'instagram',
accountId: 'account_id',
},
],
},
});media.upload is a one-step helper: it presigns, uploads the raw bytes to the
returned storage URL, and resolves with the public URL and metadata. Pass a file
path (the content type is inferred from the extension) or raw bytes with an
explicit filename.
// From a file path — content type inferred from the extension.
const media = await postzen.media.upload('./photo.jpg');
// From a Buffer or Uint8Array — filename is required.
const fromBytes = await postzen.media.upload(buffer, { filename: 'photo.jpg' });
// media => { publicUrl, key, type, size, filename }
console.log(media.publicUrl);Reference the returned publicUrl in posts.createPost:
const media = await postzen.media.upload('./photo.jpg');
await postzen.posts.createPost({
body: {
title: 'Post with media',
content: 'Uploaded through the PostZen SDK.',
publishNow: true,
mediaItems: [{ url: media.publicUrl, title: media.filename }],
platforms: [{ platform: 'instagram', accountId: 'account_id' }],
},
});const { data } = await postzen.accounts.listAccounts({
query: {
status: 'connected',
platform: 'linkedin',
},
});
for (const account of data.accounts) {
console.log(`${account.platform}: ${account.displayName}`);
}accounts.listAccounts accepts page and limit (max 100) and returns a pagination object with page, limit, total, and totalPages.
let page = 1;
let totalPages = 1;
do {
const { data } = await postzen.accounts.listAccounts({
query: { page, limit: 50 },
});
for (const account of data.accounts) {
console.log(`${account.platform}: ${account.displayName}`);
}
totalPages = data.pagination?.totalPages ?? 1;
page += 1;
} while (page <= totalPages);The SDK throws PostZenApiError for non-2xx API responses. Specialized subclasses are used for rate limits and validation errors.
import PostZen, { PostZenApiError, RateLimitError, ValidationError } from '@postzen/node';
const postzen = new PostZen();
try {
await postzen.posts.createPost({
body: {
content: 'Hello from PostZen',
publishNow: true,
platforms: [{ platform: 'twitter', accountId: 'account_id' }],
},
});
} catch (error) {
if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry in ${error.getSecondsUntilReset()} seconds.`);
} else if (error instanceof ValidationError) {
console.error('Validation failed:', error.fields);
} else if (error instanceof PostZenApiError) {
console.error(`PostZen API error ${error.statusCode}: ${error.message}`);
} else {
throw error;
}
}Error classes:
| Class | Description |
|---|---|
PostZenApiError |
Base API error with statusCode, code, details, and helper methods. |
RateLimitError |
HTTP 429 with limit, remaining, resetAt, and getSecondsUntilReset(). |
ValidationError |
HTTP 400 with fields: Record<string, string[]>. |
| Method | Description |
|---|---|
profiles.listProfiles() |
List profiles |
profiles.createProfile() |
Create a profile |
profiles.getProfile() |
Get a profile |
profiles.updateProfile() |
Update a profile |
profiles.deleteProfile() |
Delete a profile |
| Method | Description |
|---|---|
accounts.listAccounts() |
List accounts |
accounts.createPinterestBoard() |
Create Pinterest board |
accounts.getPinterestBoards() |
List Pinterest boards |
accounts.updatePinterestBoards() |
Set default Pinterest board |
accounts.disconnectAccount() |
Disconnect an account |
| Method | Description |
|---|---|
connect.listPinterestBoardsForSelection() |
List Pinterest boards for the connect flow |
connect.createConnectUrl() |
Create an OAuth connect URL |
connect.completeConnect() |
Complete an OAuth connection |
connect.selectPinterestBoard() |
Select Pinterest board |
| Method | Description |
|---|---|
media.createMediaPresign() |
Create a presigned media upload URL |
| Method | Description |
|---|---|
posts.listPostComments() |
List comments on a LinkedIn post |
posts.listPostReactions() |
List reactions on a LinkedIn post |
posts.listPosts() |
List posts |
posts.createPost() |
Create a post |
- Node.js 18 or later
- A PostZen API key (create one)
- Documentation
- API reference
- Python SDK —
postzen-sdkon PyPI - CLI —
@postzen/cli - MCP server
- Dashboard & API keys
MIT