@cutedyno/node
v1.1.0
Published
Official Node.js client for the CuteDyno API. Publish to social platforms on behalf of your own customers.
Maintainers
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/nodeQuickstart
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
