Troubleshooting
Find your error message or code on this page. Every error the SDK throws has a code, a message, why it happened and how to fix it.
try {
await bakery3.render();
} catch (error) {
console.error(error.toDisplayString()); // message, why, fix and docs link
error.code; // never changes, so you can branch on it
error.path; // where in the scene, when the error has a place
error.retryable; // is sending the same request again worth trying?
}An error you do not catch prints the same thing in the console, because its
stack starts with the message, the why, the fix and the docs link.
Content hashing needs crypto.subtle
Content hashing needs crypto.subtle, which this page does not have.
Why: crypto.subtle is only exposed on secure origins (https, or localhost).
Fix: Serve your dev app over https or from localhost.
Next.js: `next dev --experimental-https`.Version 0.1.0 of the SDK shows this when you open your dev server on a LAN
address, such as http://192.168.1.20:3000, because browsers hide
crypto.subtle on a plain http address that is not localhost. Later
versions hash without it, so update @oeave/bakery3. If you stay on 0.1.0,
serve over https or use localhost.
Could not export the scene
Could not export the scene: <exporter message>
Why: The glTF exporter walks the scene graph; a geometry with a
malformed attribute or a texture from a tainted canvas will
stop it.
Fix: Run bakery3.check() to find the object it stopped on.The common causes are:
A tainted canvas. A texture drawn from a cross-origin image without CORS
cannot be read back. Serve the image with Access-Control-Allow-Origin, or set
crossOrigin = 'anonymous' before loading it.
A malformed geometry attribute. This is a BufferAttribute with the wrong
item size, or a NaN in a position buffer. check() names the object.
A texture that only exists on the GPU. A render target has no pixels the
SDK can read. If a node material samples one, you get
shader_texture_unreadable with the material named.
No scene to render
No scene for render().
Why: render() was called with no scene, and none has been attached.
Fix: In plain three.js, pass { scene, camera } to render(), or call
bakery3.attach({ scene, camera, renderer }) once. In React Three
Fiber, use useBakery3() inside or beside <Canvas>.The message names the call that needed the scene: estimate(), bake() and
inspect() say the same. In plain Three.js, call attach() once after you
create the client:
const bakery3 = createBakery3({ token: getToken });
bakery3.attach({ scene, camera, renderer });In R3F, the usual cause is <Bakery3Provider> mounted outside <Canvas>. Put
it inside, because it reads the scene and camera from the canvas, or pass your
key to the hook with useBakery3({ token }) and leave the provider out.
render() in Node
render() and inspect() read a live scene in a browser. In Node they throw
CaptureFailed. To render from a server or a script, give
renderBatch() the URLs of your .glb files.
Unsupported material
The material cannot be rendered faithfully. You have three options, best
first. Change it to MeshStandardMaterial or MeshPhysicalMaterial, port it
to TSL, or declare a material fallback.
check() names the material, its type and its path in the scene. See
Materials.
Unsupported camera type
Pass one PerspectiveCamera or OrthographicCamera. Array, cube and stereo
cameras are not supported. See Cameras.
The render is darker or flatter than my viewport
Attach your renderer. The SDK then reads outputColorSpace, toneMapping and
toneMappingExposure from it.
bakery3.attach({ scene, camera, renderer });In React Three Fiber, useBakery3() does this for you. See
Environment & lighting.
The render is unlit, or much flatter than expected
⚠ environment_unavailable
The scene has an environment map, but its source pixels are not
reachable from the SDK.Three.js has usually processed the HDRI on scene.environment, and the
original image is gone. Tell the SDK where it came from:
rememberEnvironmentSource(hdri, '/studio.hdr');@oeave/bakery3/r3f does this for drei's <Environment files>.
Could not upload
AssetUploadIncomplete: Could not upload scene.glb (148.2 MB): 403 Forbidden
Why: The presigned upload URL was rejected. These expire, and they are
signed over an exact set of headers.
Fix: Retry the render. If it keeps failing, check for a service worker
or proxy that rewrites request headers.If it keeps failing, look for a service worker or a company proxy that adds, drops or rewrites a request header. That turns a valid upload into a 403. See Assets & caching.
Unauthorized, or 401 on every render
The message says which of these it is. Each one links to the Keys page, where you create and copy keys.
| Message starts with | What to do |
|---|---|
No API key or token. | Pass apiKey or token to createBakery3(). Sign up first if you have no account. |
apiKey is empty. | The environment variable that holds your key is not set. In a browser bundle only variables with your framework's public prefix (NEXT_PUBLIC_, VITE_) are set. |
Replace the placeholder | You pasted the example bk_sk_.... Copy a real key. |
token must be a function | Pass a function that fetches a token, not the token itself. |
That is not a Bakery3 key. | The header carries something else, often a key for another service. |
This API key or token is not valid. | The key is mistyped or revoked. Copy a live one. |
Other causes:
- You passed a
bk_pt_browser token where abk_sk_secret key is needed. Making tokens, listing renders and deleting need the secret key. - You passed a browser token to
new Bakery3Server(). Its first request refuses it and says so. - Your token route failed. When it answers with an HTML error page, the SDK
says it did not return JSON. When it returns
{ error }, the SDK shows that error. Open the route in the network tab, and check your server log: oftenBAKERY3_SECRET_KEYis not set there. - Your
tokenfunction returns the fetchResponseand not the parsed body. Add.then((r) => r.json()).
OriginNotAllowed
The token was made with allowedOrigins, and the page's origin is not in the
list. Include every origin you serve from, staging and preview deployments too.
See Auth & keys.
TokenExpired
A browser token ran out and a new one could not take its place. That happens in two ways.
You passed a browser token as apiKey. Pass a function as token instead, so
the SDK can ask for a new token when one expires:
// Stops working when the token expires:
createBakery3({ apiKey: 'bk_pt_…' });
// Fetches a fresh token when requested:
createBakery3({ token: () => mint() });Or a render was started with a token that expired more than a day ago, and a
new token cannot read it. Create tokens with a longer expiresInSeconds, or
read the render from your server with your secret key.
RenderBudgetExceeded
The render could cost more than your maxCost, so it was refused before it
started and cost nothing. Raise maxCost, lower the quality preset or reduce
the output size. See Cost & estimates.
InsufficientBalance or QuotaExceeded
InsufficientBalance means the render's maximum price does not fit in your
credit. We check the maximum and not the estimate, so a render can never
overdraw. Top up on the Billing page.
QuotaExceeded means a monthly limit you set yourself. See
Limits.
RateLimited
Your key or token sent more calls in a minute than its limit. The SDK waits and tries again on its own, so you only see this once it has given up. Send fewer calls at once, or put a catalog in a batch. The limits are under Rate limits.
Cancelled or Expired
job.result() rejects with Cancelled when the render was cancelled, from
your code or from the console. Expired means the render's files were deleted
when the project's retention ended, or by hand. The
record and its cost stay. To keep a result, copy its file into your own storage
when it completes.
QueueFull
Renders over your concurrency limit wait in a queue. QueueFull means that
queue is full, at 50 waiting renders per account. Wait for queued renders to
start, cancel some, or move to a plan with more concurrency. For a whole
catalog, use a batch. A batch is never refused as
QueueFull.
BatchBudgetExceeded or BatchClosed
A page of batch items was refused whole, and nothing in it was started.
BatchBudgetExceeded means the page would take the batch past its own
maxCost. Raise it, or put the items in another batch. BatchClosed means the
batch is sealed, paused, cancelled or completed. Resume a paused batch.
Otherwise open a new batch with a new key, and the items that already completed
are reused at no cost while they are still stored. See
Render every variant.
QueueTimeout
A queued render waited 24 hours for a free slot and was dropped without a charge. You probably queued much more than your concurrency can work through. Send it again, or move to a plan with more concurrency.
OutOfVideoMemory
The scene has too many triangles, too many texture bytes, or both. Reduce whichever is larger, or drop a quality preset. See Scene size.
RenderTimeout
If the render timed out before it reached rendering, we already tried it once
more for you. A timeout while rendering would happen again, so the render
fails. Use fewer samples or a smaller output, or split the video. In practice
this is an ultra render of a very large scene at 4K.
A render failed after every retry (attemptsExhausted)
WorkerLost: The render worker stopped reporting. (tried 3 times)
Fix: This was tried 3 times on different workers and failed the same way
each time. We have been notified. Retry from the console, or
POST /v1/renders/:id/retry, once we say it is fixed.When a render fails on our side (WorkerFailure, WorkerLost, UploadFailed,
ResultLost, RendererCrashed), we run it again on a different worker, up to
the count under Retries. error.attemptsExhausted: true
means every attempt failed the same way and we gave up. The render is failed,
you paid nothing for any attempt, and we have been notified.
Once we have fixed the cause, press Retry in the console or call
POST /v1/renders/:id/retry. You get a new render with the same request and
metadata, checked against your balance and limits again. In a batch,
POST /v1/batches/:id/retry does this for every such item at once.
When RendererCrashed happens twice on two machines, the scene is the likely
cause. Try the advice under OutOfVideoMemory.
ProtocolVersionUnsupported
Your SDK's version does not match our workers, so the render was refused.
Update @oeave/bakery3.
Unsupported animated property
The timeline refuses a property the renderer cannot animate. The error names the property, the object's path, the reason and what you can use instead. The properties you can animate are listed under Render video.
My animation stopped working after a scene rebuild
A hot reload or a variant swap replaces the objects your tracks point at. Point the tracks at the new objects:
const { healed, stale } = tl.reconcile(scene);
// healed: tracks that found their object again
// stale: tracks that did not, each with a reasonWebhook signature does not match
Check that you pass the raw body, that you read the bakery3-signature header,
that the secret belongs to this project, and that your server's clock is right
to within five minutes. See Webhooks.
See what the SDK would send
inspect() returns the manifest a render would send. It renders nothing and
costs nothing.
const manifest = await bakery3.inspect();Read it next to the check() output to find a missing file, something the
scene uses that is not supported, or a value that was captured wrong.
