# Node.js Social Media API SDK

The official Node.js / TypeScript SDK. Zero runtime dependencies (native `fetch`),
Node 18+, ships ESM and CommonJS, fully typed.

## Install

```bash
npm install @omnisocials/sdk
```

## Authentication

```ts
import { OmniSocials } from "@omnisocials/sdk";

const client = new OmniSocials({ apiKey: "omsk_live_..." });
// or set OMNISOCIALS_API_KEY and call new OmniSocials()
```

CommonJS: `const { OmniSocials, verifyWebhookSignature } = require("@omnisocials/sdk");`

## Create a post

```ts
const { data: post } = await client.posts.create({
  content: "New drop this Friday",
  channels: ["instagram", "facebook", "linkedin"],
  scheduled_at: "2026-08-01T09:00:00Z",
  media_urls: ["https://example.com/teaser.jpg"],
});
console.log(post.id, post.status);
```

Any `media_urls` (or `media_ids`) entry can also be an object carrying per-media
alt text (max 1500 chars), delivered to Mastodon, Bluesky, X (photos/GIFs), and
Pinterest:

```ts
media_urls: [{ url: "https://example.com/teaser.jpg", alt: "Red sneaker on a white background" }],
```

Publish immediately:

```ts
await client.posts.createAndPublish({
  content: "Going live right now",
  channels: ["x", "bluesky"],
});
```

## Upload media

```ts
const upload = await client.media.uploadFromUrl({
  url: "https://example.com/launch-video.mp4",
  name: "launch-video-v2",
  folder: "Campaigns",
});
console.log(upload.data.id, upload.compatibility);
```

Local files via multipart:

```ts
// From a path
const res = await client.media.upload({ file: "./photos/product.jpg", name: "product-hero" });

// Or from bytes / a Blob
import { readFile } from "node:fs/promises";
const bytes = await readFile("./photos/product.jpg");
await client.media.upload({ file: bytes, filename: "product.jpg" });
```

## Analytics

```ts
// One post's latest per-platform metrics
const { data: stats } = await client.analytics.post("post_id");
console.log(stats.platforms.instagram?.metrics);

// Batch: up to 100 posts in one call
const batch = await client.analytics.posts(["id1", "id2", "id3"]);

// Workspace-wide overview
const { data: overview } = await client.analytics.overview({ period: "30d" });
console.log(overview.total_impressions, overview.total_engagements);

// Account-level stats (followers etc)
const accounts = await client.analytics.accounts({ platform: "instagram" });
```

## Verify webhooks

```ts
import express from "express";
import { verifyWebhookSignature, WebhookVerificationError } from "@omnisocials/sdk";

const app = express();

app.post(
  "/omnisocials/webhook",
  express.raw({ type: "application/json" }), // keep the raw body!
  (req, res) => {
    try {
      const event = verifyWebhookSignature({
        payload: req.body, // Buffer of the raw body
        signature: req.get("X-OmniSocials-Signature") ?? "",
        secret: process.env.OMNISOCIALS_WEBHOOK_SECRET!,
        tolerance: 300, // seconds (default)
      });

      switch (event.type) {
        case "post.published":
          console.log("Published:", event.data.post_id, event.data.targets);
          break;
        case "post.failed":
          console.error("Failed:", event.data.post_id);
          break;
      }
      res.sendStatus(200);
    } catch (err) {
      if (err instanceof WebhookVerificationError) {
        return res.sendStatus(400);
      }
      throw err;
    }
  }
);
```

## Errors

The base class is `OmniSocialsError`. API errors are `APIError` subclasses carrying
`status`, `code`, `message`, and `body`: `ValidationError` (400/422),
`AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404),
`RateLimitError` (429, with `.retryAfter`), `ServerError` (5xx), plus
`APIConnectionError` and `WebhookVerificationError`.

```ts
import { APIError, RateLimitError, ValidationError, APIConnectionError } from "@omnisocials/sdk";

try {
  await client.posts.create({ content: "Hi", channels: ["instagram"] });
} catch (err) {
  if (err instanceof RateLimitError) {
    console.warn(`Rate limited, retry in ${err.retryAfter}s`);
  } else if (err instanceof ValidationError) {
    console.error(`Bad request (${err.code}): ${err.message}`, err.body);
  } else if (err instanceof APIConnectionError) {
    console.error("Network problem:", err.message);
  } else if (err instanceof APIError) {
    console.error(`API error ${err.status} (${err.code}): ${err.message}`);
  } else {
    throw err;
  }
}
```

## Configuration

```ts
const client = new OmniSocials({
  apiKey: "omsk_live_...",
  baseUrl: "https://api.omnisocials.com/v1", // default
  timeout: 30_000,   // per-request timeout in ms (default 30s)
  maxRetries: 2,     // automatic retries on 429 / 5xx / network errors (default 2)
});
```

Note that `timeout` is in milliseconds; the other SDKs use seconds or durations.

## Notes

- Responses destructure as `{ data }`; list endpoints return `{ data, pagination }`
- `204` deletes resolve to `null`
- `client.health()` returns `{ status, version, timestamp }`
- Source: [github.com/OmniSocials/omnisocials-node](https://github.com/OmniSocials/omnisocials-node)
