Authentication
Secret and publishable API keys, the Bearer header, the error envelope, and rate limits.
The Viewrium API lives at https://api.viewrium.com. Every request authenticates with an API key
passed in the Authorization header:
Authorization: Bearer sk_live_...Both SDKs and the embed set this header for you.
Key types
There are two kinds of key. They are not interchangeable - the API enforces what each type may do.
Secret keys (sk_)
- Run server-side only. A secret key can read and write everything its scopes allow: create products, list them, review them, publish them, manage webhooks, read usage, and more.
- Never expose an
sk_key in a browser, mobile app, or public repository. Treat it like a password. - Create and revoke secret keys in the dashboard under Settings -> Advanced -> API keys. A secret key's material is shown exactly once, on create.
Publishable keys (pk_)
- Safe to ship in a browser. A publishable key is origin-gated: it only works from the domains
listed on its project's
allowed_domains, and it is limited to two operations - fetching a single published product (GET /v1/products/{id}) and sending analytics beacons (POST /v1/beacon). - What it receives from
GET /v1/products/{id}is not the full product but an explicit allow-list (id,title,model_url,thumbnail_url,model_status), and only for a published product with a ready model. Anything else is a 404. - This is exactly what the
<viewrium-model>embed needs, and nothing more.
Secret (sk_) | Publishable (pk_) | |
|---|---|---|
| Where it runs | Server | Browser |
| Access | Full (scoped) | Read one published product + send beacons |
| Origin restriction | None | Gated to the project's allowed_domains |
| Powers | The SDKs | The embed |
Scopes
Keys can carry scopes that narrow what a secret key may do:
| Scope | Covers |
|---|---|
products:read | Listing, reading and exporting products |
products:write | Creating, updating, reviewing, publishing and deleting products |
usage:read | GET /v1/usage |
webhooks:manage | Webhook endpoint CRUD and the deliveries list |
beacon:write | POST /v1/beacon (publishable keys hold this implicitly) |
A key with no scopes has full access for its type. A non-empty list is an explicit restriction:
anything not named is denied. Publishable keys are capped structurally at products:read +
beacon:write whatever their stored scopes say. Assign scopes when you create a key in the
dashboard.
The error envelope
Every non-2xx response uses one shape:
{
"error": {
"code": "conflict",
"message": "custom_id already exists in this organization",
"details": null
}
}code- a stable, machine-readable string.message- a human-readable explanation.details- optional structured context (for example, per-field validation errors, or thereasonan order was refused).
The SDKs parse this into typed exceptions so you can branch on the failure:
from viewrium import Viewrium, ConflictError, AuthenticationError
client = Viewrium("sk_live_...")
try:
client.products.retrieve("does-not-exist")
except AuthenticationError:
... # bad or missing key (401)
except ConflictError as err:
print(err.code, err.message, err.details)import { Viewrium, ConflictError, AuthenticationError } from "@viewrium/sdk";
const client = new Viewrium({ apiKey: "sk_live_..." });
try {
await client.products.retrieve("does-not-exist");
} catch (err) {
if (err instanceof AuthenticationError) {
// bad or missing key (401)
} else if (err instanceof ConflictError) {
console.log(err.code, err.message, err.details);
}
}Status to error mapping
| HTTP | Python | JavaScript | Meaning |
|---|---|---|---|
| 400 | BadRequestError | BadRequestError | Malformed request (a resume that changes the quote, say) |
| 401 | AuthenticationError | AuthenticationError | Missing or invalid key |
| 402 | PaymentRequiredError | PaymentRequiredError | The organization's plan does not allow this - no plan, a used free trial, a failed payment, or past the plan's active-product cap (details.reason says which) |
| 403 | PermissionDeniedError | PermissionError | Valid key, not allowed (scope, origin, or role) |
| 404 | NotFoundError | NotFoundError | No such resource - or one outside your reach |
| 409 | ConflictError | ConflictError | Conflict (duplicate custom_id, a replayed resume_token, an order that cannot be placed) |
| 422 | UnprocessableEntityError | UnprocessableEntityError | Validation failed |
| 429 | RateLimitError | RateLimitError | Rate limited - or, with code: "quota_exceeded", not enough credits for a generation (details.shortfall_credits) |
| 5xx | ServerError | ServerError | The API failed to handle the request |
| - | APIConnectionError | ConnectionError | The request never reached the API |
Every SDK error extends ViewriumError and exposes status, code, message, and details.
Not found, not forbidden. A resource outside your organization's reach answers
404, not403: it does not exist for you.403means the resource is visible and your role or scope is not enough.
Rate limits
When you exceed a rate limit the API returns 429 (RateLimitError). Back off and retry with
exponential delay.