@viewrium/sdk 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,31 +1,251 @@
1
1
  # @viewrium/sdk
2
2
 
3
- The official Viewrium JavaScript/TypeScript SDK - an ergonomic, typed client for the `/v1` REST
4
- API.
3
+ The official JavaScript/TypeScript SDK for the [Viewrium](https://www.viewrium.com) API.
4
+
5
+ Viewrium is AR and 3D product visualization for furniture retailers: it turns product photos, a
6
+ video, a product page URL or an existing 3D file into catalog images, a GLB model and an
7
+ embeddable 3D/AR viewer. This package is a typed client for the Viewrium `/v1` REST API: create
8
+ products, review and publish them, export images, manage webhooks and API keys, and read usage
9
+ analytics.
10
+
11
+ - Documentation: https://www.viewrium.com/docs
12
+ - SDK reference: https://www.viewrium.com/docs/sdks/javascript
13
+ - Dashboard: https://www.viewrium.com
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @viewrium/sdk
19
+ ```
20
+
21
+ The package ships ESM and CommonJS builds with self-contained type declarations. It runs anywhere
22
+ there is a `fetch`: Node 18+, Bun, Deno, edge runtimes and the browser.
23
+
24
+ ## Authentication
25
+
26
+ Every request is authenticated with an API key, sent as `Authorization: Bearer <key>`. The SDK
27
+ sets the header for you.
28
+
29
+ - **Secret keys (`sk_`)** are for servers. They can do everything their scopes allow. Never ship
30
+ one to a browser, a mobile app or a public repository.
31
+ - **Publishable keys (`pk_`)** are safe in a browser but are origin-gated and limited to reading a
32
+ single published product, minting its model ticket and sending analytics beacons. An
33
+ organization with no allowed domains configured has its publishable keys refused everywhere. They power the
34
+ [`<viewrium-viewer>` embed](https://www.viewrium.com/docs/embed); use the embed, not this SDK, to
35
+ show a model on a storefront.
36
+
37
+ Create keys in the dashboard under **Settings -> Advanced -> API keys**. A secret key is shown
38
+ exactly once, on create. See [Authentication](https://www.viewrium.com/docs/authentication) for
39
+ scopes.
5
40
 
6
41
  ```ts
7
42
  import { Viewrium } from "@viewrium/sdk";
8
43
 
9
- const viewrium = new Viewrium({ apiKey: "sk_live_..." });
44
+ const client = new Viewrium({ apiKey: process.env.VIEWRIUM_API_KEY! });
45
+ ```
46
+
47
+ Options:
48
+
49
+ ```ts
50
+ const client = new Viewrium({
51
+ apiKey: "sk_live_...",
52
+ baseUrl: "https://api.viewrium.com", // default; trailing slash stripped
53
+ fetch: customFetch, // optional; defaults to globalThis.fetch
54
+ });
55
+ ```
56
+
57
+ `createViewrium({ apiKey })` is equivalent to `new Viewrium({ apiKey })`.
58
+
59
+ ## Quickstart
10
60
 
11
- // A fresh request always parks: the plan and the price come back inside `resume_token`.
12
- const parked = await viewrium.products.create({ image_urls: ["https://cdn/chair.png"] });
13
- if (parked.status === "parked") {
14
- const product = await viewrium.products.create({ resume_token: parked.resume_token });
61
+ `products.create` is the single generation entrypoint. Supply exactly one source: `image_urls`,
62
+ `video_url`, `source_url` (a product page) or `model_url` (an existing 3D file). Sources are URLs
63
+ that Viewrium fetches, so they must be publicly reachable.
64
+
65
+ A fresh request **parks**: nothing is written, and you get back the plan, the 3D model verdict, the
66
+ price in credits and a `resume_token`. Posting the token back commits the quote and starts the run.
67
+
68
+ ```ts
69
+ import { Viewrium } from "@viewrium/sdk";
70
+
71
+ const client = new Viewrium({ apiKey: process.env.VIEWRIUM_API_KEY! });
72
+
73
+ // 1. Quote
74
+ const quote = await client.products.create({
75
+ image_urls: [
76
+ "https://cdn.example.com/chair-front.jpg",
77
+ "https://cdn.example.com/chair-side.jpg",
78
+ ],
79
+ draft: { title: "Oak dining chair", custom_id: "sku-oak-chair", dimensions: { height: 92 } },
80
+ });
81
+ if (quote.status !== "parked") throw new Error("unexpected: already committed");
82
+ console.log(quote.cost.total_credits, quote.plan?.length, quote.model.allowed);
83
+
84
+ // 2. Commit
85
+ const committed = await client.products.create({ resume_token: quote.resume_token });
86
+ if (committed.status === "parked") throw new Error("a resume always commits");
87
+
88
+ // 3. Wait for the run (prefer webhooks in production)
89
+ let product = await client.products.retrieve(committed.id);
90
+ while (product.display_status === "generating") {
91
+ await new Promise((r) => setTimeout(r, 5000));
92
+ product = await client.products.retrieve(product.id);
15
93
  }
94
+ console.log(product.display_status, product.model?.status);
95
+
96
+ // 4. Publish
97
+ await client.products.update(product.id, { published: true });
16
98
  ```
17
99
 
18
- Resources: `products` (`create` - the single generation entrypoint - `list`, `retrieve`, `update`,
19
- `delete`, `batchDelete`, `publish`, `tags`, `review`, `updateImage`, `updateModel`, `rescale`,
20
- `order`, `export`), `apiKeys`, `usage`, `webhooks`, `projects`, `beacon`, plus `health()`.
21
- Non-2xx responses throw a typed `ViewriumError` subclass.
100
+ A resume may still edit the `draft` (title, price, tags, dimensions). It may not change the order
101
+ or the overrides: the quote is immutable, and changing what gets generated means a new request.
102
+ A `resume_token` expires after 7 days. A `model_url` source never parks; it commits straight away.
103
+
104
+ See [How generation works](https://www.viewrium.com/docs/generation) and
105
+ [the quote and commit guide](https://www.viewrium.com/docs/guides/quote-and-commit).
106
+
107
+ ## Resources
108
+
109
+ | Resource | Methods |
110
+ |---|---|
111
+ | `client.products` | `create`, `list`, `retrieve`, `update`, `delete`, `batchDelete`, `publish`, `tags`, `review`, `updateImage`, `updateModel`, `rescale`, `createTicket`, `order`, `export` |
112
+ | `client.apiKeys` | `list`, `create`, `revoke` |
113
+ | `client.usage` | `get` |
114
+ | `client.webhooks` | `list`, `create`, `retrieve`, `update`, `delete`, `listDeliveries` |
115
+ | `client.projects` | `list`, `retrieve` |
116
+ | `client.beacon` | `send` |
117
+ | `client` | `health()` |
118
+
119
+ Request and response types (`ProductCreateParams`, `ProductCreateResult`, `ProductParked`,
120
+ `Product`, `PlannedView`, `ModelPlan`, `ProductCost`, `Usage`, `WebhookEndpoint`, and so on) are
121
+ exported and derived from the API's OpenAPI schema.
122
+
123
+ ### Showing a model: tickets
124
+
125
+ No response carries a URL for a 3D model file - not the product, not a webhook. The only way to a
126
+ model's bytes is a **ticket**: a single-use URL that serves the GLB once and expires within a
127
+ minute (60 s for `view`, 30 s for `ar`). Mint one right before the viewer loads, from the server,
128
+ and hand the URL straight to the page; never store it.
129
+
130
+ ```ts
131
+ const ticket = await client.products.createTicket(productId, {
132
+ kind: "view",
133
+ origin: "https://shop.example.com", // the page that will load the model; an allowed domain
134
+ });
135
+ // ticket.url -> the page's viewer `src`; ticket.expires_at -> when it stops working
136
+ ```
137
+
138
+ A `view` ticket streams only to a request from its `origin`. An `ar` ticket is for Android's Scene
139
+ Viewer, which downloads the file outside the page: mint it on the AR tap. Minting is rate limited
140
+ per IP and per product (429).
141
+
142
+ ### Listing and filtering
143
+
144
+ ```ts
145
+ const page = await client.products.list({
146
+ status: ["published"],
147
+ tag: ["chairs"],
148
+ q: "oak",
149
+ limit: 50,
150
+ sort: "created_at",
151
+ order: "desc",
152
+ });
153
+ for (const product of page.data) {
154
+ console.log(product.id, product.title, product.display_status);
155
+ }
156
+ ```
157
+
158
+ `products.tags` and `products.export` take the same filters.
159
+
160
+ ### Exporting images
161
+
162
+ `products.export` resolves to a ZIP of finished images as a `Blob` (the 3D model is never
163
+ included). When more images need finishing than one request can do, it resolves to a queued answer
164
+ instead: the images are finished in the background, a download link is emailed, and the same call
165
+ returns the archive once they are done.
166
+
167
+ ```ts
168
+ const archive = await client.products.export({ tag: ["chairs"] });
169
+ if (archive instanceof Blob) {
170
+ // write it out
171
+ } else {
172
+ console.log(`finishing ${archive.to_finish} images; a link goes to ${archive.notify_email}`);
173
+ }
174
+ ```
175
+
176
+ ### Analytics
177
+
178
+ ```ts
179
+ const usage = await client.usage.get({
180
+ start: "2026-06-01T00:00:00Z",
181
+ end: "2026-09-01T00:00:00Z",
182
+ granularity: "day",
183
+ group_by: "product",
184
+ });
185
+ for (const row of usage.by_product ?? []) {
186
+ console.log(row.title, row.totals);
187
+ }
188
+ ```
189
+
190
+ Traffic from your own team is excluded unless you pass `include_internal: true`.
191
+
192
+ ## Errors
193
+
194
+ Non-2xx responses throw a `ViewriumError` subclass. Every error exposes `status`, `code`,
195
+ `message` and `details` from the API's error envelope.
196
+
197
+ ```ts
198
+ import {
199
+ ViewriumError,
200
+ AuthenticationError,
201
+ PaymentRequiredError,
202
+ ConflictError,
203
+ RateLimitError,
204
+ ConnectionError,
205
+ } from "@viewrium/sdk";
206
+
207
+ try {
208
+ await client.products.create({ image_urls: ["https://cdn.example.com/chair.jpg"] });
209
+ } catch (err) {
210
+ if (err instanceof AuthenticationError) {
211
+ // 401: missing or invalid key
212
+ } else if (err instanceof PaymentRequiredError) {
213
+ // 402: the plan does not allow this; err.details.reason says why
214
+ } else if (err instanceof ConflictError) {
215
+ // 409: e.g. a duplicate custom_id or a replayed resume_token
216
+ } else if (err instanceof RateLimitError) {
217
+ // 429: rate limited, or code "quota_exceeded" when credits run out
218
+ } else if (err instanceof ConnectionError) {
219
+ // the request never reached the API
220
+ } else if (err instanceof ViewriumError) {
221
+ console.log(err.status, err.code, err.message, err.details);
222
+ }
223
+ }
224
+ ```
225
+
226
+ | HTTP | Error |
227
+ |---|---|
228
+ | 400 | `BadRequestError` |
229
+ | 401 | `AuthenticationError` |
230
+ | 402 | `PaymentRequiredError` |
231
+ | 403 | `PermissionError` |
232
+ | 404 | `NotFoundError` |
233
+ | 409 | `ConflictError` |
234
+ | 422 | `UnprocessableEntityError` |
235
+ | 429 | `RateLimitError` |
236
+ | 5xx | `ServerError` |
237
+ | none | `ConnectionError` |
238
+
239
+ The SDK does not retry. On a `RateLimitError`, back off and retry with an exponential delay.
240
+
241
+ ## Links
242
+
243
+ - [Quickstart](https://www.viewrium.com/docs/quickstart)
244
+ - [JavaScript SDK reference](https://www.viewrium.com/docs/sdks/javascript)
245
+ - [Webhooks](https://www.viewrium.com/docs/webhooks)
246
+ - [API reference](https://www.viewrium.com/docs/api-reference)
247
+ - Support: support@viewrium.com
22
248
 
23
- ## Types & the build
249
+ ## License
24
250
 
25
- Request/response types are derived from `src/generated/schema.ts` - openapi-typescript output
26
- committed from the API's authoritative `apps/api/openapi.json` (the same generator
27
- `@repo/api-client` uses; run `pnpm run generate` to refresh). Owning the committed schema keeps the
28
- published `dist/index.d.ts` fully self-contained: tsup's dts bundler cannot inline a re-exported
29
- type from the source-only, unpublished `@repo/api-client`, so deriving from it would leak an
30
- unresolvable `import { paths } from "@repo/api-client"` into the published declarations. `tsup`
31
- emits ESM + CJS + a self-contained `.d.ts`. Published by `.github/workflows/publish-sdk-js.yml`.
251
+ Apache-2.0
package/dist/index.cjs CHANGED
@@ -210,6 +210,14 @@ function createProducts(request, requestBlob) {
210
210
  * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.
211
211
  */
212
212
  rescale: (id, params) => request("POST", `/v1/products/${encode(id)}/model/rescale`, { body: params }),
213
+ /**
214
+ * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way
215
+ * to its bytes (no response carries a model file URL). It works once, until `expires_at`
216
+ * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`
217
+ * key the product must be published and the ticket is bound to the page's Origin; with an
218
+ * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).
219
+ */
220
+ createTicket: (id, params) => request("POST", `/v1/products/${encode(id)}/tickets`, { body: params }),
213
221
  /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the
214
222
  * returned `resume_token` with `create`. */
215
223
  order: (id, params) => request("POST", `/v1/products/${encode(id)}/order`, { body: params }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts"],"sourcesContent":["/**\n * @viewrium/sdk - the official Viewrium JavaScript/TypeScript SDK.\n *\n * An ergonomic wrapper over the `/v1` REST API. Request/response types are derived from\n * `src/generated/schema.ts` - openapi-typescript output committed from the API's authoritative\n * `apps/api/openapi.json` (the same generator `@repo/api-client` uses; regenerate with\n * `pnpm run generate`). Owning the committed schema keeps the published `dist/index.d.ts`\n * self-contained: it cannot reference the unpublished, source-only `@repo/api-client`.\n *\n * Auth: pass an `sk_` secret key (server) or a `pk_` publishable key (browser). The key is sent\n * as `Authorization: Bearer <key>`; the API enforces what each key type may call.\n *\n * E4 (2026-09-09): the scan-era `models` resource is GONE with its routes; `products` is the\n * resource. Resource and method names match `@repo/api-client` so every client reads the same.\n */\nimport { ConnectionError, ViewriumError, createApiError } from \"./errors\";\nimport type { paths } from \"./generated/schema\";\n\nexport * from \"./errors\";\n\n// --- Derived request/response types (source of truth = apps/api/openapi.json) ----------------\n\ntype JsonContent<T> = T extends { content: { \"application/json\": infer B } } ? B : never;\n\n/** GET /v1/products/{id} resolves to one of two shapes by principal: the full product for a\n * session or `sk_` key, the allow-listed public projection for a `pk_` key. */\ntype ProductGetResult = JsonContent<paths[\"/v1/products/{id}\"][\"get\"][\"responses\"][200]>;\n/** A product with its assets (`sk_` view). */\nexport type Product = Extract<ProductGetResult, { org_id: string }>;\n/** The `pk_` view of a published product: id, title, model_url, thumbnail_url, model_status. */\nexport type PublicProduct = Exclude<ProductGetResult, { org_id: string }>;\nexport type ProductList = JsonContent<paths[\"/v1/products\"][\"get\"][\"responses\"][200]>;\n/** A catalog row - the product plus what its card needs, with no nested assets. */\nexport type ProductSummary = ProductList[\"data\"][number];\nexport type ProductListParams = NonNullable<paths[\"/v1/products\"][\"get\"][\"parameters\"][\"query\"]>;\nexport type ProductCreateParams = JsonContent<paths[\"/v1/products\"][\"post\"][\"requestBody\"]>;\n/** POST /v1/products 200 - the gate parked; resume by POSTing `resume_token`. */\nexport type ProductParked = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][200]>;\n/** One page position of a parked plan. Both park shapes carry the same row type. */\nexport type PlannedView = NonNullable<ProductParked[\"plan\"]>[number];\n/** describe's structured read of the product (D25), with its verbatim reasons. */\nexport type GateFlags = ProductParked[\"flags\"];\n/** The models layer's verdict: whether a model is allowed, what blocks it, and the views it\n * would be built from - including `unseen_views`, the invented ones. */\nexport type ModelPlan = ProductParked[\"model\"];\n/** The priced bundle, in credits (1 credit = $0.50): 1 per deliverable image, a flat 10 for a model. */\nexport type ProductCost = ProductParked[\"cost\"];\n/** POST /v1/products 202 - a committed product whose run has started. */\nexport type ProductCreated = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][202]>;\n/** POST /v1/products resolves to one of the two (discriminate on `status === \"parked\"`). */\nexport type ProductCreateResult = ProductParked | ProductCreated;\nexport type ProductUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductBatchResult = JsonContent<paths[\"/v1/products\"][\"delete\"][\"responses\"][200]>;\nexport type ProductPublishParams = JsonContent<\n paths[\"/v1/products/publish\"][\"post\"][\"requestBody\"]\n>;\nexport type Tags = JsonContent<paths[\"/v1/products/tags\"][\"get\"][\"responses\"][200]>;\nexport type TagsParams = NonNullable<paths[\"/v1/products/tags\"][\"get\"][\"parameters\"][\"query\"]>;\n/** POST /v1/products/{id}/review - the write-behind flush (image + model decisions, the\n * product's user-set status). It records decisions only. */\nexport type ProductReviewParams = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductReviewResult = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductImagePatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductImage = JsonContent<\n paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"responses\"][200]\n>;\nexport type ProductModelPatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/model\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductModel = JsonContent<paths[\"/v1/products/{id}/model\"][\"patch\"][\"responses\"][200]>;\nexport type ProductRescaleParams = JsonContent<\n paths[\"/v1/products/{id}/model/rescale\"][\"post\"][\"requestBody\"]\n>;\n/** POST /v1/products/{id}/order - the missing half; parks like a create, commits through\n * `products.create({ resume_token })`. */\nexport type ProductOrderParams = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductOrderParked = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductExportParams = NonNullable<\n paths[\"/v1/products/export\"][\"get\"][\"parameters\"][\"query\"]\n>;\n/** 202 from the export: the images are being finished in the background and a download link\n * is emailed; the same request streams the ZIP once they are done. */\nexport type ProductExportQueued = JsonContent<\n paths[\"/v1/products/export\"][\"get\"][\"responses\"][202]\n>;\n\nexport type ApiKeyList = JsonContent<paths[\"/v1/api_keys\"][\"get\"][\"responses\"][200]>;\nexport type ApiKeyCreateParams = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"requestBody\"]>;\nexport type ApiKeyCreated = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"responses\"][201]>;\n\nexport type Usage = JsonContent<paths[\"/v1/usage\"][\"get\"][\"responses\"][200]>;\nexport type UsageParams = NonNullable<paths[\"/v1/usage\"][\"get\"][\"parameters\"][\"query\"]>;\n/** One `group_by: \"product\"` row: a product's counts over the window, with its title. */\nexport type UsageProductTotals = NonNullable<Usage[\"by_product\"]>[number];\n\nexport type WebhookEndpoint = JsonContent<paths[\"/v1/webhooks/{id}\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointList = JsonContent<paths[\"/v1/webhooks\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointCreateParams = JsonContent<paths[\"/v1/webhooks\"][\"post\"][\"requestBody\"]>;\nexport type WebhookEndpointCreated = JsonContent<\n paths[\"/v1/webhooks\"][\"post\"][\"responses\"][201]\n>;\nexport type WebhookEndpointUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/webhooks/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type WebhookDeliveryList = JsonContent<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"responses\"][200]\n>;\nexport type WebhookDeliveriesParams = NonNullable<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"parameters\"][\"query\"]\n>;\n\nexport type Project = JsonContent<paths[\"/v1/projects/{id}\"][\"get\"][\"responses\"][200]>;\nexport type ProjectList = JsonContent<paths[\"/v1/projects\"][\"get\"][\"responses\"][200]>;\n\nexport type BeaconParams = JsonContent<paths[\"/v1/beacon\"][\"post\"][\"requestBody\"]>;\n\nexport type Health = JsonContent<paths[\"/v1/health\"][\"get\"][\"responses\"][200]>;\n\n// --- Client options + transport --------------------------------------------------------------\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface ViewriumOptions {\n /** `sk_` secret key (server) or `pk_` publishable key (browser). */\n apiKey: string;\n /** API origin; defaults to `https://api.viewrium.com`. Trailing slash is stripped. */\n baseUrl?: string;\n /** Injectable fetch (tests, custom agents). Defaults to `globalThis.fetch`. */\n fetch?: FetchLike;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.viewrium.com\";\n\ntype QueryValue = string | number | boolean | readonly string[] | null | undefined;\ntype QueryParams = Record<string, QueryValue>;\n\ninterface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\ntype RequestFn = <T>(method: string, path: string, options?: RequestOptions) => Promise<T>;\n/** The escape hatch for the one route that answers bytes rather than JSON (the export ZIP) -\n * or, on a 202, JSON after all. */\ntype BlobRequestFn = (\n method: string,\n path: string,\n options?: RequestOptions,\n) => Promise<Blob | ProductExportQueued>;\n\ninterface Transport {\n baseUrl: string;\n apiKey: string;\n fetch: FetchLike;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: QueryParams): string {\n const url = new URL(baseUrl + path);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const item of value as readonly string[]) url.searchParams.append(key, String(item));\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\nasync function parseError(res: Response): Promise<ViewriumError> {\n let code = \"error\";\n let message = res.statusText || `HTTP ${res.status}`;\n let details: unknown;\n try {\n const data: unknown = await res.json();\n if (data && typeof data === \"object\" && \"error\" in data) {\n const envelope = (data as { error: unknown }).error;\n if (envelope && typeof envelope === \"object\") {\n const e = envelope as Record<string, unknown>;\n if (typeof e.code === \"string\") code = e.code;\n if (typeof e.message === \"string\") message = e.message;\n details = e.details;\n }\n }\n } catch {\n // Non-JSON error body; fall back to status text.\n }\n return createApiError(res.status, { code, message, details });\n}\n\nasync function send(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Response> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n authorization: `Bearer ${transport.apiKey}`,\n };\n const init: RequestInit = { method, headers };\n if (options?.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(options.body);\n }\n\n let res: Response;\n try {\n res = await transport.fetch(buildUrl(transport.baseUrl, path, options?.query), init);\n } catch (cause) {\n throw new ConnectionError(\"The request to the Viewrium API could not be sent.\", { cause });\n }\n\n if (!res.ok) {\n throw await parseError(res);\n }\n return res;\n}\n\nasync function doRequest<T>(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<T> {\n const res = await send(transport, method, path, options);\n if (res.status === 204) {\n return undefined as T;\n }\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n}\n\nasync function doBlobRequest(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Blob | ProductExportQueued> {\n const res = await send(transport, method, path, options);\n if (res.status === 202) return (await res.json()) as ProductExportQueued;\n return res.blob();\n}\n\nconst encode = encodeURIComponent;\n\n// --- Resource namespaces ---------------------------------------------------------------------\n\nfunction createProducts(request: RequestFn, requestBlob: BlobRequestFn) {\n return {\n /**\n * POST /v1/products - the single generation entrypoint. Supply exactly one source\n * (`image_paths` | `image_urls` | `video_url` | `source_url` | `model_url` |\n * `resume_token`). A fresh request ALWAYS parks (200, `status === \"parked\"`, nothing\n * written): the plan, the model verdict and the price come back inside `resume_token`,\n * which you POST back to commit (202, `ProductCreated`). A `model_url` commits at once.\n */\n create: (params: ProductCreateParams) =>\n request<ProductCreateResult>(\"POST\", \"/v1/products\", { body: params }),\n /**\n * GET /v1/products - the catalog: one org (`org_id`, or the caller's only org), or\n * `scope: \"all\"` for every org an agency member or staff reaches. Filters AND together.\n */\n list: (params?: ProductListParams) =>\n request<ProductList>(\"GET\", \"/v1/products\", { query: params as QueryParams | undefined }),\n /** GET /v1/products/{id} - the product with its assets (a `pk_` key gets `PublicProduct`). */\n retrieve: (id: string) => request<Product>(\"GET\", `/v1/products/${encode(id)}`),\n /**\n * PATCH /v1/products/{id} - the customer's fields, the user-set status and the publish\n * toggle. 409 on a `custom_id` another product in the org already holds.\n */\n update: (id: string, params: ProductUpdateParams) =>\n request<Product>(\"PATCH\", `/v1/products/${encode(id)}`, { body: params }),\n /** DELETE /v1/products/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/products/${encode(id)}`),\n /** DELETE /v1/products?ids=... - per-id affected/not_found/forbidden buckets. */\n batchDelete: (ids: readonly string[]) =>\n request<ProductBatchResult>(\"DELETE\", \"/v1/products\", { query: { ids } }),\n /** POST /v1/products/publish - the bulk publish toggle; one product toggles through `update`. */\n publish: (params: ProductPublishParams) =>\n request<ProductBatchResult>(\"POST\", \"/v1/products/publish\", { body: params }),\n /** GET /v1/products/tags - distinct tags across the same scope the list takes. */\n tags: (params?: TagsParams) =>\n request<Tags>(\"GET\", \"/v1/products/tags\", { query: params as QueryParams | undefined }),\n /** POST /v1/products/{id}/review - the review flush: image and model decisions plus the\n * product's user-set status, validated together and applied in order. */\n review: (id: string, params: ProductReviewParams) =>\n request<ProductReviewResult>(\"POST\", `/v1/products/${encode(id)}/review`, { body: params }),\n /** PATCH /v1/products/{id}/images/{image_id} - one image's decision and overrides. */\n updateImage: (id: string, imageId: string, params: ProductImagePatchParams) =>\n request<ProductImage>(\"PATCH\", `/v1/products/${encode(id)}/images/${encode(imageId)}`, {\n body: params,\n }),\n /** PATCH /v1/products/{id}/model - the model's review decision. */\n updateModel: (id: string, params: ProductModelPatchParams) =>\n request<ProductModel>(\"PATCH\", `/v1/products/${encode(id)}/model`, { body: params }),\n /**\n * POST /v1/products/{id}/model/rescale - re-apply real-world scale to the GLB in place.\n * Every supplied axis is applied to the geometry; `unit` covers them (omitted = cm) and is\n * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.\n */\n rescale: (id: string, params: ProductRescaleParams) =>\n request<Product>(\"POST\", `/v1/products/${encode(id)}/model/rescale`, { body: params }),\n /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the\n * returned `resume_token` with `create`. */\n order: (id: string, params: ProductOrderParams) =>\n request<ProductOrderParked>(\"POST\", `/v1/products/${encode(id)}/order`, { body: params }),\n /** GET /v1/products/export - a ZIP of finished images (never the 3D model), resolved as a\n * `Blob`; or `ProductExportQueued` (202) when the images are being finished in the\n * background and a download link is emailed - repeat the request later to stream it. */\n export: (params?: ProductExportParams) =>\n requestBlob(\"GET\", \"/v1/products/export\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createApiKeys(request: RequestFn) {\n return {\n /** GET /v1/api_keys */\n list: () => request<ApiKeyList>(\"GET\", \"/v1/api_keys\"),\n /** POST /v1/api_keys - the full key is returned exactly once. */\n create: (params: ApiKeyCreateParams) =>\n request<ApiKeyCreated>(\"POST\", \"/v1/api_keys\", { body: params }),\n /** DELETE /v1/api_keys/{id} - soft revoke. */\n revoke: (id: string) => request<void>(\"DELETE\", `/v1/api_keys/${encode(id)}`),\n };\n}\n\nfunction createUsage(request: RequestFn) {\n return {\n /** GET /v1/usage - buckets and totals over the window; `group_by: \"product\"` adds the\n * window's best-performing products in `by_product`. */\n get: (params?: UsageParams) =>\n request<Usage>(\"GET\", \"/v1/usage\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createWebhooks(request: RequestFn) {\n return {\n /** GET /v1/webhooks */\n list: () => request<WebhookEndpointList>(\"GET\", \"/v1/webhooks\"),\n /** POST /v1/webhooks - includes the signing secret. */\n create: (params: WebhookEndpointCreateParams) =>\n request<WebhookEndpointCreated>(\"POST\", \"/v1/webhooks\", { body: params }),\n /** GET /v1/webhooks/{id} */\n retrieve: (id: string) => request<WebhookEndpoint>(\"GET\", `/v1/webhooks/${encode(id)}`),\n /** PATCH /v1/webhooks/{id} */\n update: (id: string, params: WebhookEndpointUpdateParams) =>\n request<WebhookEndpoint>(\"PATCH\", `/v1/webhooks/${encode(id)}`, { body: params }),\n /** DELETE /v1/webhooks/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/webhooks/${encode(id)}`),\n /** GET /v1/webhooks/{id}/deliveries */\n listDeliveries: (id: string, params?: WebhookDeliveriesParams) =>\n request<WebhookDeliveryList>(\"GET\", `/v1/webhooks/${encode(id)}/deliveries`, {\n query: params as QueryParams | undefined,\n }),\n };\n}\n\nfunction createProjects(request: RequestFn) {\n return {\n /** GET /v1/projects */\n list: () => request<ProjectList>(\"GET\", \"/v1/projects\"),\n /** GET /v1/projects/{id} */\n retrieve: (id: string) => request<Project>(\"GET\", `/v1/projects/${encode(id)}`),\n };\n}\n\nfunction createBeacon(request: RequestFn) {\n return {\n /** POST /v1/beacon - ingest a view/AR analytics event (204). */\n send: (params: BeaconParams) => request<void>(\"POST\", \"/v1/beacon\", { body: params }),\n };\n}\n\n// --- Client ----------------------------------------------------------------------------------\n\nexport class Viewrium {\n readonly baseUrl: string;\n readonly products: ReturnType<typeof createProducts>;\n readonly apiKeys: ReturnType<typeof createApiKeys>;\n readonly usage: ReturnType<typeof createUsage>;\n readonly webhooks: ReturnType<typeof createWebhooks>;\n readonly projects: ReturnType<typeof createProjects>;\n readonly beacon: ReturnType<typeof createBeacon>;\n\n readonly #request: RequestFn;\n\n constructor(options: ViewriumOptions) {\n if (!options || !options.apiKey) {\n throw new ViewriumError(\"A Viewrium client requires an `apiKey` (sk_ or pk_).\");\n }\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n\n const provided = options.fetch;\n const globalFetch = typeof globalThis.fetch === \"function\" ? globalThis.fetch.bind(globalThis) : undefined;\n const fetchImpl = provided ?? globalFetch;\n if (!fetchImpl) {\n throw new ViewriumError(\"No fetch implementation available; pass `options.fetch`.\");\n }\n\n const transport: Transport = { baseUrl: this.baseUrl, apiKey: options.apiKey, fetch: fetchImpl };\n const request: RequestFn = <T>(method: string, path: string, opts?: RequestOptions) =>\n doRequest<T>(transport, method, path, opts);\n const requestBlob: BlobRequestFn = (method, path, opts) =>\n doBlobRequest(transport, method, path, opts);\n this.#request = request;\n\n this.products = createProducts(request, requestBlob);\n this.apiKeys = createApiKeys(request);\n this.usage = createUsage(request);\n this.webhooks = createWebhooks(request);\n this.projects = createProjects(request);\n this.beacon = createBeacon(request);\n }\n\n /** GET /v1/health */\n health(): Promise<Health> {\n return this.#request<Health>(\"GET\", \"/v1/health\");\n }\n}\n\n/** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */\nexport function createViewrium(options: ViewriumOptions): Viewrium {\n return new Viewrium(options);\n}\n","/**\n * Typed errors for the Viewrium SDK.\n *\n * The API returns one error envelope: `{ \"error\": { code, message, details } }`.\n * Non-2xx responses are parsed into a `ViewriumError` subclass keyed by HTTP status;\n * consumers branch with `instanceof`.\n */\n\nexport interface ViewriumErrorOptions {\n status?: number | null;\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class ViewriumError extends Error {\n /** HTTP status, or `null` for transport/connection failures. */\n readonly status: number | null;\n /** Machine-readable code from the error envelope. */\n readonly code: string;\n /** Optional structured details from the error envelope. */\n readonly details: unknown;\n\n constructor(message: string, options: ViewriumErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = new.target.name;\n this.status = options.status ?? null;\n this.code = options.code ?? \"error\";\n this.details = options.details;\n }\n}\n\n/** 400 - malformed request. */\nexport class BadRequestError extends ViewriumError {}\n/** 401 - missing or invalid API key. */\nexport class AuthenticationError extends ViewriumError {}\n/**\n * 402 - the organization's plan does not allow this: no plan, a used free trial, a failed payment,\n * or past the plan's active-product cap. `details.reason` says which.\n */\nexport class PaymentRequiredError extends ViewriumError {}\n/** 403 - the key is valid but not allowed (scope, origin, or role). */\nexport class PermissionError extends ViewriumError {}\n/** 404 - no such resource. */\nexport class NotFoundError extends ViewriumError {}\n/** 409 - conflict (e.g. duplicate `custom_id`, model not in a rescalable state). */\nexport class ConflictError extends ViewriumError {}\n/** 422 - request failed validation. */\nexport class UnprocessableEntityError extends ViewriumError {}\n/** 429 - rate limited, or not enough credits (`code: \"quota_exceeded\"`, `details.shortfall_credits`). */\nexport class RateLimitError extends ViewriumError {}\n/** 5xx - the API failed to handle the request. */\nexport class ServerError extends ViewriumError {}\n/** The request never reached the API (DNS/TLS/offline/abort). */\nexport class ConnectionError extends ViewriumError {}\n\nexport interface ApiErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nconst BY_STATUS: Record<number, new (message: string, options: ViewriumErrorOptions) => ViewriumError> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 402: PaymentRequiredError,\n 403: PermissionError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n};\n\n/** Build the right `ViewriumError` subclass for an HTTP status + parsed envelope. */\nexport function createApiError(status: number, payload: ApiErrorPayload): ViewriumError {\n const options: ViewriumErrorOptions = { status, code: payload.code, details: payload.details };\n const Specific = BY_STATUS[status];\n if (Specific) {\n return new Specific(payload.message, options);\n }\n if (status >= 500) {\n return new ServerError(payload.message, options);\n }\n return new ViewriumError(payload.message, options);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts"],"sourcesContent":["/**\n * @viewrium/sdk - the official Viewrium JavaScript/TypeScript SDK.\n *\n * An ergonomic wrapper over the `/v1` REST API. Request/response types are derived from\n * `src/generated/schema.ts` - openapi-typescript output committed from the API's authoritative\n * `apps/api/openapi.json` (the same generator `@repo/api-client` uses; regenerate with\n * `pnpm run generate`). Owning the committed schema keeps the published `dist/index.d.ts`\n * self-contained: it cannot reference the unpublished, source-only `@repo/api-client`.\n *\n * Auth: pass an `sk_` secret key (server) or a `pk_` publishable key (browser). The key is sent\n * as `Authorization: Bearer <key>`; the API enforces what each key type may call.\n *\n * E4 (2026-09-09): the scan-era `models` resource is GONE with its routes; `products` is the\n * resource. Resource and method names match `@repo/api-client` so every client reads the same.\n */\nimport { ConnectionError, ViewriumError, createApiError } from \"./errors\";\nimport type { paths } from \"./generated/schema\";\n\nexport * from \"./errors\";\n\n// --- Derived request/response types (source of truth = apps/api/openapi.json) ----------------\n\ntype JsonContent<T> = T extends { content: { \"application/json\": infer B } } ? B : never;\n\n/** GET /v1/products/{id} resolves to one of two shapes by principal: the full product for a\n * session or `sk_` key, the allow-listed public projection for a `pk_` key. */\ntype ProductGetResult = JsonContent<paths[\"/v1/products/{id}\"][\"get\"][\"responses\"][200]>;\n/** A product with its assets (`sk_` view). */\nexport type Product = Extract<ProductGetResult, { org_id: string }>;\n/** The `pk_` view of a published product: id, title, thumbnail_url, model_status, wall_mounted. The model\n * file itself is reached through `products.createTicket`. */\nexport type PublicProduct = Exclude<ProductGetResult, { org_id: string }>;\nexport type ProductList = JsonContent<paths[\"/v1/products\"][\"get\"][\"responses\"][200]>;\n/** A catalog row - the product plus what its card needs, with no nested assets. */\nexport type ProductSummary = ProductList[\"data\"][number];\nexport type ProductListParams = NonNullable<paths[\"/v1/products\"][\"get\"][\"parameters\"][\"query\"]>;\nexport type ProductCreateParams = JsonContent<paths[\"/v1/products\"][\"post\"][\"requestBody\"]>;\n/** POST /v1/products 200 - the gate parked; resume by POSTing `resume_token`. */\nexport type ProductParked = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][200]>;\n/** One page position of a parked plan. Both park shapes carry the same row type. */\nexport type PlannedView = NonNullable<ProductParked[\"plan\"]>[number];\n/** describe's structured read of the product (D25), with its verbatim reasons. */\nexport type GateFlags = ProductParked[\"flags\"];\n/** The models layer's verdict: whether a model is allowed, what blocks it, and the views it\n * would be built from - including `unseen_views`, the invented ones. */\nexport type ModelPlan = ProductParked[\"model\"];\n/** The priced bundle, in credits (1 credit = $0.50): 1 per deliverable image, a flat 10 for a model. */\nexport type ProductCost = ProductParked[\"cost\"];\n/** POST /v1/products 202 - a committed product whose run has started. */\nexport type ProductCreated = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][202]>;\n/** POST /v1/products resolves to one of the two (discriminate on `status === \"parked\"`). */\nexport type ProductCreateResult = ProductParked | ProductCreated;\nexport type ProductUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductBatchResult = JsonContent<paths[\"/v1/products\"][\"delete\"][\"responses\"][200]>;\nexport type ProductPublishParams = JsonContent<\n paths[\"/v1/products/publish\"][\"post\"][\"requestBody\"]\n>;\nexport type Tags = JsonContent<paths[\"/v1/products/tags\"][\"get\"][\"responses\"][200]>;\nexport type TagsParams = NonNullable<paths[\"/v1/products/tags\"][\"get\"][\"parameters\"][\"query\"]>;\n/** POST /v1/products/{id}/review - the write-behind flush (image + model decisions, the\n * product's user-set status). It records decisions only. */\nexport type ProductReviewParams = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductReviewResult = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductImagePatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductImage = JsonContent<\n paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"responses\"][200]\n>;\nexport type ProductModelPatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/model\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductModel = JsonContent<paths[\"/v1/products/{id}/model\"][\"patch\"][\"responses\"][200]>;\nexport type ProductRescaleParams = JsonContent<\n paths[\"/v1/products/{id}/model/rescale\"][\"post\"][\"requestBody\"]\n>;\n/** POST /v1/products/{id}/tickets - `{ kind: \"view\" | \"ar\", origin? }`. */\nexport type TicketParams = JsonContent<paths[\"/v1/products/{id}/tickets\"][\"post\"][\"requestBody\"]>;\n/** A single-use model URL: `{ url, kind, expires_at }`. */\nexport type Ticket = JsonContent<paths[\"/v1/products/{id}/tickets\"][\"post\"][\"responses\"][200]>;\n/** POST /v1/products/{id}/order - the missing half; parks like a create, commits through\n * `products.create({ resume_token })`. */\nexport type ProductOrderParams = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductOrderParked = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductExportParams = NonNullable<\n paths[\"/v1/products/export\"][\"get\"][\"parameters\"][\"query\"]\n>;\n/** 202 from the export: the images are being finished in the background and a download link\n * is emailed; the same request streams the ZIP once they are done. */\nexport type ProductExportQueued = JsonContent<\n paths[\"/v1/products/export\"][\"get\"][\"responses\"][202]\n>;\n\nexport type ApiKeyList = JsonContent<paths[\"/v1/api_keys\"][\"get\"][\"responses\"][200]>;\nexport type ApiKeyCreateParams = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"requestBody\"]>;\nexport type ApiKeyCreated = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"responses\"][201]>;\n\nexport type Usage = JsonContent<paths[\"/v1/usage\"][\"get\"][\"responses\"][200]>;\nexport type UsageParams = NonNullable<paths[\"/v1/usage\"][\"get\"][\"parameters\"][\"query\"]>;\n/** One `group_by: \"product\"` row: a product's counts over the window, with its title. */\nexport type UsageProductTotals = NonNullable<Usage[\"by_product\"]>[number];\n\nexport type WebhookEndpoint = JsonContent<paths[\"/v1/webhooks/{id}\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointList = JsonContent<paths[\"/v1/webhooks\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointCreateParams = JsonContent<paths[\"/v1/webhooks\"][\"post\"][\"requestBody\"]>;\nexport type WebhookEndpointCreated = JsonContent<\n paths[\"/v1/webhooks\"][\"post\"][\"responses\"][201]\n>;\nexport type WebhookEndpointUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/webhooks/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type WebhookDeliveryList = JsonContent<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"responses\"][200]\n>;\nexport type WebhookDeliveriesParams = NonNullable<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"parameters\"][\"query\"]\n>;\n\nexport type Project = JsonContent<paths[\"/v1/projects/{id}\"][\"get\"][\"responses\"][200]>;\nexport type ProjectList = JsonContent<paths[\"/v1/projects\"][\"get\"][\"responses\"][200]>;\n\nexport type BeaconParams = JsonContent<paths[\"/v1/beacon\"][\"post\"][\"requestBody\"]>;\n\nexport type Health = JsonContent<paths[\"/v1/health\"][\"get\"][\"responses\"][200]>;\n\n// --- Client options + transport --------------------------------------------------------------\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface ViewriumOptions {\n /** `sk_` secret key (server) or `pk_` publishable key (browser). */\n apiKey: string;\n /** API origin; defaults to `https://api.viewrium.com`. Trailing slash is stripped. */\n baseUrl?: string;\n /** Injectable fetch (tests, custom agents). Defaults to `globalThis.fetch`. */\n fetch?: FetchLike;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.viewrium.com\";\n\ntype QueryValue = string | number | boolean | readonly string[] | null | undefined;\ntype QueryParams = Record<string, QueryValue>;\n\ninterface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\ntype RequestFn = <T>(method: string, path: string, options?: RequestOptions) => Promise<T>;\n/** The escape hatch for the one route that answers bytes rather than JSON (the export ZIP) -\n * or, on a 202, JSON after all. */\ntype BlobRequestFn = (\n method: string,\n path: string,\n options?: RequestOptions,\n) => Promise<Blob | ProductExportQueued>;\n\ninterface Transport {\n baseUrl: string;\n apiKey: string;\n fetch: FetchLike;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: QueryParams): string {\n const url = new URL(baseUrl + path);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const item of value as readonly string[]) url.searchParams.append(key, String(item));\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\nasync function parseError(res: Response): Promise<ViewriumError> {\n let code = \"error\";\n let message = res.statusText || `HTTP ${res.status}`;\n let details: unknown;\n try {\n const data: unknown = await res.json();\n if (data && typeof data === \"object\" && \"error\" in data) {\n const envelope = (data as { error: unknown }).error;\n if (envelope && typeof envelope === \"object\") {\n const e = envelope as Record<string, unknown>;\n if (typeof e.code === \"string\") code = e.code;\n if (typeof e.message === \"string\") message = e.message;\n details = e.details;\n }\n }\n } catch {\n // Non-JSON error body; fall back to status text.\n }\n return createApiError(res.status, { code, message, details });\n}\n\nasync function send(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Response> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n authorization: `Bearer ${transport.apiKey}`,\n };\n const init: RequestInit = { method, headers };\n if (options?.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(options.body);\n }\n\n let res: Response;\n try {\n res = await transport.fetch(buildUrl(transport.baseUrl, path, options?.query), init);\n } catch (cause) {\n throw new ConnectionError(\"The request to the Viewrium API could not be sent.\", { cause });\n }\n\n if (!res.ok) {\n throw await parseError(res);\n }\n return res;\n}\n\nasync function doRequest<T>(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<T> {\n const res = await send(transport, method, path, options);\n if (res.status === 204) {\n return undefined as T;\n }\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n}\n\nasync function doBlobRequest(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Blob | ProductExportQueued> {\n const res = await send(transport, method, path, options);\n if (res.status === 202) return (await res.json()) as ProductExportQueued;\n return res.blob();\n}\n\nconst encode = encodeURIComponent;\n\n// --- Resource namespaces ---------------------------------------------------------------------\n\nfunction createProducts(request: RequestFn, requestBlob: BlobRequestFn) {\n return {\n /**\n * POST /v1/products - the single generation entrypoint. Supply exactly one source\n * (`image_paths` | `image_urls` | `video_url` | `source_url` | `model_url` |\n * `resume_token`). A fresh request ALWAYS parks (200, `status === \"parked\"`, nothing\n * written): the plan, the model verdict and the price come back inside `resume_token`,\n * which you POST back to commit (202, `ProductCreated`). A `model_url` commits at once.\n */\n create: (params: ProductCreateParams) =>\n request<ProductCreateResult>(\"POST\", \"/v1/products\", { body: params }),\n /**\n * GET /v1/products - the catalog: one org (`org_id`, or the caller's only org), or\n * `scope: \"all\"` for every org an agency member or staff reaches. Filters AND together.\n */\n list: (params?: ProductListParams) =>\n request<ProductList>(\"GET\", \"/v1/products\", { query: params as QueryParams | undefined }),\n /** GET /v1/products/{id} - the product with its assets (a `pk_` key gets `PublicProduct`). */\n retrieve: (id: string) => request<Product>(\"GET\", `/v1/products/${encode(id)}`),\n /**\n * PATCH /v1/products/{id} - the customer's fields, the user-set status and the publish\n * toggle. 409 on a `custom_id` another product in the org already holds.\n */\n update: (id: string, params: ProductUpdateParams) =>\n request<Product>(\"PATCH\", `/v1/products/${encode(id)}`, { body: params }),\n /** DELETE /v1/products/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/products/${encode(id)}`),\n /** DELETE /v1/products?ids=... - per-id affected/not_found/forbidden buckets. */\n batchDelete: (ids: readonly string[]) =>\n request<ProductBatchResult>(\"DELETE\", \"/v1/products\", { query: { ids } }),\n /** POST /v1/products/publish - the bulk publish toggle; one product toggles through `update`. */\n publish: (params: ProductPublishParams) =>\n request<ProductBatchResult>(\"POST\", \"/v1/products/publish\", { body: params }),\n /** GET /v1/products/tags - distinct tags across the same scope the list takes. */\n tags: (params?: TagsParams) =>\n request<Tags>(\"GET\", \"/v1/products/tags\", { query: params as QueryParams | undefined }),\n /** POST /v1/products/{id}/review - the review flush: image and model decisions plus the\n * product's user-set status, validated together and applied in order. */\n review: (id: string, params: ProductReviewParams) =>\n request<ProductReviewResult>(\"POST\", `/v1/products/${encode(id)}/review`, { body: params }),\n /** PATCH /v1/products/{id}/images/{image_id} - one image's decision and overrides. */\n updateImage: (id: string, imageId: string, params: ProductImagePatchParams) =>\n request<ProductImage>(\"PATCH\", `/v1/products/${encode(id)}/images/${encode(imageId)}`, {\n body: params,\n }),\n /** PATCH /v1/products/{id}/model - the model's review decision. */\n updateModel: (id: string, params: ProductModelPatchParams) =>\n request<ProductModel>(\"PATCH\", `/v1/products/${encode(id)}/model`, { body: params }),\n /**\n * POST /v1/products/{id}/model/rescale - re-apply real-world scale to the GLB in place.\n * Every supplied axis is applied to the geometry; `unit` covers them (omitted = cm) and is\n * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.\n */\n rescale: (id: string, params: ProductRescaleParams) =>\n request<Product>(\"POST\", `/v1/products/${encode(id)}/model/rescale`, { body: params }),\n /**\n * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way\n * to its bytes (no response carries a model file URL). It works once, until `expires_at`\n * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`\n * key the product must be published and the ticket is bound to the page's Origin; with an\n * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).\n */\n createTicket: (id: string, params: TicketParams) =>\n request<Ticket>(\"POST\", `/v1/products/${encode(id)}/tickets`, { body: params }),\n /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the\n * returned `resume_token` with `create`. */\n order: (id: string, params: ProductOrderParams) =>\n request<ProductOrderParked>(\"POST\", `/v1/products/${encode(id)}/order`, { body: params }),\n /** GET /v1/products/export - a ZIP of finished images (never the 3D model), resolved as a\n * `Blob`; or `ProductExportQueued` (202) when the images are being finished in the\n * background and a download link is emailed - repeat the request later to stream it. */\n export: (params?: ProductExportParams) =>\n requestBlob(\"GET\", \"/v1/products/export\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createApiKeys(request: RequestFn) {\n return {\n /** GET /v1/api_keys */\n list: () => request<ApiKeyList>(\"GET\", \"/v1/api_keys\"),\n /** POST /v1/api_keys - the full key is returned exactly once. */\n create: (params: ApiKeyCreateParams) =>\n request<ApiKeyCreated>(\"POST\", \"/v1/api_keys\", { body: params }),\n /** DELETE /v1/api_keys/{id} - soft revoke. */\n revoke: (id: string) => request<void>(\"DELETE\", `/v1/api_keys/${encode(id)}`),\n };\n}\n\nfunction createUsage(request: RequestFn) {\n return {\n /** GET /v1/usage - buckets and totals over the window; `group_by: \"product\"` adds the\n * window's best-performing products in `by_product`. */\n get: (params?: UsageParams) =>\n request<Usage>(\"GET\", \"/v1/usage\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createWebhooks(request: RequestFn) {\n return {\n /** GET /v1/webhooks */\n list: () => request<WebhookEndpointList>(\"GET\", \"/v1/webhooks\"),\n /** POST /v1/webhooks - includes the signing secret. */\n create: (params: WebhookEndpointCreateParams) =>\n request<WebhookEndpointCreated>(\"POST\", \"/v1/webhooks\", { body: params }),\n /** GET /v1/webhooks/{id} */\n retrieve: (id: string) => request<WebhookEndpoint>(\"GET\", `/v1/webhooks/${encode(id)}`),\n /** PATCH /v1/webhooks/{id} */\n update: (id: string, params: WebhookEndpointUpdateParams) =>\n request<WebhookEndpoint>(\"PATCH\", `/v1/webhooks/${encode(id)}`, { body: params }),\n /** DELETE /v1/webhooks/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/webhooks/${encode(id)}`),\n /** GET /v1/webhooks/{id}/deliveries */\n listDeliveries: (id: string, params?: WebhookDeliveriesParams) =>\n request<WebhookDeliveryList>(\"GET\", `/v1/webhooks/${encode(id)}/deliveries`, {\n query: params as QueryParams | undefined,\n }),\n };\n}\n\nfunction createProjects(request: RequestFn) {\n return {\n /** GET /v1/projects */\n list: () => request<ProjectList>(\"GET\", \"/v1/projects\"),\n /** GET /v1/projects/{id} */\n retrieve: (id: string) => request<Project>(\"GET\", `/v1/projects/${encode(id)}`),\n };\n}\n\nfunction createBeacon(request: RequestFn) {\n return {\n /** POST /v1/beacon - ingest a view/AR analytics event (204). */\n send: (params: BeaconParams) => request<void>(\"POST\", \"/v1/beacon\", { body: params }),\n };\n}\n\n// --- Client ----------------------------------------------------------------------------------\n\nexport class Viewrium {\n readonly baseUrl: string;\n readonly products: ReturnType<typeof createProducts>;\n readonly apiKeys: ReturnType<typeof createApiKeys>;\n readonly usage: ReturnType<typeof createUsage>;\n readonly webhooks: ReturnType<typeof createWebhooks>;\n readonly projects: ReturnType<typeof createProjects>;\n readonly beacon: ReturnType<typeof createBeacon>;\n\n readonly #request: RequestFn;\n\n constructor(options: ViewriumOptions) {\n if (!options || !options.apiKey) {\n throw new ViewriumError(\"A Viewrium client requires an `apiKey` (sk_ or pk_).\");\n }\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n\n const provided = options.fetch;\n const globalFetch = typeof globalThis.fetch === \"function\" ? globalThis.fetch.bind(globalThis) : undefined;\n const fetchImpl = provided ?? globalFetch;\n if (!fetchImpl) {\n throw new ViewriumError(\"No fetch implementation available; pass `options.fetch`.\");\n }\n\n const transport: Transport = { baseUrl: this.baseUrl, apiKey: options.apiKey, fetch: fetchImpl };\n const request: RequestFn = <T>(method: string, path: string, opts?: RequestOptions) =>\n doRequest<T>(transport, method, path, opts);\n const requestBlob: BlobRequestFn = (method, path, opts) =>\n doBlobRequest(transport, method, path, opts);\n this.#request = request;\n\n this.products = createProducts(request, requestBlob);\n this.apiKeys = createApiKeys(request);\n this.usage = createUsage(request);\n this.webhooks = createWebhooks(request);\n this.projects = createProjects(request);\n this.beacon = createBeacon(request);\n }\n\n /** GET /v1/health */\n health(): Promise<Health> {\n return this.#request<Health>(\"GET\", \"/v1/health\");\n }\n}\n\n/** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */\nexport function createViewrium(options: ViewriumOptions): Viewrium {\n return new Viewrium(options);\n}\n","/**\n * Typed errors for the Viewrium SDK.\n *\n * The API returns one error envelope: `{ \"error\": { code, message, details } }`.\n * Non-2xx responses are parsed into a `ViewriumError` subclass keyed by HTTP status;\n * consumers branch with `instanceof`.\n */\n\nexport interface ViewriumErrorOptions {\n status?: number | null;\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class ViewriumError extends Error {\n /** HTTP status, or `null` for transport/connection failures. */\n readonly status: number | null;\n /** Machine-readable code from the error envelope. */\n readonly code: string;\n /** Optional structured details from the error envelope. */\n readonly details: unknown;\n\n constructor(message: string, options: ViewriumErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = new.target.name;\n this.status = options.status ?? null;\n this.code = options.code ?? \"error\";\n this.details = options.details;\n }\n}\n\n/** 400 - malformed request. */\nexport class BadRequestError extends ViewriumError {}\n/** 401 - missing or invalid API key. */\nexport class AuthenticationError extends ViewriumError {}\n/**\n * 402 - the organization's plan does not allow this: no plan, a used free trial, a failed payment,\n * or past the plan's active-product cap. `details.reason` says which.\n */\nexport class PaymentRequiredError extends ViewriumError {}\n/** 403 - the key is valid but not allowed (scope, origin, or role). */\nexport class PermissionError extends ViewriumError {}\n/** 404 - no such resource. */\nexport class NotFoundError extends ViewriumError {}\n/** 409 - conflict (e.g. duplicate `custom_id`, model not in a rescalable state). */\nexport class ConflictError extends ViewriumError {}\n/** 422 - request failed validation. */\nexport class UnprocessableEntityError extends ViewriumError {}\n/** 429 - rate limited, or not enough credits (`code: \"quota_exceeded\"`, `details.shortfall_credits`). */\nexport class RateLimitError extends ViewriumError {}\n/** 5xx - the API failed to handle the request. */\nexport class ServerError extends ViewriumError {}\n/** The request never reached the API (DNS/TLS/offline/abort). */\nexport class ConnectionError extends ViewriumError {}\n\nexport interface ApiErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nconst BY_STATUS: Record<number, new (message: string, options: ViewriumErrorOptions) => ViewriumError> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 402: PaymentRequiredError,\n 403: PermissionError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n};\n\n/** Build the right `ViewriumError` subclass for an HTTP status + parsed envelope. */\nexport function createApiError(status: number, payload: ApiErrorPayload): ViewriumError {\n const options: ViewriumErrorOptions = { status, code: payload.code, details: payload.details };\n const Specific = BY_STATUS[status];\n if (Specific) {\n return new Specific(payload.message, options);\n }\n if (status >= 500) {\n return new ServerError(payload.message, options);\n }\n return new ViewriumError(payload.message, options);\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -225,7 +225,8 @@ interface paths {
225
225
  /**
226
226
  * Rescale Product Model
227
227
  * @description Re-apply real-world scale to the GLB in place; the product keeps the axes the
228
- * customer stated. 409 without a ready model; 502 when the resize itself fails.
228
+ * customer stated. 409 without a ready model, or for one with no measurable geometry; 502
229
+ * when the resize itself fails.
229
230
  */
230
231
  post: operations["rescale_product_model_v1_products__id__model_rescale_post"];
231
232
  delete?: never;
@@ -234,6 +235,38 @@ interface paths {
234
235
  patch?: never;
235
236
  trace?: never;
236
237
  };
238
+ "/v1/products/{id}/tickets": {
239
+ parameters: {
240
+ query?: never;
241
+ header?: never;
242
+ path?: never;
243
+ cookie?: never;
244
+ };
245
+ get?: never;
246
+ put?: never;
247
+ /**
248
+ * Create Product Ticket
249
+ * @description A single-use URL for the product's 3D file - the ONLY way to its bytes (there is no
250
+ * model file URL in any response). It answers `model/gltf-binary` ONCE, until `expires_at`
251
+ * (60 s for `view`, 30 s for `ar`). Mint it right before the viewer loads, never store it.
252
+ *
253
+ * - publishable key: a PUBLISHED product only; the ticket is bound to the page's Origin (one
254
+ * of the org's allowed domains).
255
+ * - secret key / session: the org's own products, drafts included; bound to `origin` (the
256
+ * page that will fetch the file), else the request's Origin.
257
+ *
258
+ * A `view` ticket streams only to its bound origin; an `ar` ticket also to Android's Scene
259
+ * Viewer, which sends none. 404 for a product the caller cannot see (or, for a pk, one that is
260
+ * not published); 409 without a ready model; 400 for an origin outside the allowed domains;
261
+ * 429 past the mint limits.
262
+ */
263
+ post: operations["create_product_ticket_v1_products__id__tickets_post"];
264
+ delete?: never;
265
+ options?: never;
266
+ head?: never;
267
+ patch?: never;
268
+ trace?: never;
269
+ };
237
270
  "/v1/products/{id}/model": {
238
271
  parameters: {
239
272
  query?: never;
@@ -976,7 +1009,12 @@ interface components {
976
1009
  ProductModelPatch: {
977
1010
  review_status?: components["schemas"]["ReviewStatus"] | null;
978
1011
  };
979
- /** ProductModelResponse */
1012
+ /**
1013
+ * ProductModelResponse
1014
+ * @description The product's 3D model. There is NO file URL here, on purpose (Dave, 2026-10-16: no
1015
+ * model file URL anywhere, merchants included): the bytes are reached only through a
1016
+ * single-use ticket, `POST /v1/products/{id}/tickets`.
1017
+ */
980
1018
  ProductModelResponse: {
981
1019
  /**
982
1020
  * Id
@@ -986,8 +1024,6 @@ interface components {
986
1024
  status: components["schemas"]["AssetStatus"];
987
1025
  /** @default pending */
988
1026
  review_status: components["schemas"]["ReviewStatus"];
989
- /** Glb Url */
990
- glb_url?: string | null;
991
1027
  /** Thumbnail Url */
992
1028
  thumbnail_url?: string | null;
993
1029
  /** Scale Factor */
@@ -1555,7 +1591,8 @@ interface components {
1555
1591
  * storefronts, so anything here is world-readable. An allow-list fails closed: a field
1556
1592
  * added to ProductResponse tomorrow is NOT exposed here until someone adds it on purpose.
1557
1593
  * Only a PUBLISHED product with a READY model is served at all (the `public_products()`
1558
- * gate, D72); the images half is post-launch backlog #4.
1594
+ * gate, D72); the images half is post-launch backlog #4. The model file itself is NOT here:
1595
+ * the embed mints a single-use ticket for it (`POST /v1/products/{id}/tickets`).
1559
1596
  */
1560
1597
  PublicProductResponse: {
1561
1598
  /**
@@ -1565,11 +1602,14 @@ interface components {
1565
1602
  id: string;
1566
1603
  /** Title */
1567
1604
  title: string;
1568
- /** Model Url */
1569
- model_url?: string | null;
1570
1605
  /** Thumbnail Url */
1571
1606
  thumbnail_url?: string | null;
1572
1607
  model_status?: components["schemas"]["AssetStatus"] | null;
1608
+ /**
1609
+ * Wall Mounted
1610
+ * @default false
1611
+ */
1612
+ wall_mounted: boolean;
1573
1613
  };
1574
1614
  /**
1575
1615
  * ReviewStatus
@@ -1668,6 +1708,47 @@ interface components {
1668
1708
  /** Data */
1669
1709
  data: components["schemas"]["TagCount"][];
1670
1710
  };
1711
+ /**
1712
+ * TicketKind
1713
+ * @description `model_tickets.kind` (migration section 46): what one single-use model ticket is for.
1714
+ * `view` streams to the page that minted it (its Origin must match); `ar` is Android's Scene
1715
+ * Viewer, which re-downloads the file outside the page and sends no Origin.
1716
+ * @enum {string}
1717
+ */
1718
+ TicketKind: "view" | "ar";
1719
+ /**
1720
+ * TicketRequest
1721
+ * @description POST /v1/products/{id}/tickets - one single-use ticket for the product's 3D file.
1722
+ *
1723
+ * `view` is for the page itself: the redeem streams the file only to a request whose Origin
1724
+ * is the one the ticket is bound to. `ar` is for Android's Scene Viewer, which downloads the
1725
+ * file outside the page (it sends no Origin) - mint it on the AR tap, it lives 30 s.
1726
+ *
1727
+ * `origin` is for a SERVER minting on a page's behalf (a secret key, or our dashboard's own
1728
+ * server): the origin of the page that will fetch the file. It must be one of the org's
1729
+ * allowed domains (or ours). A publishable key never sends it - a pk ticket is bound to the
1730
+ * request's own Origin, which the key's allowed domains already gated.
1731
+ */
1732
+ TicketRequest: {
1733
+ kind: components["schemas"]["TicketKind"];
1734
+ /** Origin */
1735
+ origin?: string | null;
1736
+ };
1737
+ /**
1738
+ * TicketResponse
1739
+ * @description A single-use URL for the product's 3D file (`model/gltf-binary`). It works ONCE, until
1740
+ * `expires_at`, and for a view ticket only from its bound origin. Do not store it.
1741
+ */
1742
+ TicketResponse: {
1743
+ /** Url */
1744
+ url: string;
1745
+ kind: components["schemas"]["TicketKind"];
1746
+ /**
1747
+ * Expires At
1748
+ * Format: date-time
1749
+ */
1750
+ expires_at: string;
1751
+ };
1671
1752
  /** UsageBucket */
1672
1753
  UsageBucket: {
1673
1754
  /**
@@ -2423,6 +2504,41 @@ interface operations {
2423
2504
  };
2424
2505
  };
2425
2506
  };
2507
+ create_product_ticket_v1_products__id__tickets_post: {
2508
+ parameters: {
2509
+ query?: never;
2510
+ header?: never;
2511
+ path: {
2512
+ id: string;
2513
+ };
2514
+ cookie?: never;
2515
+ };
2516
+ requestBody: {
2517
+ content: {
2518
+ "application/json": components["schemas"]["TicketRequest"];
2519
+ };
2520
+ };
2521
+ responses: {
2522
+ /** @description Successful Response */
2523
+ 200: {
2524
+ headers: {
2525
+ [name: string]: unknown;
2526
+ };
2527
+ content: {
2528
+ "application/json": components["schemas"]["TicketResponse"];
2529
+ };
2530
+ };
2531
+ /** @description Validation Error */
2532
+ 422: {
2533
+ headers: {
2534
+ [name: string]: unknown;
2535
+ };
2536
+ content: {
2537
+ "application/json": components["schemas"]["HTTPValidationError"];
2538
+ };
2539
+ };
2540
+ };
2541
+ };
2426
2542
  update_product_model_v1_products__id__model_patch: {
2427
2543
  parameters: {
2428
2544
  query?: never;
@@ -2942,7 +3058,8 @@ type ProductGetResult = JsonContent<paths["/v1/products/{id}"]["get"]["responses
2942
3058
  type Product = Extract<ProductGetResult, {
2943
3059
  org_id: string;
2944
3060
  }>;
2945
- /** The `pk_` view of a published product: id, title, model_url, thumbnail_url, model_status. */
3061
+ /** The `pk_` view of a published product: id, title, thumbnail_url, model_status, wall_mounted. The model
3062
+ * file itself is reached through `products.createTicket`. */
2946
3063
  type PublicProduct = Exclude<ProductGetResult, {
2947
3064
  org_id: string;
2948
3065
  }>;
@@ -2980,6 +3097,10 @@ type ProductImage = JsonContent<paths["/v1/products/{id}/images/{image_id}"]["pa
2980
3097
  type ProductModelPatchParams = JsonContent<NonNullable<paths["/v1/products/{id}/model"]["patch"]["requestBody"]>>;
2981
3098
  type ProductModel = JsonContent<paths["/v1/products/{id}/model"]["patch"]["responses"][200]>;
2982
3099
  type ProductRescaleParams = JsonContent<paths["/v1/products/{id}/model/rescale"]["post"]["requestBody"]>;
3100
+ /** POST /v1/products/{id}/tickets - `{ kind: "view" | "ar", origin? }`. */
3101
+ type TicketParams = JsonContent<paths["/v1/products/{id}/tickets"]["post"]["requestBody"]>;
3102
+ /** A single-use model URL: `{ url, kind, expires_at }`. */
3103
+ type Ticket = JsonContent<paths["/v1/products/{id}/tickets"]["post"]["responses"][200]>;
2983
3104
  /** POST /v1/products/{id}/order - the missing half; parks like a create, commits through
2984
3105
  * `products.create({ resume_token })`. */
2985
3106
  type ProductOrderParams = JsonContent<paths["/v1/products/{id}/order"]["post"]["requestBody"]>;
@@ -3194,7 +3315,6 @@ declare function createProducts(request: RequestFn, requestBlob: BlobRequestFn):
3194
3315
  id: string;
3195
3316
  status: components["schemas"]["AssetStatus"];
3196
3317
  review_status: components["schemas"]["ReviewStatus"];
3197
- glb_url?: string | null;
3198
3318
  thumbnail_url?: string | null;
3199
3319
  scale_factor?: number | null;
3200
3320
  width_axis?: ("x" | "z") | null;
@@ -3249,6 +3369,18 @@ declare function createProducts(request: RequestFn, requestBlob: BlobRequestFn):
3249
3369
  warnings?: components["schemas"]["ProductWarning"][];
3250
3370
  open_request_count: number;
3251
3371
  }>;
3372
+ /**
3373
+ * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way
3374
+ * to its bytes (no response carries a model file URL). It works once, until `expires_at`
3375
+ * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`
3376
+ * key the product must be published and the ticket is bound to the page's Origin; with an
3377
+ * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).
3378
+ */
3379
+ createTicket: (id: string, params: TicketParams) => Promise<{
3380
+ url: string;
3381
+ kind: components["schemas"]["TicketKind"];
3382
+ expires_at: string;
3383
+ }>;
3252
3384
  /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the
3253
3385
  * returned `resume_token` with `create`. */
3254
3386
  order: (id: string, params: ProductOrderParams) => Promise<{
@@ -3285,7 +3417,7 @@ declare function createApiKeys(request: RequestFn): {
3285
3417
  id: string;
3286
3418
  org_id?: string | null;
3287
3419
  project_id?: string | null;
3288
- key_type: components["schemas"]["KeyType"];
3420
+ key_type: components["schemas"] /** PATCH /v1/webhooks/{id} */["KeyType"];
3289
3421
  key_prefix: string;
3290
3422
  name?: string | null;
3291
3423
  scopes: string[];
@@ -3400,4 +3532,4 @@ declare class Viewrium {
3400
3532
  /** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */
3401
3533
  declare function createViewrium(options: ViewriumOptions): Viewrium;
3402
3534
 
3403
- export { type ApiErrorPayload, type ApiKeyCreateParams, type ApiKeyCreated, type ApiKeyList, AuthenticationError, BadRequestError, type BeaconParams, ConflictError, ConnectionError, type FetchLike, type GateFlags, type Health, type ModelPlan, NotFoundError, PaymentRequiredError, PermissionError, type PlannedView, type Product, type ProductBatchResult, type ProductCost, type ProductCreateParams, type ProductCreateResult, type ProductCreated, type ProductExportParams, type ProductExportQueued, type ProductImage, type ProductImagePatchParams, type ProductList, type ProductListParams, type ProductModel, type ProductModelPatchParams, type ProductOrderParams, type ProductOrderParked, type ProductParked, type ProductPublishParams, type ProductRescaleParams, type ProductReviewParams, type ProductReviewResult, type ProductSummary, type ProductUpdateParams, type Project, type ProjectList, type PublicProduct, RateLimitError, ServerError, type Tags, type TagsParams, UnprocessableEntityError, type Usage, type UsageParams, type UsageProductTotals, Viewrium, ViewriumError, type ViewriumErrorOptions, type ViewriumOptions, type WebhookDeliveriesParams, type WebhookDeliveryList, type WebhookEndpoint, type WebhookEndpointCreateParams, type WebhookEndpointCreated, type WebhookEndpointList, type WebhookEndpointUpdateParams, createApiError, createViewrium };
3535
+ export { type ApiErrorPayload, type ApiKeyCreateParams, type ApiKeyCreated, type ApiKeyList, AuthenticationError, BadRequestError, type BeaconParams, ConflictError, ConnectionError, type FetchLike, type GateFlags, type Health, type ModelPlan, NotFoundError, PaymentRequiredError, PermissionError, type PlannedView, type Product, type ProductBatchResult, type ProductCost, type ProductCreateParams, type ProductCreateResult, type ProductCreated, type ProductExportParams, type ProductExportQueued, type ProductImage, type ProductImagePatchParams, type ProductList, type ProductListParams, type ProductModel, type ProductModelPatchParams, type ProductOrderParams, type ProductOrderParked, type ProductParked, type ProductPublishParams, type ProductRescaleParams, type ProductReviewParams, type ProductReviewResult, type ProductSummary, type ProductUpdateParams, type Project, type ProjectList, type PublicProduct, RateLimitError, ServerError, type Tags, type TagsParams, type Ticket, type TicketParams, UnprocessableEntityError, type Usage, type UsageParams, type UsageProductTotals, Viewrium, ViewriumError, type ViewriumErrorOptions, type ViewriumOptions, type WebhookDeliveriesParams, type WebhookDeliveryList, type WebhookEndpoint, type WebhookEndpointCreateParams, type WebhookEndpointCreated, type WebhookEndpointList, type WebhookEndpointUpdateParams, createApiError, createViewrium };
package/dist/index.d.ts CHANGED
@@ -225,7 +225,8 @@ interface paths {
225
225
  /**
226
226
  * Rescale Product Model
227
227
  * @description Re-apply real-world scale to the GLB in place; the product keeps the axes the
228
- * customer stated. 409 without a ready model; 502 when the resize itself fails.
228
+ * customer stated. 409 without a ready model, or for one with no measurable geometry; 502
229
+ * when the resize itself fails.
229
230
  */
230
231
  post: operations["rescale_product_model_v1_products__id__model_rescale_post"];
231
232
  delete?: never;
@@ -234,6 +235,38 @@ interface paths {
234
235
  patch?: never;
235
236
  trace?: never;
236
237
  };
238
+ "/v1/products/{id}/tickets": {
239
+ parameters: {
240
+ query?: never;
241
+ header?: never;
242
+ path?: never;
243
+ cookie?: never;
244
+ };
245
+ get?: never;
246
+ put?: never;
247
+ /**
248
+ * Create Product Ticket
249
+ * @description A single-use URL for the product's 3D file - the ONLY way to its bytes (there is no
250
+ * model file URL in any response). It answers `model/gltf-binary` ONCE, until `expires_at`
251
+ * (60 s for `view`, 30 s for `ar`). Mint it right before the viewer loads, never store it.
252
+ *
253
+ * - publishable key: a PUBLISHED product only; the ticket is bound to the page's Origin (one
254
+ * of the org's allowed domains).
255
+ * - secret key / session: the org's own products, drafts included; bound to `origin` (the
256
+ * page that will fetch the file), else the request's Origin.
257
+ *
258
+ * A `view` ticket streams only to its bound origin; an `ar` ticket also to Android's Scene
259
+ * Viewer, which sends none. 404 for a product the caller cannot see (or, for a pk, one that is
260
+ * not published); 409 without a ready model; 400 for an origin outside the allowed domains;
261
+ * 429 past the mint limits.
262
+ */
263
+ post: operations["create_product_ticket_v1_products__id__tickets_post"];
264
+ delete?: never;
265
+ options?: never;
266
+ head?: never;
267
+ patch?: never;
268
+ trace?: never;
269
+ };
237
270
  "/v1/products/{id}/model": {
238
271
  parameters: {
239
272
  query?: never;
@@ -976,7 +1009,12 @@ interface components {
976
1009
  ProductModelPatch: {
977
1010
  review_status?: components["schemas"]["ReviewStatus"] | null;
978
1011
  };
979
- /** ProductModelResponse */
1012
+ /**
1013
+ * ProductModelResponse
1014
+ * @description The product's 3D model. There is NO file URL here, on purpose (Dave, 2026-10-16: no
1015
+ * model file URL anywhere, merchants included): the bytes are reached only through a
1016
+ * single-use ticket, `POST /v1/products/{id}/tickets`.
1017
+ */
980
1018
  ProductModelResponse: {
981
1019
  /**
982
1020
  * Id
@@ -986,8 +1024,6 @@ interface components {
986
1024
  status: components["schemas"]["AssetStatus"];
987
1025
  /** @default pending */
988
1026
  review_status: components["schemas"]["ReviewStatus"];
989
- /** Glb Url */
990
- glb_url?: string | null;
991
1027
  /** Thumbnail Url */
992
1028
  thumbnail_url?: string | null;
993
1029
  /** Scale Factor */
@@ -1555,7 +1591,8 @@ interface components {
1555
1591
  * storefronts, so anything here is world-readable. An allow-list fails closed: a field
1556
1592
  * added to ProductResponse tomorrow is NOT exposed here until someone adds it on purpose.
1557
1593
  * Only a PUBLISHED product with a READY model is served at all (the `public_products()`
1558
- * gate, D72); the images half is post-launch backlog #4.
1594
+ * gate, D72); the images half is post-launch backlog #4. The model file itself is NOT here:
1595
+ * the embed mints a single-use ticket for it (`POST /v1/products/{id}/tickets`).
1559
1596
  */
1560
1597
  PublicProductResponse: {
1561
1598
  /**
@@ -1565,11 +1602,14 @@ interface components {
1565
1602
  id: string;
1566
1603
  /** Title */
1567
1604
  title: string;
1568
- /** Model Url */
1569
- model_url?: string | null;
1570
1605
  /** Thumbnail Url */
1571
1606
  thumbnail_url?: string | null;
1572
1607
  model_status?: components["schemas"]["AssetStatus"] | null;
1608
+ /**
1609
+ * Wall Mounted
1610
+ * @default false
1611
+ */
1612
+ wall_mounted: boolean;
1573
1613
  };
1574
1614
  /**
1575
1615
  * ReviewStatus
@@ -1668,6 +1708,47 @@ interface components {
1668
1708
  /** Data */
1669
1709
  data: components["schemas"]["TagCount"][];
1670
1710
  };
1711
+ /**
1712
+ * TicketKind
1713
+ * @description `model_tickets.kind` (migration section 46): what one single-use model ticket is for.
1714
+ * `view` streams to the page that minted it (its Origin must match); `ar` is Android's Scene
1715
+ * Viewer, which re-downloads the file outside the page and sends no Origin.
1716
+ * @enum {string}
1717
+ */
1718
+ TicketKind: "view" | "ar";
1719
+ /**
1720
+ * TicketRequest
1721
+ * @description POST /v1/products/{id}/tickets - one single-use ticket for the product's 3D file.
1722
+ *
1723
+ * `view` is for the page itself: the redeem streams the file only to a request whose Origin
1724
+ * is the one the ticket is bound to. `ar` is for Android's Scene Viewer, which downloads the
1725
+ * file outside the page (it sends no Origin) - mint it on the AR tap, it lives 30 s.
1726
+ *
1727
+ * `origin` is for a SERVER minting on a page's behalf (a secret key, or our dashboard's own
1728
+ * server): the origin of the page that will fetch the file. It must be one of the org's
1729
+ * allowed domains (or ours). A publishable key never sends it - a pk ticket is bound to the
1730
+ * request's own Origin, which the key's allowed domains already gated.
1731
+ */
1732
+ TicketRequest: {
1733
+ kind: components["schemas"]["TicketKind"];
1734
+ /** Origin */
1735
+ origin?: string | null;
1736
+ };
1737
+ /**
1738
+ * TicketResponse
1739
+ * @description A single-use URL for the product's 3D file (`model/gltf-binary`). It works ONCE, until
1740
+ * `expires_at`, and for a view ticket only from its bound origin. Do not store it.
1741
+ */
1742
+ TicketResponse: {
1743
+ /** Url */
1744
+ url: string;
1745
+ kind: components["schemas"]["TicketKind"];
1746
+ /**
1747
+ * Expires At
1748
+ * Format: date-time
1749
+ */
1750
+ expires_at: string;
1751
+ };
1671
1752
  /** UsageBucket */
1672
1753
  UsageBucket: {
1673
1754
  /**
@@ -2423,6 +2504,41 @@ interface operations {
2423
2504
  };
2424
2505
  };
2425
2506
  };
2507
+ create_product_ticket_v1_products__id__tickets_post: {
2508
+ parameters: {
2509
+ query?: never;
2510
+ header?: never;
2511
+ path: {
2512
+ id: string;
2513
+ };
2514
+ cookie?: never;
2515
+ };
2516
+ requestBody: {
2517
+ content: {
2518
+ "application/json": components["schemas"]["TicketRequest"];
2519
+ };
2520
+ };
2521
+ responses: {
2522
+ /** @description Successful Response */
2523
+ 200: {
2524
+ headers: {
2525
+ [name: string]: unknown;
2526
+ };
2527
+ content: {
2528
+ "application/json": components["schemas"]["TicketResponse"];
2529
+ };
2530
+ };
2531
+ /** @description Validation Error */
2532
+ 422: {
2533
+ headers: {
2534
+ [name: string]: unknown;
2535
+ };
2536
+ content: {
2537
+ "application/json": components["schemas"]["HTTPValidationError"];
2538
+ };
2539
+ };
2540
+ };
2541
+ };
2426
2542
  update_product_model_v1_products__id__model_patch: {
2427
2543
  parameters: {
2428
2544
  query?: never;
@@ -2942,7 +3058,8 @@ type ProductGetResult = JsonContent<paths["/v1/products/{id}"]["get"]["responses
2942
3058
  type Product = Extract<ProductGetResult, {
2943
3059
  org_id: string;
2944
3060
  }>;
2945
- /** The `pk_` view of a published product: id, title, model_url, thumbnail_url, model_status. */
3061
+ /** The `pk_` view of a published product: id, title, thumbnail_url, model_status, wall_mounted. The model
3062
+ * file itself is reached through `products.createTicket`. */
2946
3063
  type PublicProduct = Exclude<ProductGetResult, {
2947
3064
  org_id: string;
2948
3065
  }>;
@@ -2980,6 +3097,10 @@ type ProductImage = JsonContent<paths["/v1/products/{id}/images/{image_id}"]["pa
2980
3097
  type ProductModelPatchParams = JsonContent<NonNullable<paths["/v1/products/{id}/model"]["patch"]["requestBody"]>>;
2981
3098
  type ProductModel = JsonContent<paths["/v1/products/{id}/model"]["patch"]["responses"][200]>;
2982
3099
  type ProductRescaleParams = JsonContent<paths["/v1/products/{id}/model/rescale"]["post"]["requestBody"]>;
3100
+ /** POST /v1/products/{id}/tickets - `{ kind: "view" | "ar", origin? }`. */
3101
+ type TicketParams = JsonContent<paths["/v1/products/{id}/tickets"]["post"]["requestBody"]>;
3102
+ /** A single-use model URL: `{ url, kind, expires_at }`. */
3103
+ type Ticket = JsonContent<paths["/v1/products/{id}/tickets"]["post"]["responses"][200]>;
2983
3104
  /** POST /v1/products/{id}/order - the missing half; parks like a create, commits through
2984
3105
  * `products.create({ resume_token })`. */
2985
3106
  type ProductOrderParams = JsonContent<paths["/v1/products/{id}/order"]["post"]["requestBody"]>;
@@ -3194,7 +3315,6 @@ declare function createProducts(request: RequestFn, requestBlob: BlobRequestFn):
3194
3315
  id: string;
3195
3316
  status: components["schemas"]["AssetStatus"];
3196
3317
  review_status: components["schemas"]["ReviewStatus"];
3197
- glb_url?: string | null;
3198
3318
  thumbnail_url?: string | null;
3199
3319
  scale_factor?: number | null;
3200
3320
  width_axis?: ("x" | "z") | null;
@@ -3249,6 +3369,18 @@ declare function createProducts(request: RequestFn, requestBlob: BlobRequestFn):
3249
3369
  warnings?: components["schemas"]["ProductWarning"][];
3250
3370
  open_request_count: number;
3251
3371
  }>;
3372
+ /**
3373
+ * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way
3374
+ * to its bytes (no response carries a model file URL). It works once, until `expires_at`
3375
+ * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`
3376
+ * key the product must be published and the ticket is bound to the page's Origin; with an
3377
+ * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).
3378
+ */
3379
+ createTicket: (id: string, params: TicketParams) => Promise<{
3380
+ url: string;
3381
+ kind: components["schemas"]["TicketKind"];
3382
+ expires_at: string;
3383
+ }>;
3252
3384
  /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the
3253
3385
  * returned `resume_token` with `create`. */
3254
3386
  order: (id: string, params: ProductOrderParams) => Promise<{
@@ -3285,7 +3417,7 @@ declare function createApiKeys(request: RequestFn): {
3285
3417
  id: string;
3286
3418
  org_id?: string | null;
3287
3419
  project_id?: string | null;
3288
- key_type: components["schemas"]["KeyType"];
3420
+ key_type: components["schemas"] /** PATCH /v1/webhooks/{id} */["KeyType"];
3289
3421
  key_prefix: string;
3290
3422
  name?: string | null;
3291
3423
  scopes: string[];
@@ -3400,4 +3532,4 @@ declare class Viewrium {
3400
3532
  /** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */
3401
3533
  declare function createViewrium(options: ViewriumOptions): Viewrium;
3402
3534
 
3403
- export { type ApiErrorPayload, type ApiKeyCreateParams, type ApiKeyCreated, type ApiKeyList, AuthenticationError, BadRequestError, type BeaconParams, ConflictError, ConnectionError, type FetchLike, type GateFlags, type Health, type ModelPlan, NotFoundError, PaymentRequiredError, PermissionError, type PlannedView, type Product, type ProductBatchResult, type ProductCost, type ProductCreateParams, type ProductCreateResult, type ProductCreated, type ProductExportParams, type ProductExportQueued, type ProductImage, type ProductImagePatchParams, type ProductList, type ProductListParams, type ProductModel, type ProductModelPatchParams, type ProductOrderParams, type ProductOrderParked, type ProductParked, type ProductPublishParams, type ProductRescaleParams, type ProductReviewParams, type ProductReviewResult, type ProductSummary, type ProductUpdateParams, type Project, type ProjectList, type PublicProduct, RateLimitError, ServerError, type Tags, type TagsParams, UnprocessableEntityError, type Usage, type UsageParams, type UsageProductTotals, Viewrium, ViewriumError, type ViewriumErrorOptions, type ViewriumOptions, type WebhookDeliveriesParams, type WebhookDeliveryList, type WebhookEndpoint, type WebhookEndpointCreateParams, type WebhookEndpointCreated, type WebhookEndpointList, type WebhookEndpointUpdateParams, createApiError, createViewrium };
3535
+ export { type ApiErrorPayload, type ApiKeyCreateParams, type ApiKeyCreated, type ApiKeyList, AuthenticationError, BadRequestError, type BeaconParams, ConflictError, ConnectionError, type FetchLike, type GateFlags, type Health, type ModelPlan, NotFoundError, PaymentRequiredError, PermissionError, type PlannedView, type Product, type ProductBatchResult, type ProductCost, type ProductCreateParams, type ProductCreateResult, type ProductCreated, type ProductExportParams, type ProductExportQueued, type ProductImage, type ProductImagePatchParams, type ProductList, type ProductListParams, type ProductModel, type ProductModelPatchParams, type ProductOrderParams, type ProductOrderParked, type ProductParked, type ProductPublishParams, type ProductRescaleParams, type ProductReviewParams, type ProductReviewResult, type ProductSummary, type ProductUpdateParams, type Project, type ProjectList, type PublicProduct, RateLimitError, ServerError, type Tags, type TagsParams, type Ticket, type TicketParams, UnprocessableEntityError, type Usage, type UsageParams, type UsageProductTotals, Viewrium, ViewriumError, type ViewriumErrorOptions, type ViewriumOptions, type WebhookDeliveriesParams, type WebhookDeliveryList, type WebhookEndpoint, type WebhookEndpointCreateParams, type WebhookEndpointCreated, type WebhookEndpointList, type WebhookEndpointUpdateParams, createApiError, createViewrium };
package/dist/index.js CHANGED
@@ -171,6 +171,14 @@ function createProducts(request, requestBlob) {
171
171
  * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.
172
172
  */
173
173
  rescale: (id, params) => request("POST", `/v1/products/${encode(id)}/model/rescale`, { body: params }),
174
+ /**
175
+ * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way
176
+ * to its bytes (no response carries a model file URL). It works once, until `expires_at`
177
+ * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`
178
+ * key the product must be published and the ticket is bound to the page's Origin; with an
179
+ * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).
180
+ */
181
+ createTicket: (id, params) => request("POST", `/v1/products/${encode(id)}/tickets`, { body: params }),
174
182
  /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the
175
183
  * returned `resume_token` with `create`. */
176
184
  order: (id, params) => request("POST", `/v1/products/${encode(id)}/order`, { body: params }),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["/**\n * Typed errors for the Viewrium SDK.\n *\n * The API returns one error envelope: `{ \"error\": { code, message, details } }`.\n * Non-2xx responses are parsed into a `ViewriumError` subclass keyed by HTTP status;\n * consumers branch with `instanceof`.\n */\n\nexport interface ViewriumErrorOptions {\n status?: number | null;\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class ViewriumError extends Error {\n /** HTTP status, or `null` for transport/connection failures. */\n readonly status: number | null;\n /** Machine-readable code from the error envelope. */\n readonly code: string;\n /** Optional structured details from the error envelope. */\n readonly details: unknown;\n\n constructor(message: string, options: ViewriumErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = new.target.name;\n this.status = options.status ?? null;\n this.code = options.code ?? \"error\";\n this.details = options.details;\n }\n}\n\n/** 400 - malformed request. */\nexport class BadRequestError extends ViewriumError {}\n/** 401 - missing or invalid API key. */\nexport class AuthenticationError extends ViewriumError {}\n/**\n * 402 - the organization's plan does not allow this: no plan, a used free trial, a failed payment,\n * or past the plan's active-product cap. `details.reason` says which.\n */\nexport class PaymentRequiredError extends ViewriumError {}\n/** 403 - the key is valid but not allowed (scope, origin, or role). */\nexport class PermissionError extends ViewriumError {}\n/** 404 - no such resource. */\nexport class NotFoundError extends ViewriumError {}\n/** 409 - conflict (e.g. duplicate `custom_id`, model not in a rescalable state). */\nexport class ConflictError extends ViewriumError {}\n/** 422 - request failed validation. */\nexport class UnprocessableEntityError extends ViewriumError {}\n/** 429 - rate limited, or not enough credits (`code: \"quota_exceeded\"`, `details.shortfall_credits`). */\nexport class RateLimitError extends ViewriumError {}\n/** 5xx - the API failed to handle the request. */\nexport class ServerError extends ViewriumError {}\n/** The request never reached the API (DNS/TLS/offline/abort). */\nexport class ConnectionError extends ViewriumError {}\n\nexport interface ApiErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nconst BY_STATUS: Record<number, new (message: string, options: ViewriumErrorOptions) => ViewriumError> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 402: PaymentRequiredError,\n 403: PermissionError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n};\n\n/** Build the right `ViewriumError` subclass for an HTTP status + parsed envelope. */\nexport function createApiError(status: number, payload: ApiErrorPayload): ViewriumError {\n const options: ViewriumErrorOptions = { status, code: payload.code, details: payload.details };\n const Specific = BY_STATUS[status];\n if (Specific) {\n return new Specific(payload.message, options);\n }\n if (status >= 500) {\n return new ServerError(payload.message, options);\n }\n return new ViewriumError(payload.message, options);\n}\n","/**\n * @viewrium/sdk - the official Viewrium JavaScript/TypeScript SDK.\n *\n * An ergonomic wrapper over the `/v1` REST API. Request/response types are derived from\n * `src/generated/schema.ts` - openapi-typescript output committed from the API's authoritative\n * `apps/api/openapi.json` (the same generator `@repo/api-client` uses; regenerate with\n * `pnpm run generate`). Owning the committed schema keeps the published `dist/index.d.ts`\n * self-contained: it cannot reference the unpublished, source-only `@repo/api-client`.\n *\n * Auth: pass an `sk_` secret key (server) or a `pk_` publishable key (browser). The key is sent\n * as `Authorization: Bearer <key>`; the API enforces what each key type may call.\n *\n * E4 (2026-09-09): the scan-era `models` resource is GONE with its routes; `products` is the\n * resource. Resource and method names match `@repo/api-client` so every client reads the same.\n */\nimport { ConnectionError, ViewriumError, createApiError } from \"./errors\";\nimport type { paths } from \"./generated/schema\";\n\nexport * from \"./errors\";\n\n// --- Derived request/response types (source of truth = apps/api/openapi.json) ----------------\n\ntype JsonContent<T> = T extends { content: { \"application/json\": infer B } } ? B : never;\n\n/** GET /v1/products/{id} resolves to one of two shapes by principal: the full product for a\n * session or `sk_` key, the allow-listed public projection for a `pk_` key. */\ntype ProductGetResult = JsonContent<paths[\"/v1/products/{id}\"][\"get\"][\"responses\"][200]>;\n/** A product with its assets (`sk_` view). */\nexport type Product = Extract<ProductGetResult, { org_id: string }>;\n/** The `pk_` view of a published product: id, title, model_url, thumbnail_url, model_status. */\nexport type PublicProduct = Exclude<ProductGetResult, { org_id: string }>;\nexport type ProductList = JsonContent<paths[\"/v1/products\"][\"get\"][\"responses\"][200]>;\n/** A catalog row - the product plus what its card needs, with no nested assets. */\nexport type ProductSummary = ProductList[\"data\"][number];\nexport type ProductListParams = NonNullable<paths[\"/v1/products\"][\"get\"][\"parameters\"][\"query\"]>;\nexport type ProductCreateParams = JsonContent<paths[\"/v1/products\"][\"post\"][\"requestBody\"]>;\n/** POST /v1/products 200 - the gate parked; resume by POSTing `resume_token`. */\nexport type ProductParked = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][200]>;\n/** One page position of a parked plan. Both park shapes carry the same row type. */\nexport type PlannedView = NonNullable<ProductParked[\"plan\"]>[number];\n/** describe's structured read of the product (D25), with its verbatim reasons. */\nexport type GateFlags = ProductParked[\"flags\"];\n/** The models layer's verdict: whether a model is allowed, what blocks it, and the views it\n * would be built from - including `unseen_views`, the invented ones. */\nexport type ModelPlan = ProductParked[\"model\"];\n/** The priced bundle, in credits (1 credit = $0.50): 1 per deliverable image, a flat 10 for a model. */\nexport type ProductCost = ProductParked[\"cost\"];\n/** POST /v1/products 202 - a committed product whose run has started. */\nexport type ProductCreated = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][202]>;\n/** POST /v1/products resolves to one of the two (discriminate on `status === \"parked\"`). */\nexport type ProductCreateResult = ProductParked | ProductCreated;\nexport type ProductUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductBatchResult = JsonContent<paths[\"/v1/products\"][\"delete\"][\"responses\"][200]>;\nexport type ProductPublishParams = JsonContent<\n paths[\"/v1/products/publish\"][\"post\"][\"requestBody\"]\n>;\nexport type Tags = JsonContent<paths[\"/v1/products/tags\"][\"get\"][\"responses\"][200]>;\nexport type TagsParams = NonNullable<paths[\"/v1/products/tags\"][\"get\"][\"parameters\"][\"query\"]>;\n/** POST /v1/products/{id}/review - the write-behind flush (image + model decisions, the\n * product's user-set status). It records decisions only. */\nexport type ProductReviewParams = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductReviewResult = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductImagePatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductImage = JsonContent<\n paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"responses\"][200]\n>;\nexport type ProductModelPatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/model\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductModel = JsonContent<paths[\"/v1/products/{id}/model\"][\"patch\"][\"responses\"][200]>;\nexport type ProductRescaleParams = JsonContent<\n paths[\"/v1/products/{id}/model/rescale\"][\"post\"][\"requestBody\"]\n>;\n/** POST /v1/products/{id}/order - the missing half; parks like a create, commits through\n * `products.create({ resume_token })`. */\nexport type ProductOrderParams = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductOrderParked = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductExportParams = NonNullable<\n paths[\"/v1/products/export\"][\"get\"][\"parameters\"][\"query\"]\n>;\n/** 202 from the export: the images are being finished in the background and a download link\n * is emailed; the same request streams the ZIP once they are done. */\nexport type ProductExportQueued = JsonContent<\n paths[\"/v1/products/export\"][\"get\"][\"responses\"][202]\n>;\n\nexport type ApiKeyList = JsonContent<paths[\"/v1/api_keys\"][\"get\"][\"responses\"][200]>;\nexport type ApiKeyCreateParams = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"requestBody\"]>;\nexport type ApiKeyCreated = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"responses\"][201]>;\n\nexport type Usage = JsonContent<paths[\"/v1/usage\"][\"get\"][\"responses\"][200]>;\nexport type UsageParams = NonNullable<paths[\"/v1/usage\"][\"get\"][\"parameters\"][\"query\"]>;\n/** One `group_by: \"product\"` row: a product's counts over the window, with its title. */\nexport type UsageProductTotals = NonNullable<Usage[\"by_product\"]>[number];\n\nexport type WebhookEndpoint = JsonContent<paths[\"/v1/webhooks/{id}\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointList = JsonContent<paths[\"/v1/webhooks\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointCreateParams = JsonContent<paths[\"/v1/webhooks\"][\"post\"][\"requestBody\"]>;\nexport type WebhookEndpointCreated = JsonContent<\n paths[\"/v1/webhooks\"][\"post\"][\"responses\"][201]\n>;\nexport type WebhookEndpointUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/webhooks/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type WebhookDeliveryList = JsonContent<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"responses\"][200]\n>;\nexport type WebhookDeliveriesParams = NonNullable<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"parameters\"][\"query\"]\n>;\n\nexport type Project = JsonContent<paths[\"/v1/projects/{id}\"][\"get\"][\"responses\"][200]>;\nexport type ProjectList = JsonContent<paths[\"/v1/projects\"][\"get\"][\"responses\"][200]>;\n\nexport type BeaconParams = JsonContent<paths[\"/v1/beacon\"][\"post\"][\"requestBody\"]>;\n\nexport type Health = JsonContent<paths[\"/v1/health\"][\"get\"][\"responses\"][200]>;\n\n// --- Client options + transport --------------------------------------------------------------\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface ViewriumOptions {\n /** `sk_` secret key (server) or `pk_` publishable key (browser). */\n apiKey: string;\n /** API origin; defaults to `https://api.viewrium.com`. Trailing slash is stripped. */\n baseUrl?: string;\n /** Injectable fetch (tests, custom agents). Defaults to `globalThis.fetch`. */\n fetch?: FetchLike;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.viewrium.com\";\n\ntype QueryValue = string | number | boolean | readonly string[] | null | undefined;\ntype QueryParams = Record<string, QueryValue>;\n\ninterface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\ntype RequestFn = <T>(method: string, path: string, options?: RequestOptions) => Promise<T>;\n/** The escape hatch for the one route that answers bytes rather than JSON (the export ZIP) -\n * or, on a 202, JSON after all. */\ntype BlobRequestFn = (\n method: string,\n path: string,\n options?: RequestOptions,\n) => Promise<Blob | ProductExportQueued>;\n\ninterface Transport {\n baseUrl: string;\n apiKey: string;\n fetch: FetchLike;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: QueryParams): string {\n const url = new URL(baseUrl + path);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const item of value as readonly string[]) url.searchParams.append(key, String(item));\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\nasync function parseError(res: Response): Promise<ViewriumError> {\n let code = \"error\";\n let message = res.statusText || `HTTP ${res.status}`;\n let details: unknown;\n try {\n const data: unknown = await res.json();\n if (data && typeof data === \"object\" && \"error\" in data) {\n const envelope = (data as { error: unknown }).error;\n if (envelope && typeof envelope === \"object\") {\n const e = envelope as Record<string, unknown>;\n if (typeof e.code === \"string\") code = e.code;\n if (typeof e.message === \"string\") message = e.message;\n details = e.details;\n }\n }\n } catch {\n // Non-JSON error body; fall back to status text.\n }\n return createApiError(res.status, { code, message, details });\n}\n\nasync function send(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Response> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n authorization: `Bearer ${transport.apiKey}`,\n };\n const init: RequestInit = { method, headers };\n if (options?.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(options.body);\n }\n\n let res: Response;\n try {\n res = await transport.fetch(buildUrl(transport.baseUrl, path, options?.query), init);\n } catch (cause) {\n throw new ConnectionError(\"The request to the Viewrium API could not be sent.\", { cause });\n }\n\n if (!res.ok) {\n throw await parseError(res);\n }\n return res;\n}\n\nasync function doRequest<T>(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<T> {\n const res = await send(transport, method, path, options);\n if (res.status === 204) {\n return undefined as T;\n }\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n}\n\nasync function doBlobRequest(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Blob | ProductExportQueued> {\n const res = await send(transport, method, path, options);\n if (res.status === 202) return (await res.json()) as ProductExportQueued;\n return res.blob();\n}\n\nconst encode = encodeURIComponent;\n\n// --- Resource namespaces ---------------------------------------------------------------------\n\nfunction createProducts(request: RequestFn, requestBlob: BlobRequestFn) {\n return {\n /**\n * POST /v1/products - the single generation entrypoint. Supply exactly one source\n * (`image_paths` | `image_urls` | `video_url` | `source_url` | `model_url` |\n * `resume_token`). A fresh request ALWAYS parks (200, `status === \"parked\"`, nothing\n * written): the plan, the model verdict and the price come back inside `resume_token`,\n * which you POST back to commit (202, `ProductCreated`). A `model_url` commits at once.\n */\n create: (params: ProductCreateParams) =>\n request<ProductCreateResult>(\"POST\", \"/v1/products\", { body: params }),\n /**\n * GET /v1/products - the catalog: one org (`org_id`, or the caller's only org), or\n * `scope: \"all\"` for every org an agency member or staff reaches. Filters AND together.\n */\n list: (params?: ProductListParams) =>\n request<ProductList>(\"GET\", \"/v1/products\", { query: params as QueryParams | undefined }),\n /** GET /v1/products/{id} - the product with its assets (a `pk_` key gets `PublicProduct`). */\n retrieve: (id: string) => request<Product>(\"GET\", `/v1/products/${encode(id)}`),\n /**\n * PATCH /v1/products/{id} - the customer's fields, the user-set status and the publish\n * toggle. 409 on a `custom_id` another product in the org already holds.\n */\n update: (id: string, params: ProductUpdateParams) =>\n request<Product>(\"PATCH\", `/v1/products/${encode(id)}`, { body: params }),\n /** DELETE /v1/products/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/products/${encode(id)}`),\n /** DELETE /v1/products?ids=... - per-id affected/not_found/forbidden buckets. */\n batchDelete: (ids: readonly string[]) =>\n request<ProductBatchResult>(\"DELETE\", \"/v1/products\", { query: { ids } }),\n /** POST /v1/products/publish - the bulk publish toggle; one product toggles through `update`. */\n publish: (params: ProductPublishParams) =>\n request<ProductBatchResult>(\"POST\", \"/v1/products/publish\", { body: params }),\n /** GET /v1/products/tags - distinct tags across the same scope the list takes. */\n tags: (params?: TagsParams) =>\n request<Tags>(\"GET\", \"/v1/products/tags\", { query: params as QueryParams | undefined }),\n /** POST /v1/products/{id}/review - the review flush: image and model decisions plus the\n * product's user-set status, validated together and applied in order. */\n review: (id: string, params: ProductReviewParams) =>\n request<ProductReviewResult>(\"POST\", `/v1/products/${encode(id)}/review`, { body: params }),\n /** PATCH /v1/products/{id}/images/{image_id} - one image's decision and overrides. */\n updateImage: (id: string, imageId: string, params: ProductImagePatchParams) =>\n request<ProductImage>(\"PATCH\", `/v1/products/${encode(id)}/images/${encode(imageId)}`, {\n body: params,\n }),\n /** PATCH /v1/products/{id}/model - the model's review decision. */\n updateModel: (id: string, params: ProductModelPatchParams) =>\n request<ProductModel>(\"PATCH\", `/v1/products/${encode(id)}/model`, { body: params }),\n /**\n * POST /v1/products/{id}/model/rescale - re-apply real-world scale to the GLB in place.\n * Every supplied axis is applied to the geometry; `unit` covers them (omitted = cm) and is\n * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.\n */\n rescale: (id: string, params: ProductRescaleParams) =>\n request<Product>(\"POST\", `/v1/products/${encode(id)}/model/rescale`, { body: params }),\n /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the\n * returned `resume_token` with `create`. */\n order: (id: string, params: ProductOrderParams) =>\n request<ProductOrderParked>(\"POST\", `/v1/products/${encode(id)}/order`, { body: params }),\n /** GET /v1/products/export - a ZIP of finished images (never the 3D model), resolved as a\n * `Blob`; or `ProductExportQueued` (202) when the images are being finished in the\n * background and a download link is emailed - repeat the request later to stream it. */\n export: (params?: ProductExportParams) =>\n requestBlob(\"GET\", \"/v1/products/export\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createApiKeys(request: RequestFn) {\n return {\n /** GET /v1/api_keys */\n list: () => request<ApiKeyList>(\"GET\", \"/v1/api_keys\"),\n /** POST /v1/api_keys - the full key is returned exactly once. */\n create: (params: ApiKeyCreateParams) =>\n request<ApiKeyCreated>(\"POST\", \"/v1/api_keys\", { body: params }),\n /** DELETE /v1/api_keys/{id} - soft revoke. */\n revoke: (id: string) => request<void>(\"DELETE\", `/v1/api_keys/${encode(id)}`),\n };\n}\n\nfunction createUsage(request: RequestFn) {\n return {\n /** GET /v1/usage - buckets and totals over the window; `group_by: \"product\"` adds the\n * window's best-performing products in `by_product`. */\n get: (params?: UsageParams) =>\n request<Usage>(\"GET\", \"/v1/usage\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createWebhooks(request: RequestFn) {\n return {\n /** GET /v1/webhooks */\n list: () => request<WebhookEndpointList>(\"GET\", \"/v1/webhooks\"),\n /** POST /v1/webhooks - includes the signing secret. */\n create: (params: WebhookEndpointCreateParams) =>\n request<WebhookEndpointCreated>(\"POST\", \"/v1/webhooks\", { body: params }),\n /** GET /v1/webhooks/{id} */\n retrieve: (id: string) => request<WebhookEndpoint>(\"GET\", `/v1/webhooks/${encode(id)}`),\n /** PATCH /v1/webhooks/{id} */\n update: (id: string, params: WebhookEndpointUpdateParams) =>\n request<WebhookEndpoint>(\"PATCH\", `/v1/webhooks/${encode(id)}`, { body: params }),\n /** DELETE /v1/webhooks/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/webhooks/${encode(id)}`),\n /** GET /v1/webhooks/{id}/deliveries */\n listDeliveries: (id: string, params?: WebhookDeliveriesParams) =>\n request<WebhookDeliveryList>(\"GET\", `/v1/webhooks/${encode(id)}/deliveries`, {\n query: params as QueryParams | undefined,\n }),\n };\n}\n\nfunction createProjects(request: RequestFn) {\n return {\n /** GET /v1/projects */\n list: () => request<ProjectList>(\"GET\", \"/v1/projects\"),\n /** GET /v1/projects/{id} */\n retrieve: (id: string) => request<Project>(\"GET\", `/v1/projects/${encode(id)}`),\n };\n}\n\nfunction createBeacon(request: RequestFn) {\n return {\n /** POST /v1/beacon - ingest a view/AR analytics event (204). */\n send: (params: BeaconParams) => request<void>(\"POST\", \"/v1/beacon\", { body: params }),\n };\n}\n\n// --- Client ----------------------------------------------------------------------------------\n\nexport class Viewrium {\n readonly baseUrl: string;\n readonly products: ReturnType<typeof createProducts>;\n readonly apiKeys: ReturnType<typeof createApiKeys>;\n readonly usage: ReturnType<typeof createUsage>;\n readonly webhooks: ReturnType<typeof createWebhooks>;\n readonly projects: ReturnType<typeof createProjects>;\n readonly beacon: ReturnType<typeof createBeacon>;\n\n readonly #request: RequestFn;\n\n constructor(options: ViewriumOptions) {\n if (!options || !options.apiKey) {\n throw new ViewriumError(\"A Viewrium client requires an `apiKey` (sk_ or pk_).\");\n }\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n\n const provided = options.fetch;\n const globalFetch = typeof globalThis.fetch === \"function\" ? globalThis.fetch.bind(globalThis) : undefined;\n const fetchImpl = provided ?? globalFetch;\n if (!fetchImpl) {\n throw new ViewriumError(\"No fetch implementation available; pass `options.fetch`.\");\n }\n\n const transport: Transport = { baseUrl: this.baseUrl, apiKey: options.apiKey, fetch: fetchImpl };\n const request: RequestFn = <T>(method: string, path: string, opts?: RequestOptions) =>\n doRequest<T>(transport, method, path, opts);\n const requestBlob: BlobRequestFn = (method, path, opts) =>\n doBlobRequest(transport, method, path, opts);\n this.#request = request;\n\n this.products = createProducts(request, requestBlob);\n this.apiKeys = createApiKeys(request);\n this.usage = createUsage(request);\n this.webhooks = createWebhooks(request);\n this.projects = createProjects(request);\n this.beacon = createBeacon(request);\n }\n\n /** GET /v1/health */\n health(): Promise<Health> {\n return this.#request<Health>(\"GET\", \"/v1/health\");\n }\n}\n\n/** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */\nexport function createViewrium(options: ViewriumOptions): Viewrium {\n return new Viewrium(options);\n}\n"],"mappings":";AAeO,IAAM,gBAAN,cAA4B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,SAAiB,UAAgC,CAAC,GAAG;AAC/D,UAAM,SAAS,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,MAAS;AACjF,SAAK,OAAO,WAAW;AACvB,SAAK,SAAS,QAAQ,UAAU;AAChC,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,UAAU,QAAQ;AAAA,EACzB;AACF;AAGO,IAAM,kBAAN,cAA8B,cAAc;AAAC;AAE7C,IAAM,sBAAN,cAAkC,cAAc;AAAC;AAKjD,IAAM,uBAAN,cAAmC,cAAc;AAAC;AAElD,IAAM,kBAAN,cAA8B,cAAc;AAAC;AAE7C,IAAM,gBAAN,cAA4B,cAAc;AAAC;AAE3C,IAAM,gBAAN,cAA4B,cAAc;AAAC;AAE3C,IAAM,2BAAN,cAAuC,cAAc;AAAC;AAEtD,IAAM,iBAAN,cAA6B,cAAc;AAAC;AAE5C,IAAM,cAAN,cAA0B,cAAc;AAAC;AAEzC,IAAM,kBAAN,cAA8B,cAAc;AAAC;AAQpD,IAAM,YAAmG;AAAA,EACvG,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGO,SAAS,eAAe,QAAgB,SAAyC;AACtF,QAAM,UAAgC,EAAE,QAAQ,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ;AAC7F,QAAM,WAAW,UAAU,MAAM;AACjC,MAAI,UAAU;AACZ,WAAO,IAAI,SAAS,QAAQ,SAAS,OAAO;AAAA,EAC9C;AACA,MAAI,UAAU,KAAK;AACjB,WAAO,IAAI,YAAY,QAAQ,SAAS,OAAO;AAAA,EACjD;AACA,SAAO,IAAI,cAAc,QAAQ,SAAS,OAAO;AACnD;;;AC2DA,IAAM,mBAAmB;AAyBzB,SAAS,SAAS,SAAiB,MAAc,OAA6B;AAC5E,QAAM,MAAM,IAAI,IAAI,UAAU,IAAI;AAClC,MAAI,OAAO;AACT,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,UAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,mBAAW,QAAQ,MAA4B,KAAI,aAAa,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,MAC1F,OAAO;AACL,YAAI,aAAa,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,SAAS;AACtB;AAEA,eAAe,WAAW,KAAuC;AAC/D,MAAI,OAAO;AACX,MAAI,UAAU,IAAI,cAAc,QAAQ,IAAI,MAAM;AAClD,MAAI;AACJ,MAAI;AACF,UAAM,OAAgB,MAAM,IAAI,KAAK;AACrC,QAAI,QAAQ,OAAO,SAAS,YAAY,WAAW,MAAM;AACvD,YAAM,WAAY,KAA4B;AAC9C,UAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,cAAM,IAAI;AACV,YAAI,OAAO,EAAE,SAAS,SAAU,QAAO,EAAE;AACzC,YAAI,OAAO,EAAE,YAAY,SAAU,WAAU,EAAE;AAC/C,kBAAU,EAAE;AAAA,MACd;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,eAAe,IAAI,QAAQ,EAAE,MAAM,SAAS,QAAQ,CAAC;AAC9D;AAEA,eAAe,KACb,WACA,QACA,MACA,SACmB;AACnB,QAAM,UAAkC;AAAA,IACtC,QAAQ;AAAA,IACR,eAAe,UAAU,UAAU,MAAM;AAAA,EAC3C;AACA,QAAM,OAAoB,EAAE,QAAQ,QAAQ;AAC5C,MAAI,SAAS,SAAS,QAAW;AAC/B,YAAQ,cAAc,IAAI;AAC1B,SAAK,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA,EACzC;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,UAAU,MAAM,SAAS,UAAU,SAAS,MAAM,SAAS,KAAK,GAAG,IAAI;AAAA,EACrF,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,sDAAsD,EAAE,MAAM,CAAC;AAAA,EAC3F;AAEA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAM,WAAW,GAAG;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,eAAe,UACb,WACA,QACA,MACA,SACY;AACZ,QAAM,MAAM,MAAM,KAAK,WAAW,QAAQ,MAAM,OAAO;AACvD,MAAI,IAAI,WAAW,KAAK;AACtB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,SAAQ,OAAO,KAAK,MAAM,IAAI,IAAI;AACpC;AAEA,eAAe,cACb,WACA,QACA,MACA,SACqC;AACrC,QAAM,MAAM,MAAM,KAAK,WAAW,QAAQ,MAAM,OAAO;AACvD,MAAI,IAAI,WAAW,IAAK,QAAQ,MAAM,IAAI,KAAK;AAC/C,SAAO,IAAI,KAAK;AAClB;AAEA,IAAM,SAAS;AAIf,SAAS,eAAe,SAAoB,aAA4B;AACtE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQL,QAAQ,CAAC,WACP,QAA6B,QAAQ,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKvE,MAAM,CAAC,WACL,QAAqB,OAAO,gBAAgB,EAAE,OAAO,OAAkC,CAAC;AAAA;AAAA,IAE1F,UAAU,CAAC,OAAe,QAAiB,OAAO,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,IAK9E,QAAQ,CAAC,IAAY,WACnB,QAAiB,SAAS,gBAAgB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAE1E,QAAQ,CAAC,OAAe,QAAc,UAAU,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA;AAAA,IAE5E,aAAa,CAAC,QACZ,QAA4B,UAAU,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA;AAAA,IAE1E,SAAS,CAAC,WACR,QAA4B,QAAQ,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAE9E,MAAM,CAAC,WACL,QAAc,OAAO,qBAAqB,EAAE,OAAO,OAAkC,CAAC;AAAA;AAAA;AAAA,IAGxF,QAAQ,CAAC,IAAY,WACnB,QAA6B,QAAQ,gBAAgB,OAAO,EAAE,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAE5F,aAAa,CAAC,IAAY,SAAiB,WACzC,QAAsB,SAAS,gBAAgB,OAAO,EAAE,CAAC,WAAW,OAAO,OAAO,CAAC,IAAI;AAAA,MACrF,MAAM;AAAA,IACR,CAAC;AAAA;AAAA,IAEH,aAAa,CAAC,IAAY,WACxB,QAAsB,SAAS,gBAAgB,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMrF,SAAS,CAAC,IAAY,WACpB,QAAiB,QAAQ,gBAAgB,OAAO,EAAE,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA;AAAA,IAGvF,OAAO,CAAC,IAAY,WAClB,QAA4B,QAAQ,gBAAgB,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA,IAI1F,QAAQ,CAAC,WACP,YAAY,OAAO,uBAAuB,EAAE,OAAO,OAAkC,CAAC;AAAA,EAC1F;AACF;AAEA,SAAS,cAAc,SAAoB;AACzC,SAAO;AAAA;AAAA,IAEL,MAAM,MAAM,QAAoB,OAAO,cAAc;AAAA;AAAA,IAErD,QAAQ,CAAC,WACP,QAAuB,QAAQ,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAEjE,QAAQ,CAAC,OAAe,QAAc,UAAU,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA,EAC9E;AACF;AAEA,SAAS,YAAY,SAAoB;AACvC,SAAO;AAAA;AAAA;AAAA,IAGL,KAAK,CAAC,WACJ,QAAe,OAAO,aAAa,EAAE,OAAO,OAAkC,CAAC;AAAA,EACnF;AACF;AAEA,SAAS,eAAe,SAAoB;AAC1C,SAAO;AAAA;AAAA,IAEL,MAAM,MAAM,QAA6B,OAAO,cAAc;AAAA;AAAA,IAE9D,QAAQ,CAAC,WACP,QAAgC,QAAQ,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAE1E,UAAU,CAAC,OAAe,QAAyB,OAAO,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA;AAAA,IAEtF,QAAQ,CAAC,IAAY,WACnB,QAAyB,SAAS,gBAAgB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;AAAA;AAAA,IAElF,QAAQ,CAAC,OAAe,QAAc,UAAU,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA;AAAA,IAE5E,gBAAgB,CAAC,IAAY,WAC3B,QAA6B,OAAO,gBAAgB,OAAO,EAAE,CAAC,eAAe;AAAA,MAC3E,OAAO;AAAA,IACT,CAAC;AAAA,EACL;AACF;AAEA,SAAS,eAAe,SAAoB;AAC1C,SAAO;AAAA;AAAA,IAEL,MAAM,MAAM,QAAqB,OAAO,cAAc;AAAA;AAAA,IAEtD,UAAU,CAAC,OAAe,QAAiB,OAAO,gBAAgB,OAAO,EAAE,CAAC,EAAE;AAAA,EAChF;AACF;AAEA,SAAS,aAAa,SAAoB;AACxC,SAAO;AAAA;AAAA,IAEL,MAAM,CAAC,WAAyB,QAAc,QAAQ,cAAc,EAAE,MAAM,OAAO,CAAC;AAAA,EACtF;AACF;AAIO,IAAM,WAAN,MAAe;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAET,YAAY,SAA0B;AACpC,QAAI,CAAC,WAAW,CAAC,QAAQ,QAAQ;AAC/B,YAAM,IAAI,cAAc,sDAAsD;AAAA,IAChF;AACA,SAAK,WAAW,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AAEvE,UAAM,WAAW,QAAQ;AACzB,UAAM,cAAc,OAAO,WAAW,UAAU,aAAa,WAAW,MAAM,KAAK,UAAU,IAAI;AACjG,UAAM,YAAY,YAAY;AAC9B,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,cAAc,0DAA0D;AAAA,IACpF;AAEA,UAAM,YAAuB,EAAE,SAAS,KAAK,SAAS,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAC/F,UAAM,UAAqB,CAAI,QAAgB,MAAc,SAC3D,UAAa,WAAW,QAAQ,MAAM,IAAI;AAC5C,UAAM,cAA6B,CAAC,QAAQ,MAAM,SAChD,cAAc,WAAW,QAAQ,MAAM,IAAI;AAC7C,SAAK,WAAW;AAEhB,SAAK,WAAW,eAAe,SAAS,WAAW;AACnD,SAAK,UAAU,cAAc,OAAO;AACpC,SAAK,QAAQ,YAAY,OAAO;AAChC,SAAK,WAAW,eAAe,OAAO;AACtC,SAAK,WAAW,eAAe,OAAO;AACtC,SAAK,SAAS,aAAa,OAAO;AAAA,EACpC;AAAA;AAAA,EAGA,SAA0B;AACxB,WAAO,KAAK,SAAiB,OAAO,YAAY;AAAA,EAClD;AACF;AAGO,SAAS,eAAe,SAAoC;AACjE,SAAO,IAAI,SAAS,OAAO;AAC7B;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["/**\n * Typed errors for the Viewrium SDK.\n *\n * The API returns one error envelope: `{ \"error\": { code, message, details } }`.\n * Non-2xx responses are parsed into a `ViewriumError` subclass keyed by HTTP status;\n * consumers branch with `instanceof`.\n */\n\nexport interface ViewriumErrorOptions {\n status?: number | null;\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class ViewriumError extends Error {\n /** HTTP status, or `null` for transport/connection failures. */\n readonly status: number | null;\n /** Machine-readable code from the error envelope. */\n readonly code: string;\n /** Optional structured details from the error envelope. */\n readonly details: unknown;\n\n constructor(message: string, options: ViewriumErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = new.target.name;\n this.status = options.status ?? null;\n this.code = options.code ?? \"error\";\n this.details = options.details;\n }\n}\n\n/** 400 - malformed request. */\nexport class BadRequestError extends ViewriumError {}\n/** 401 - missing or invalid API key. */\nexport class AuthenticationError extends ViewriumError {}\n/**\n * 402 - the organization's plan does not allow this: no plan, a used free trial, a failed payment,\n * or past the plan's active-product cap. `details.reason` says which.\n */\nexport class PaymentRequiredError extends ViewriumError {}\n/** 403 - the key is valid but not allowed (scope, origin, or role). */\nexport class PermissionError extends ViewriumError {}\n/** 404 - no such resource. */\nexport class NotFoundError extends ViewriumError {}\n/** 409 - conflict (e.g. duplicate `custom_id`, model not in a rescalable state). */\nexport class ConflictError extends ViewriumError {}\n/** 422 - request failed validation. */\nexport class UnprocessableEntityError extends ViewriumError {}\n/** 429 - rate limited, or not enough credits (`code: \"quota_exceeded\"`, `details.shortfall_credits`). */\nexport class RateLimitError extends ViewriumError {}\n/** 5xx - the API failed to handle the request. */\nexport class ServerError extends ViewriumError {}\n/** The request never reached the API (DNS/TLS/offline/abort). */\nexport class ConnectionError extends ViewriumError {}\n\nexport interface ApiErrorPayload {\n code: string;\n message: string;\n details?: unknown;\n}\n\nconst BY_STATUS: Record<number, new (message: string, options: ViewriumErrorOptions) => ViewriumError> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 402: PaymentRequiredError,\n 403: PermissionError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n};\n\n/** Build the right `ViewriumError` subclass for an HTTP status + parsed envelope. */\nexport function createApiError(status: number, payload: ApiErrorPayload): ViewriumError {\n const options: ViewriumErrorOptions = { status, code: payload.code, details: payload.details };\n const Specific = BY_STATUS[status];\n if (Specific) {\n return new Specific(payload.message, options);\n }\n if (status >= 500) {\n return new ServerError(payload.message, options);\n }\n return new ViewriumError(payload.message, options);\n}\n","/**\n * @viewrium/sdk - the official Viewrium JavaScript/TypeScript SDK.\n *\n * An ergonomic wrapper over the `/v1` REST API. Request/response types are derived from\n * `src/generated/schema.ts` - openapi-typescript output committed from the API's authoritative\n * `apps/api/openapi.json` (the same generator `@repo/api-client` uses; regenerate with\n * `pnpm run generate`). Owning the committed schema keeps the published `dist/index.d.ts`\n * self-contained: it cannot reference the unpublished, source-only `@repo/api-client`.\n *\n * Auth: pass an `sk_` secret key (server) or a `pk_` publishable key (browser). The key is sent\n * as `Authorization: Bearer <key>`; the API enforces what each key type may call.\n *\n * E4 (2026-09-09): the scan-era `models` resource is GONE with its routes; `products` is the\n * resource. Resource and method names match `@repo/api-client` so every client reads the same.\n */\nimport { ConnectionError, ViewriumError, createApiError } from \"./errors\";\nimport type { paths } from \"./generated/schema\";\n\nexport * from \"./errors\";\n\n// --- Derived request/response types (source of truth = apps/api/openapi.json) ----------------\n\ntype JsonContent<T> = T extends { content: { \"application/json\": infer B } } ? B : never;\n\n/** GET /v1/products/{id} resolves to one of two shapes by principal: the full product for a\n * session or `sk_` key, the allow-listed public projection for a `pk_` key. */\ntype ProductGetResult = JsonContent<paths[\"/v1/products/{id}\"][\"get\"][\"responses\"][200]>;\n/** A product with its assets (`sk_` view). */\nexport type Product = Extract<ProductGetResult, { org_id: string }>;\n/** The `pk_` view of a published product: id, title, thumbnail_url, model_status, wall_mounted. The model\n * file itself is reached through `products.createTicket`. */\nexport type PublicProduct = Exclude<ProductGetResult, { org_id: string }>;\nexport type ProductList = JsonContent<paths[\"/v1/products\"][\"get\"][\"responses\"][200]>;\n/** A catalog row - the product plus what its card needs, with no nested assets. */\nexport type ProductSummary = ProductList[\"data\"][number];\nexport type ProductListParams = NonNullable<paths[\"/v1/products\"][\"get\"][\"parameters\"][\"query\"]>;\nexport type ProductCreateParams = JsonContent<paths[\"/v1/products\"][\"post\"][\"requestBody\"]>;\n/** POST /v1/products 200 - the gate parked; resume by POSTing `resume_token`. */\nexport type ProductParked = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][200]>;\n/** One page position of a parked plan. Both park shapes carry the same row type. */\nexport type PlannedView = NonNullable<ProductParked[\"plan\"]>[number];\n/** describe's structured read of the product (D25), with its verbatim reasons. */\nexport type GateFlags = ProductParked[\"flags\"];\n/** The models layer's verdict: whether a model is allowed, what blocks it, and the views it\n * would be built from - including `unseen_views`, the invented ones. */\nexport type ModelPlan = ProductParked[\"model\"];\n/** The priced bundle, in credits (1 credit = $0.50): 1 per deliverable image, a flat 10 for a model. */\nexport type ProductCost = ProductParked[\"cost\"];\n/** POST /v1/products 202 - a committed product whose run has started. */\nexport type ProductCreated = JsonContent<paths[\"/v1/products\"][\"post\"][\"responses\"][202]>;\n/** POST /v1/products resolves to one of the two (discriminate on `status === \"parked\"`). */\nexport type ProductCreateResult = ProductParked | ProductCreated;\nexport type ProductUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductBatchResult = JsonContent<paths[\"/v1/products\"][\"delete\"][\"responses\"][200]>;\nexport type ProductPublishParams = JsonContent<\n paths[\"/v1/products/publish\"][\"post\"][\"requestBody\"]\n>;\nexport type Tags = JsonContent<paths[\"/v1/products/tags\"][\"get\"][\"responses\"][200]>;\nexport type TagsParams = NonNullable<paths[\"/v1/products/tags\"][\"get\"][\"parameters\"][\"query\"]>;\n/** POST /v1/products/{id}/review - the write-behind flush (image + model decisions, the\n * product's user-set status). It records decisions only. */\nexport type ProductReviewParams = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductReviewResult = JsonContent<\n paths[\"/v1/products/{id}/review\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductImagePatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductImage = JsonContent<\n paths[\"/v1/products/{id}/images/{image_id}\"][\"patch\"][\"responses\"][200]\n>;\nexport type ProductModelPatchParams = JsonContent<\n NonNullable<paths[\"/v1/products/{id}/model\"][\"patch\"][\"requestBody\"]>\n>;\nexport type ProductModel = JsonContent<paths[\"/v1/products/{id}/model\"][\"patch\"][\"responses\"][200]>;\nexport type ProductRescaleParams = JsonContent<\n paths[\"/v1/products/{id}/model/rescale\"][\"post\"][\"requestBody\"]\n>;\n/** POST /v1/products/{id}/tickets - `{ kind: \"view\" | \"ar\", origin? }`. */\nexport type TicketParams = JsonContent<paths[\"/v1/products/{id}/tickets\"][\"post\"][\"requestBody\"]>;\n/** A single-use model URL: `{ url, kind, expires_at }`. */\nexport type Ticket = JsonContent<paths[\"/v1/products/{id}/tickets\"][\"post\"][\"responses\"][200]>;\n/** POST /v1/products/{id}/order - the missing half; parks like a create, commits through\n * `products.create({ resume_token })`. */\nexport type ProductOrderParams = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"requestBody\"]\n>;\nexport type ProductOrderParked = JsonContent<\n paths[\"/v1/products/{id}/order\"][\"post\"][\"responses\"][200]\n>;\nexport type ProductExportParams = NonNullable<\n paths[\"/v1/products/export\"][\"get\"][\"parameters\"][\"query\"]\n>;\n/** 202 from the export: the images are being finished in the background and a download link\n * is emailed; the same request streams the ZIP once they are done. */\nexport type ProductExportQueued = JsonContent<\n paths[\"/v1/products/export\"][\"get\"][\"responses\"][202]\n>;\n\nexport type ApiKeyList = JsonContent<paths[\"/v1/api_keys\"][\"get\"][\"responses\"][200]>;\nexport type ApiKeyCreateParams = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"requestBody\"]>;\nexport type ApiKeyCreated = JsonContent<paths[\"/v1/api_keys\"][\"post\"][\"responses\"][201]>;\n\nexport type Usage = JsonContent<paths[\"/v1/usage\"][\"get\"][\"responses\"][200]>;\nexport type UsageParams = NonNullable<paths[\"/v1/usage\"][\"get\"][\"parameters\"][\"query\"]>;\n/** One `group_by: \"product\"` row: a product's counts over the window, with its title. */\nexport type UsageProductTotals = NonNullable<Usage[\"by_product\"]>[number];\n\nexport type WebhookEndpoint = JsonContent<paths[\"/v1/webhooks/{id}\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointList = JsonContent<paths[\"/v1/webhooks\"][\"get\"][\"responses\"][200]>;\nexport type WebhookEndpointCreateParams = JsonContent<paths[\"/v1/webhooks\"][\"post\"][\"requestBody\"]>;\nexport type WebhookEndpointCreated = JsonContent<\n paths[\"/v1/webhooks\"][\"post\"][\"responses\"][201]\n>;\nexport type WebhookEndpointUpdateParams = JsonContent<\n NonNullable<paths[\"/v1/webhooks/{id}\"][\"patch\"][\"requestBody\"]>\n>;\nexport type WebhookDeliveryList = JsonContent<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"responses\"][200]\n>;\nexport type WebhookDeliveriesParams = NonNullable<\n paths[\"/v1/webhooks/{id}/deliveries\"][\"get\"][\"parameters\"][\"query\"]\n>;\n\nexport type Project = JsonContent<paths[\"/v1/projects/{id}\"][\"get\"][\"responses\"][200]>;\nexport type ProjectList = JsonContent<paths[\"/v1/projects\"][\"get\"][\"responses\"][200]>;\n\nexport type BeaconParams = JsonContent<paths[\"/v1/beacon\"][\"post\"][\"requestBody\"]>;\n\nexport type Health = JsonContent<paths[\"/v1/health\"][\"get\"][\"responses\"][200]>;\n\n// --- Client options + transport --------------------------------------------------------------\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface ViewriumOptions {\n /** `sk_` secret key (server) or `pk_` publishable key (browser). */\n apiKey: string;\n /** API origin; defaults to `https://api.viewrium.com`. Trailing slash is stripped. */\n baseUrl?: string;\n /** Injectable fetch (tests, custom agents). Defaults to `globalThis.fetch`. */\n fetch?: FetchLike;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.viewrium.com\";\n\ntype QueryValue = string | number | boolean | readonly string[] | null | undefined;\ntype QueryParams = Record<string, QueryValue>;\n\ninterface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\ntype RequestFn = <T>(method: string, path: string, options?: RequestOptions) => Promise<T>;\n/** The escape hatch for the one route that answers bytes rather than JSON (the export ZIP) -\n * or, on a 202, JSON after all. */\ntype BlobRequestFn = (\n method: string,\n path: string,\n options?: RequestOptions,\n) => Promise<Blob | ProductExportQueued>;\n\ninterface Transport {\n baseUrl: string;\n apiKey: string;\n fetch: FetchLike;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: QueryParams): string {\n const url = new URL(baseUrl + path);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const item of value as readonly string[]) url.searchParams.append(key, String(item));\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\nasync function parseError(res: Response): Promise<ViewriumError> {\n let code = \"error\";\n let message = res.statusText || `HTTP ${res.status}`;\n let details: unknown;\n try {\n const data: unknown = await res.json();\n if (data && typeof data === \"object\" && \"error\" in data) {\n const envelope = (data as { error: unknown }).error;\n if (envelope && typeof envelope === \"object\") {\n const e = envelope as Record<string, unknown>;\n if (typeof e.code === \"string\") code = e.code;\n if (typeof e.message === \"string\") message = e.message;\n details = e.details;\n }\n }\n } catch {\n // Non-JSON error body; fall back to status text.\n }\n return createApiError(res.status, { code, message, details });\n}\n\nasync function send(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Response> {\n const headers: Record<string, string> = {\n accept: \"application/json\",\n authorization: `Bearer ${transport.apiKey}`,\n };\n const init: RequestInit = { method, headers };\n if (options?.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(options.body);\n }\n\n let res: Response;\n try {\n res = await transport.fetch(buildUrl(transport.baseUrl, path, options?.query), init);\n } catch (cause) {\n throw new ConnectionError(\"The request to the Viewrium API could not be sent.\", { cause });\n }\n\n if (!res.ok) {\n throw await parseError(res);\n }\n return res;\n}\n\nasync function doRequest<T>(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<T> {\n const res = await send(transport, method, path, options);\n if (res.status === 204) {\n return undefined as T;\n }\n const text = await res.text();\n return (text ? JSON.parse(text) : undefined) as T;\n}\n\nasync function doBlobRequest(\n transport: Transport,\n method: string,\n path: string,\n options?: RequestOptions,\n): Promise<Blob | ProductExportQueued> {\n const res = await send(transport, method, path, options);\n if (res.status === 202) return (await res.json()) as ProductExportQueued;\n return res.blob();\n}\n\nconst encode = encodeURIComponent;\n\n// --- Resource namespaces ---------------------------------------------------------------------\n\nfunction createProducts(request: RequestFn, requestBlob: BlobRequestFn) {\n return {\n /**\n * POST /v1/products - the single generation entrypoint. Supply exactly one source\n * (`image_paths` | `image_urls` | `video_url` | `source_url` | `model_url` |\n * `resume_token`). A fresh request ALWAYS parks (200, `status === \"parked\"`, nothing\n * written): the plan, the model verdict and the price come back inside `resume_token`,\n * which you POST back to commit (202, `ProductCreated`). A `model_url` commits at once.\n */\n create: (params: ProductCreateParams) =>\n request<ProductCreateResult>(\"POST\", \"/v1/products\", { body: params }),\n /**\n * GET /v1/products - the catalog: one org (`org_id`, or the caller's only org), or\n * `scope: \"all\"` for every org an agency member or staff reaches. Filters AND together.\n */\n list: (params?: ProductListParams) =>\n request<ProductList>(\"GET\", \"/v1/products\", { query: params as QueryParams | undefined }),\n /** GET /v1/products/{id} - the product with its assets (a `pk_` key gets `PublicProduct`). */\n retrieve: (id: string) => request<Product>(\"GET\", `/v1/products/${encode(id)}`),\n /**\n * PATCH /v1/products/{id} - the customer's fields, the user-set status and the publish\n * toggle. 409 on a `custom_id` another product in the org already holds.\n */\n update: (id: string, params: ProductUpdateParams) =>\n request<Product>(\"PATCH\", `/v1/products/${encode(id)}`, { body: params }),\n /** DELETE /v1/products/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/products/${encode(id)}`),\n /** DELETE /v1/products?ids=... - per-id affected/not_found/forbidden buckets. */\n batchDelete: (ids: readonly string[]) =>\n request<ProductBatchResult>(\"DELETE\", \"/v1/products\", { query: { ids } }),\n /** POST /v1/products/publish - the bulk publish toggle; one product toggles through `update`. */\n publish: (params: ProductPublishParams) =>\n request<ProductBatchResult>(\"POST\", \"/v1/products/publish\", { body: params }),\n /** GET /v1/products/tags - distinct tags across the same scope the list takes. */\n tags: (params?: TagsParams) =>\n request<Tags>(\"GET\", \"/v1/products/tags\", { query: params as QueryParams | undefined }),\n /** POST /v1/products/{id}/review - the review flush: image and model decisions plus the\n * product's user-set status, validated together and applied in order. */\n review: (id: string, params: ProductReviewParams) =>\n request<ProductReviewResult>(\"POST\", `/v1/products/${encode(id)}/review`, { body: params }),\n /** PATCH /v1/products/{id}/images/{image_id} - one image's decision and overrides. */\n updateImage: (id: string, imageId: string, params: ProductImagePatchParams) =>\n request<ProductImage>(\"PATCH\", `/v1/products/${encode(id)}/images/${encode(imageId)}`, {\n body: params,\n }),\n /** PATCH /v1/products/{id}/model - the model's review decision. */\n updateModel: (id: string, params: ProductModelPatchParams) =>\n request<ProductModel>(\"PATCH\", `/v1/products/${encode(id)}/model`, { body: params }),\n /**\n * POST /v1/products/{id}/model/rescale - re-apply real-world scale to the GLB in place.\n * Every supplied axis is applied to the geometry; `unit` covers them (omitted = cm) and is\n * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.\n */\n rescale: (id: string, params: ProductRescaleParams) =>\n request<Product>(\"POST\", `/v1/products/${encode(id)}/model/rescale`, { body: params }),\n /**\n * POST /v1/products/{id}/tickets - a single-use URL for the product's 3D file, the ONLY way\n * to its bytes (no response carries a model file URL). It works once, until `expires_at`\n * (60 s for `view`, 30 s for `ar`); a `view` ticket only from its bound origin. With a `pk_`\n * key the product must be published and the ticket is bound to the page's Origin; with an\n * `sk_` key pass `origin` (the page that will load the model, one of your allowed domains).\n */\n createTicket: (id: string, params: TicketParams) =>\n request<Ticket>(\"POST\", `/v1/products/${encode(id)}/tickets`, { body: params }),\n /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the\n * returned `resume_token` with `create`. */\n order: (id: string, params: ProductOrderParams) =>\n request<ProductOrderParked>(\"POST\", `/v1/products/${encode(id)}/order`, { body: params }),\n /** GET /v1/products/export - a ZIP of finished images (never the 3D model), resolved as a\n * `Blob`; or `ProductExportQueued` (202) when the images are being finished in the\n * background and a download link is emailed - repeat the request later to stream it. */\n export: (params?: ProductExportParams) =>\n requestBlob(\"GET\", \"/v1/products/export\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createApiKeys(request: RequestFn) {\n return {\n /** GET /v1/api_keys */\n list: () => request<ApiKeyList>(\"GET\", \"/v1/api_keys\"),\n /** POST /v1/api_keys - the full key is returned exactly once. */\n create: (params: ApiKeyCreateParams) =>\n request<ApiKeyCreated>(\"POST\", \"/v1/api_keys\", { body: params }),\n /** DELETE /v1/api_keys/{id} - soft revoke. */\n revoke: (id: string) => request<void>(\"DELETE\", `/v1/api_keys/${encode(id)}`),\n };\n}\n\nfunction createUsage(request: RequestFn) {\n return {\n /** GET /v1/usage - buckets and totals over the window; `group_by: \"product\"` adds the\n * window's best-performing products in `by_product`. */\n get: (params?: UsageParams) =>\n request<Usage>(\"GET\", \"/v1/usage\", { query: params as QueryParams | undefined }),\n };\n}\n\nfunction createWebhooks(request: RequestFn) {\n return {\n /** GET /v1/webhooks */\n list: () => request<WebhookEndpointList>(\"GET\", \"/v1/webhooks\"),\n /** POST /v1/webhooks - includes the signing secret. */\n create: (params: WebhookEndpointCreateParams) =>\n request<WebhookEndpointCreated>(\"POST\", \"/v1/webhooks\", { body: params }),\n /** GET /v1/webhooks/{id} */\n retrieve: (id: string) => request<WebhookEndpoint>(\"GET\", `/v1/webhooks/${encode(id)}`),\n /** PATCH /v1/webhooks/{id} */\n update: (id: string, params: WebhookEndpointUpdateParams) =>\n request<WebhookEndpoint>(\"PATCH\", `/v1/webhooks/${encode(id)}`, { body: params }),\n /** DELETE /v1/webhooks/{id} */\n delete: (id: string) => request<void>(\"DELETE\", `/v1/webhooks/${encode(id)}`),\n /** GET /v1/webhooks/{id}/deliveries */\n listDeliveries: (id: string, params?: WebhookDeliveriesParams) =>\n request<WebhookDeliveryList>(\"GET\", `/v1/webhooks/${encode(id)}/deliveries`, {\n query: params as QueryParams | undefined,\n }),\n };\n}\n\nfunction createProjects(request: RequestFn) {\n return {\n /** GET /v1/projects */\n list: () => request<ProjectList>(\"GET\", \"/v1/projects\"),\n /** GET /v1/projects/{id} */\n retrieve: (id: string) => request<Project>(\"GET\", `/v1/projects/${encode(id)}`),\n };\n}\n\nfunction createBeacon(request: RequestFn) {\n return {\n /** POST /v1/beacon - ingest a view/AR analytics event (204). */\n send: (params: BeaconParams) => request<void>(\"POST\", \"/v1/beacon\", { body: params }),\n };\n}\n\n// --- Client ----------------------------------------------------------------------------------\n\nexport class Viewrium {\n readonly baseUrl: string;\n readonly products: ReturnType<typeof createProducts>;\n readonly apiKeys: ReturnType<typeof createApiKeys>;\n readonly usage: ReturnType<typeof createUsage>;\n readonly webhooks: ReturnType<typeof createWebhooks>;\n readonly projects: ReturnType<typeof createProjects>;\n readonly beacon: ReturnType<typeof createBeacon>;\n\n readonly #request: RequestFn;\n\n constructor(options: ViewriumOptions) {\n if (!options || !options.apiKey) {\n throw new ViewriumError(\"A Viewrium client requires an `apiKey` (sk_ or pk_).\");\n }\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n\n const provided = options.fetch;\n const globalFetch = typeof globalThis.fetch === \"function\" ? globalThis.fetch.bind(globalThis) : undefined;\n const fetchImpl = provided ?? globalFetch;\n if (!fetchImpl) {\n throw new ViewriumError(\"No fetch implementation available; pass `options.fetch`.\");\n }\n\n const transport: Transport = { baseUrl: this.baseUrl, apiKey: options.apiKey, fetch: fetchImpl };\n const request: RequestFn = <T>(method: string, path: string, opts?: RequestOptions) =>\n doRequest<T>(transport, method, path, opts);\n const requestBlob: BlobRequestFn = (method, path, opts) =>\n doBlobRequest(transport, method, path, opts);\n this.#request = request;\n\n this.products = createProducts(request, requestBlob);\n this.apiKeys = createApiKeys(request);\n this.usage = createUsage(request);\n this.webhooks = createWebhooks(request);\n this.projects = createProjects(request);\n this.beacon = createBeacon(request);\n }\n\n /** GET /v1/health */\n health(): Promise<Health> {\n return this.#request<Health>(\"GET\", \"/v1/health\");\n }\n}\n\n/** Convenience factory: `createViewrium({ apiKey })` is `new Viewrium({ apiKey })`. */\nexport function createViewrium(options: ViewriumOptions): Viewrium {\n return new Viewrium(options);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,26 @@
1
1
  {
2
2
  "name": "@viewrium/sdk",
3
- "version": "2.0.0",
4
- "description": "Official Viewrium JavaScript/TypeScript SDK",
3
+ "version": "2.1.0",
4
+ "description": "Official TypeScript/JavaScript client for the Viewrium API: AR and 3D product visualization for furniture retailers.",
5
+ "keywords": [
6
+ "viewrium",
7
+ "sdk",
8
+ "api-client",
9
+ "3d",
10
+ "ar",
11
+ "augmented-reality",
12
+ "glb",
13
+ "product-visualization",
14
+ "furniture",
15
+ "ecommerce",
16
+ "typescript"
17
+ ],
18
+ "homepage": "https://www.viewrium.com/docs/sdks/javascript",
19
+ "bugs": {
20
+ "url": "https://www.viewrium.com/docs",
21
+ "email": "support@viewrium.com"
22
+ },
23
+ "author": "Viewrium",
5
24
  "license": "Apache-2.0",
6
25
  "type": "module",
7
26
  "sideEffects": false,
@@ -17,6 +36,7 @@
17
36
  },
18
37
  "files": [
19
38
  "dist",
39
+ "README.md",
20
40
  "LICENSE"
21
41
  ],
22
42
  "publishConfig": {
@@ -28,8 +48,8 @@
28
48
  "tsup": "^8.5.1",
29
49
  "typescript": "^5.9.3",
30
50
  "vitest": "^4.1.10",
31
- "@repo/typescript-config": "0.0.0",
32
- "@repo/eslint-config": "0.0.0"
51
+ "@repo/eslint-config": "0.0.0",
52
+ "@repo/typescript-config": "0.0.0"
33
53
  },
34
54
  "scripts": {
35
55
  "generate": "openapi-typescript ../../apps/api/openapi.json --output src/generated/schema.ts",