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) |
| Binary | postnitro |
| Requires | Node.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 key | Same key as the Embed API — how to get one |
| Output | JSON to stdout on success (exit 0); JSON error to stderr (exit 1) |
| GitHub | postnitro/postnitro-agent |
Installation
# Install globally from npm (recommended)
npm install -g @postnitro/cli
postnitro --help
# Or run without installing
npx @postnitro/cli --helpInstall 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-agentFor Claude Code specifically, you can instead add it as a plugin:
# in Claude Code
/plugin marketplace add postnitro/postnitro-agent
/plugin install postnitro@postnitro-agentThe 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):
--api-key <key>on any commandPOSTNITRO_API_KEYenvironment variable- 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 keySecurity:
auth set-keystores your key in plaintext at~/.postnitro-cli/config.json. Restrict its permissions, avoid shared machines, and use--api-keyorPOSTNITRO_API_KEYin 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 generate | postnitro carousel import (slides array) |
| image (single) | postnitro image generate | postnitro 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:
| Option | Description |
|---|---|
--api-key <key> | PostNitro API key (falls back to POSTNITRO_API_KEY, then saved config) |
-V, --version | Print the CLI version |
-h, --help | Show help for any command or subcommand |
Command reference
auth
Manage the saved PostNitro API key.
| Command | Description |
|---|---|
auth set-key <apiKey> | Save an API key to ~/.postnitro-cli/config.json |
auth status | Show whether a key is configured (masked) and where it’s coming from |
auth clear | Remove the saved API key |
defaults
Save default templateId / brandId / presetId / responseType so create commands don’t need them every time.
| Command | Options | Description |
|---|---|---|
defaults get | — | Show 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 gettemplate
Browse available carousel templates.
| Command | Options | Description |
|---|---|---|
template list | --page <n> (default 1) · --limit <n> (default 10, max 50) | List templates with IDs, names, and dimensions |
postnitro template list --limit 20brand
Manage brand kits (logo, name, handle stamped on designs). See the Brands API for full field definitions.
| Command | Description |
|---|---|
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:
| Option | Description |
|---|---|
--name <name> | Brand display name |
--handle <handle> | Social handle, e.g. @postnitroai |
--image <url> | Logo image URL |
--company-detail | Treat 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-image | Hide 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).
| Command | Options | Description |
|---|---|---|
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.
| Command | Description |
|---|---|
social list | List 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> --yes | Disconnect an account. Destructive, requires --yes, cannot be undone |
carousel
Generate, import, and inspect multi-slide carousel posts.
| Command | Description |
|---|---|
carousel import-template | Print 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:
| Option | Description |
|---|---|
--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 |
--wait | Poll until complete and print the final output |
--generate-images + --image-context + --image-placement + --image-strategy | AI image generation |
carousel import options: same as above except no --type / --instructions / --preset-id, plus:
| Option | Description |
|---|---|
--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"}
]}' --waitCarousel 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).
| Command | Description |
|---|---|
image import-template | Print 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:
| Option | Description |
|---|---|
--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"}' --waitImage 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.
| Command | Description |
|---|---|
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> --yes | Delete a scheduled post/draft. Destructive, requires --yes |
schedule create / update options:
| Option | Description |
|---|---|
--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 designId — not 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 --yesgenerate-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.
| Option | Description |
|---|---|
--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-id | As in import |
--status / --scheduled-at | Required (as in schedule create) |
--design-id · --file · --post-content · --selected-accounts · --*-post-settings · --post-settings | Scheduling body (as in schedule create) |
| AI-image flags | --generate-images, --image-context, --image-placement, --image-strategy |
Flag note: here
--fileis the schedule body (likegenerate-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.
| Flag | Values | Default | Notes |
|---|---|---|---|
--generate-images | (boolean) | off | Opt in. Also implied by any flag below. |
--image-context <text> | string | — | Required when generating images: a short visual brief for the prompts |
--image-placement <mode> | auto | background | in-line | auto | auto = AI decides per slide |
--image-strategy <mode> | strategic | all | strategic | strategic ≈ 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 animageGenerationfield (theGENERATE_IMAGESstep); aFAILEDstatus 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 instatuslogs. - 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):
| Field | Type | Default | Notes |
|---|---|---|---|
hasHeader | boolean | true | Show a header |
columnCount | number | 1 | 1–3 |
displayCounterAs | string | "counter" | "counter" or "none" |
columnDisplay | string | "grid" | "grid" (comparative) or "cycle" (sequential — put all data in the first column) |
columnData | array | [] | 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
idfields on columns and items are caller-provided and required — the API stores them as-is and does not auto-generate them. IflayoutTypeisinfographicbutlayoutConfigis 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.
| Flag | Shape |
|---|---|
--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 apostTitle).
Response types & output
--response-type accepts three values (CLI default is PDF):
| Value | Behavior |
|---|---|
PDF | Renders and returns a PDF (single file URL in data) |
PNG | Renders and returns PNG image(s) (one URL per slide in data) |
DESIGN | Skips 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
| Variable | Required | Default | Description |
|---|---|---|---|
POSTNITRO_API_KEY | No* | — | Your PostNitro Embed API key |
POSTNITRO_CONFIG_DIR | No | ~/.postnitro-cli | Where 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 error | Fix |
|---|---|
No API key found | Pass --api-key, set POSTNITRO_API_KEY, or run auth set-key |
Missing --brand-id ... multiple candidates | Pass the ID or save one via defaults set |
Scheduled at is in the past | Use a future ISO-8601 datetime (≥ 5 min ahead, trailing Z) |
Slides must be an array with at least 3 entries | Provide starting + body + ending slides |
An IMAGE post takes a single slide object, not an array | Use --slide (object) for images, --slides (array) for carousels |
AI image generation requires --image-context | Add --image-context "<brief>" when using --generate-images |
Refusing to delete ... without --yes | Add --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> --helpCLI vs. MCP vs. Embed API vs. Embed SDK
| Best for | |
|---|---|
| CLI | Terminal workflows, shell scripts, CI pipelines, and AI agents that shell out — JSON in, JSON out |
| MCP Server | AI assistants and agents creating and scheduling posts conversationally (Claude, Cursor, custom agents) |
| Embed API | Programmatic automation — Make.com, Zapier, n8n, cron jobs, custom backends |
| Embed SDK | Letting your users visually create and edit carousels inside your web app |
Support
- Email: support@postnitro.ai
- Live Chat: Available on postnitro.ai
- npm:
@postnitro/cli - GitHub: postnitro/postnitro-agent