{"_id":"@claudiumarius/invoicestudio-sdk","name":"@claudiumarius/invoicestudio-sdk","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@claudiumarius/invoicestudio-sdk","version":"1.0.0","description":"Official TypeScript/JavaScript SDK for the InvoiceStudio API.","license":"UNLICENSED","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./webhooks":{"types":"./dist/webhooks.d.ts","import":"./dist/webhooks.js","default":"./dist/webhooks.js"},"./package.json":"./package.json"},"types":"./dist/index.d.ts","sideEffects":false,"engines":{"node":">=18"},"repository":{"type":"git","url":"git+https://github.com/claudiuwork/invoice-generator.git","directory":"sdk/typescript"},"bugs":{"url":"https://invoicestudio.co/developers"},"homepage":"https://invoicestudio.co/developers","publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","test":"vitest run","prepublishOnly":"npm run build"},"keywords":["invoice","invoicestudio","ubl","pdf","api"],"devDependencies":{"@types/node":"^22.10.2","typescript":"^5.7.2","vitest":"^2.1.8"},"_id":"@claudiumarius/invoicestudio-sdk@1.0.0","gitHead":"cd4c93364dcc2f72490ad218078b058c9eec9d1c","_nodeVersion":"24.10.0","_npmVersion":"11.6.0","dist":{"integrity":"sha512-ZRN2Hab+Bnfz4X99PV0yJiF4rBz7K2iOzkZv6jpQwVM/+c5zauQi4w6wlXnLGma/V+iP4hcs1o7i2J3D4dMy4Q==","shasum":"821bd6dac7e1beca9f4520ec4df94a391fedfaf6","tarball":"https://registry.npmjs.org/@claudiumarius/invoicestudio-sdk/-/invoicestudio-sdk-1.0.0.tgz","fileCount":22,"unpackedSize":61453,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD+GCenVpiRqxW95XtnVGmt57UQdpd4LtVujO6Wl+RN0QIgYkQyzDsjKjhkWRKI6LgXPV4vuEAKmMPHmW+0eEkgx7I="}]},"_npmUser":{"name":"claudiumarius","email":"limban.claudiu@gmail.com"},"directories":{},"maintainers":[{"name":"claudiumarius","email":"limban.claudiu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/invoicestudio-sdk_1.0.0_1788606851429_0.9722249411174801"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T11:14:11.217Z","1.0.0":"2026-09-05T11:14:11.567Z","modified":"2026-09-05T11:14:11.811Z"},"maintainers":[{"name":"claudiumarius","email":"limban.claudiu@gmail.com"}],"description":"Official TypeScript/JavaScript SDK for the InvoiceStudio API.","homepage":"https://invoicestudio.co/developers","keywords":["invoice","invoicestudio","ubl","pdf","api"],"repository":{"type":"git","url":"git+https://github.com/claudiuwork/invoice-generator.git","directory":"sdk/typescript"},"bugs":{"url":"https://invoicestudio.co/developers"},"license":"UNLICENSED","readme":"# @claudiumarius/invoicestudio-sdk\n\nOfficial TypeScript / JavaScript SDK for the [InvoiceStudio API](https://invoicestudio.co/developers).\n\nZero runtime dependencies, ESM only, Node 18+.\n\nThe client entry (`InvoiceStudio`, `paginate`, `InvoiceStudioError`) imports nothing from\n`node:*` and runs on any runtime with `fetch` — Node, Deno, Bun, Workers, the browser.\nWebhook verification needs real crypto and is Node-only; import it from the\n`@claudiumarius/invoicestudio-sdk/webhooks` subpath (it is also re-exported from the root entry for\nconvenience on Node).\n\n## Install\n\n```bash\nnpm install @claudiumarius/invoicestudio-sdk\n```\n\n## Quickstart\n\n```ts\nimport { InvoiceStudio } from '@claudiumarius/invoicestudio-sdk'\nimport { writeFile } from 'node:fs/promises'\n\nconst client = new InvoiceStudio(process.env.INVOICESTUDIO_API_KEY!)\n\nconst invoice = await client.invoices.create({\n  version: 1,\n  type: 'invoice',\n  number: 'INV-1001',\n  issueDate: '2026-09-01',\n  dueDate: '2026-09-15',\n  currency: 'EUR',\n  from: 'Acme GmbH\\nHauptstrasse 1\\n10115 Berlin',\n  to: 'Globex Ltd\\n5 King Street\\nLondon',\n  items: [{ name: 'Consulting', quantity: 10, unitCostCents: 12000 }],\n  tax: { title: 'VAT', kind: 'percent', value: 19 },\n})\n\nawait writeFile('invoice.pdf', await client.invoices.pdf(invoice.id))\n\nconst shared = await client.invoices.share(invoice.id)\nconsole.log(shared.share_url) // https://invoicestudio.co/s/…\n```\n\n### Client options\n\n```ts\nnew InvoiceStudio(apiKey, {\n  baseUrl: 'https://invoicestudio.co/api/v1', // default; a trailing slash is fine\n  fetch: myFetch,                             // default: global fetch\n  maxRetries: 2,                              // default\n  timeoutMs: 30_000,                          // default, per attempt; 0 disables\n})\n```\n\n### Cancellation and timeouts\n\nEvery method takes an optional `signal`. Each attempt also gets its own `timeoutMs`\ndeadline covering both the headers and the response body, combined with your signal. An abort — yours or a timeout — throws an\n`InvoiceStudioError` with `status: 0` and `code: 'internal'`, never a raw `DOMException`;\nthe original abort reason is on `error.cause`. A caller-driven abort is final and is never\nretried; a timeout is treated as a transport failure and follows the normal retry rules.\n\n```ts\nconst controller = new AbortController()\nsetTimeout(() => controller.abort(), 1000)\nconst invoices = await client.invoices.list({ limit: 100, signal: controller.signal })\n```\n\n## API\n\n| Method | Returns |\n| --- | --- |\n| `client.me()` | `Account` — tier and this month's document usage |\n| `client.invoices.create(input, { idempotencyKey? })` | `Invoice` |\n| `client.invoices.retrieve(id)` | `Invoice` |\n| `client.invoices.list({ limit?, startingAfter? })` | `List<Invoice>` |\n| `client.invoices.del(id)` | `Deleted` |\n| `client.invoices.pdf(id)` | `Uint8Array` |\n| `client.invoices.ubl(id)` | `string` (UBL 2.1 XML) |\n| `client.invoices.share(id)` | `Invoice` with `share_url` set |\n| `client.webhooks.create({ url, events })` | `WebhookEndpoint & { secret }` — the secret is shown once |\n| `client.webhooks.list()` | `List<WebhookEndpoint>` |\n| `client.webhooks.retrieve(id)` | `WebhookEndpoint` |\n| `client.webhooks.del(id)` | `Deleted` |\n\nEvery endpoint returns a body — `del()` resolves the `{ id, object, deleted: true }` stub,\nnot `undefined`. An empty or malformed 2xx body throws `InvoiceStudioError` (`Empty\nresponse` / `Invalid JSON response`) rather than resolving a null-ish value.\n\nEvery method takes a trailing options object with `signal?: AbortSignal`\n(`invoices.create` also takes `idempotencyKey`). Use\n`InvoiceStudioError.isInvoiceStudioError(err)` instead of `instanceof` if two copies of\nthe package may end up in one process.\n\nExported types: `Account`, `Invoice`, `InvoiceInput`, `LineItem`, `List<T>`, `Deleted`,\n`WebhookEndpoint`, `WebhookEvent`, `WebhookEventPayload`, `ApiErrorCode`.\n\n## Error handling\n\nAny non-2xx response (after retries) throws an `InvoiceStudioError` built from the API's\nerror envelope.\n\n```ts\nimport { InvoiceStudioError } from '@claudiumarius/invoicestudio-sdk'\n\ntry {\n  await client.invoices.create(input)\n} catch (err) {\n  if (err instanceof InvoiceStudioError) {\n    err.status    // 400\n    err.code      // 'invalid_request' — also: unauthorized, forbidden, not_found,\n                  //   rate_limited, quota_exceeded, idempotency_conflict, internal\n    err.type      // 'invalid_request_error'\n    err.param     // 'items[0].quantity' when the error is field-specific\n    err.requestId // 'req_…' — quote this in support requests\n  }\n  throw err\n}\n```\n\n## Pagination\n\n`invoices.list()` is cursor-paged, newest first. `paginate` walks every page for you.\n\n```ts\nimport { paginate } from '@claudiumarius/invoicestudio-sdk'\n\nfor await (const invoice of paginate((cursor) =>\n  client.invoices.list({ limit: 100, startingAfter: cursor }),\n)) {\n  console.log(invoice.number, invoice.total_cents)\n}\n```\n\n## Retries and idempotency\n\n* `invoices.create()` always sends an `Idempotency-Key` (a UUID v4 when you do not supply\n  one), and reuses the same key across the SDK's own retries — a retried create can never\n  produce a second invoice. Pass your own with `{ idempotencyKey }` to make retries safe\n  across process restarts too.\n* Requests are retried on `429` and on `5xx` / network errors, up to `maxRetries`\n  (default 2). A `429` honours the `Retry-After` header (capped at 30 s); everything else\n  backs off 500 ms × 2ⁿ.\n* `webhooks.create()` has no idempotency key, so it is retried **only** on `429`, which\n  provably never reached the handler. A `5xx` or a dropped connection is never retried —\n  the endpoint may already exist. Every other method retries `429`, `5xx` and network\n  failures.\n* Backoff carries ±20% jitter. `Retry-After` is honoured in both its delta-seconds and\n  HTTP-date forms, capped at 30 s.\n\n## Webhook verification\n\nDeliveries carry an `InvoiceStudio-Signature: t=<unix-seconds>,v1=<hex>` header, where\n`v1` is HMAC-SHA256 of `\"<t>.<raw body>\"` keyed with the endpoint secret (`whsec_…`).\nVerify against the **raw** body, before JSON parsing; the tolerance is 300 seconds.\n\n### Express\n\n```ts\nimport express from 'express'\nimport { constructEvent } from '@claudiumarius/invoicestudio-sdk/webhooks'\nimport { InvoiceStudioError } from '@claudiumarius/invoicestudio-sdk'\n\nconst app = express()\n\napp.post('/hooks/invoicestudio', express.raw({ type: 'application/json' }), (req, res) => {\n  try {\n    const event = constructEvent(\n      process.env.INVOICESTUDIO_WEBHOOK_SECRET!,\n      req.header('InvoiceStudio-Signature') ?? '',\n      req.body, // Buffer — the raw body\n    )\n    switch (event.type) {\n      case 'invoice.created':\n      case 'invoice.shared':\n      case 'invoice.viewed':\n      case 'invoice.deleted':\n        console.log(event.type, event.data.object.id)\n    }\n  } catch (err) {\n    if (err instanceof InvoiceStudioError) return res.status(400).send('bad signature')\n    throw err\n  }\n  res.sendStatus(200) // any 2xx marks the delivery successful\n})\n```\n\n### Next.js route handler\n\n```ts\n// app/api/hooks/invoicestudio/route.ts\nimport { constructEvent } from '@claudiumarius/invoicestudio-sdk/webhooks'\n\nexport const runtime = 'nodejs'\n\nexport async function POST(req: Request) {\n  const body = await req.text() // raw, before parsing\n  try {\n    const event = constructEvent(\n      process.env.INVOICESTUDIO_WEBHOOK_SECRET!,\n      req.headers.get('invoicestudio-signature') ?? '',\n      body,\n    )\n    console.log(event.type, event.data.object.id)\n  } catch {\n    return new Response('bad signature', { status: 400 })\n  }\n  return new Response(null, { status: 204 })\n}\n```\n\n`verifyWebhookSignature(secret, header, body, toleranceSeconds?)` returns a boolean if you\nwould rather parse the body yourself.\n\nA payload over 256 KB arrives truncated (`event.data.truncated === true`) with only\n`{ id, object }` in `data.object` — re-fetch the resource with `invoices.retrieve(id)`.\nFailed deliveries are retried after 1m, 5m, 30m, 2h and 12h (6 attempts total), so handle\nevents idempotently by `event.id`.\n\n## Documentation\n\n<https://invoicestudio.co/developers>\n","readmeFilename":"README.md","_rev":"1-0f4c3dbe3675ad8e57e100190dd860ba"}