{"_id":"@ai-newsletter/sdk","_rev":"3-8b423b51d4f3138e8abf69e37327079b","name":"@ai-newsletter/sdk","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.1":{"name":"@ai-newsletter/sdk","version":"1.0.1","keywords":["ai-newsletter","signal-ai","newsletter","email","sdk","api"],"license":"MIT","_id":"@ai-newsletter/sdk@1.0.1","maintainers":[{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"}],"homepage":"https://ai-newsletter.app/developers","bugs":{"url":"https://github.com/ai-newsletter/sdks/issues"},"dist":{"shasum":"b21eb29bbd415c46988d80fb54c3cf62af91360a","tarball":"https://registry.npmjs.org/@ai-newsletter/sdk/-/sdk-1.0.1.tgz","fileCount":3,"integrity":"sha512-RIGvaAHMEBbChEats8gJ32Uh2wF/Kr3cua83AonFVHf2pveYvPDXTUKUdozHNwaxMMJiG1YCo/nSUkhn9kobZA==","signatures":[{"sig":"MEUCIAE8BjhIX8QCgwBgh7uJvLLdWzDmKPhBwool4QaDHeuZAiEAlvUG70H+G9O5VsyEEFf++rp2RYzq4G8sWXAtEDgXV4A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15653},"main":"./src/index.ts","type":"module","types":"./src/index.ts","engines":{"node":">=18"},"exports":{".":"./src/index.ts"},"gitHead":"27701d6c1aaa6920ce4b7d6967efaa8610f00fb6","scripts":{"test":"vitest run"},"_npmUser":{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"},"repository":{"url":"git+https://github.com/ai-newsletter/sdks.git","type":"git","directory":"node"},"_npmVersion":"10.8.2","description":"Official TypeScript SDK for the Signal AI / AI Newsletter public REST API.","directories":{},"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.1_1778573975010_0.10146151223646616","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@ai-newsletter/sdk","version":"1.0.2","keywords":["ai-newsletter","signal-ai","newsletter","email","sdk","api"],"license":"MIT","_id":"@ai-newsletter/sdk@1.0.2","maintainers":[{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"}],"homepage":"https://ai-newsletter.app/developers","bugs":{"url":"https://github.com/ai-newsletter/sdks/issues"},"dist":{"shasum":"594b200277805c3a4d8b704928efe4821fb11e64","tarball":"https://registry.npmjs.org/@ai-newsletter/sdk/-/sdk-1.0.2.tgz","fileCount":3,"integrity":"sha512-kLDe4TAKjEBS4f88RoQgu76sT/DGRpMAgAMMVY9WwZwuDNW2RuLUM2eisBAHrItrggyQ4mdsGqdX9ea0oecmkw==","signatures":[{"sig":"MEQCIBfi1AUX50z5ih7C9rrxWAlTB3kpPzIEj9HObh32uAfOAiBgyQZdqof7YcJtbRbagLNT/9IqgKw5ir03HsCnioOzNw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24667},"main":"./src/index.ts","type":"module","types":"./src/index.ts","engines":{"node":">=18"},"exports":{".":"./src/index.ts"},"gitHead":"383f6c203b3af7ce0cfc4e3a6ac9fcaa049a1053","scripts":{"test":"vitest run"},"_npmUser":{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"},"repository":{"url":"git+https://github.com/ai-newsletter/sdks.git","type":"git","directory":"node"},"_npmVersion":"10.8.2","description":"Official TypeScript SDK for the Signal AI / AI Newsletter public REST API.","directories":{},"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.2_1778576856644_0.5492711388951546","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@ai-newsletter/sdk","version":"1.0.3","description":"Official TypeScript SDK for the AI Newsletter public REST API.","type":"module","main":"./src/index.ts","types":"./src/index.ts","exports":{".":"./src/index.ts"},"scripts":{"test":"vitest run"},"devDependencies":{"vitest":"^2.1.0"},"engines":{"node":">=18"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/ai-newsletter/sdks.git","directory":"node"},"homepage":"https://ai-newsletter.app/developers","keywords":["ai-newsletter","signal-ai","newsletter","email","sdk","api"],"_id":"@ai-newsletter/sdk@1.0.3","gitHead":"6d018b185752f139f8545dae3a05fbcd3231544a","bugs":{"url":"https://github.com/ai-newsletter/sdks/issues"},"_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-iatWFe7wIp1isUsUVXFaSwtU/7jdgnmfEVnfQPLa+ZLkm+IKlk3XA8vOvmkmwHZ/n98M6tbYVbCt5+2/Se8z0Q==","shasum":"81292ce356922df1841ac5e36f5a7d9d0d11131c","tarball":"https://registry.npmjs.org/@ai-newsletter/sdk/-/sdk-1.0.3.tgz","fileCount":3,"unpackedSize":24647,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD7TSkG7sfX1D7SA34qat7E2eDkdXzQq933KmVBQf3i0wIhAOHVb8mzjazZcxVocu/9UNMWAZFbf1GcqmMLDpgkK/MG"}]},"_npmUser":{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"},"directories":{},"maintainers":[{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_1.0.3_1778580293879_0.5449449006070044"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-12T08:19:34.931Z","modified":"2026-05-12T10:04:54.354Z","1.0.1":"2026-05-12T08:19:35.161Z","1.0.2":"2026-05-12T09:07:36.811Z","1.0.3":"2026-05-12T10:04:54.215Z"},"bugs":{"url":"https://github.com/ai-newsletter/sdks/issues"},"license":"MIT","homepage":"https://ai-newsletter.app/developers","keywords":["ai-newsletter","signal-ai","newsletter","email","sdk","api"],"repository":{"type":"git","url":"git+https://github.com/ai-newsletter/sdks.git","directory":"node"},"description":"Official TypeScript SDK for the AI Newsletter public REST API.","maintainers":[{"name":"ai-newsletter","email":"gabriel.afilipoie@ai-newsletter.app"}],"readme":"# @ai-newsletter/sdk\n\nOfficial TypeScript / JavaScript SDK for the **AI Newsletter** public REST API\n(`/v1`). Works in Node 18+, modern browsers, Deno, Bun, and Cloudflare\nWorkers — anywhere `fetch` and `crypto.subtle` are available.\n\n- npm: <https://www.npmjs.com/package/@ai-newsletter/sdk>\n- Source: <https://github.com/ai-newsletter/sdks/tree/main/node>\n- Interactive API reference:\n  <https://qdqyoolnfejkojdcufkk.supabase.co/functions/v1/v1-docs?format=html>\n- OpenAPI JSON:\n  <https://qdqyoolnfejkojdcufkk.supabase.co/functions/v1/v1-docs>\n\n---\n\n## Install\n\n```bash\nnpm  install @ai-newsletter/sdk\npnpm add     @ai-newsletter/sdk\nyarn add     @ai-newsletter/sdk\nbun  add     @ai-newsletter/sdk\n```\n\nRequires **Node 18+** (or any runtime with native `fetch` and Web Crypto).\n\n---\n\n## Get an API key\n\n1. Sign in at <https://ai-newsletter.app>.\n2. Open **Settings → Public API** (`/settings/api`).\n3. Click **Create key**, pick the scopes you need, copy the key — it is\n   shown **once**.\n\nKey prefixes:\n\n| Prefix      | Environment | Notes                                                       |\n| ----------- | ----------- | ----------------------------------------------------------- |\n| `sk_live_…` | Production  | Hits real subscribers, real SES sends, real webhooks.        |\n| `sk_test_…` | Sandbox     | Reads/writes parallel test tables. Sends never invoke SES.   |\n\n### Available scopes\n\n| Scope               | Allows                                              |\n| ------------------- | --------------------------------------------------- |\n| `read:me`           | Read account context (`account.me`)                 |\n| `read:subscribers`  | List subscribers                                    |\n| `write:subscribers` | Add subscribers, batch import, unsubscribe          |\n| `send:newsletters`  | Trigger transactional / broadcast sends             |\n| `read:sends`        | Read send job status                                |\n| `manage:webhooks`   | CRUD webhook endpoints, list & replay deliveries    |\n| `read:analytics`    | Read newsletter and campaign analytics              |\n\n---\n\n## Initialize the client\n\n```ts\nimport { AiNewsletter } from '@ai-newsletter/sdk';\n\nconst client = new AiNewsletter({\n  apiKey: process.env.AI_NEWSLETTER_API_KEY!, // sk_live_… or sk_test_…\n});\n\nconsole.log(client.isTest); // true when using an sk_test_ key\n```\n\n### Options\n\n```ts\nnew AiNewsletter({\n  apiKey: '…',                       // required\n  baseUrl: 'https://…/functions/v1', // override the default base URL\n  maxRetries: 3,                     // retries for 408/425/429/5xx + network. default 3\n  timeoutMs: 30_000,                 // per-request timeout. default 30_000\n  fetch: customFetch,                // inject a fetch implementation (tests, edge runtimes)\n});\n```\n\n---\n\n## Endpoint reference\n\nAll methods return the unwrapped `data` field of the response envelope and\nthrow `AiNewsletterError` on non-2xx responses.\n\n### Account — `client.account`\n\n#### `account.me(): Promise<MeResponse>`\n\n```ts\nconst me = await client.account.me();\n// { user_id, plan, display_name, newsletters: [{ id, name, slug }, …] }\n```\n\n---\n\n### Subscribers — `client.subscribers`\n\n#### `list({ newsletter_id, status?, cursor?, limit? })`\n\n```ts\nconst page = await client.subscribers.list({\n  newsletter_id: 'd5…',\n  status: 'subscribed', // 'subscribed' | 'unsubscribed' | 'pending'\n  limit: 100,           // default 50, max 100\n});\n// { items: Subscriber[], next_cursor: string | null }\n```\n\n#### `iterate({ newsletter_id, status?, limit? })` — async iterator\n\nWalks every page transparently.\n\n```ts\nfor await (const sub of client.subscribers.iterate({ newsletter_id: 'd5…' })) {\n  console.log(sub.email);\n}\n```\n\n#### `create({ newsletter_id, email, name? })`\n\n```ts\nconst sub = await client.subscribers.create({\n  newsletter_id: 'd5…',\n  email: 'jane@example.com',\n  name: 'Jane',\n});\n```\n\n#### `batch({ newsletter_id, subscribers })`\n\nBulk-import up to **1,000** subscribers per call. Idempotent for **24h**\nvia the auto-generated `Idempotency-Key`.\n\n```ts\nconst result = await client.subscribers.batch({\n  newsletter_id: 'd5…',\n  subscribers: [\n    { email: 'a@example.com' },\n    { email: 'b@example.com', name: 'B' },\n  ],\n});\n// { created, skipped, failed, total, results: [{ email, status, id?, error? }, …] }\n```\n\nPer-row `status` is one of `created` / `duplicate` / `suppressed` / `invalid`.\n\n#### `unsubscribe(id)`\n\nSoft unsubscribe. The subscriber is never hard-deleted.\n\n```ts\nawait client.subscribers.unsubscribe('sub_…');\n```\n\n---\n\n### Sends — `client.sends`\n\n#### `create({ newsletter_id, type, to?, subject?, html?, text?, draft_id? })`\n\n```ts\n// Transactional\nconst job = await client.sends.create({\n  newsletter_id: 'd5…',\n  type: 'transactional',\n  to: 'jane@example.com',\n  subject: 'Welcome!',\n  html: '<p>Hi Jane</p>',\n});\n\n// Broadcast from an existing draft\nawait client.sends.create({\n  newsletter_id: 'd5…',\n  type: 'broadcast',\n  draft_id: 'draft_…',\n});\n```\n\n#### `retrieve(id)`\n\n```ts\nconst job = await client.sends.retrieve('snd_…');\n// { id, status, type, newsletter_id, recipient_count, error, created_at, completed_at }\n```\n\n#### `list({ cursor?, limit? })` and `iterate({ limit? })`\n\n```ts\nfor await (const job of client.sends.iterate()) {\n  console.log(job.id, job.status);\n}\n```\n\n---\n\n### Webhooks — `client.webhooks`\n\n#### `list()`\n\n```ts\nconst { items } = await client.webhooks.list();\n```\n\n#### `create({ url, event_types })`\n\nThe response includes `secret` **once** — store it; it is used to verify\nincoming deliveries.\n\n```ts\nconst endpoint = await client.webhooks.create({\n  url: 'https://yourapp.com/webhooks/ai-newsletter',\n  event_types: ['send.completed', 'subscriber.unsubscribed'],\n});\nconsole.log(endpoint.secret); // store securely\n```\n\n#### `update(id, { url?, event_types?, is_active? })`\n\n#### `delete(id)`\n\n#### `deliveries(endpointId)`\n\n```ts\nconst { items } = await client.webhooks.deliveries('whk_…');\n```\n\n#### `replay(endpointId, deliveryId)`\n\nClones the original payload into a new pending delivery.\n\n```ts\nawait client.webhooks.replay('whk_…', 'whd_…');\n```\n\n#### Supported event types\n\n| Event                     | Triggered when                       |\n| ------------------------- | ------------------------------------ |\n| `send.queued`             | A send job has been accepted          |\n| `send.completed`          | A send job finished successfully      |\n| `send.failed`             | A send job failed                     |\n| `subscriber.created`      | A new subscriber was added            |\n| `subscriber.unsubscribed` | A subscriber unsubscribed             |\n| `newsletter.published`    | A campaign was published              |\n\n---\n\n### Analytics — `client.analytics`\n\nRequires the `read:analytics` scope.\n\n#### `listNewsletters()`\n\n```ts\nconst { items } = await client.analytics.listNewsletters();\n```\n\n#### `forNewsletter(id)`\n\n```ts\nconst stats = await client.analytics.forNewsletter('d5…');\nconsole.log(stats.open_rate, stats.click_rate);\n```\n\n#### `forCampaign(id)`\n\n```ts\nconst stats = await client.analytics.forCampaign('cmp_…');\n```\n\nTest keys return zeroed shapes so demos never read live data.\n\n---\n\n## Pagination\n\nList endpoints (`subscribers.list`, `sends.list`, …) return:\n\n```ts\n{ items: T[], next_cursor: string | null }\n```\n\nThe cursor is opaque (base64 of `iso_created_at|id`). Pass it back as\n`cursor` to fetch the next page, or use the `iterate()` async iterator to\nlet the SDK do it for you.\n\n---\n\n## Idempotency\n\nEvery non-`GET` request automatically includes an `Idempotency-Key`\nheader. Replays within **24h** return the original result, so retries\nduring network blips are safe.\n\n`subscribers.batch` is the most useful target — re-running the same job\nwill not double-import rows.\n\n---\n\n## Retries & timeouts\n\nThe SDK retries `408`, `425`, `429`, `500`, `502`, `503`, `504`, and any\nnetwork error using exponential backoff with jitter. `Retry-After` (when\npresent) is honoured.\n\n```ts\nnew AiNewsletter({\n  apiKey: '…',\n  maxRetries: 5,        // default 3\n  timeoutMs: 60_000,    // default 30s, per request\n});\n```\n\n---\n\n## Errors\n\n```ts\nimport { AiNewsletterError } from '@ai-newsletter/sdk';\n\ntry {\n  await client.subscribers.create({ ... });\n} catch (e) {\n  if (e instanceof AiNewsletterError) {\n    console.log(e.status, e.code, e.message, e.retryAfter, e.body);\n  }\n}\n```\n\n| `code`             | HTTP | Meaning                                              |\n| ------------------ | ---- | ---------------------------------------------------- |\n| `missing_api_key`  | 401  | No `Authorization` header                            |\n| `invalid_api_key`  | 401  | Key not recognized                                   |\n| `revoked_api_key`  | 401  | Key has been revoked                                 |\n| `scope_denied`     | 403  | Key lacks the required scope                         |\n| `rate_limited`     | 429  | Rate limit or auto-throttle hit. See `Retry-After`   |\n| `invalid_request`  | 422  | Body or query failed validation                      |\n| `not_found`        | 404  | Resource does not exist or is not yours              |\n| `conflict`         | 409  | `Idempotency-Key` reused with a different payload    |\n| `internal_error`   | 500  | Unexpected server error — safe to retry              |\n\n---\n\n## Verify webhooks\n\nHeader format: `X-Webhook-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>`.\n\n```ts\nimport express from 'express';\nimport { verifyWebhookSignature } from '@ai-newsletter/sdk';\n\nconst app = express();\napp.post(\n  '/webhooks/ai-newsletter',\n  express.raw({ type: 'application/json' }),\n  async (req, res) => {\n    const sig = req.header('X-Webhook-Signature')!;\n    const ok = await verifyWebhookSignature(\n      req.body.toString('utf8'),\n      sig,\n      process.env.WEBHOOK_SECRET!,\n    );\n    if (!ok) return res.status(401).end();\n\n    const event = JSON.parse(req.body.toString('utf8'));\n    // handle event.type / event.data\n    res.status(200).end();\n  },\n);\n```\n\n`verifyWebhookSignature(rawBody, header, secret, toleranceSeconds = 300)`\nrejects timestamps outside the tolerance window to prevent replay.\n\n---\n\n## Rate limits\n\n| Plan               | Burst / sustained |\n| ------------------ | ----------------- |\n| Free / Starter     | 60 req/min        |\n| Pro                | 300 req/min       |\n| Business           | 1,000 req/min     |\n\nResponse headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`,\n`Retry-After` (on 429). Auto-throttling temporarily blocks keys with\nabnormal error rates — see your dashboard for status.\n\n---\n\n## Test mode\n\nPass an `sk_test_…` key — every call hits the parallel sandbox tables,\nsends short-circuit SES, and webhooks fire to test endpoints only.\n`client.isTest` reports the mode.\n\n---\n\n## Self-hosting\n\nOverride the base URL if you run a fork of the API:\n\n```ts\nnew AiNewsletter({\n  apiKey: '…',\n  baseUrl: 'https://your-fork.example.com/functions/v1',\n});\n```\n\n---\n\n## License\n\nMIT — see [`LICENSE`](../LICENSE) and [`CHANGELOG.md`](../CHANGELOG.md).\n","readmeFilename":"README.md"}