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 viewriumOr with uv:
uv add viewriumClient
from viewrium import Viewrium
client = Viewrium("sk_live_...") # positional api_keyOptions:
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
| Resource | Methods |
|---|---|
client.products | create, list, retrieve, update, delete, batch_delete, publish, tags, review, update_image, update_model, rescale, order, export |
client.api_keys | list, create, revoke |
client.usage | get |
client.webhooks | list, create, retrieve, update, delete, list_deliveries |
client.projects | list, retrieve |
client.beacon | send |
client | health() |
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))