Viewrium Docs
SDKs

JavaScript SDK

Install and use @viewrium/sdk, the official typed TypeScript client for the /v1 API.

@viewrium/sdk is the official JavaScript/TypeScript SDK - an ergonomic, fully typed client for the Viewrium /v1 API. It ships ESM and CommonJS builds with self-contained type declarations, and runs anywhere there is a fetch (Node 18+, Bun, Deno, edge runtimes, the browser).

Install

npm install @viewrium/sdk

Client

import { Viewrium } from "@viewrium/sdk";

const client = new Viewrium({ apiKey: "sk_live_..." });

Options:

const client = new Viewrium({
  apiKey: "sk_live_...",
  baseUrl: "https://api.viewrium.com", // default; trailing slash stripped
  fetch: customFetch,                  // optional; defaults to globalThis.fetch
});

There is also a createViewrium({ apiKey }) factory that is equivalent to new Viewrium(...).

Use a secret (sk_) key only on a server. For browser viewers, use the <viewrium-model> embed with a publishable (pk_) key instead of the SDK.

Resources

ResourceMethods
client.productscreate, list, retrieve, update, delete, batchDelete, publish, tags, review, updateImage, updateModel, rescale, order, export
client.apiKeyslist, create, revoke
client.usageget
client.webhookslist, create, retrieve, update, delete, listDeliveries
client.projectslist, retrieve
client.beaconsend
clienthealth()

Request and response types (ProductCreateParams, Product, ProductCreateResult, ProductParked, PlannedView, ModelPlan, ProductCost, UsageProductTotals, and so on) are exported and derived directly from the API's OpenAPI schema.

products.create resolves to two shapes

create resolves to a ProductCreateResult - the parked quote for a fresh request, or the committed product once you post the token back. Discriminate on status:

const result = await client.products.create({
  image_urls: ["https://cdn.example.com/chair.jpg"],
  draft: { title: "Oak dining chair" },
});

if (result.status === "parked") {
  console.log(result.cost.total_credits, result.model.unseen_views);
  const product = await client.products.create({ resume_token: result.resume_token });
  if (product.status === "parked") throw new Error("a resume always commits");
  console.log(product.id, product.display_status);
}

See the verification gate guide for the whole flow.

Listing and filtering

const page = await client.products.list({
  scope: "org",
  status: ["published"],
  tag: ["chairs"],
  tag_match: "all",
  q: "oak",
  custom_id: "sku-123",
  limit: 50,
  offset: 0,
  sort: "created_at",
  order: "desc",
});
for (const product of page.data) {
  console.log(product.id, product.title, product.display_status);
}

products.tags and products.export take the same filters.

Analytics

usage.get takes an arbitrary window, not a preset. group_by: "product" additionally returns the window's best-performing products, ranked by 3D views then AR entries and capped at 20, each with its title.

const usage = await client.usage.get({
  start: "2026-06-01T00:00:00Z",
  end: "2026-09-01T00:00:00Z",
  granularity: "day",          // or "hour"
  event_type: "view",
  include_internal: false,     // your own team's traffic is excluded by default
  group_by: "product",
});

for (const row of usage.by_product ?? []) {
  console.log(row.title, row.totals);
}

The export archive

products.export resolves to the ZIP as a Blob:

const archive = await client.products.export({ tag: ["chairs"], include: ["images", "model"] });

Errors

Non-2xx responses throw a typed ViewriumError subclass. Branch with instanceof:

import {
  Viewrium,
  ViewriumError,
  AuthenticationError,
  ConflictError,
  RateLimitError,
  ConnectionError,
} from "@viewrium/sdk";

const client = new Viewrium({ apiKey: "sk_live_..." });
try {
  await client.products.create({ image_urls: ["https://cdn/chair.jpg"] });
} catch (err) {
  if (err instanceof AuthenticationError) {
    // 401
  } else if (err instanceof ConflictError) {
    // 409, e.g. duplicate custom_id
  } else if (err instanceof RateLimitError) {
    // 429 - back off and retry
  } else if (err instanceof ConnectionError) {
    // never reached the API
  } else if (err instanceof ViewriumError) {
    console.log(err.status, err.code, err.message, err.details);
  }
}

The full status-to-error table is on the Authentication page.

Full example

import { Viewrium } from "@viewrium/sdk";

const client = new Viewrium({ apiKey: "sk_live_..." });

const quote = await client.products.create({
  image_urls: ["https://cdn.example.com/chair.jpg"],
  draft: { title: "Oak dining chair", custom_id: "sku-oak-chair", dimensions: { height: 92 } },
});
if (quote.status !== "parked") throw new Error("unexpected: already committed");

const committed = await client.products.create({ resume_token: quote.resume_token });
if (committed.status === "parked") throw new Error("a resume always commits");

let product = await client.products.retrieve(committed.id);
while (product.display_status === "generating") {
  await new Promise((r) => setTimeout(r, 5000));
  product = await client.products.retrieve(product.id);
}

console.log(product.display_status, product.model?.glb_url);
await client.products.update(product.id, { published: true });

On this page