Skip to content
Adapters

World Labs

generateWorld() calls World Labs Marble. A prompt, image, or video returns a viewer URL, and often splat files, a mesh, and a panorama. The adapter has no chat.

1. Install

shell
npm i @tanstack/ai @tanstack/ai-worldlabs

2. Set the API key

Create a key in the World Labs platform. Put it in the environment of your server:

shell
WORLDLABS_API_KEY=your-worldlabs-key

worldlabsWorld reads WORLDLABS_API_KEY. To pass the key in code, use createWorldLabsWorld:

ts
import { createWorldLabsWorld } from '@tanstack/ai-worldlabs'

const adapter = createWorldLabsWorld('marble-1.1', process.env.WORLDLABS_API_KEY!)

3. Generate a world on the server

Add a POST route at /api/world. The call waits until the world is ready, so give it a long timeout:

ts
import { generateWorld } from '@tanstack/ai'
import { worldlabsWorld } from '@tanstack/ai-worldlabs'

export async function POST() {
  const world = await generateWorld({
    adapter: worldlabsWorld('marble-1.1'),
    prompt: 'A mystical forest with glowing mushrooms',
    threadId: 'thread-1',
    timeout: 10 * 60 * 1000,
  })

  return Response.json({
    url: world.url,
    thumbnailUrl: world.assets?.thumbnailUrl,
  })
}

World Labs bills in credits for each generation.

4. Show the world in the browser

world.url is the Marble viewer (https://marble.worldlabs.ai/world/{id}). Open it in a new tab. Do not put it in an iframe:

ts
const world = await fetch('/api/world', { method: 'POST' }).then((response) =>
  response.json(),
)

if (typeof world.url === 'string') {
  const link = document.createElement('a')
  link.href = world.url
  link.target = '_blank'
  link.textContent = 'Open in Marble'
  document.body.append(link)
}

if (typeof world.thumbnailUrl === 'string') {
  const image = document.createElement('img')
  image.src = world.thumbnailUrl
  image.alt = 'Preview of the generated world'
  document.body.append(image)
}

You now have a 3D world and a link to explore it.

A full app is in examples/ts-react-media. Open the World tab and pick a Marble model. The example loads one SPZ splat file in Spark (it prefers 500k) through /api/marble-splat. If there is no splat URL, it shows a thumbnail and an Open in Marble link.

Image, multi-image, and video

prompt is the text. Put the media in modelOptions:

ts
import { generateWorld } from '@tanstack/ai'
import { worldlabsWorld } from '@tanstack/ai-worldlabs'

const world = await generateWorld({
  adapter: worldlabsWorld('marble-1.1'),
  prompt: 'A cozy living room',
  threadId: 'thread-1',
  modelOptions: {
    image: { uri: 'https://example.com/room.jpg' },
    isPano: 'auto',
  },
})

Pass only one of image, images, or video. See Media options for each field.

Models and options

Pass the id as a string literal, so TypeScript can narrow the options.

IdNotes
marble-1.1-plusDynamic world sizing
marble-1.1The World Labs default model id
marble-1.0Marble 1.0
marble-1.0-draftFaster, at draft quality

The package exports the list as WORLDLABS_WORLD_MODELS.

Media options

OptionMeaning
imageOne image. Give uri, mediaAssetId, or dataBase64
imagesSeveral images of the same scene, each with an optional azimuth
videoOne video. The same source fields as image
isPanoauto, true, or false, for a single image

Job options

OptionMeaning
waitThe default is true. Set false to get the operationId at once
pollIntervalMsThe delay between polls when wait is true. The default is 2000

Optional metadata: displayName, seed, tags, disableRecaption, and permission.

Advanced

What the result holds

  • world.url: the Marble viewer URL.
  • world.assets: optional. It has splat, mesh, panorama, and thumbnail links only when the World Labs response includes them. These URLs are often signed CDN links.
  • world.assets.splats.spzUrls: splat URLs by quality key, for example 100k, 500k, and full_res.

Jobs that do not wait

On serverless, a job that runs for minutes can hit the limit of your platform. You have two choices:

  • Pass a long timeout to generateWorld(), as in step 3.
  • Set modelOptions: { wait: false } and poll the job yourself.

With wait: false, world.status is waiting and world.operationId is set. The adapter cannot resume that id. A new generateWorld() call with wait: true starts a new job. To get the result, poll GET /marble/v1/operations/{operationId} yourself.

Bring your own key

worldlabsByok from @tanstack/ai-worldlabs/byok declares World Labs for the BYOK store, with WORLDLABS_API_KEY as its env var. See Bring Your Own Key.

API reference

FactoryCreates
worldlabsWorld(model, config?)A world adapter. Reads WORLDLABS_API_KEY when config.apiKey is not set.
createWorldLabsWorld(model, apiKey, config?)The same adapter with an explicit API key.

The config fields:

  • apiKey: the World Labs API key.
  • baseUrl: the API base URL. The default is https://api.worldlabs.ai.
  • defaultHeaders: extra request headers.
  • fetch: your own fetch function.