Viewrium Docs
SDKs

Python SDK

Install and use viewrium, the official typed Python client for the /v1 API.

viewrium is the official Python SDK - a synchronous, typed client for the Viewrium /v1 API, published on PyPI.

Install

pip install viewrium

Or with uv:

uv add viewrium

Client

from viewrium import Viewrium

client = Viewrium("sk_live_...")            # positional api_key

Options:

client = Viewrium(
    "sk_live_...",
    base_url="https://api.viewrium.com",    # default
    timeout=30.0,                            # seconds
)

The client is also a context manager, which closes the underlying HTTP connection for you:

with Viewrium("sk_live_...") as client:
    page = client.products.list()

Request models

Write operations take typed pydantic request objects, importable from the top-level package:

from viewrium import (
    ProductCreateRequest,
    ProductDraft,
    ProductOrder,
    ProductOverrides,
    ProductUpdateRequest,
    ProductPublishRequest,
    ProductRescaleRequest,
    ProductOrderRequest,
    ProductReviewRequest,
    ProductImagePatch,
    ProductModelPatch,
    ProductFilters,
    ModelDimensions,
    ApiKeyCreateRequest,
    WebhookEndpointCreateRequest,
    WebhookEndpointUpdateRequest,
    BeaconRequest,
)

Only the fields you actually set are sent. That matters twice: an explicit None on an image patch clears an override where an omitted field leaves it alone, and a resume that never mentions order does not restate the order the quote was priced for.

Enums are exported too: ProductStatus, DisplayStatus, AssetStatus, ReviewStatus, WarningKind, ListScope, TagMatch, ProductSortField, SortOrder, ExportInclude, SourceType, KeyType, BeaconEventType, UsageEventType, WebhookEventType, DimensionUnit, WidthAxis.

Resources

ResourceMethods
client.productscreate, list, retrieve, update, delete, batch_delete, publish, tags, review, update_image, update_model, rescale, order, export
client.api_keyslist, create, revoke
client.usageget
client.webhookslist, create, retrieve, update, delete, list_deliveries
client.projectslist, retrieve
client.beaconsend
clienthealth()

products.create returns two types

create returns a ProductParked for a fresh request - the quote, with nothing written - or a Product once you commit the token:

from viewrium import ProductCreateRequest, ProductParked

result = client.products.create(ProductCreateRequest(image_urls=["https://cdn/chair.jpg"]))

if isinstance(result, ProductParked):
    print(result.cost.total_credits, result.model.unseen_views)
    result = client.products.create(ProductCreateRequest(resume_token=result.resume_token))

print(result.id, result.display_status)

See the verification gate guide for the whole flow.

Listing and filtering

The catalog's filters are one ProductFilters object, shared by list, tags and export exactly as the API shares them. Paging and sorting are keyword arguments on list.

from viewrium import DisplayStatus, ListScope, ProductFilters, ProductSortField, SortOrder

page = client.products.list(
    ProductFilters(
        scope=ListScope.ORG,
        status=[DisplayStatus.PUBLISHED],
        tag=["chairs"],
        q="oak",
        custom_id="sku-123",
    ),
    limit=50,
    offset=0,
    sort=ProductSortField.CREATED_AT,
    order=SortOrder.DESC,
)
for product in page.data:
    print(product.id, product.title, product.display_status)
print(page.total)

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.

from datetime import UTC, datetime, timedelta

from viewrium import UsageEventType

end = datetime.now(UTC)
usage = client.usage.get(
    start=end - timedelta(days=90),
    end=end,
    granularity="day",            # or "hour"
    event_type=UsageEventType.VIEW,
    include_internal=False,       # your own team's traffic is excluded by default
    group_by="product",
)

for row in usage.by_product:
    print(row.title, row.totals)

The export archive

products.export returns the ZIP as bytes:

from viewrium import ExportInclude, ProductFilters

archive = client.products.export(
    ProductFilters(tag=["chairs"]),
    include=[ExportInclude.IMAGES, ExportInclude.MODEL],
)
open("catalog.zip", "wb").write(archive)

Errors

Non-2xx responses raise a ViewriumError subclass. Catch the specific type you care about:

from viewrium import (
    Viewrium,
    ViewriumError,
    AuthenticationError,
    ConflictError,
    RateLimitError,
    APIConnectionError,
)

client = Viewrium("sk_live_...")
try:
    client.products.create(ProductCreateRequest(image_urls=["https://cdn/chair.jpg"]))
except AuthenticationError:
    ...        # 401
except ConflictError as err:
    ...        # 409, e.g. duplicate custom_id
except RateLimitError:
    ...        # 429 - back off and retry
except APIConnectionError:
    ...        # never reached the API
except ViewriumError as err:
    print(err.status, err.code, err.message, err.details)

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

Full example

import time

from viewrium import (
    ModelDimensions,
    ProductCreateRequest,
    ProductDraft,
    ProductParked,
    ProductUpdateRequest,
    Viewrium,
)

with Viewrium("sk_live_...") as client:
    result = client.products.create(
        ProductCreateRequest(
            image_urls=["https://cdn.example.com/chair.jpg"],
            draft=ProductDraft(
                title="Oak dining chair",
                custom_id="sku-oak-chair",
                dimensions=ModelDimensions(height=92),
            ),
        )
    )

    if isinstance(result, ProductParked):
        print("costs", result.cost.total_credits, "credits")
        result = client.products.create(
            ProductCreateRequest(resume_token=result.resume_token)
        )
    assert not isinstance(result, ProductParked)

    product = result
    while product.display_status == "generating":
        time.sleep(5)
        product = client.products.retrieve(product.id)

    print(product.display_status, product.model.glb_url if product.model else None)
    client.products.update(product.id, ProductUpdateRequest(published=True))

On this page