npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cutedyno/node

v1.1.0

Published

Official Node.js client for the CuteDyno API. Publish to social platforms on behalf of your own customers.

Readme

@cutedyno/node

Official Node.js client for the CuteDyno API. Publish to Instagram, TikTok, LinkedIn, Facebook, and YouTube on behalf of your own customers.

Fully typed from the OpenAPI spec — request bodies, filters, and responses are checked at compile time.

npm install @cutedyno/node

Quickstart

import { CuteDyno } from '@cutedyno/node';

const cutedyno = new CuteDyno(); // reads CUTEDYNO_API_KEY

const { accounts } = await cutedyno.accounts.list();

const { post } = await cutedyno.posts.create({
  content: 'Shipping today.',
  accountIds: [accounts[0].id],
  publishNow: true,
});

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

Create a key at cutedyno.com/dashboard/api.

Configuration

const cutedyno = new CuteDyno({
  apiKey: process.env.CUTEDYNO_API_KEY,  // default: CUTEDYNO_API_KEY
  baseUrl: 'https://api.cutedyno.com',   // default: CUTEDYNO_API_URL, then production
  timeout: 30_000,                        // per request, ms
  maxRetries: 2,                          // rate limits and server errors
  profileId: 'prof_123',                  // default profile for every call
  onRateLimit: ({ remaining, reset }) => {
    console.log(`${remaining} requests left, resets in ${reset}s`);
  },
});

Serving many customers

Each of your customers gets a profile. One key can act across all of them: pass profileId per call, or set it once on the client.

const { profile } = await cutedyno.profiles.create({ name: 'Acme Corp' });

const { authUrl } = await cutedyno.accounts.connect({
  platform: 'instagram',
  profileId: profile.id,
  redirectUrl: 'https://yourapp.com/settings/social',
});
// Send your customer to authUrl. A connection.completed webhook fires when they finish.

await cutedyno.posts.create({
  profileId: profile.id,
  content: 'Hello from Acme.',
  accountIds: ['acct_123'],
  scheduledAt: '2026-08-01T15:00:00Z',
});

Media

Scripts with local files

media.upload presigns, transfers the bytes, and returns the URL to post with:

import { readFile } from 'node:fs/promises';

const { publicUrl } = await cutedyno.media.upload({
  fileName: 'launch.mp4',
  fileType: 'video/mp4',
  data: await readFile('./launch.mp4'),
});

await cutedyno.posts.create({
  content: 'Our new release.',
  media: [{ type: 'video', url: publicUrl }],
  accountIds: ['acct_123'],
});

AI agents and browser upload

When the caller cannot read files from disk, create a hosted upload session instead:

const { id, uploadUrl } = await cutedyno.media.createUploadSession();

// Send the user to uploadUrl. They upload in the browser, then click Done.

let session = await cutedyno.media.getUploadSession(id);
while (session.status === 'pending') {
  await new Promise((r) => setTimeout(r, 2000));
  session = await cutedyno.media.getUploadSession(id);
}

await cutedyno.posts.create({
  content: 'Posted via browser upload.',
  media: session.files.map((file) => ({ type: file.type, url: file.url })),
  accountIds: ['acct_123'],
  publishNow: true,
});

Analytics

const live = await cutedyno.analytics.get();
console.log(live.summary.periodViews, live.accounts.length);

const history = await cutedyno.analytics.history({
  from: '2026-01-01',
  to: '2026-01-31',
});
console.log(history.topContent[0]?.title, history.insights);

Errors

Every failure throws a CuteDynoError carrying a stable code. Branch on the code, never on the message.

import { CuteDynoError } from '@cutedyno/node';

try {
  await cutedyno.posts.create({ content: 'Hi', accountIds: ['acct_123'] });
} catch (error) {
  if (error instanceof CuteDynoError) {
    if (error.code === 'accounts_not_found') {
      // Ask the customer to connect the account again.
    } else if (error.isRateLimit) {
      // Already retried; back off further or queue the work.
    }
    console.error(error.code, error.requestId, error.docsUrl);
  }
}

isRetryable, isRateLimit, and isAuthError cover the common branches. Rate limits and server errors are retried automatically with backoff, honouring Retry-After.

Idempotency

Writes carry an Idempotency-Key automatically, so an automatic retry can never publish twice. Supply your own to make a retry safe across process restarts:

await cutedyno.posts.create(
  { content: 'Hi', accountIds: ['acct_123'] },
  { idempotencyKey: `order-${orderId}` }
);

Webhooks

Verify with the raw body. Framework JSON middleware changes the bytes the signature was computed over.

import express from 'express';
import { verifyWebhook, WebhookVerificationError } from '@cutedyno/node';

app.post(
  '/webhooks/cutedyno',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    let event;
    try {
      event = verifyWebhook({
        payload: req.body,
        signature: req.header('CuteDyno-Signature'),
        secret: process.env.CUTEDYNO_WEBHOOK_SECRET!,
      });
    } catch (error) {
      if (error instanceof WebhookVerificationError) return res.sendStatus(400);
      throw error;
    }

    res.sendStatus(200); // acknowledge fast, then process out of band
    void handle(event);
  }
);

event.id is stable across retries, so dedupe on it.

Pagination

for await (const post of cutedyno.posts.iterate({ status: 'failed' })) {
  const { history } = await cutedyno.posts.history(post.id);
  console.log(post.id, history.at(-1));
}

API surface

| Namespace | Methods | | --- | --- | | profiles | list, create, current, retrieve, update, del | | apiKeys | list, create, revoke | | accounts | list, connect, getConnectSession | | posts | list, iterate, create, validate, retrieve, update, cancel, publish, retry, history | | comments | list | | media | upload, createUpload, createUploadSession, getUploadSession | | analytics | get, history | | webhooks | list, create, update, rotateSecret, test, deliveries, del | | policy | retrieve, update | | events | list | | logs | list |

Types

Model types are re-exported for use in your own signatures:

import type { Post, Account, Profile, WebhookEvent, Platform } from '@cutedyno/node';

Links

MIT