New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@chirpie/sdk

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@chirpie/sdk

Chirpie SDK: TypeScript client for the Chirpie social media API

Source
npmnpm
Version
1.0.44
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

@chirpie/sdk

One typed client for posting to X, Bluesky, LinkedIn, Threads, Mastodon, Instagram, Facebook and Telegram. Chirpie handles the OAuth, the token refresh, the media upload and the per-platform quirks, so posting to eight networks is eight calls to the same method rather than eight SDKs.

Threads, scheduling, drafts, deletion and analytics are all in here too, and the same account you post from works identically through the CLI, the MCP server, and the REST API.

Install

npm install @chirpie/sdk

Quick start

import { ChirpieClient } from "@chirpie/sdk";

const chirpie = new ChirpieClient({ apiKey: process.env.CHIRPIE_API_KEY! });

const accounts = await chirpie.listAccounts();

const post = await chirpie.createPost({
  account_id: accounts[0].id,
  text: "Shipped a new release.",
});

console.log(post.status, post.platform_post_url);

Get an API key from the dashboard, or run npx chirpie login, which saves one to ~/.chirpie/config.json.

Configuration

const chirpie = new ChirpieClient({
  apiKey: "chirpie_sk_...",
  baseUrl: "https://chirpie.ai", // optional, this is the default
});

Or pick up credentials the CLI already saved:

import { ChirpieClient, requireConfig } from "@chirpie/sdk";

const config = requireConfig(); // CHIRPIE_API_KEY, else ~/.chirpie/config.json
const chirpie = new ChirpieClient({ apiKey: config.api_key, baseUrl: config.base_url });

CHIRPIE_API_KEY takes precedence over the config file. CHIRPIE_BASE_URL overrides the base URL.

Posts

// Publish now
await chirpie.createPost({ account_id, text: "Hello" });

// Publish later. Absolute ISO 8601 with a timezone; relative offsets are rejected.
await chirpie.createPost({ account_id, text: "Later", schedule_at: "2026-04-01T14:00:00Z" });

// With media. Chirpie downloads the URLs and uploads them to the platform.
await chirpie.createPost({ account_id, text: "Look", media_urls: ["https://example.com/a.png"] });

// Or upload a file first, and describe it for people using a screen reader.
const media = await chirpie.uploadMedia({ data: bytes, filename: "shot.png" });
await chirpie.createPost({
  account_id,
  text: "Look",
  media: [{ id: media.id, alt: "The new dashboard" }],
});

// Read back, newest first. Page with limit/offset; a short page is the last one.
const posts = await chirpie.listPosts({ status: "published", limit: 20, offset: 0 });
const one = await chirpie.getPost(posts[0].id);

// Edit a post that has not gone out yet. Leaving `schedule_at` out keeps the time
// it already has, so this never publishes a queued post.
await chirpie.updatePost(one.id, { text: "Now with the typo fixed" });
await chirpie.updatePost(one.id, { schedule_at: "2027-04-02T09:00:00Z" });

// Delete. The post is taken down from the platform, and reported deleted only
// once the platform confirms it is gone. Chirpie keeps the post, marked deleted,
// so it stays in your history. Instagram and TikTok publish no delete API and
// throw `501 delete_unsupported`. Deleting any post of a scheduled
// thread cancels the whole thread, and `cancelled_ids` lists every post that
// went with it.
const { cancelled_ids } = await chirpie.deletePost(one.id);

// Hide. Nothing reaches the platform: the post stays exactly as it is and
// `unhidePost` puts it back. A thread or a multi-account send moves whole.
await chirpie.hidePost(one.id);
await chirpie.unhidePost(one.id);

// Hidden posts are left out of every listing unless you ask for them.
await chirpie.listPosts({ include_hidden: true });

A thread is atomic: if any part fails, every part that had already published is deleted from the platform and the whole thread's quota is refunded. Read what happened with ChirpieApiError.threadRollback(). upstream_error means nothing is left on the platform and a retry is safe; thread_rollback_incomplete means the posts in still_live are really still up, so a retry would publish them twice.

The request field is schedule_at; the response field is scheduled_at. Sending scheduled_at back is rejected rather than published immediately.

Several accounts in one call

Name account_ids instead of account_id to publish the same post to up to 25 accounts at once.

const { group_id, results } = await chirpie.createPost({
  account_ids: [x_account, bsky_account, linkedin_account],
  text: "Shared text",
  account_configurations: {
    // This one account publishes its own text and no media.
    [bsky_account]: { text: "Shorter, for Bluesky", media: [] },
  },
});

for (const result of results) {
  if (!result.success) console.error(result.account_id, result.error?.message);
}

// Every post of the group, read back together.
const posts = await chirpie.listPosts({ group_id });

results carries one entry per account, in the order they were named. The call resolves even when some accounts failed, so check success on each: the accounts that worked stay published. Anything that fails the request as a whole (a character limit, a media rule, a spent quota) throws as usual and nothing is published. Each account may be named once.

An override says only what differs: an absent field inherits the request's own, and naming any media field replaces the shared media for that account outright. Every post carries the group_id it belongs to, or null when it went to a single account.

Threads work the same way, with posts replacing the whole thread for one account:

await chirpie.createThread({
  account_ids: [x_account, bsky_account],
  posts: [{ text: "One" }, { text: "Two" }],
  account_configurations: {
    [bsky_account]: { posts: [{ text: "A" }, { text: "B" }, { text: "C" }] },
  },
});

Threads

const thread = await chirpie.createThread({
  account_id,
  posts: [{ text: "One" }, { text: "Two" }, { text: "Three" }],
  schedule_at: "2026-04-01T14:00:00Z", // optional
});

2 to 25 posts. X, Bluesky, Threads, Mastodon and Telegram publish them as a native reply chain; LinkedIn, Instagram and Facebook publish each item as a standalone post. A thread counts as N posts against your quota.

Drafts

draft: true saves a post or a thread without sending it. Nothing reaches the platform, nothing is queued, and nothing counts against your quota until you promote it.

const draft = await chirpie.createPost({
  account_id,
  text: "Half an idea. Finish it later.",
  draft: true,
});

// Always present, and empty when nothing would go wrong.
for (const w of draft.warnings) console.log(w.platform, w.code, w.message);

// A draft thread may be a single part while you are still writing it.
await chirpie.createThread({ account_id, posts: [{ text: "Opening line" }], draft: true });

// Ask for them by name: a listing with no status leaves drafts out.
const drafts = await chirpie.listPosts({ status: "draft" });

// Change the time it remembers, still a draft
await chirpie.updatePost(draft.id, { schedule_at: "2027-04-02T09:00:00Z", draft: true });

// Promote it: queue it, or send it now. Never both.
await chirpie.updatePost(draft.id, { schedule_at: "2027-04-02T09:00:00Z" });
await chirpie.updatePost(draft.id, { publish: true });

A draft is held to far less than a post: the text may be empty, the media may be missing, and a schedule_at on it is only the time you have in mind. Promotion runs every rule a create runs and spends the quota the draft never spent, so anything refused leaves the draft exactly as it was. The answer is a new post carrying promoted_from_draft_id, or promoted_from_draft_ids for a draft thread, which is promoted whole.

Accounts

// Accounts plus your plan's limits in one call
const { accounts, accounts_limit, accounts_active } = await chirpie.listAccountsWithLimits();

// Connect. OAuth platforms return a URL for the user to open.
const { authorization_url } = await chirpie.connectXAccount();
await chirpie.connectBlueskyAccount({ platform: "bluesky", identifier: "you.bsky.social", app_password: "xxxx-xxxx-xxxx-xxxx" });
await chirpie.connectTelegramAccount({ platform: "telegram", bot_token: "...", chat_id: "@yourchannel" });

// Choose which accounts publish. Deactivating frees a plan slot and keeps the
// account connected, but CANCELS its scheduled posts: `scheduled_posts` says how
// many would go, `canceled_posts` on the result says how many did.
const off = await chirpie.deactivateAccount(accounts[0].id);
await chirpie.activateAccount(accounts[1].id);

// Done with an account? Disconnecting frees a plan slot and cancels its scheduled
// posts like deactivating, but also removes the stored credential, so connecting it
// again means authorizing it on the platform again. Published posts are kept.
const gone = await chirpie.disconnectAccount(accounts[2].id);
console.log(gone.disconnected, gone.canceled_posts);

Also available: connectLinkedInAccount (pass "pages", or call connectLinkedInPagesAccount, for the LinkedIn Pages you administer: coming soon), connectMastodonAccount, and connectThreadsAccount, connectInstagramAccount and connectFacebookAccount (coming soon). Threads, Instagram, Facebook, Pinterest, TikTok, YouTube and Google Business Profile are coming soon; accounts already connected keep posting as normal.

Your own X developer app

Connect X accounts through your own X app so posts bill your X API credits, and X link posts are not surcharged. Full walkthrough: chirpie.ai/docs/x-byo-keys.

await chirpie.setXKeys({ client_id, client_secret, label: "Acme social app" });
await chirpie.getXKeysStatus(); // never returns the secret
await chirpie.removeXKeys();

Analytics and keys

const metrics = await chirpie.getPostAnalytics(post.id); // cached for 1 hour
const { key, expires_at } = await chirpie.createKey("My Bot"); // shown once, expires in 90 days
await chirpie.listKeys();
await chirpie.revokeKey(id);

Errors

import { ChirpieApiError, ChirpieError } from "@chirpie/sdk";

try {
  await chirpie.createPost({ account_id, text });
} catch (err) {
  if (err instanceof ChirpieApiError) {
    err.code;    // "usage_limit_exceeded", "rate_limited", "not_found", ...
    err.status;  // 400, 401, 402, 404, 429, 502
    err.message; // the API's own message, safe to show a user
  } else if (err instanceof ChirpieError) {
    err.message; // network or configuration problem
  }
}

The full code list is at chirpie.ai/docs/errors.

Types

Every input and response type is exported, including ApiPost, ApiThread, ApiAccount, AccountList, ApiAnalytics, ApiKeyInfo, CreatePostInput, CreateThreadInput, ListPostsOptions, and the draft types DraftWarning, DraftPostResponse, DraftThreadResponse, FanOutDraftPostResponse and FanOutDraftThreadResponse.

MIT

Keywords

chirpie

FAQs

Package last updated on 20 Sep 2026

Related posts