# PostEverywhere SDK Guide > Official PostEverywhere Node.js SDK and CLI for scheduling social media posts. Install via npm, authenticate, and start publishing. **Source:** https://posteverywhere.ai/docs/integrations/sdk **Section:** Integrations **API reference:** https://posteverywhere.ai/docs/api/reference --- PostEverywhere provides a Node.js SDK, a CLI, and REST API access for any language. Pick the approach that fits your workflow. ## Publish from Node.js with the official SDK ### Install the PostEverywhere SDK ```bash npm install @posteverywhere/sdk ``` ### Initialization ```typescript import { PostEverywhere } from '@posteverywhere/sdk'; const client = new PostEverywhere({ apiKey: process.env.POSTEVERYWHERE_API_KEY, // pe_live_... }); ``` > **Caution:** Never hard-code your API key. Use environment variables or a secrets manager. See [Authentication](/docs/authentication) for details. ### List Connected Accounts ```typescript const { accounts } = await client.accounts.list(); for (const account of accounts) { console.log(`${account.platform} — ${account.display_name} (${account.health?.status ?? 'unknown'})`); } ``` ### Create and Schedule a Post ```typescript const post = await client.posts.create({ content: 'Just shipped a major update! Check it out at example.com', account_ids: [123, 456], scheduled_for: '2026-04-07T14:00:00Z', // canonical — always UTC }); console.log(`Post ${post.post_id} scheduled for ${post.scheduled_for}`); ``` > **Round-trip safe** > > The response from `client.posts.create()` has the same top-level field names as the request, so you can take any post and create a clone by passing its fields straight back in. `scheduled_for`, `account_ids`, `media_ids`, `platform_content`, `timezone`, `content`: all round-trip. ### Save a Draft, Then Schedule It Pass `draft: true` to save a post without publishing or scheduling it (`account_ids` is optional for a draft). Review it later, then schedule it with `POST /v1/posts/{id}/schedule`, sending either `scheduled_for` or `publish_now: true`. The schedule endpoint only works on drafts. > SDK 1.4.2's TypeScript types lag the API here: they don't declare `draft`, mark `account_ids` as required, and type `status` without `draft`. The API accepts all of it; until the types catch up, cast the params (`as any`) or call the endpoint directly. ```typescript // 1. Save a draft (nothing publishes yet) const draft = await client.posts.create({ content: 'Proposed announcement — review before sending.', draft: true, // account_ids optional for drafts }); // 2. ...review it (e.g. client.posts.list({ status: 'draft' }))... // 3. Schedule (or publish now) the approved draft await fetch( `https://app.posteverywhere.ai/api/v1/posts/${draft.post_id}/schedule`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.POSTEVERYWHERE_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ scheduled_for: '2026-04-15T14:30:00Z', // OR: publish_now: true account_ids: [123, 456], // optional if the draft already had targets }), }, ); ``` This "draft → review → schedule" flow is the recommended human-in-the-loop pattern for AI agents: see [Building AI Agents](/docs/integrations/agents). ### Platform-Specific Content Customize content per platform using `platform_content`: ```typescript const post = await client.posts.create({ content: 'Default content for all platforms', account_ids: [123, 456, 789], platform_content: { x: { content: 'Short version for X (under 280 chars)', }, linkedin: { content: 'Longer, more detailed version for LinkedIn with professional tone...', }, instagram: { content: 'Visual-first caption with #hashtags', }, }, scheduled_for: '2026-04-07T14:00:00Z', }); ``` ### Upload Media ```typescript import fs from 'fs'; // One call does presign → upload → complete for you const media = await client.media.upload( new Blob([fs.readFileSync('./product-launch.jpg')], { type: 'image/jpeg' }), { filename: 'product-launch.jpg', contentType: 'image/jpeg' }, ); // Manual control instead: client.media.getUploadUrl({ filename, content_type, file_size }) // returns { upload_url, media_id }; upload the bytes, then client.media.complete(media_id). // Use it in a post await client.posts.create({ content: 'Our new product is here!', account_ids: [123], media_ids: [media.id], }); ``` ### Schedule a Week of Posts Loop through your posts and call `create()` for each with a future `scheduled_for`: ```typescript const posts = [ { content: 'Monday motivation: ship fast, learn faster', scheduled_for: '2026-04-07T09:00:00Z' }, { content: 'Tuesday tip: automate your social media with APIs', scheduled_for: '2026-04-08T09:00:00Z' }, { content: 'Wednesday win: our users scheduled 10,000 posts last week', scheduled_for: '2026-04-09T09:00:00Z' }, { content: 'Thursday thought: the best content strategy is consistency', scheduled_for: '2026-04-10T09:00:00Z' }, { content: 'Friday launch: new API endpoints are live!', scheduled_for: '2026-04-11T09:00:00Z' }, ]; const results = await Promise.allSettled( posts.map((p) => client.posts.create({ content: p.content, account_ids: [123, 456], scheduled_for: p.scheduled_for, }) ) ); const created = results.filter((r) => r.status === 'fulfilled').length; const failed = results.filter((r) => r.status === 'rejected').length; console.log(`Scheduled ${created} posts, ${failed} failed`); ``` ### Check Post Results ```typescript const results = await client.posts.results('post_789'); // `results` is a per-platform array with account_id, account_name, status, // platform_post_url, error (if failed), and published_at. (SDK 1.4.2's // PostDestination type lags the API: account_name and platform_post_url exist at runtime.) for (const dest of results.results) { console.log(`${dest.platform} (@${dest.account_name}): ${dest.status}`); if (dest.status === 'failed' && dest.error) { console.log(` [${dest.error.code}] ${dest.error.message}`); } else if (dest.status === 'done') { console.log(` ${dest.platform_post_url}`); } } ``` ## Publish from the terminal with the CLI The [`@posteverywhere/cli`](https://www.npmjs.com/package/@posteverywhere/cli) package manages posts and accounts from your terminal, or from any AI agent that can run shell commands. Every command prints structured **JSON** to stdout (errors go to stderr with a non-zero exit), so it's easy to pipe into `jq` or parse programmatically. Nothing to install: run it with `npx`. ### Authentication The CLI reads your API key from an environment variable, or from a saved device-flow login (`posteverywhere login`), so a login step is optional: ```bash export POSTEVERYWHERE_API_KEY="pe_live_..." # from the Developers page ``` ### Common commands ```bash # Verify the key — shows the account, plan, and quota npx @posteverywhere/cli whoami # List connected accounts (you need the numeric `id`s to post) npx @posteverywhere/cli accounts # Publish now to accounts 123 and 456 npx @posteverywhere/cli post -c "Just shipped v2.0! 🚀" -a 123,456 # Schedule (ISO-8601, UTC) — add -s npx @posteverywhere/cli post -c "Big announcement" -a 123,456 -s 2026-07-01T09:00:00Z # Import an image or MP4 video by URL, then attach the returned media_id # (videos import async — poll get_media until media_status is "ready") npx @posteverywhere/cli upload https://example.com/banner.jpg # → { "media_id": "..." } npx @posteverywhere/cli post -c "New drop" -a 123 -m # Inspect posts and per-platform results npx @posteverywhere/cli posts --status published --limit 10 npx @posteverywhere/cli results npx @posteverywhere/cli retry # retry failed platforms # Why can't an account post? (needs reconnect, quota, etc.) npx @posteverywhere/cli account:health # AI captions + analytics summary npx @posteverywhere/cli caption -t "summer sale" --platform instagram --tone playful npx @posteverywhere/cli analytics --period month ``` Run `npx @posteverywhere/cli help` for the full command list. Prefer a global install? `npm i -g @posteverywhere/cli` lets you drop the `npx @posteverywhere/cli` prefix and just call `posteverywhere `. ### CLI in CI/CD pipelines Use the CLI in GitHub Actions or other CI to announce releases as part of your pipeline: ```yaml # .github/workflows/release.yml - name: Announce release on social media env: POSTEVERYWHERE_API_KEY: ${{ secrets.POSTEVERYWHERE_API_KEY }} run: | npx @posteverywhere/cli post \ -c "v${{ github.event.release.tag_name }} is live! ${{ github.event.release.html_url }}" \ -a 123,456 ``` ### Built for AI agents The package ships a `SKILL.md` that teaches agents (Claude, Cursor, ChatGPT, OpenAI Codex, and others) the command surface for auto-discovery. If you'd rather connect over MCP than shell out, see the [MCP Server guide](/docs/integrations/mcp): the hosted endpoint exposes the same capabilities with no install. (For ChatGPT, connect via the hosted connector on the [agents page](https://posteverywhere.ai/agents).) ## Publish from Python A dedicated Python SDK is not yet available. In the meantime, you can call the REST API directly with `requests`: ```python import requests import os API_KEY = os.environ["POSTEVERYWHERE_API_KEY"] BASE_URL = "https://app.posteverywhere.ai/api/v1" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } # List accounts accounts = requests.get(f"{BASE_URL}/accounts", headers=headers).json() print(accounts["data"]) # Create a scheduled post post = requests.post( f"{BASE_URL}/posts", headers=headers, json={ "content": "Hello from Python!", "account_ids": [123, 456], "scheduled_for": "2026-04-07T14:00:00Z", # canonical — UTC }, ).json() print(f"Post {post['data']['id']} scheduled for {post['data']['scheduled_for']}") ``` ## Publish with cURL For quick one-off requests or shell scripts: ```bash # List accounts curl https://app.posteverywhere.ai/api/v1/accounts \ -H "Authorization: Bearer $POSTEVERYWHERE_API_KEY" # Create a post curl -X POST https://app.posteverywhere.ai/api/v1/posts \ -H "Authorization: Bearer $POSTEVERYWHERE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Hello from cURL!", "account_ids": [123], "scheduled_for": "2026-04-07T14:00:00Z" }' # Upload media — one-call from a public URL (preferred when source is online) curl -X POST https://app.posteverywhere.ai/api/v1/media/upload-from-url \ -H "Authorization: Bearer $POSTEVERYWHERE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/hero.webp"}' # Upload media — 3-step flow (for local files) # Step 1: Get presigned URL. Required fields: filename, content_type, size. curl -X POST https://app.posteverywhere.ai/api/v1/media/upload \ -H "Authorization: Bearer $POSTEVERYWHERE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filename": "photo.jpg", "content_type": "image/jpeg", "size": 2048576}' # Step 2: PUT/POST the bytes to the returned upload_url # Step 3: POST /api/v1/media/{media_id}/complete to finalise ``` ## Where to go after your first SDK call - [Quick Start](/docs/quick-start): Make your first API call in 5 minutes - [Authentication](/docs/authentication): API key scopes and security - [MCP Server](/docs/integrations/mcp): Use PostEverywhere from Claude, Cursor, or Windsurf - [API Reference](/docs/api/create-post): Full endpoint documentation **Related:** [the CLI](/docs/cli) · [automating posting with an API](/blog/automate-social-media-posting-api) · [the no-code Zapier route](/docs/integrations/zapier) · [the Claude walkthrough](/docs/integrations/claude)