Skip to Content

PostNitro CLI

The PostNitro CLI creates on-brand social media posts — multi-slide carousels and single images — and schedules them to LinkedIn, Instagram, TikTok, and Threads, all from the command line. It’s a thin, scriptable wrapper over the Embed API, built for AI agents, automations, and scripts: every command takes flags or JSON in and prints JSON out (clean stdout on success, JSON error on stderr) — no colors, tables, or interactive prompts to parse around. It uses the same Embed API key and credits as the REST API and MCP server.

Quick Reference

Package@postnitro/cli (npm)
Binarypostnitro
RequiresNode.js ≥ 18, and a PostNitro account on a paid plan
Authentication--api-key, POSTNITRO_API_KEY, or a saved key — keys start with pn-
API keySame key as the Embed API — how to get one
OutputJSON to stdout on success (exit 0); JSON error to stderr (exit 1)
GitHubpostnitro/postnitro-agent 

Installation

# Install globally from npm (recommended) npm install -g @postnitro/cli postnitro --help # Or run without installing npx @postnitro/cli --help

Install as a skill

You can also install PostNitro as a skill for your AI coding agent — it teaches the agent to drive the CLI on your behalf:

npx skills add postnitro/postnitro-agent

For Claude Code specifically, you can instead add it as a plugin:

# in Claude Code /plugin marketplace add postnitro/postnitro-agent /plugin install postnitro@postnitro-agent

The skill is defined in SKILL.md at the repo root  (with a copy under skills/postnitro/); the Claude Code plugin manifests live in .claude-plugin/.

Authentication

Get an API key from PostNitro → Profile → Embed → Generate API Key (see Obtaining an API Key). Provide it in any of these ways (highest precedence first):

  1. --api-key <key> on any command
  2. POSTNITRO_API_KEY environment variable
  3. Saved config — postnitro auth set-key <key> (stored at ~/.postnitro-cli/config.json)
postnitro auth set-key pn-xxxxxxxxxxxx # save it once postnitro auth status # check what's configured (key is masked) postnitro auth clear # remove the saved key

Security: auth set-key stores your key in plaintext at ~/.postnitro-cli/config.json. Restrict its permissions, avoid shared machines, and use --api-key or POSTNITRO_API_KEY in CI.

Core concepts

Post type × content source. Creating a post is two independent choices:

generate (AI writes it)import (you supply content)
carousel (multi-slide)postnitro carousel generatepostnitro carousel import (slides array)
image (single)postnitro image generatepostnitro image import (single slide object)

Asynchronous by default. Creating a post kicks off a background job and returns an embedPostId. Add --wait to poll to completion and get the final output in one call, or poll manually with status / output.

embedPostId vs designId. Creation returns an embedPostId (the job). To schedule a post you need its designId (from the --wait result or output). Never pass an embedPostId to schedule — see carousel identifiers.

Editor link. Every completed post’s output includes an editorUrl — a deep link to open the design in the PostNitro editor.

JSON in / JSON out. Complex inputs can be passed inline as JSON (agent-friendly) or from a file; inline always wins. Every command prints JSON to stdout on success (exit 0) or a JSON error to stderr (exit 1).

Smart defaults. --template-id / --brand-id / --preset-id resolve in this order: explicit flag → saved default (defaults set) → auto-select when your workspace has exactly one candidate.

Global options

Available on every command:

OptionDescription
--api-key <key>PostNitro API key (falls back to POSTNITRO_API_KEY, then saved config)
-V, --versionPrint the CLI version
-h, --helpShow help for any command or subcommand

Command reference

auth

Manage the saved PostNitro API key.

CommandDescription
auth set-key <apiKey>Save an API key to ~/.postnitro-cli/config.json
auth statusShow whether a key is configured (masked) and where it’s coming from
auth clearRemove the saved API key

defaults

Save default templateId / brandId / presetId / responseType so create commands don’t need them every time.

CommandOptionsDescription
defaults getShow saved defaults for the active API key
defaults set--template-id <id> --brand-id <id> --preset-id <id> --response-type <PDF|PNG|DESIGN>Save one or more defaults (at least one required)
postnitro defaults set --template-id tpl_123 --brand-id brd_456 --preset-id prs_789 --response-type PDF postnitro defaults get

template

Browse available carousel templates.

CommandOptionsDescription
template list--page <n> (default 1) · --limit <n> (default 10, max 50)List templates with IDs, names, and dimensions
postnitro template list --limit 20

brand

Manage brand kits (logo, name, handle stamped on designs). See the Brands API for full field definitions.

CommandDescription
brand list [--page <n>] [--limit <n>]List brand kits
brand get <id>Fetch a single brand kit
brand create [options]Create a brand kit (required: --name, --handle, --image, or --data/--file)
brand update <id> [options]Update a brand kit (all fields required by the API — fetch current values first)

Options for create / update:

OptionDescription
--name <name>Brand display name
--handle <handle>Social handle, e.g. @postnitroai
--image <url>Logo image URL
--company-detailTreat as a company brand (vs personal)
--data <json>Full brand object as inline JSON (overrides --file and the flags)
--file <path>Full brand object from a JSON file (overridden by --data)
--no-show-name / --no-show-handle / --no-show-imageHide that element on slides
postnitro brand create --name "PostNitro" --handle "@postnitroai" --image "https://.../logo.png" --company-detail postnitro brand create --data '{"name":"PostNitro","handle":"@postnitroai","image":"https://.../logo.png","isCompanyDetail":true}'

preset

Browse available AI generation presets (each bundles social platform, tone, audience, language, slide count, and model).

CommandOptionsDescription
preset list--page <n> · --limit <n> (max 50)List AI presets with IDs

social

Manage connected social accounts. See the Social Accounts API for full field definitions.

CommandDescription
social listList connected accounts, grouped by platform (returns the IDs used for --selected-accounts)
social get <id>Fetch a single account, including usage stats
social disconnect <id> --yesDisconnect an account. Destructive, requires --yes, cannot be undone

Generate, import, and inspect multi-slide carousel posts.

CommandDescription
carousel import-templatePrint the exact slide structure and rules for carousel import
carousel generate [options]Generate a carousel with AI
carousel import [options]Create a carousel from your own slides
carousel status <embedPostId>Check generation status + processing logs
carousel output <embedPostId>Retrieve the final output (files, designId, editorUrl)

carousel generate options:

OptionDescription
--context <text>Required. Topic/prompt, or an article/X URL (depending on --type)
--type <text|article|x>How --context is interpreted (default text)
--instructions <text>Extra guidance for the AI
--template-id <id> / --brand-id <id> / --preset-id <id>Override saved defaults
--response-type <PDF|PNG|DESIGN>Output format (default PDF) — see Response types
--requestor-id <id>Optional custom tracking ID
--waitPoll until complete and print the final output
--generate-images + --image-context + --image-placement + --image-strategyAI image generation

carousel import options: same as above except no --type / --instructions / --preset-id, plus:

OptionDescription
--slides <json>Slides as inline JSON — a bare array or {"slides":[...]} (overrides --file)
--file <path>Path to a JSON file containing the slides array
# AI-generated postnitro carousel generate --context "5 tips for remote work" --type text --wait postnitro carousel generate --context "https://example.com/post" --type article --wait # Import your own slides postnitro carousel import --slides '{"slides":[ {"type":"starting_slide","heading":"Your Title","description":"Intro"}, {"type":"body_slide","heading":"Key Point","description":"Details"}, {"type":"ending_slide","heading":"Take Action!","cta_button":"Learn More"} ]}' --wait

Carousel slide rules: the array must contain exactly 1 starting_slide (first), ≥1 body_slide, and exactly 1 ending_slide (last). Every slide needs a heading. Optional per slide: sub_heading, description, image, background_image, cta_button, and layoutType/layoutConfig (see Infographic layout). Run carousel import-template for the authoritative schema.

image

Generate, import, and inspect single-image posts. Mirrors carousel, but produces one image and import takes a single slide object (not an array).

CommandDescription
image import-templatePrint the exact slide object and rules for image import
image generate [options]Generate a single image with AI (same options as carousel generate)
image import [options]Create an image from your own content
image status <embedPostId>Check generation status + logs
image output <embedPostId>Retrieve the final output

image import options: same as carousel import, but slide input is a single object:

OptionDescription
--slide <json>The slide as inline JSON — a single object (overrides --file)
--file <path>Path to a JSON file with a single slide object (or { "slides": {...} })
postnitro image generate --context "Announce our new scheduling feature" --wait postnitro image import --slide '{"heading":"Welcome!","sub_heading":"Subtitle","cta_button":"Learn more"}' --wait

Image slide fields (single object; heading required, all others optional): heading, sub_heading, description, cta_button, image, background_image, plus layoutType/layoutConfig for infographics. Any other field is rejected. Sending an array is rejected — arrays are carousel-only.

schedule

Manage scheduled posts and drafts. Full CRUD is also available via the Schedule API.

CommandDescription
schedule list --from <date> --to <date>List scheduled posts/drafts in a date range
schedule get <id>Fetch a single scheduled post
schedule create [options]Create a scheduled post or draft
schedule update <id> [options]Update a post — REPLACES state, send the full intended body
schedule delete <id> --yesDelete a scheduled post/draft. Destructive, requires --yes

schedule create / update options:

OptionDescription
--status <DRAFT|SCHEDULED>Required. SCHEDULED publishes at the given time; DRAFT saves without publishing
--scheduled-at <iso>Required. ISO-8601 datetime, must be ≥ 5 minutes in the future (trailing Z)
--design-id <id>The design to attach (from output’s designIdnot the embedPostId)
--post-content <json>Captions keyed by platform: common, linkedin, instagram, tiktok, facebook, threads
--selected-accounts <json>Array of social-account IDs
--linkedin-post-settings / --instagram-post-settings / --tiktok-post-settings / --threads-post-settings <json>Per-platform settings — see Platform settings
--post-settings <json>Reel settings, e.g. {"videoDuration":30}
--file <path>JSON file with any of the above (inline flags override it)

A post must have either --design-id or non-empty --post-content. Hashtags are auto-extracted from captions.

postnitro schedule create \ --status SCHEDULED \ --scheduled-at "2026-12-31T13:00:00Z" \ --design-id dsg_abc \ --selected-accounts '["acct_123"]' \ --linkedin-post-settings '{"postType":"document","postTitle":"5 remote work tips"}' \ --post-content '{"common":"New carousel 🚀 #remotework"}' postnitro schedule list --from "2026-01-01" --to "2026-12-31" postnitro schedule delete sch_123 --yes

generate-and-schedule

One-shot: generate with AI → wait → schedule in a single call. May take 30–180s.

Takes every generate flag (--context required, --type, --instructions, --preset-id, …), plus --post-type CAROUSEL|IMAGE (default CAROUSEL), the AI-image flags, and all the schedule create flags (--status and --scheduled-at required). --file here is the schedule body.

If generation succeeds but scheduling fails, the error returns the designId so you can retry schedule create without regenerating (and re-spending credits).

postnitro generate-and-schedule \ --context "How AI agents automate content" --type text \ --status SCHEDULED --scheduled-at "2026-12-31T12:00:00Z" \ --selected-accounts '["acct_123"]' \ --linkedin-post-settings '{"postType":"document","postTitle":"How AI agents automate content"}' \ --post-content '{"common":"New drop 🚀 #ai #automation"}'

import-and-schedule

One-shot: import your own content → wait → schedule in a single call. The import-side counterpart of generate-and-schedule.

OptionDescription
--post-type <CAROUSEL|IMAGE>Post kind (default CAROUSEL)
--slides <json>CAROUSEL slides as inline JSON (array or {"slides":[...]})
--slide <json>IMAGE slide as inline JSON (single object)
--slides-file <path>Slides from a file (array for CAROUSEL, object for IMAGE)
--template-id / --brand-id / --response-type / --requestor-idAs in import
--status / --scheduled-atRequired (as in schedule create)
--design-id · --file · --post-content · --selected-accounts · --*-post-settings · --post-settingsScheduling body (as in schedule create)
AI-image flags--generate-images, --image-context, --image-placement, --image-strategy

Flag note: here --file is the schedule body (like generate-and-schedule), so slide content comes from --slides / --slide / --slides-file, not --file.

postnitro import-and-schedule \ --slides '{"slides":[ ... ]}' \ --status SCHEDULED --scheduled-at "2026-12-31T12:00:00Z" \ --selected-accounts '["acct_123"]' \ --post-content '{"common":"..."}'

AI image generation

Opt-in: have AI generate images and bake them into the design before rendering. Available on all four create commands and both one-shot commands, for carousels and images alike.

FlagValuesDefaultNotes
--generate-images(boolean)offOpt in. Also implied by any flag below.
--image-context <text>stringRequired when generating images: a short visual brief for the prompts
--image-placement <mode>auto | background | in-lineautoauto = AI decides per slide
--image-strategy <mode>strategic | allstrategicstrategic ≈ 50% of slides; all = every eligible slide
postnitro carousel generate --context "How scheduling saves marketers time" \ --generate-images --image-context "upbeat and professional, product-focused" \ --image-placement auto --image-strategy all --wait
  • Best-effort. The post still completes if image generation fails or isn’t permitted. With --wait, the result carries an imageGeneration field (the GENERATE_IMAGES step); a FAILED status there explains why (e.g. free plan can’t generate AI images, or the org is over its AI-image quota). Without --wait, the same step appears in status logs.
  • Credits. AI images consume the organization’s separate AI-image quota — the post’s slide-based credits are unchanged.
  • Adds latency (real images are rendered).

Infographic layout

On either import command, set layoutType: "infographic" on a slide (with a layoutConfig) to render data columns (max 3) instead of an image. Works on both carousel slides and the single image slide.

layoutConfig fields (omitted fields use the defaults shown):

FieldTypeDefaultNotes
hasHeaderbooleantrueShow a header
columnCountnumber11–3
displayCounterAsstring"counter""counter" or "none"
columnDisplaystring"grid""grid" (comparative) or "cycle" (sequential — put all data in the first column)
columnDataarray[]The columns

Each columnData entry: id (string), header (string), content (array). Each content item: id (string), icon (string | null), title (string), description (HTML string, e.g. <p dir="ltr">…</p>), titleEnabled (boolean), descriptionEnabled (boolean).

Important: the id fields on columns and items are caller-provided and required — the API stores them as-is and does not auto-generate them. If layoutType is infographic but layoutConfig is missing, the slide falls back to the default layout.

{ "heading": "Our 2026 Results", "layoutType": "infographic", "layoutConfig": { "hasHeader": true, "columnCount": 3, "columnDisplay": "grid", "displayCounterAs": "counter", "columnData": [ { "id": "col-1", "header": "Growth", "content": [ { "id": "item-1", "icon": null, "title": "+42%", "description": "<p dir=\"ltr\">Revenue YoY</p>", "titleEnabled": true, "descriptionEnabled": true } ]} ] } }

Platform settings

Pass per-platform behavior via the --*-post-settings flags when scheduling. Settings are conditionally required based on the platforms among your --selected-accounts. See Platform settings for the full reference.

FlagShape
--linkedin-post-settings{"postType":"carousel|document|image|reel","postTitle":"..."}document needs a 5–90 char postTitle
--instagram-post-settings{"postType":"carousel|image|reel","postAsStory":false}
--tiktok-post-settings{"postType":"carousel|reel","privacyLevel":"PUBLIC_TO_EVERYONE|MUTUAL_FOLLOW_FRIENDS|SELF_ONLY","canComment":true,"canDuet":true,"canStitch":true,"autoAddMusic":false,"postTitle":null,"isBrandedContent":false,"isYourBrand":false,"isThirdPartyBrand":false,"isAIGeneratedContent":true}
--threads-post-settings{"postType":"carousel|image|reel"}
--post-settings (reel){"videoDuration":30,"audioId":"..."}

LinkedIn tip: PDF carousels are normally posted as postType: "document" (which needs a postTitle).

Response types & output

--response-type accepts three values (CLI default is PDF):

ValueBehavior
PDFRenders and returns a PDF (single file URL in data)
PNGRenders and returns PNG image(s) (one URL per slide in data)
DESIGNSkips rendering. Creates the editable design only — no file. Fastest/lightest when you only need to schedule or edit

Every completed output includes designId, name, size, and editorUrl. PDF/PNG additionally include type, mimeType, and data (the file URLs); DESIGN omits those three.

// PNG output (excerpt) { "designId": "dsg_abc", "name": "5 remote work tips", "editorUrl": "https://postnitro.ai/app/carousel-maker/pn-xyz/dsg_abc", "type": "png", "mimeType": "image/png", "data": ["https://.../slide_0.png", "https://.../slide_1.png"] }

Environment variables

VariableRequiredDefaultDescription
POSTNITRO_API_KEYNo*Your PostNitro Embed API key
POSTNITRO_CONFIG_DIRNo~/.postnitro-cliWhere the saved key + defaults are stored

* Either --api-key, POSTNITRO_API_KEY, or a saved key (auth set-key) is required.

Errors & exit codes

Every command prints JSON. Failures go to stderr and exit non-zero:

{ "success": false, "error": { "message": "PostNitro API error (422): ...", "statusCode": 422 } }
  • Exit 0 — success (JSON on stdout)
  • Exit 1 — error (JSON on stderr)
Common errorFix
No API key foundPass --api-key, set POSTNITRO_API_KEY, or run auth set-key
Missing --brand-id ... multiple candidatesPass the ID or save one via defaults set
Scheduled at is in the pastUse a future ISO-8601 datetime (≥ 5 min ahead, trailing Z)
Slides must be an array with at least 3 entriesProvide starting + body + ending slides
An IMAGE post takes a single slide object, not an arrayUse --slide (object) for images, --slides (array) for carousels
AI image generation requires --image-contextAdd --image-context "<brief>" when using --generate-images
Refusing to delete ... without --yesAdd --yes to confirm destructive actions

Credits & pricing

The CLI consumes the same credits as the Embed API:

  • Content import: ~1 credit per slide
  • AI generation: ~2 credits per slide
  • AI images: consume the organization’s separate AI-image quota (not post credits); unavailable on the free plan
  • Response type (PDF/PNG/DESIGN) does not change credit cost

Command cheat sheet

# Auth postnitro auth set-key <key> | status | clear # Discovery + defaults postnitro template list | brand list | preset list | social list postnitro defaults set --template-id <id> --brand-id <id> --preset-id <id> --response-type PDF # Create (carousel or image; async — use --wait; result has designId + editorUrl) # --response-type PDF|PNG|DESIGN | optional AI images: --generate-images --image-context "brief" [--image-placement …] [--image-strategy …] postnitro carousel generate --context "topic|url" --type text|article|x [--instructions "…"] --wait postnitro carousel import (--slides '{"slides":[…]}' | --file ./slides.json) --wait postnitro image generate --context "topic|url" --type text|article|x --wait postnitro image import (--slide '{"heading":"…"}' | --file ./slide.json) --wait postnitro carousel status <embedPostId> | carousel output <embedPostId> # (also: image status | output) # Brand kits postnitro brand create (--name --handle --image [--company-detail] | --data '{…}') postnitro brand update <id> # Schedule (use designId, not embedPostId; --scheduled-at ≥ 5 min in the future) postnitro schedule create --status SCHEDULED|DRAFT --scheduled-at "<iso>" --design-id <id> \ --selected-accounts '["<id>"]' --linkedin-post-settings '{"postType":"document","postTitle":"…"}' \ --post-content '{"common":"caption"}' postnitro schedule list --from "<date>" --to "<date>" | get <id> | update <id> | delete <id> --yes # Social accounts postnitro social list | get <id> | disconnect <id> --yes # One-shot: create + schedule (both take --post-type + the AI-image flags) postnitro generate-and-schedule --context "topic" --status SCHEDULED --scheduled-at "<iso>" postnitro import-and-schedule --slides '{"slides":[…]}' --status SCHEDULED --scheduled-at "<iso>" # or --slide for image # Help postnitro --help | postnitro <command> --help | postnitro <command> <subcommand> --help

CLI vs. MCP vs. Embed API vs. Embed SDK

Best for
CLITerminal workflows, shell scripts, CI pipelines, and AI agents that shell out — JSON in, JSON out
MCP ServerAI assistants and agents creating and scheduling posts conversationally (Claude, Cursor, custom agents)
Embed APIProgrammatic automation — Make.com, Zapier, n8n, cron jobs, custom backends
Embed SDKLetting your users visually create and edit carousels inside your web app

Support

Last updated on