# AI agent skill Source: https://docs.worldlabs.ai/api/agent-skill Install the World Labs API skill for coding agents ## Install the skill The World Labs API skill gives coding agents API guidance and an OpenAPI snapshot for client generation, world generation, media asset uploads, and operation polling. ```bash theme={null} npx skills add worldlabsai/marble-developer-api-skill --skill marble-developer-api ``` Install it globally if you want agents to use it across projects: ```bash theme={null} npx skills add worldlabsai/marble-developer-api-skill --skill marble-developer-api --global ``` ## Verify the install List the skills exposed by the public mirror: ```bash theme={null} npx skills add worldlabsai/marble-developer-api-skill --list ``` You should see `marble-developer-api` in the output. ## Use the skill Ask your coding agent to invoke the skill by name: ```text theme={null} Use the marble-developer-api skill to build a TypeScript client for World API v1. ``` ```text theme={null} Use the marble-developer-api skill to upload an image, generate a world, and poll the operation. ``` Never paste a real API key into prompts, logs, screenshots, or committed files. The skill source is mirrored at [worldlabsai/marble-developer-api-skill](https://github.com/worldlabsai/marble-developer-api-skill). # Errors and troubleshooting Source: https://docs.worldlabs.ai/api/errors How to read World API error responses and fix the most common failures, including 400 Bad request ## Error responses The World API uses standard HTTP status codes. Most errors return a JSON body with a `detail` field that describes the problem: ```json theme={null} { "detail": "Failed to download asset from https://example.com/image.jpg. Please check the URL and try again." } ``` Schema validation errors (`422`) return `detail` as an array, with one entry per invalid field: ```json theme={null} { "detail": [ { "loc": ["body", "world_prompt", "type"], "msg": "Field required", "type": "missing" } ] } ``` Some requests are rejected before they reach validation and come back with a generic message and no `detail` field, such as `Bad request`. The most common cause is a body that isn't valid JSON — see [Request body is not valid JSON](#request-body-is-not-valid-json). Many responses include a `request_id`. It's a trace identifier for the World Labs team, not something you can look up yourself. Keep it and include it (with your endpoint and request body, API key redacted) when you contact [support@worldlabs.ai](mailto:support@worldlabs.ai). ## Status codes | Status | Meaning | What to do | | --------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` Bad request | The request was invalid, or the prompt/input violated content policy. | Read `detail` when present. See [common causes](#common-400-causes) below. | | `402` Payment required | The account has insufficient API credits. | Add credits or enable auto-refill on the [billing page](https://platform.worldlabs.ai/billing). API credits are separate from Marble app credits — see the [FAQ](/api/faq). | | `404` Not found | The world, operation, or media asset doesn't exist or isn't owned by your API user. | Verify the ID and that it was created with the same API key. | | `422` Unprocessable entity | The body didn't match the request schema. | Read the field path in each `detail[]` entry and fix that field. | | `429` Too many requests | You exceeded a rate limit. | Back off and retry. See [Rate limits](/api/rate-limits). | | `500` Internal server error | The request couldn't be processed. | Retry after a short delay; contact support if it persists. | ## Finding the specific reason Where the failure reason lives depends on how the call fails: * **Immediate HTTP errors** (the `worlds:generate` call itself returns `4xx`): read the status code and the `detail` field, then apply the fixes below. * **Generation failures** happen later. A `worlds:generate` call that returns `200` with an `operation_id` can still fail during generation. Poll [`GET /marble/v1/operations/{operation_id}`](/api/reference/operations/get); when `done` is `true` with a non-null `error`, read `error.code` and `error.message`. ## Common 400 causes ### Request body is not valid JSON A frequent symptom is a generic `Bad request` with no `detail`, even though the same payload works with `curl`. Some HTTP clients send a language-native object or map through its default string conversion instead of serializing it to JSON. That produces a body like this: ```text theme={null} {model=marble-1.0, permission={allow_id_access=false, public=false}, world_prompt={...}} ``` That isn't JSON — it has unquoted keys, `=` instead of `:`, and no string quoting, so it's rejected before it reaches the API. Valid JSON looks like `{"model":"marble-1.0",...}`. Fix: serialize the payload to a JSON string before sending, and set `Content-Type: application/json`. Log the exact bytes you pass to your HTTP client to confirm they're JSON. ### Image or video URL can't be fetched When you use `source: "uri"`, World Labs fetches the URL from its servers with no cookies, authentication, or referer. Hotlink-protected or login-gated hosts (some CDNs and image-search result links) block these server-side fetches, which returns a `400`. Test the URL from a fresh environment — for example `curl` with no cookies or referer. If it doesn't return the raw image or video bytes, the API can't use it either. Use one of these instead: * Upload the file as a media asset, then reference its `media_asset_id`. See [Prepare a media asset upload](/api/reference/media-assets/prepare-upload) and the [Quickstart upload flow](/api#image-input). * Inline a small file with `source: "data_base64"`. ### Content policy A prompt or input image or video can be rejected for content-policy reasons, also as a `400`. Adjust the input and retry. ### Invalid model `model` must be one of the supported values. See [Models](/api/models) for the current list. # Tools & examples Source: https://docs.worldlabs.ai/api/examples End-to-end examples for learning how to use the World API. These projects are intended for experimentation, not production use. ## API examples (Node & Python) Minimal scripts and a simple web app for generating worlds using the raw API. [View on GitHub](https://github.com/worldlabsai/worldlabs-api-examples) ## Client & splat utilities (Python) Python client and utilities for saving/loading splats and rendering out videos. [View on GitHub](https://github.com/worldlabsai/worldlabs-api-python) ## Spark Render worlds on the web with SparkJS. SparkJS is a high-performance 3D Gaussian splatting renderer built on top of THREE.js. It is the recommended way to render World Labs splat assets in the browser. Spark supports: * Fast splat rendering on desktop and mobile * Integration with other THREE.js meshes * SPZ, PLY, SOGS, KSPLAT, and SPLAT formats * Dynamic and procedural splat effects [Explore SparkJS](https://sparkjs.dev/) # Frequently asked questions Source: https://docs.worldlabs.ai/api/faq Common questions and answers about the World API ### Why does `worlds:generate` return `400 Bad request`? A `400 Bad request` means the request was rejected before generation started. The response body is generic and includes a `request_id`, but not the exact cause. Other status codes narrow things down: `401` is an authentication problem, `402` is insufficient API credits, `422` is a schema validation error, and `429` is a rate limit. The most common cause is a request body that is not valid JSON. This is the usual reason a request that works in `curl` fails from another HTTP client. Some libraries send an object's string form instead of JSON. For example, a Java `Map.toString()` produces `key=value` pairs with unquoted keys: ```text theme={null} # Rejected: a stringified map, not JSON {model=marble-1.0, world_prompt={type=text, text_prompt=A sunlit atrium}} # Accepted: serialized JSON {"model":"marble-1.0","world_prompt":{"type":"text","text_prompt":"A sunlit atrium"}} ``` Serialize the payload with a JSON library (Jackson, Gson, `encoding/json`, and similar) before sending it, and set `Content-Type: application/json`. Setting that header while sending non-JSON bytes still fails. To confirm, log the exact bytes you send and compare them against a working `curl` payload. Other causes to check: * An input URL that World Labs cannot fetch. For an `image_prompt` or `video_prompt` with `source: "uri"`, the URL must be reachable with no cookies, referer, or auth. Hosts that block hotlinking or use expiring links fail. Upload the file as a [media asset](/api/reference/media-assets/prepare-upload) or inline it with `source: "data_base64"` instead. * Input that was rejected by content policy. * An invalid `model` value. Use one of `marble-1.0-draft`, `marble-1.0`, `marble-1.1`, or `marble-1.1-plus`. * A field constraint, such as `display_name` over 64 characters or more than 10 `tags`. Keep the `request_id` from the response so support can trace the exact failure. ### Can I retrieve PLY files from the API? Yes. Use [Export a world](/api/reference/worlds/export) to convert a generated world's SPZ splats into a cached `.ply` download: ```bash theme={null} curl -X POST "https://api.worldlabs.ai/marble/v1/worlds/:export" \ -H "WLT-Api-Key: $WLT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"asset_type":"splats","format":"ply","resolution":"full_res"}' ``` The response is an operation. PLY conversions usually return with `done: true` and a signed download URL in `response.url`. You can also export a high-quality mesh with `{"asset_type":"mesh","format":"glb"}`; mesh exports are asynchronous, so poll the returned operation until it completes. ### How do I convert a generated world to real-world (metric) scale? Generated splat assets are exported in arbitrary model units, not meters. The `semantics_metadata` object on the world response tells you how to convert to a metric, ground-aligned frame: * `metric_scale_factor`: multiply every XYZ coordinate (and isotropic scale) by this to get meters. * `ground_plane_offset`: after scaling, subtract this from the Y coordinate to place the ground plane at y=0. ```python theme={null} def to_metric(xyz, semantics): scaled = xyz * semantics.metric_scale_factor scaled[..., 1] -= semantics.ground_plane_offset return scaled ``` For Gaussian splat sizes, renderer coordinate systems, and log-scale `scale_0` fields, see [Rendering Marble SPZ files in third-party engines](/api/rendering-spz). ### How does API billing work? Billing for the World API is separate from billing for the Marble web app. * Credits purchased for the Marble app cannot be used with the API * API usage requires credits purchased through the World Labs Platform If you plan to use the API, make sure you purchase credits on the World Labs Platform, NOT in the Marble app. A Marble subscription — including a Free plan — says nothing about your API billing. The two are separate accounts even when you sign in with the same email, so being on the Marble Free plan does not mean your API usage is free. ### Why was I charged when I have not used the API recently? Almost certainly this is an overage charge, and it covers an earlier billing period. API credits are prepaid: you buy them up front and requests draw down that balance, so normal usage produces no charge at all. But requests are admitted based on a low-balance threshold rather than a per-request cost estimate, so a request that starts while your balance is low can cost more than you have left and take your balance below zero. That uncovered usage — your overage — is the only part that gets invoiced, and it settles at the end of the monthly billing period rather than at the time of the request. So the charge can arrive weeks after your last request: a charge dated early August covers overage from July. The credits are converted to USD at your account's credit rate (\$1.00 USD per 1,250 credits on standard pay-as-you-go accounts). Note that having auto-refill disabled does not prevent this. Auto-refill only controls whether your balance is topped up automatically; it does not cap usage. To reconcile a charge, compare the billing period on the invoice against your usage events for that period on the [usage page](https://platform.worldlabs.ai/usage). See [Pricing](/api/pricing#overage) for details. ### How do I get a panorama image from my world generation? Every world generation includes a panorama image in the response, accessible via `assets.imagery.pano_url`. This panorama is automatically generated as part of the world creation process. The panorama URL will be available in the response at `operation.response.assets.imagery.pano_url` or when you fetch the world via `GET /marble/v1/worlds/{world_id}`. ### What is the difference between `marble-1.0`, `marble-1.1`, and `marble-1.1-plus`? World Labs currently offers three API model variants for world generation: * `marble-1.0` provides standard world generation at a fixed cost. * `marble-1.1` provides newer standard world generation at a fixed cost. * `marble-1.1-plus` provides the most expansive generation path and may add 0-1,500 variable world generation cost for larger worlds, determined automatically by the system during inference. For the latest pricing details, see the [API pricing page](/api/pricing). ### Where can I read more about World Labs policies? Please view our [Terms of Service](/terms-of-service) and [Privacy Policy](/privacy-policy) for details. # Quickstart Source: https://docs.worldlabs.ai/api/index Learn how to use the World API ## Quickstart Create API keys, manage billing, and check account settings. See request patterns for text, image, pano, multi-image, and video inputs. Sign in to the [World Labs Platform](https://platform.worldlabs.ai) with your Marble account. If you don't have a Marble account, you'll be prompted to create one. Visit the [billing page](https://platform.worldlabs.ai/billing). Add a payment method to your account and then purchase some credits to get started. Generate an API key from the [API keys page](https://platform.worldlabs.ai/api-keys). Save your API key in a secure location and never share it with anyone. To verify your development setup is working, we recommend creating a world from only a text prompt. You can also create a world from an image, multiple images of the same scene, or a video. This example uses `marble-1.1`, which corresponds to Marble 1.1. For the largest worlds, use `marble-1.1-plus`. Marble 1.1 Plus uses more credits to create a bigger world when prompted for outdoor or larger indoor spaces. Make a `POST` request to the [`/marble/v1/worlds:generate`](/api/reference/worlds/generate) endpoint. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "Mystical Forest", "model": "marble-1.1", "world_prompt": { "type": "text", "text_prompt": "A mystical forest with glowing mushrooms" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'Mystical Forest', model: 'marble-1.1', world_prompt: { type: 'text', text_prompt: 'A mystical forest with glowing mushrooms' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "Mystical Forest", "model": "marble-1.1", "world_prompt": { "type": "text", "text_prompt": "A mystical forest with glowing mushrooms" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This will return an Operation object. ```json Response theme={null} { "operation_id": "20bffbb1-4ba7-453f-a116-93eaw1a6843e", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expires_at": "2025-01-15T11:30:00Z", "done": false, "error": null, "metadata": null, "response": null } ``` Poll the [`/marble/v1/operations/{operation_id}`](/api/reference/operations/get) endpoint until the operation is done. ```bash Request theme={null} curl -X GET 'https://api.worldlabs.ai/marble/v1/operations/20bffbb1-4ba7-453f-a116-93eaw1a6843e' \ -H 'WLT-Api-Key: YOUR_API_KEY' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/operations/20bffbb1-4ba7-453f-a116-93eaw1a6843e', { method: 'GET', headers: { 'WLT-Api-Key': 'YOUR_API_KEY' } }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/operations/20bffbb1-4ba7-453f-a116-93eaw1a6843e" headers = { "WLT-Api-Key": "YOUR_API_KEY" } response = requests.get(url, headers=headers) print(response.text) ``` This will return an Operation object. If the operation is not done, it will return a `200` status code and the Operation object will have a `done` field set to `false`: ```json Response theme={null} { "operation_id": "20bffbb1-4ba7-453f-a116-93eaw1a6843e", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expires_at": "2025-01-15T11:30:00Z", "done": false, "error": null, "metadata": { "progress": { "status": "IN_PROGRESS", "description": "World generation in progress" }, "world_id": "dc2c65e4-68d3-4210-a01e-7a54cc9ded2a" }, "response": null } ``` World generation should take **about 5 minutes** to complete. Once the world is generated, the `done` field will be set to `true` and the `response` field will contain the generated World: ```json Response theme={null} { "operation_id": "20bffbb1-4ba7-453f-a116-93eab1a6843e", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:35:00Z", "expires_at": "2025-01-15T11:30:00Z", "done": true, "error": null, "metadata": { "progress": { "status": "SUCCEEDED", "description": "World generation completed successfully" }, "world_id": "dc2c65e4-68d3-4210-a01e-7a54cc9ded2a" }, "response": { "id": "dc2c65e4-68d3-4210-a01e-7a54cc9ded2a", "display_name": "", "tags": null, "world_marble_url": "https://marble.worldlabs.ai/world/dc2c65e4-68d3-4210-a01e-7a54cc9ded2a", "assets": { "caption": "The scene is a fantastical forest...", "thumbnail_url": "", "splats": { "spz_urls": { "500k": "<500k_spz_url>", "100k": "<100k_spz_url>", "full_res": "" }, "semantics_metadata": { "metric_scale_factor": 1.23, "ground_plane_offset": 0.42 } }, "mesh": { "collider_mesh_url": "" }, "imagery": { "pano_url": "" } }, "created_at": null, "updated_at": null, "permission": null, "world_prompt": null, "model": null } } ``` The `response` field contains a snapshot of the World at the time the operation completed. This allows you to access the generated assets without making a separate API call. Note that some fields like `display_name`, `created_at`, `updated_at`, `world_prompt`, and `model` may be empty or null in this snapshot. Use the [`GET /marble/v1/worlds/{world_id}`](/api/reference/worlds/get) endpoint to fetch the complete, up-to-date world. You can view the generated world in Marble at `https://marble.worldlabs.ai/world/{world_id}`. If you need to fetch the most up-to-date version of the world later, use the `world_id` to retrieve it. ```bash Request theme={null} curl -X GET 'https://api.worldlabs.ai/marble/v1/worlds/dc2c65e4-68d3-4210-a01e-7a54cc9ded2a' \ -H 'WLT-Api-Key: YOUR_API_KEY' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds/dc2c65e4-68d3-4210-a01e-7a54cc9ded2a', { method: 'GET', headers: { 'WLT-Api-Key': 'YOUR_API_KEY' } }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds/dc2c65e4-68d3-4210-a01e-7a54cc9ded2a" headers = { "WLT-Api-Key": "YOUR_API_KEY" } response = requests.get(url, headers=headers) print(response.text) ``` This returns the latest version of the world: ```json Response theme={null} { "world": { "id": "dc2c65e4-68d3-4210-a01e-7a54cc9ded2a", "display_name": "Mystical Forest", "tags": null, "world_marble_url": "https://marble.worldlabs.ai/world/dc2c65e4-68d3-4210-a01e-7a54cc9ded2a", "assets": { "caption": "The scene is a fantastical forest...", "thumbnail_url": "", "splats": { "spz_urls": { "500k": "<500k_spz_url>", "full_res": "", "100k": "<100k_spz_url>" }, "semantics_metadata": { "metric_scale_factor": 1.23, "ground_plane_offset": 0.42 } }, "mesh": { "collider_mesh_url": "" }, "imagery": { "pano_url": "" } }, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:35:00Z", "permission": null, "world_prompt": { "type": "text", "text_prompt": "The scene is a fantastical forest..." }, "model": "marble-1.1" } } ``` The world object includes: * `assets.splats.spz_urls`: 3D Gaussian splat files in SPZ format (100k, 500k, and full resolution) * `assets.splats.semantics_metadata`: Per-world scale and ground-plane fields for metric splat rendering * `assets.mesh.collider_mesh_url`: Collider mesh in GLB format * `assets.mesh.hq_mesh_url`: High-quality textured mesh in GLB format, once exported * `assets.mesh.full_res_mesh_url`: Full-resolution vertex-colored mesh in GLB format, once exported * `assets.imagery.pano_url`: Panorama image * `assets.caption`: AI-generated description of the world * `assets.thumbnail_url`: Thumbnail image for the world * `world_prompt`: The prompt used to generate the world (may be recaptioned) * `model`: The model used for generation The `semantics_metadata` numbers shown above are example values. Use the values returned for the world you are rendering. To download additional formats, call [`POST /marble/v1/worlds/{world_id}:export`](/api/reference/worlds/export). Use `{"asset_type":"splats","format":"ply"}` for a cached `.ply` splat download, or `{"asset_type":"mesh","format":"glb"}` to start a high-quality mesh export. See [Rendering Marble SPZ files in third-party engines](/api/rendering-spz) for the splat scale formula. You can create a world from a single image using either a public URL or by uploading a local file. Recommended image formats: `jpg`, `jpeg`, `png`, `webp`. If your image is already hosted at a public URL, you can reference it directly. Make a `POST` request to the [`/marble/v1/worlds:generate`](/api/reference/worlds/generate) endpoint with your image URL. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Image World", "world_prompt": { "type": "image", "image_prompt": { "source": "uri", "uri": "https://example.com/my-image.jpg" }, "text_prompt": "A beautiful landscape" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Image World', world_prompt: { type: 'image', image_prompt: { source: 'uri', uri: 'https://example.com/my-image.jpg' }, text_prompt: 'A beautiful landscape' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Image World", "world_prompt": { "type": "image", "image_prompt": { "source": "uri", "uri": "https://example.com/my-image.jpg" }, "text_prompt": "A beautiful landscape" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. To use a local image file, first upload it as a media asset, then reference it in your generation request. Make a `POST` request to [`/marble/v1/media-assets:prepare_upload`](/api/reference/media-assets/prepare-upload) to get a signed upload URL. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "file_name": "my-image.jpg", "kind": "image", "extension": "jpg" }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ file_name: 'my-image.jpg', kind: 'image', extension: 'jpg' }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload" payload = { "file_name": "my-image.jpg", "kind": "image", "extension": "jpg" } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns the media asset and upload information: ```json Response theme={null} { "media_asset": { "id": "550e8400-e29b-41d4-a716-446655440000", "file_name": "my-image.jpg", "kind": "image", "extension": "jpg", "created_at": "2025-01-15T10:30:00Z", "updated_at": null, "metadata": null }, "upload_info": { "upload_url": "", "upload_method": "PUT", "required_headers": { "x-goog-content-length-range": "0,1048576000" } } } ``` Upload your image to the signed URL using the method and headers from the response. ```bash Request theme={null} curl -X PUT '' \ -H 'x-goog-content-length-range: 0,1048576000' \ --data-binary '@/path/to/my-image.jpg' ``` ```javascript Request theme={null} const fs = require('fs'); const imageBuffer = fs.readFileSync('/path/to/my-image.jpg'); await fetch('', { method: 'PUT', headers: upload_info.required_headers, body: imageBuffer }); ``` ```python Request theme={null} import requests with open('/path/to/my-image.jpg', 'rb') as f: image_data = f.read() requests.put( '', headers=upload_info['required_headers'], data=image_data ) ``` Use the `media_asset_id` from Step 1 to generate a world. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Image World", "world_prompt": { "type": "image", "image_prompt": { "source": "media_asset", "media_asset_id": "550e8400-e29b-41d4-a716-446655440000" }, "text_prompt": "A beautiful landscape" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Image World', world_prompt: { type: 'image', image_prompt: { source: 'media_asset', media_asset_id: '550e8400-e29b-41d4-a716-446655440000' }, text_prompt: 'A beautiful landscape' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Image World", "world_prompt": { "type": "image", "image_prompt": { "source": "media_asset", "media_asset_id": "550e8400-e29b-41d4-a716-446655440000" }, "text_prompt": "A beautiful landscape" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. The `text_prompt` field is optional. If omitted, a caption will be automatically generated from your image. `is_pano` defaults to `auto`. In auto mode, the API detects valid equirectangular panorama inputs before generation. Set `is_pano` to `true` when you already know the image is a valid pano, or `false` when a wide image should be treated as a standard image. You can create a world from multiple images of the same scene, each with an optional azimuth (horizontal angle in degrees). Recommended image formats: `jpg`, `jpeg`, `png`, `webp`. If your images are already hosted at public URLs, you can reference them directly. Make a `POST` request to the [`/marble/v1/worlds:generate`](/api/reference/worlds/generate) endpoint with your image URLs and their azimuth positions. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Multi-Image World", "world_prompt": { "type": "multi-image", "multi_image_prompt": [ { "azimuth": 0, "content": { "source": "uri", "uri": "https://example.com/front.jpg" } }, { "azimuth": 180, "content": { "source": "uri", "uri": "https://example.com/back.jpg" } } ], "text_prompt": "A cozy living room" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Multi-Image World', world_prompt: { type: 'multi-image', multi_image_prompt: [ { azimuth: 0, content: { source: 'uri', uri: 'https://example.com/front.jpg' } }, { azimuth: 180, content: { source: 'uri', uri: 'https://example.com/back.jpg' } } ], text_prompt: 'A cozy living room' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Multi-Image World", "world_prompt": { "type": "multi-image", "multi_image_prompt": [ { "azimuth": 0, "content": { "source": "uri", "uri": "https://example.com/front.jpg" } }, { "azimuth": 180, "content": { "source": "uri", "uri": "https://example.com/back.jpg" } } ], "text_prompt": "A cozy living room" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. To use local image files, first upload each as a media asset, then reference them in your generation request. For each image, prepare the upload and upload the file as shown in the [image input example](#from-local-file). ```bash Request theme={null} # Prepare upload for first image curl -X POST 'https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "file_name": "front.jpg", "kind": "image", "extension": "jpg" }' # Upload the file to the returned upload_url curl -X PUT '' \ -H 'Content-Type: image/jpeg' \ --data-binary '@/path/to/front.jpg' # Repeat for each additional image ``` ```javascript Request theme={null} const fs = require('fs'); async function uploadImage(filePath, fileName) { // Prepare upload const prepareResponse = await fetch('https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ file_name: fileName, kind: 'image', extension: 'jpg' }) }); const { media_asset, upload_info } = await prepareResponse.json(); // Upload file const imageBuffer = fs.readFileSync(filePath); await fetch(upload_info.upload_url, { method: 'PUT', headers: upload_info.required_headers, body: imageBuffer }); return media_asset.id; } const frontId = await uploadImage('/path/to/front.jpg', 'front.jpg'); const backId = await uploadImage('/path/to/back.jpg', 'back.jpg'); ``` ```python Request theme={null} import requests def upload_image(file_path, file_name): # Prepare upload prepare_response = requests.post( 'https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload', headers={ 'WLT-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, json={ 'file_name': file_name, 'kind': 'image', 'extension': 'jpg' } ) data = prepare_response.json() media_asset = data['media_asset'] upload_info = data['upload_info'] # Upload file with open(file_path, 'rb') as f: requests.put( upload_info['upload_url'], headers=upload_info['required_headers'], data=f.read() ) return media_asset['id'] front_id = upload_image('/path/to/front.jpg', 'front.jpg') back_id = upload_image('/path/to/back.jpg', 'back.jpg') ``` Use the media asset IDs to generate a world. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Multi-Image World", "world_prompt": { "type": "multi-image", "multi_image_prompt": [ { "azimuth": 0, "content": { "source": "media_asset", "media_asset_id": "" } }, { "azimuth": 180, "content": { "source": "media_asset", "media_asset_id": "" } } ], "text_prompt": "A cozy living room" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Multi-Image World', world_prompt: { type: 'multi-image', multi_image_prompt: [ { azimuth: 0, content: { source: 'media_asset', media_asset_id: frontId } }, { azimuth: 180, content: { source: 'media_asset', media_asset_id: backId } } ], text_prompt: 'A cozy living room' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Multi-Image World", "world_prompt": { "type": "multi-image", "multi_image_prompt": [ { "azimuth": 0, "content": { "source": "media_asset", "media_asset_id": front_id } }, { "azimuth": 180, "content": { "source": "media_asset", "media_asset_id": back_id } } ], "text_prompt": "A cozy living room" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. The `azimuth` field specifies the horizontal angle (in degrees) where the image was taken. Use `0` for front, `90` for right, `180` for back, `270` for left. The `text_prompt` field is optional. If omitted, a caption will be automatically generated. You can create a world from a video using either a public URL or by uploading a local file. Recommended video formats: `mp4`, `mov`, `mkv`. If your video is already hosted at a public URL, you can reference it directly. Make a `POST` request to the [`/marble/v1/worlds:generate`](/api/reference/worlds/generate) endpoint with your video URL. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Video World", "world_prompt": { "type": "video", "video_prompt": { "source": "uri", "uri": "https://example.com/my-video.mp4" }, "text_prompt": "A scenic mountain landscape" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Video World', world_prompt: { type: 'video', video_prompt: { source: 'uri', uri: 'https://example.com/my-video.mp4' }, text_prompt: 'A scenic mountain landscape' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Video World", "world_prompt": { "type": "video", "video_prompt": { "source": "uri", "uri": "https://example.com/my-video.mp4" }, "text_prompt": "A scenic mountain landscape" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. To use a local video file, first upload it as a media asset, then reference it in your generation request. Make a `POST` request to [`/marble/v1/media-assets:prepare_upload`](/api/reference/media-assets/prepare-upload) to get a signed upload URL. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "file_name": "my-video.mp4", "kind": "video", "extension": "mp4" }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ file_name: 'my-video.mp4', kind: 'video', extension: 'mp4' }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/media-assets:prepare_upload" payload = { "file_name": "my-video.mp4", "kind": "video", "extension": "mp4" } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns the media asset and upload information: ```json Response theme={null} { "media_asset": { "id": "550e8400-e29b-41d4-a716-446655440000", "file_name": "my-video.mp4", "kind": "video", "extension": "mp4", "created_at": "2025-01-15T10:30:00Z", "updated_at": null, "metadata": null }, "upload_info": { "upload_url": "", "upload_method": "PUT", "required_headers": { "x-goog-content-length-range": "0,1048576000" } } } ``` Upload your video to the signed URL using the method and headers from the response. ```bash Request theme={null} curl -X PUT '' \ -H 'x-goog-content-length-range: 0,1048576000' \ --data-binary '@/path/to/my-video.mp4' ``` ```javascript Request theme={null} const fs = require('fs'); const videoBuffer = fs.readFileSync('/path/to/my-video.mp4'); await fetch('', { method: 'PUT', headers: upload_info.required_headers, body: videoBuffer }); ``` ```python Request theme={null} import requests with open('/path/to/my-video.mp4', 'rb') as f: video_data = f.read() requests.put( '', headers=upload_info['required_headers'], data=video_data ) ``` Use the `media_asset_id` from Step 1 to generate a world. ```bash Request theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "My Video World", "world_prompt": { "type": "video", "video_prompt": { "source": "media_asset", "media_asset_id": "550e8400-e29b-41d4-a716-446655440000" }, "text_prompt": "A scenic mountain landscape" } }' ``` ```javascript Request theme={null} const response = await fetch('https://api.worldlabs.ai/marble/v1/worlds:generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'WLT-Api-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ display_name: 'My Video World', world_prompt: { type: 'video', video_prompt: { source: 'media_asset', media_asset_id: '550e8400-e29b-41d4-a716-446655440000' }, text_prompt: 'A scenic mountain landscape' } }) }); const data = await response.json(); console.log(data); ``` ```python Request theme={null} import requests url = "https://api.worldlabs.ai/marble/v1/worlds:generate" payload = { "display_name": "My Video World", "world_prompt": { "type": "video", "video_prompt": { "source": "media_asset", "media_asset_id": "550e8400-e29b-41d4-a716-446655440000" }, "text_prompt": "A scenic mountain landscape" } } headers = { "WLT-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.text) ``` This returns an Operation object. Poll the operation as shown in the text input example until `done` is `true`. The completed operation's `response` field will contain the generated World. The `text_prompt` field is optional. If omitted, a caption will be automatically generated from your video. # Interactive World Examples Source: https://docs.worldlabs.ai/api/interactive-world-examples Open-source examples that combine World Labs worlds with realtime rendering, physics, controllers, and creative tooling. Build interactive experiences by combining Marble-generated assets with realtime rendering, physics, and game controls. These examples show how developers can splats, collider meshes, and browser-based tooling for more interactive worlds. Interactive tavern demo with physics projectiles in a Gaussian splat scene Add dynamic objects to an interactive scene. A tavern demo that combines Spark Gaussian splat rendering, Rapier physics, and Three.js. It includes first-person controls, projectile collisions, animated characters, spatial audio, and a debug view for the collision mesh. A Claude skillset that turns a single image into a 3D environment workflow, including static Gaussian splats, dynamic object meshes, and audio assets. A browser-based tool for loading `.spz` or `.rad` splat scenes, drawing box, sphere, and cylinder colliders, and exporting collision geometry as `.glb` files. A third-person character controller template using Spark 2.0 LoD, Three.js, and Rapier physics. It includes a kinematic controller, camera controls, animation blending, and collider mesh support. A physics-based first-person controller for navigating Gaussian splat scenes with SparkJS, Three.js, and Rapier. It includes collision, jumping, gamepad support, a ball shooter, post-processing, and debug controls. # Models Source: https://docs.worldlabs.ai/api/models How Marble model names map to World API model parameters ## Model mapping The World API currently supports the following Marble models: | Marble model | API `model` | | ------------------ | ------------------ | | `Marble 1.1 Plus` | `marble-1.1-plus` | | `Marble 1.1` | `marble-1.1` | | `Marble 1.0` | `marble-1.0` | | `Marble 1.0 Draft` | `marble-1.0-draft` | The API still supports the legacy `model` value for Marble 1.0 models. Support for these legacy values will be removed in a future release. | Current `model` value | Legacy `model` value | | --------------------- | -------------------- | | `marble-1.0` | `Marble 0.1-plus` | | `marble-1.0-draft` | `Marble 0.1-mini` | The API currently supports a default model of `marble-1.0` if no model is specified in order to maintain compability. This will change in a future release to default to `marble-1.1`. # Pricing Source: https://docs.worldlabs.ai/api/pricing Understanding API usage and credits ## Credits World API billing is separate from Marble web app billing. * Credits purchased for the Marble app at [marble.worldlabs.ai](https://marble.worldlabs.ai) CANNOT be used with the API * API usage requires credits purchased through the World Labs Platform at [platform.worldlabs.ai](https://platform.worldlabs.ai) If you plan to use the API, make sure you purchase credits on the World Labs Platform, not in the Marble app. The World Labs API uses a credit-based pricing model. You may purchase credits at a fixed rate of \$1.00 USD per 1,250 credits through the [World Labs Platform](https://platform.worldlabs.ai/billing). The minimum purchase is 6,250 credits or \$5.00 USD. API credits do not expire. You can check your current API credit balance with [`GET /marble/v1/credits`](/api/reference/credits/get). ### Auto-refill You may enable auto-refill to avoid service interruptions by automatically purchasing credits when your balance is low. On the [billing page](https://platform.worldlabs.ai/billing), you may enable and configure auto-refill once you have a payment method on file. You may configure the threshold at which auto-refill is triggered, as well as the target balance to refill to. Note that when auto-refill is triggered, your balance will not settle at the target balance. This is because the refill is applied before the cost of the API request is deducted from your balance. For example, assume your threshold is 10,000 credits and your target balance is 20,000 credits, and you have a balance of 10,000 credits. 1. You make an API request that costs 1,500 credits. We would observe that your balance would drop to 8,500 credits, which is below your threshold. 2. The auto-refill would then be triggered and you would be charged to bring your balance to 20,000 credits. 3. Finally, your balance would drop to 18,500 credits to charge for the API request. ## Usage events Credits are consumed as you use the API. API requests may map to one or more usage events, and each usage event may have its own cost in credits associated with it. The total cost of an API request is the sum of the costs of all the usage events it maps to. The cost of each usage event is determined largely by the compute resources required to complete the underlying operation. You may view your usage event history in the [usage page](https://platform.worldlabs.ai/usage). Note that not all API requests consume credits, such as API key creation, media asset upload and management, and Operation polling. If your credit balance is too low when a request arrives, the API returns `402 Payment Required` with a response body that links to the [billing page](https://platform.worldlabs.ai/billing), where you can add credits or enable auto-refill. ### Overage The credit check above compares your balance against a low-balance threshold. It is not a per-request cost estimate, so it does not guarantee that your remaining balance covers the request it admits. A request that starts while your balance is low can cost more credits than you have left and take your balance below zero. Credits used beyond your prepaid balance are not refused and not written off. They become an overage. Unlike prepaid credits, which you pay for up front and draw down as you go, an overage is settled after the fact: at the end of each monthly billing period, usage your prepaid credits did not cover is converted to USD at your account's credit rate — \$1.00 USD per 1,250 credits on standard pay-as-you-go accounts, the same rate at which credits are sold — and charged to your payment method on file. Two things about overage charges surprise people: * **The charge arrives after the usage.** Usage in one month is invoiced at the start of the next, so a charge can land weeks after your last API request. * **Auto-refill does not gate it.** Auto-refill only controls whether we top up your balance automatically. Turning it off does not cap your usage and does not prevent an end-of-period overage charge. To check what an overage charge covers, compare the billing period shown on the invoice against your usage events for that period on the [usage page](https://platform.worldlabs.ai/usage). ### World generation pricing Generating a world using the [World Generation API](/api/reference/worlds/generate) is the most common API request. However, the number of usage events and the cost of generating a world depends on the input type. The World Generation API requires a panorama image (pano) to convert into a 3D world, so it first generates a pano from your input if a pano is not provided. Image prompts use `is_pano: "auto"` by default; when the API detects a valid equirectangular pano, it skips pano generation. As a result, a World Generation API request often includes two usage events: 1. Pano generation (if needed) 2. World generation (from pano) For **Marble 1.0 / `marble-1.0`** and **Marble 1.1 / `marble-1.1`**, the **world generation** usage event is billed at **1,500 credits**. Draft world generation (`marble-1.0-draft`) is billed at **150 credits**. For **Marble 1.1 Plus / `marble-1.1-plus`**, a request always includes **1 world generation** event billed at **1,500 credits**. For larger worlds, our system may automatically determine to include up to 1,500 additional credits for variable world generation. Depending on your input type, you may also incur a **pano generation** usage event. If you generate from an existing pano and the API treats it as a pano, there is no pano generation step, so there is no additional pano generation cost. #### Usage event costs | Usage event | Credits | | --------------------------------- | ------: | | Pano generation (image, pano) | 0 | | Pano generation (text) | 80 | | Pano generation (image, non-pano) | 80 | | Pano generation (multi-image) | 100 | | Pano generation (video) | 100 | | Draft world generation | 150 | | World generation | 1,500 | | HQ mesh export | 3,500 | #### Draft model totals (`marble-1.0-draft`) | Input type | Pano generation | World generation | Total | | ---------------- | --------------: | ---------------: | ----: | | Image (pano) | 0 | 150 | 150 | | Text | 80 | 150 | 230 | | Image (non-pano) | 80 | 150 | 230 | | Multi-image | 100 | 150 | 250 | | Video | 100 | 150 | 250 | #### Standard model totals (`marble-1.0`, `marble-1.1`) | Input type | Pano generation | World generation | Total | | ---------------- | --------------: | ---------------: | ----: | | Image (pano) | 0 | 1,500 | 1,500 | | Text | 80 | 1,500 | 1,580 | | Image (non-pano) | 80 | 1,500 | 1,580 | | Multi-image | 100 | 1,500 | 1,600 | | Video | 100 | 1,500 | 1,600 | #### Marble 1.1 Plus totals (`marble-1.1-plus`) | Input type | Pano generation | Base world generation | Variable world generation | Total | | ---------------- | --------------: | --------------------: | ------------------------- | ----------- | | Image (pano) | 0 | 1,500 | 0-1,500 | 1,500-3,000 | | Text | 80 | 1,500 | 0-1,500 | 1,580-3,080 | | Image (non-pano) | 80 | 1,500 | 0-1,500 | 1,580-3,080 | | Multi-image | 100 | 1,500 | 0-1,500 | 1,600-3,100 | | Video | 100 | 1,500 | 0-1,500 | 1,600-3,100 | #### Observed Marble 1.1 Plus costs The variable world generation charge depends on the size of the world, so actual costs vary by request. The table below shows observed succeeded `marble-1.1-plus` World Generation API costs through June 25, 2026. 1,250 credits = \$1.00. | Input type | Mean | Median | P75 | P90 | P95 | P99 | Max | | ---------------- | ------: | ------: | ------: | ------: | ------: | ------: | ------: | | Image (pano) | \$1.707 | \$1.200 | \$2.400 | \$2.400 | \$2.400 | \$2.400 | \$2.400 | | Text | \$1.600 | \$1.264 | \$2.224 | \$2.464 | \$2.464 | \$2.464 | \$2.464 | | Image (non-pano) | \$1.755 | \$1.264 | \$2.464 | \$2.464 | \$2.464 | \$2.464 | \$2.464 | | Multi-image | \$1.770 | \$1.280 | \$2.480 | \$2.480 | \$2.480 | \$2.480 | \$2.480 | | Video | \$1.735 | \$1.280 | \$2.480 | \$2.480 | \$2.480 | \$2.480 | \$2.480 | Overall `marble-1.1-plus` costs across these input types: * Mean: \$1.714 * Median: \$1.280 * P75/P90: \$2.464 * P95/P99/max: \$2.480 ### Mesh export pricing Exporting a high-quality mesh with the [Export API](/api/reference/worlds/export) (`{"asset_type": "mesh", "format": "glb"}`) is billed as a single **HQ mesh export** usage event of **3,500 credits** (\$2.80). A single export run produces both mesh variants (`textured` and `vertex_colored`), so you are charged once per world, not once per variant. You are only charged when a new export run starts: * If the world already has an exported mesh, the API returns the cached result at no cost. * If a mesh export is already running for the world, the API returns the existing operation at no cost. PLY splat exports (`{"asset_type": "splats", "format": "ply"}`) are free. | Usage event | Credits | | -------------- | ------: | | HQ mesh export | 3,500 | # Rate limits Source: https://docs.worldlabs.ai/api/rate-limits Rate limits and throughput options for World API world generation ## Rate limits World API rate limits protect capacity and keep world generation available for all users. The limits on this page apply to requests that start a world generation with [`POST /marble/v1/worlds:generate`](/api/reference/worlds/generate). There are two relevant types of limits: 1. **Start-rate limits** control how many world generation requests your API user can start over a time window. 2. **Throughput tiers** determine whether your account uses the default limits or an approved higher-throughput configuration. ## Current limits | Tier | Requests covered | Limit | | ------------------------------------ | -------------------------------- | ---------------------------------------------------- | | Default | World generation starts | About 3 requests per minute and 60 requests per hour | | Higher-throughput, approved accounts | Standard world generation starts | About 30 requests per minute | | Higher-throughput, approved accounts | Draft world generation starts | About 90 requests per minute | Standard world generation means non-draft models such as `marble-1.0`, `marble-1.1`, and `marble-1.1-plus`. Draft world generation means `marble-1.0-draft`. Limits are enforced for the API user or account associated with the request, not for each API key. ## How limits are applied * The limits on this page apply to generation starts, not to the number of generations already running. * Usage is tracked in rolling windows, and enforcement is approximate. Short bursts can hit a limit even when average usage over the full window appears lower. * These limits are maximum allowed usage, not guaranteed minimum throughput. Available capacity can vary with service load. * Each accepted world generation usually takes about 5 minutes to complete. Use [`GET /marble/v1/operations/{operation_id}`](/api/reference/operations/get) to poll for the result. ## 429 errors If you exceed a rate limit, the API returns `429 Too Many Requests`. Treat a `429` as a signal to slow generation starts for that API user or account. When handling `429` responses: 1. Pause before retrying. If the response includes `Retry-After`, wait at least that long. 2. Retry with exponential backoff and jitter. 3. Avoid retrying many queued starts at once after a pause. 4. Reduce concurrent start attempts if you continue seeing `429` responses. A generation request that returns `429` has not been accepted. After retrying successfully, the response will contain an Operation object; poll that operation instead of resubmitting the same start request. ## Requesting higher limits If your workload needs sustained concurrent generation starts, contact [support@worldlabs.ai](mailto:support@worldlabs.ai) or your World Labs account representative with: * The API user or account email. * The models you plan to use. * Expected starts per minute and starts per hour. * Whether traffic is bursty or sustained. * A short description of the customer workflow or launch timeline. Higher limits are reviewed per account and may use separate standard and draft world generation limits. # Get credits Source: https://docs.worldlabs.ai/api/reference/credits/get GET /marble/v1/credits Get remaining API credits for the authenticated user. Returns: CreditsResponse with the current aggregate API credit balance. Raises: HTTPException: 404 if the caller is not an API-enabled user HTTPException: 503 if the billing backend is unavailable # Credits Source: https://docs.worldlabs.ai/api/reference/credits/index Check the API credit balance for the authenticated account. Credits endpoints expose billing state needed by API clients before starting work that consumes credits. **GET** `/marble/v1/credits` Return the current aggregate API credit balance for the authenticated user. Use this endpoint before submitting expensive jobs when your client needs to warn users about low balance or pause automated submissions. # API reference Source: https://docs.worldlabs.ai/api/reference/index Browse Marble Public API endpoints by resource. The Marble Public API is organized around the resources you use to create and manage generated worlds. Create worlds, list API-generated worlds, retrieve generated assets, and delete worlds you own. Prepare uploads for input images or videos, then reference those assets in world generation requests. Poll long-running generation and pano jobs until they complete or fail. Generate an RGB panorama from depth input and a text description. Check the remaining API credit balance for the authenticated account. ## Common workflow Use [Prepare media asset upload](/api/reference/media-assets/prepare-upload) for local images or videos that should become world-generation inputs. Use [Generate a world](/api/reference/worlds/generate) with a supported world prompt. Use [Get an operation](/api/reference/operations/get) with the returned `operation_id` until `done` is `true`. Use [Get a world](/api/reference/worlds/get) to fetch the final world metadata and generated asset URLs. # Get media asset Source: https://docs.worldlabs.ai/api/reference/media-assets/get GET /marble/v1/media-assets/{media_asset_id} Get a media asset by ID. Retrieves metadata for a previously created media asset. Args: media_asset_id: The media asset identifier. Returns: MediaAsset object with media_asset_id, file_name, extension, kind, metadata, created_at, and updated_at. Raises: HTTPException: 404 if not found # Media assets Source: https://docs.worldlabs.ai/api/reference/media-assets/index Upload input media for world generation requests. Media asset endpoints let you upload images or videos to managed storage before using them as world-generation inputs. **POST** `/marble/v1/media-assets:prepare_upload` Create a media asset record and receive the signed upload URL and required headers. **GET** `/marble/v1/media-assets/{media_asset_id}` Retrieve metadata for an uploaded media asset. ## Upload flow Call [Prepare media asset upload](/api/reference/media-assets/prepare-upload) with the file name, extension, and media kind. Send the file to the returned signed URL with the required headers from the response. Use the returned `media_asset_id` in [Generate a world](/api/reference/worlds/generate). # Prepare media asset upload Source: https://docs.worldlabs.ai/api/reference/media-assets/prepare-upload POST /marble/v1/media-assets:prepare_upload Prepare a media asset upload for use in world generation. This API endpoint creates a media asset record and returns a signed upload URL. Use this workflow to upload images or videos that you want to reference in world generation requests. ## Workflow 1. **Prepare Upload** (this endpoint): Get a `media_asset_id` and `upload_url` 2. **Upload File**: Use the signed URL to upload your file 3. **Generate World**: Reference the `media_asset_id` in `/worlds:generate` with source type "media_asset" ## Request Parameters - `file_name`: Your file's name (e.g., "landscape.jpg") - `extension`: File extension without dot (e.g., "jpg", "png", "mp4") - `kind`: Either "image" or "video" - `metadata`: Optional custom metadata object ## Response Returns a `MediaAssetPrepareUploadResponse` containing: - `media_asset`: Object with `media_asset_id` (use this in world generation) - `upload_info`: Object with `upload_url`, `required_headers`, and `curl_example` ## Uploading Your File Use the returned `upload_url` and `required_headers` to upload your file: ```bash curl --request PUT \ --url \ --header "Content-Type: " \ --header ": " \ --upload-file /path/to/your/file ``` Replace: - ``: The `upload_url` from the response - ``: MIME type (e.g., `image/png`, `image/jpeg`, `video/mp4`) - `: `: Each header from `required_headers` - `/path/to/your/file`: Path to your local file ## Example Usage in World Generation After uploading, use the `media_asset_id` in a world generation request: ```json { "world_prompt": { "type": "image", "image_prompt": { "source": "media_asset", "media_asset_id": "" } } } ``` # OpenAPI spec Source: https://docs.worldlabs.ai/api/reference/openapi View the OpenAPI specification file We use an OpenAPI spec to generate endpoint documentation. You can consume the spec directly or browse API reference pages. ````yaml theme={null} components: schemas: Content: description: 'Represents content (media, text, images) that can be stored inline or via URL. Supports both direct data storage (up to 10MB) and URL references (up to 20MB).' properties: data_base64: anyOf: - type: string - type: 'null' title: Data Base64 extension: anyOf: - type: string - type: 'null' description: File extension without dot examples: - jpg - png - pdf - txt title: Extension uri: anyOf: - type: string - type: 'null' title: Uri title: Content type: object CreditsResponse: description: Remaining credits for the authenticated API user. properties: remaining_credits: description: Remaining API credits available to the authenticated user. minimum: 0.0 title: Remaining Credits type: number required: - remaining_credits title: CreditsResponse type: object DataBase64Reference: description: Reference to content via base64-encoded data. properties: data_base64: description: Base64-encoded content data title: Data Base64 type: string extension: anyOf: - type: string - type: 'null' description: File extension without dot (e.g., 'jpg', 'png') title: Extension source: const: data_base64 default: data_base64 title: Source type: string required: - data_base64 title: DataBase64Reference type: object DeleteWorldResponse: description: Response from world deletion endpoint. properties: deleted: description: Whether the world was successfully deleted title: Deleted type: boolean world_id: description: ID of the deleted world title: World Id type: string required: - world_id - deleted title: DeleteWorldResponse type: object DepthPanoPrompt: description: 'For models conditioned on a depth pano and text. When depth_pano_image is a log-encoded PNG, z_min and z_max are required to decode it correctly. When it is an EXR file containing raw float depth values, z_min and z_max will both be ignored. Please set z_min and z_max both to None when using an EXR file.' properties: depth_pano_image: $ref: '#/components/schemas/Content' text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: depth-pano default: depth-pano title: Type type: string z_max: anyOf: - type: number - type: 'null' title: Z Max z_min: anyOf: - type: number - type: 'null' title: Z Min required: - depth_pano_image title: DepthPanoPrompt type: object GenerateWorldResponse: description: Response from world generation endpoint. properties: cost: anyOf: - $ref: '#/components/schemas/OperationCost' - type: 'null' description: Settled credit cost for the operation. Populated only on successful completion. created_at: anyOf: - format: date-time type: string - type: 'null' description: Creation timestamp title: Created At done: description: True if the operation is completed title: Done type: boolean error: anyOf: - $ref: '#/components/schemas/OperationError' - type: 'null' description: Error information if the operation failed expires_at: anyOf: - format: date-time type: string - type: 'null' description: Expiration timestamp title: Expires At metadata: anyOf: - additionalProperties: true type: object - type: 'null' description: Service-specific metadata, such as progress percentage title: Metadata operation_id: description: Operation identifier title: Operation Id type: string response: anyOf: - {} - type: 'null' description: Result payload when done=true and no error. Structure depends on operation type. title: Response updated_at: anyOf: - format: date-time type: string - type: 'null' description: Last update timestamp title: Updated At required: - operation_id - done title: GenerateWorldResponse type: object GetOperationResponse_Union_World__PanoDepthToRgbResult__: properties: cost: anyOf: - $ref: '#/components/schemas/OperationCost' - type: 'null' description: Settled credit cost for the operation. Populated only on successful completion. created_at: anyOf: - format: date-time type: string - type: 'null' description: Creation timestamp title: Created At done: description: True if the operation is completed title: Done type: boolean error: anyOf: - $ref: '#/components/schemas/OperationError' - type: 'null' description: Error information if the operation failed expires_at: anyOf: - format: date-time type: string - type: 'null' description: Expiration timestamp title: Expires At metadata: anyOf: - additionalProperties: true type: object - type: 'null' description: Service-specific metadata, such as progress percentage title: Metadata operation_id: description: Operation identifier title: Operation Id type: string response: anyOf: - $ref: '#/components/schemas/World' - $ref: '#/components/schemas/PanoDepthToRgbResult' - type: 'null' description: Result payload when done=true and no error. Structure depends on operation type. title: Response updated_at: anyOf: - format: date-time type: string - type: 'null' description: Last update timestamp title: Updated At required: - operation_id - done title: GetOperationResponse[Union[World, PanoDepthToRgbResult]] type: object HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' title: Detail type: array title: HTTPValidationError type: object ImagePrompt: description: 'Image-to-world generation. Generates a world from an image. text_prompt is optional - if not provided, it will be generated via recaptioning. Recommended image formats: jpg, jpeg, png, webp.' properties: disable_recaption: anyOf: - type: boolean - type: 'null' description: If True, use text_prompt as-is without recaptioning title: Disable Recaption image_prompt: description: Image content for world generation discriminator: mapping: data_base64: '#/components/schemas/DataBase64Reference' media_asset: '#/components/schemas/MediaAssetReference' uri: '#/components/schemas/UriReference' propertyName: source oneOf: - $ref: '#/components/schemas/MediaAssetReference' - $ref: '#/components/schemas/UriReference' - $ref: '#/components/schemas/DataBase64Reference' title: Image Prompt is_pano: $ref: '#/components/schemas/PanoDetectionMode' default: auto description: How to treat the image input as a panorama. `auto` detects valid equirectangular panoramas, `true` always uses the image as a panorama, and `false` treats it as a standard image. text_prompt: anyOf: - type: string - type: 'null' description: Optional text guidance (auto-generated if not provided) title: Text Prompt type: const: image default: image title: Type type: string required: - image_prompt title: ImagePrompt type: object ImageryAssets: description: Imagery asset URLs. properties: pano_url: anyOf: - type: string - type: 'null' description: Panorama image URL title: Pano Url title: ImageryAssets type: object InpaintPanoPrompt: description: For models that inpaint the masked portion of a pano image. properties: pano_image: $ref: '#/components/schemas/Content' pano_mask: $ref: '#/components/schemas/Content' text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: inpaint-pano default: inpaint-pano title: Type type: string required: - pano_image - pano_mask title: InpaintPanoPrompt type: object ListWorldsRequest: description: Request to list API-generated worlds with optional filters. examples: - model: marble-1.0 page_size: 20 sort_by: created_at status: SUCCEEDED - page_size: 50 sort_by: created_at status: SUCCEEDED tags: - fantasy - nature - created_after: '2024-01-01T00:00:00Z' created_before: '2024-12-31T23:59:59Z' page_size: 100 sort_by: created_at - model: marble-1.0-draft page_size: 30 tags: - landscape - page_size: 20 page_token: eyJzbmFwc2hvdF90aW1lIjoiMjAyNC0wMS0wMVQwMDowMDowMFoiLCJsYXN0X2NyZWF0ZWRfYXQiOiIyMDI0LTAxLTAxVDAwOjAwOjAwWiIsImxhc3RfaWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAifQ== sort_by: created_at properties: created_after: anyOf: - format: date-time type: string - type: 'null' description: Filter worlds created after this timestamp (inclusive) title: Created After created_before: anyOf: - format: date-time type: string - type: 'null' description: Filter worlds created before this timestamp (exclusive) title: Created Before is_public: anyOf: - type: boolean - type: 'null' description: Filter by visibility. true=public only, false=private only, null=all title: Is Public model: anyOf: - enum: - marble-1.0-draft - marble-1.0 - marble-1.1 - marble-1.1-plus type: string - enum: - Marble 0.1-mini - Marble 0.1-plus - Marble 1.1-plus type: string - type: 'null' description: Filter by model used for generation. Legacy names are deprecated; use the new lowercase names. title: Model page_size: default: 20 description: Number of results per page (1-100) maximum: 100.0 minimum: 1.0 title: Page Size type: integer page_token: anyOf: - type: string - type: 'null' description: Cursor token for pagination (opaque base64 string from previous response). Use cursor from next_page_token in previous response for consistent pagination. title: Page Token sort_by: default: created_at description: Sort results by created_at or updated_at enum: - created_at - updated_at title: Sort By type: string status: anyOf: - enum: - SUCCEEDED - PENDING - FAILED - RUNNING type: string - type: 'null' description: Filter by world status title: Status tags: anyOf: - items: type: string type: array - type: 'null' description: Filter by tags (returns worlds with ANY of these tags) title: Tags title: ListWorldsRequest type: object ListWorldsResponse: description: Response containing a list of API-generated worlds. properties: next_page_token: anyOf: - type: string - type: 'null' description: Token for fetching the next page of results title: Next Page Token worlds: description: List of worlds items: $ref: '#/components/schemas/World' title: Worlds type: array required: - worlds title: ListWorldsResponse type: object MediaAsset: description: 'A user-uploaded media asset stored in managed storage. MediaAssets can be images, videos, or binary blobs that are used as input to world generation.' properties: created_at: description: Creation timestamp format: date-time title: Created At type: string extension: anyOf: - type: string - type: 'null' description: File extension without dot examples: - mp4 - png - jpg title: Extension file_name: description: File name title: File Name type: string kind: $ref: '#/components/schemas/MediaAssetKind' description: High-level media type examples: - image - video media_asset_id: description: Server-generated media asset identifier title: Media Asset Id type: string metadata: anyOf: - additionalProperties: true type: object - type: 'null' description: Optional application-specific metadata title: Metadata updated_at: anyOf: - format: date-time type: string - type: 'null' description: Last update timestamp title: Updated At required: - media_asset_id - file_name - kind - created_at title: MediaAsset type: object MediaAssetKind: description: High-level media asset type. enum: - image - video title: MediaAssetKind type: string MediaAssetPrepareUploadRequest: description: Request to prepare a media asset upload. properties: extension: anyOf: - type: string - type: 'null' description: File extension without dot examples: - mp4 - png - jpg title: Extension file_name: description: File name maxLength: 64 title: File Name type: string kind: $ref: '#/components/schemas/MediaAssetKind' description: High-level media type examples: - image - video metadata: anyOf: - additionalProperties: true type: object - type: 'null' description: Optional application-specific metadata title: Metadata required: - file_name - kind title: MediaAssetPrepareUploadRequest type: object MediaAssetPrepareUploadResponse: description: Response from preparing a media asset upload. properties: media_asset: $ref: '#/components/schemas/MediaAsset' description: The created media asset upload_info: $ref: '#/components/schemas/UploadUrlInfo' description: Upload URL information required: - media_asset - upload_info title: MediaAssetPrepareUploadResponse type: object MediaAssetReference: description: Reference to a previously uploaded MediaAsset. properties: media_asset_id: description: ID of a MediaAsset resource previously created and marked READY title: Media Asset Id type: string source: const: media_asset default: media_asset title: Source type: string required: - media_asset_id title: MediaAssetReference type: object MeshAssets: description: Mesh asset URLs. properties: collider_mesh_url: anyOf: - type: string - type: 'null' description: Collider mesh URL title: Collider Mesh Url title: MeshAssets type: object MultiImagePrompt-Input: description: 'Multi-image-to-world generation. Generates a world from multiple images. text_prompt is optional. Recommended image formats: jpg, jpeg, png, webp.' properties: disable_recaption: anyOf: - type: boolean - type: 'null' description: If True, use text_prompt as-is without recaptioning title: Disable Recaption multi_image_prompt: description: List of images with optional spherical locations items: $ref: '#/components/schemas/SphericallyLocatedContent-Input' title: Multi Image Prompt type: array reconstruct_images: default: false description: Whether to use reconstruction mode (allows up to 8 images, otherwise 4) title: Reconstruct Images type: boolean text_prompt: anyOf: - type: string - type: 'null' description: Optional text guidance (auto-generated if not provided) title: Text Prompt type: const: multi-image default: multi-image title: Type type: string required: - multi_image_prompt title: MultiImagePrompt type: object MultiImagePrompt-Output: description: For world models supporting multi-image (+ text) input. properties: multi_image_prompt: items: $ref: '#/components/schemas/SphericallyLocatedContent-Output' title: Multi Image Prompt type: array reconstruct_images: default: false title: Reconstruct Images type: boolean text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: multi-image default: multi-image title: Type type: string required: - multi_image_prompt title: MultiImagePrompt type: object OperationCost: description: Settled credit cost for a successful operation. properties: line_items: description: Usage events that make up the operation's total credits items: $ref: '#/components/schemas/OperationCostLineItem' title: Line Items type: array total_credits: description: Total credits charged for the operation title: Total Credits type: integer required: - total_credits title: OperationCost type: object OperationCostLineItem: description: A single usage event contributing to an operation's total credits. properties: credits: description: Credits charged for this usage event title: Credits type: integer name: description: Human-readable usage event name title: Name type: string required: - name - credits title: OperationCostLineItem type: object OperationError: description: Error information for a failed operation. properties: code: anyOf: - type: integer - type: 'null' description: Error code title: Code message: anyOf: - type: string - type: 'null' description: Error message title: Message title: OperationError type: object Operation_PanoDepthToRgbResult_: properties: cost: anyOf: - $ref: '#/components/schemas/OperationCost' - type: 'null' description: Settled credit cost for the operation. Populated only on successful completion. created_at: anyOf: - format: date-time type: string - type: 'null' description: Creation timestamp title: Created At done: description: True if the operation is completed title: Done type: boolean error: anyOf: - $ref: '#/components/schemas/OperationError' - type: 'null' description: Error information if the operation failed expires_at: anyOf: - format: date-time type: string - type: 'null' description: Expiration timestamp title: Expires At metadata: anyOf: - additionalProperties: true type: object - type: 'null' description: Service-specific metadata, such as progress percentage title: Metadata operation_id: description: Operation identifier title: Operation Id type: string response: anyOf: - $ref: '#/components/schemas/PanoDepthToRgbResult' - type: 'null' description: Result payload when done=true and no error. Structure depends on operation type. updated_at: anyOf: - format: date-time type: string - type: 'null' description: Last update timestamp title: Updated At required: - operation_id - done title: Operation[PanoDepthToRgbResult] type: object PanoDepthToRgbRequest: description: "Request to generate an RGB panorama from a depth panorama.\n\n\ Provide a depth panorama and a text prompt describing\nthe desired appearance.\ \ The depth map supplies the\nscene geometry; the model synthesizes textures\ \ that\nloosely adhere to that geometry and returns a panoramic RGB image.\n\ \nAccepted depth inputs:\n\n* **EXR**: float depth values. Omit ``z_min``\n\ \ and ``z_max``.\n* **PNG**: depth values normalized to [0, 1].\n Provide\ \ both ``z_min`` and ``z_max`` so the service\n can decode the PNG correctly.\n\ \nFor a complete PNG-based workflow, see the\n`web-chisel-depth-png example\ \ `.\n\ \nThis endpoint returns a long-running ``Operation``.\nPoll ``GET /operations/{operation_id}``\ \ until\n``done`` is ``true``, then read the generated\npanorama URL from\ \ ``response.pano_url``." examples: - depth_pano_image: source: uri uri: https://example.com/depth.exr text_prompt: The scene is a warm, rustic cabin interior with a realistic style and a cozy winter atmosphere. Wooden walls and exposed beams frame the room, while a stone fireplace casts soft amber light across simple furnishings and natural textures. The 360 scene is faultless. - depth_pano_image: source: uri uri: https://example.com/greenhouse-depth.exr text_prompt: A peaceful greenhouse workshop filled with plants and glassware, with soft sunlight, weathered wood, and ivy creeping along the walls. - depth_pano_image: source: uri uri: https://example.com/depth.png text_prompt: The scene is a warm Mediterranean kitchen rendered in a realistic style with a bright, welcoming coastal atmosphere. The overall tone is relaxed and sunlit, combining natural materials with crisp handcrafted details. Blue ceramic tiles wrap the backsplash and continue across key architectural surfaces, giving the room a cool accent against creamy plaster walls. Wooden cabinetry lines the perimeter in a balanced arrangement, with paneled doors, open shelving, and brass hardware contributing to the room's refined but lived-in character. A broad countertop of pale stone runs between the cabinets, creating a continuous work surface that anchors the cooking area. A central table with a solid wood top occupies the middle of the room, surrounded by simple chairs and small ceramic vessels that reinforce the artisanal style. Arched openings connect the kitchen to adjacent areas, while French doors admit generous daylight and suggest an outdoor terrace nearby. Terracotta accents, woven textures, and neatly arranged cookware add visual richness without cluttering the space. The cabinetry, tiled surfaces, and stone counters form a coherent ring around the room, while the table remains the central focal element within the composition. The 360 scene is faultless. z_max: 100.0 z_min: 0.1 properties: depth_pano_image: description: Depth panorama image (EXR or PNG). Provide via URI, media_asset, or base64 (max 100 MB). discriminator: mapping: data_base64: '#/components/schemas/DataBase64Reference' media_asset: '#/components/schemas/MediaAssetReference' uri: '#/components/schemas/UriReference' propertyName: source oneOf: - $ref: '#/components/schemas/MediaAssetReference' - $ref: '#/components/schemas/UriReference' - $ref: '#/components/schemas/DataBase64Reference' title: Depth Pano Image seed: anyOf: - maximum: 4294967295.0 minimum: 0.0 type: integer - type: 'null' description: Random seed for reproducible generation. title: Seed text_prompt: description: Text description of the desired appearance. title: Text Prompt type: string z_max: anyOf: - exclusiveMinimum: 0.0 type: number - type: 'null' description: Maximum depth. Required for PNG depth maps (normalized to [0, 1]). Must be provided together with z_min and must be greater than z_min. Omit for EXR depth maps. title: Z Max z_min: anyOf: - exclusiveMinimum: 0.0 type: number - type: 'null' description: Minimum depth. Required for PNG depth maps (normalized to [0, 1]). Must be provided together with z_max. Omit for EXR depth maps. title: Z Min required: - depth_pano_image - text_prompt title: PanoDepthToRgbRequest type: object PanoDepthToRgbResult: description: 'Result from depth-to-RGB panorama generation. Returned inside ``Operation.response`` when the operation completes successfully (``done=true``).' properties: pano_url: anyOf: - type: string - type: 'null' description: URL of the generated RGB panorama image. Available when the operation succeeds. title: Pano Url title: PanoDepthToRgbResult type: object PanoDetectionMode: enum: - auto - true - false Permission: description: Access control permissions for a resource. properties: allow_id_access: default: false title: Allow Id Access type: boolean allowed_readers: items: type: string title: Allowed Readers type: array allowed_writers: items: type: string title: Allowed Writers type: array public: default: false title: Public type: boolean title: Permission type: object Prompt: description: 'For world models generating a world from a single image (+ text). Images can be generated using the :image-generation method. If no text prompt is provided, it will be generated via recaption.' properties: image_prompt: $ref: '#/components/schemas/Content' is_pano: default: false title: Is Pano type: boolean text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: image default: image title: Type type: string required: - image_prompt title: Prompt type: object SphericallyLocatedContent-Input: description: Content with a preferred location on the sphere. properties: azimuth: anyOf: - type: number - type: 'null' description: Azimuth angle in degrees title: Azimuth content: description: The content at this location discriminator: mapping: data_base64: '#/components/schemas/DataBase64Reference' media_asset: '#/components/schemas/MediaAssetReference' uri: '#/components/schemas/UriReference' propertyName: source oneOf: - $ref: '#/components/schemas/MediaAssetReference' - $ref: '#/components/schemas/UriReference' - $ref: '#/components/schemas/DataBase64Reference' title: Content required: - content title: SphericallyLocatedContent type: object SphericallyLocatedContent-Output: description: Content with a preferred location on the sphere. properties: azimuth: anyOf: - type: number - type: 'null' title: Azimuth data_base64: anyOf: - type: string - type: 'null' title: Data Base64 extension: anyOf: - type: string - type: 'null' description: File extension without dot examples: - jpg - png - pdf - txt title: Extension uri: anyOf: - type: string - type: 'null' title: Uri title: SphericallyLocatedContent type: object SplatAssets: description: Gaussian splat asset URLs. properties: semantics_metadata: anyOf: - $ref: '#/components/schemas/WorldSemanticsMetadata' - type: 'null' description: Semantic metadata for the world spz_urls: anyOf: - additionalProperties: type: string type: object - type: 'null' description: URLs for SPZ format Gaussian splat files title: Spz Urls title: SplatAssets type: object UploadUrlInfo: description: Information required to upload raw bytes directly to storage. properties: curl_example: anyOf: - type: string - type: 'null' description: Optional curl example for convenience title: Curl Example required_headers: anyOf: - additionalProperties: type: string type: object - type: 'null' description: Headers that MUST be included when uploading (e.g. Content-Type) title: Required Headers upload_method: description: Upload method title: Upload Method type: string upload_url: description: Signed URL for uploading bytes via PUT title: Upload Url type: string required: - upload_url - upload_method title: UploadUrlInfo type: object UriReference: description: Reference to content via a publicly accessible URL. properties: source: const: uri default: uri title: Source type: string uri: description: Publicly accessible URL pointing to the media title: Uri type: string required: - uri title: UriReference type: object ValidationError: properties: loc: items: anyOf: - type: string - type: integer title: Location type: array msg: title: Message type: string type: title: Error Type type: string required: - loc - msg - type title: ValidationError type: object VideoPrompt-Input: description: 'Video-to-world generation. Generates a world from a video. text_prompt is optional. Recommended video formats: mp4, webm, mov, avi. Maximum video size: 100MB.' properties: disable_recaption: anyOf: - type: boolean - type: 'null' description: If True, use text_prompt as-is without recaptioning title: Disable Recaption text_prompt: anyOf: - type: string - type: 'null' description: Optional text guidance (auto-generated if not provided) title: Text Prompt type: const: video default: video title: Type type: string video_prompt: description: Video content for world generation discriminator: mapping: data_base64: '#/components/schemas/DataBase64Reference' media_asset: '#/components/schemas/MediaAssetReference' uri: '#/components/schemas/UriReference' propertyName: source oneOf: - $ref: '#/components/schemas/MediaAssetReference' - $ref: '#/components/schemas/UriReference' - $ref: '#/components/schemas/DataBase64Reference' title: Video Prompt required: - video_prompt title: VideoPrompt type: object VideoPrompt-Output: description: For world models supporting video (+ text) input. properties: text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: video default: video title: Type type: string video_prompt: $ref: '#/components/schemas/Content' required: - video_prompt title: VideoPrompt type: object World: description: A generated world, including asset URLs. properties: assets: anyOf: - $ref: '#/components/schemas/WorldAssets' - type: 'null' description: Generated world assets created_at: anyOf: - format: date-time type: string - type: 'null' description: Creation timestamp title: Created At display_name: description: Display name title: Display Name type: string model: anyOf: - type: string - type: 'null' description: Model used for generation title: Model permission: anyOf: - $ref: '#/components/schemas/Permission' - type: 'null' description: Access control permissions for the world tags: anyOf: - items: type: string type: array - type: 'null' description: Tags associated with the world title: Tags updated_at: anyOf: - format: date-time type: string - type: 'null' description: Last update timestamp title: Updated At world_id: description: World identifier title: World Id type: string world_marble_url: description: World Marble URL title: World Marble Url type: string world_prompt: anyOf: - discriminator: mapping: depth-pano: '#/components/schemas/DepthPanoPrompt' image: '#/components/schemas/Prompt' inpaint-pano: '#/components/schemas/InpaintPanoPrompt' multi-image: '#/components/schemas/MultiImagePrompt-Output' text: '#/components/schemas/WorldTextPrompt-Output' video: '#/components/schemas/VideoPrompt-Output' propertyName: type oneOf: - $ref: '#/components/schemas/WorldTextPrompt-Output' - $ref: '#/components/schemas/Prompt' - $ref: '#/components/schemas/MultiImagePrompt-Output' - $ref: '#/components/schemas/VideoPrompt-Output' - $ref: '#/components/schemas/DepthPanoPrompt' - $ref: '#/components/schemas/InpaintPanoPrompt' - type: 'null' description: World prompt title: World Prompt required: - world_id - display_name - world_marble_url title: World type: object WorldAssets: description: Downloadable outputs of world generation. properties: caption: anyOf: - type: string - type: 'null' description: AI-generated description of the world title: Caption imagery: anyOf: - $ref: '#/components/schemas/ImageryAssets' - type: 'null' description: Imagery assets mesh: anyOf: - $ref: '#/components/schemas/MeshAssets' - type: 'null' description: Mesh assets splats: anyOf: - $ref: '#/components/schemas/SplatAssets' - type: 'null' description: Gaussian splat assets thumbnail_url: anyOf: - type: string - type: 'null' description: Thumbnail URL for the world title: Thumbnail Url title: WorldAssets type: object WorldSemanticsMetadata: description: "Semantic metadata describing how to interpret the generated splat\n\ asset in real-world units.\n\nThe exported asset is in arbitrary model units.\ \ Apply the transform\nbelow to convert to a metric, ground-aligned frame:\n\ \n metric_xyz = raw_xyz * metric_scale_factor\n aligned_xyz = metric_xyz\ \ - (0, ground_plane_offset, 0)\n\nBoth fields are populated on every world\ \ generated by the current\npipeline. They remain Optional only for backwards\ \ compatibility with\nworlds generated before December 2025, which predate\ \ metric-scale\nestimation.\n\nTODO: once pre-December-2025 worlds are migrated\ \ or aged out, tighten\nthese fields to non-Optional and drop the `| None`\ \ from the type." properties: ground_plane_offset: anyOf: - type: number - type: 'null' description: Vertical (Y-axis) offset, in meters, of the detected ground plane in the metric-scaled frame. Subtract from Y after applying `metric_scale_factor` to place the ground at y=0. title: Ground Plane Offset metric_scale_factor: anyOf: - type: number - type: 'null' description: Multiplicative factor that converts raw asset coordinates to meters. Multiply all XYZ positions (and isotropic scales) by this value. A value of 1.0 means scale could not be inferred and the asset was left in model units. title: Metric Scale Factor title: WorldSemanticsMetadata type: object WorldTextPrompt-Input: description: 'Text-to-world generation. Generates a world from a text description. text_prompt is REQUIRED.' properties: disable_recaption: anyOf: - type: boolean - type: 'null' description: If True, use text_prompt as-is without recaptioning title: Disable Recaption text_prompt: anyOf: - type: string - type: 'null' description: Optional text guidance (auto-generated if not provided) title: Text Prompt type: const: text default: text title: Type type: string title: WorldTextPrompt type: object WorldTextPrompt-Output: description: Input prompt class for text-conditioned world generation. properties: text_prompt: anyOf: - type: string - type: 'null' title: Text Prompt type: const: text default: text title: Type type: string title: WorldTextPrompt type: object WorldsGenerateRequest: description: Request to generate a world from text, image, multi-image, or video input. examples: - display_name: Coastal Castle model: marble-1.0 permission: public: false seed: 42 tags: - fantasy - coastal world_prompt: text_prompt: A grand, ivy-covered castle rises from the shoreline at sunset, its reflection shimmering in the tranquil ocean waves. type: text - display_name: Greenhouse Workshop model: marble-1.0-draft world_prompt: image_prompt: source: uri uri: https://example.com/my-image.jpg text_prompt: A peaceful, sunlit greenhouse workshop filled with plants and glassware, where soft light filters through cracked windows and ivy creeps along the walls. type: image - display_name: World from an Existing Pano model: marble-1.1 world_prompt: image_prompt: source: uri uri: https://example.com/my-equirect-pano.jpg is_pano: auto type: image - permission: public: true world_prompt: type: video video_prompt: media_asset_id: 550e8400e29b41d4a716446655440000 source: media_asset - display_name: World from Multiple Images model: marble-1.1 world_prompt: multi_image_prompt: - azimuth: 0 content: source: uri uri: https://example.com/image1.jpg - azimuth: 180 content: source: uri uri: https://example.com/image2.jpg type: multi-image properties: display_name: anyOf: - maxLength: 64 type: string - type: 'null' description: Optional human-readable title for the world (max 64 characters). Stored as world metadata and returned in world responses; does not affect generation. If omitted, World Labs may generate a title from the prompt. title: Display Name model: anyOf: - enum: - marble-1.0-draft - marble-1.0 - marble-1.1 - marble-1.1-plus type: string - enum: - Marble 0.1-mini - Marble 0.1-plus - Marble 1.1-plus type: string default: marble-1.1 description: The model to use for generation. marble-1.1-plus includes dynamic world sizing. Legacy names ('Marble 0.1-plus', etc.) still work but are deprecated and will be removed in a future release. title: Model permission: $ref: '#/components/schemas/Permission' default: allow_id_access: false allowed_readers: [] allowed_writers: [] public: false description: The permission for the world seed: anyOf: - maximum: 4294967295.0 minimum: 0.0 type: integer - type: 'null' description: Random seed for generation title: Seed tags: anyOf: - items: type: string maxItems: 10 type: array - type: 'null' description: Optional tags for the world (max 10 tags, each up to 32 characters) title: Tags world_prompt: description: The prompt specifying how to generate the world discriminator: mapping: image: '#/components/schemas/ImagePrompt' multi-image: '#/components/schemas/MultiImagePrompt-Input' text: '#/components/schemas/WorldTextPrompt-Input' video: '#/components/schemas/VideoPrompt-Input' propertyName: type oneOf: - $ref: '#/components/schemas/WorldTextPrompt-Input' - $ref: '#/components/schemas/ImagePrompt' - $ref: '#/components/schemas/MultiImagePrompt-Input' - $ref: '#/components/schemas/VideoPrompt-Input' title: World Prompt required: - world_prompt title: WorldsGenerateRequest type: object securitySchemes: ApiKeyAuth: description: API key for authentication. Get your key from the developer portal. in: header name: WLT-Api-Key type: apiKey info: description: Public-facing API for the Marble platform summary: Marble Public API v1 title: Marble Public API v1 version: 1.0.0 openapi: 3.1.0 paths: /marble/v1/credits: get: description: "Get remaining API credits for the authenticated user.\n\nReturns:\n\ \ CreditsResponse with the current aggregate API credit balance.\n\nRaises:\n\ \ HTTPException: 404 if the caller is not an API-enabled user\n HTTPException:\ \ 503 if the billing backend is unavailable" operationId: get_credits_marble_v1_credits_get responses: '200': content: application/json: schema: $ref: '#/components/schemas/CreditsResponse' description: Successful Response summary: Get Credits tags: - credits /marble/v1/media-assets/{media_asset_id}: get: description: "Get a media asset by ID.\n\nRetrieves metadata for a previously\ \ created media asset.\n\nArgs:\n media_asset_id: The media asset identifier.\n\ \nReturns:\n MediaAsset object with media_asset_id, file_name, extension,\ \ kind,\n metadata, created_at, and updated_at.\n\nRaises:\n HTTPException:\ \ 404 if not found" operationId: get_media_asset_marble_v1_media_assets__media_asset_id__get parameters: - in: path name: media_asset_id required: true schema: title: Media Asset Id type: string responses: '200': content: application/json: schema: $ref: '#/components/schemas/MediaAsset' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Get Media Asset /marble/v1/media-assets:prepare_upload: post: description: "Prepare a media asset upload for use in world generation.\n\n\ This API endpoint creates a media asset record and returns a signed upload\ \ URL.\nUse this workflow to upload images or videos that you want to reference\ \ in world\ngeneration requests.\n\n## Workflow\n\n1. **Prepare Upload** (this\ \ endpoint): Get a `media_asset_id` and `upload_url`\n2. **Upload File**:\ \ Use the signed URL to upload your file\n3. **Generate World**: Reference\ \ the `media_asset_id` in `/worlds:generate` with\n source type \"media_asset\"\ \n\n## Request Parameters\n\n- `file_name`: Your file's name (e.g., \"landscape.jpg\"\ )\n- `extension`: File extension without dot (e.g., \"jpg\", \"png\", \"mp4\"\ )\n- `kind`: Either \"image\" or \"video\"\n- `metadata`: Optional custom\ \ metadata object\n\n## Response\n\nReturns a `MediaAssetPrepareUploadResponse`\ \ containing:\n\n- `media_asset`: Object with `media_asset_id` (use this in\ \ world generation)\n- `upload_info`: Object with `upload_url`, `required_headers`,\ \ and `curl_example`\n\n## Uploading Your File\n\nUse the returned `upload_url`\ \ and `required_headers` to upload your file:\n\n```bash\ncurl --request PUT\ \ \\\n --url \\\n --header \"Content-Type: \"\ \ \\\n --header \": \" \\\n --upload-file /path/to/your/file\n\ ```\n\nReplace:\n- ``: The `upload_url` from the response\n- ``:\ \ MIME type (e.g., `image/png`, `image/jpeg`, `video/mp4`)\n- `:\ \ `: Each header from `required_headers`\n- `/path/to/your/file`:\ \ Path to your local file\n\n## Example Usage in World Generation\n\nAfter\ \ uploading, use the `media_asset_id` in a world generation request:\n\n```json\n\ {\n \"world_prompt\": {\n \"type\": \"image\",\n \"image_prompt\":\ \ {\n \"source\": \"media_asset\",\n \"media_asset_id\": \"\"\ \n }\n }\n}\n```" operationId: prepare_media_asset_upload_marble_v1_media_assets_prepare_upload_post requestBody: content: application/json: schema: $ref: '#/components/schemas/MediaAssetPrepareUploadRequest' required: true responses: '200': content: application/json: schema: $ref: '#/components/schemas/MediaAssetPrepareUploadResponse' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Prepare a media asset upload /marble/v1/operations/{operation_id}: get: description: "Get an operation by ID.\n\nPoll this endpoint to check the status\ \ of a long-running operation.\nWhen done=true, the response field contains\ \ the generated world.\n\nArgs:\n operation_id: The operation identifier\ \ from /worlds:generate.\n\nReturns:\n GetOperationResponse[World] with:\n\ \ - operation_id: Operation identifier\n - created_at: Creation\ \ timestamp\n - updated_at: Last update timestamp\n - expires_at:\ \ Expiration timestamp\n - done: true when complete, false while in\ \ progress\n - error: Error details if failed, null otherwise\n \ \ - metadata: Progress information and world_id\n - response: Generated\ \ World if done=true, null otherwise\n - cost: Settled credit cost\ \ breakdown; populated only when the\n operation completed successfully\ \ (done=true with no error)\n and a public price is known for its\ \ operation type. Failed\n or in-progress operations return null.\n\ \nRaises:\n HTTPException: 401 if unauthorized\n HTTPException: 404\ \ if operation not found\n HTTPException: 500 if request fails" operationId: get_operation_marble_v1_operations__operation_id__get parameters: - in: path name: operation_id required: true schema: title: Operation Id type: string responses: '200': content: application/json: schema: $ref: '#/components/schemas/GetOperationResponse_Union_World__PanoDepthToRgbResult__' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Get Operation /marble/v1/pano:depth_to_rgb: post: description: "Generate an RGB panorama from a depth panorama.\n\nProvide a depth\ \ panorama and a text prompt\ndescribing the desired appearance. The depth\ \ map\nsupplies the scene geometry; the model synthesizes\ntextures that match\ \ that geometry and returns a\npanoramic RGB image.\n\nAccepted depth inputs:\n\ \n* **EXR**: float depth values. Omit\n ``z_min`` and ``z_max``.\n* **PNG**:\ \ depth values normalized to [0, 1].\n Provide both ``z_min`` and ``z_max``\ \ so the\n service can decode the PNG correctly.\n\nFor a complete PNG-based\ \ workflow, see the\n[web-chisel-depth-png example](https://github.com/worldlabsai/worldlabs-api-examples/tree/main/web-chisel-depth-png).\n\ \nReturns a long-running ``Operation``. Poll\n``GET /operations/{operation_id}``\ \ until\n``done`` is ``true``, then read the generated\npanorama URL from\ \ ``response.pano_url``.\n\nRaises:\n HTTPException: 400 if invalid request\n\ \ HTTPException: 402 if insufficient credits\n HTTPException: 500 if\ \ generation could not start" operationId: pano_depth_to_rgb_marble_v1_pano_depth_to_rgb_post requestBody: content: application/json: schema: $ref: '#/components/schemas/PanoDepthToRgbRequest' required: true responses: '200': content: application/json: schema: $ref: '#/components/schemas/Operation_PanoDepthToRgbResult_' description: Successful Response '402': content: application/json: example: detail: Insufficient API credits to start this request. Add credits or enable auto-refill at https://platform.worldlabs.ai/billing. description: The account has insufficient API credits for this request. '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Pano Depth To Rgb /marble/v1/worlds/{world_id}: delete: description: "Delete a world by ID.\n\nPermanently deletes a world and its associated\ \ assets.\nOnly the world owner can delete a world.\n\nArgs:\n world_id:\ \ The unique identifier of the world to delete.\n\nReturns:\n DeleteWorldResponse\ \ confirming the deletion.\n\nRaises:\n HTTPException: 403 if user is not\ \ the world owner\n HTTPException: 404 if world not found\n HTTPException:\ \ 500 if deletion fails" operationId: delete_world_marble_v1_worlds__world_id__delete parameters: - in: path name: world_id required: true schema: title: World Id type: string responses: '200': content: application/json: schema: $ref: '#/components/schemas/DeleteWorldResponse' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Delete World get: description: "Get a world by ID.\n\nRetrieves a world's details including generated\ \ assets if available.\nOnly the world owner or users with access to public\ \ worlds can retrieve them.\n\nArgs:\n world_id: The unique identifier\ \ of the world.\n\nReturns:\n World object with world_id, display_name,\ \ tags, assets, created_at,\n updated_at, permission, model, world_prompt,\ \ and world_marble_url.\n\nRaises:\n HTTPException: 404 if world not found\ \ or access denied" operationId: get_world_marble_v1_worlds__world_id__get parameters: - in: path name: world_id required: true schema: title: World Id type: string responses: '200': content: application/json: schema: $ref: '#/components/schemas/World' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Get World /marble/v1/worlds:generate: post: description: "Start world generation.\n\nCreates a new world generation job\ \ and returns a long-running operation.\nPoll the /operations/{operation_id}\ \ endpoint to check generation status\nand retrieve the generated world when\ \ complete.\n\nArgs:\n request: The world generation request containing\ \ world_prompt, display_name,\n tags, model, seed, and permission settings.\n\ \nReturns:\n GenerateWorldResponse with operation_id and timestamps. Use\ \ the operation_id\n to poll for completion.\n\nRaises:\n HTTPException:\ \ 400 if invalid request or content violates policies\n HTTPException:\ \ 402 if insufficient credits\n HTTPException: 500 if generation could\ \ not be started" operationId: generate_world_marble_v1_worlds_generate_post requestBody: content: application/json: schema: $ref: '#/components/schemas/WorldsGenerateRequest' required: true responses: '200': content: application/json: schema: $ref: '#/components/schemas/GenerateWorldResponse' description: Successful Response '402': content: application/json: example: detail: Insufficient API credits to start this request. Add credits or enable auto-refill at https://platform.worldlabs.ai/billing. description: The account has insufficient API credits for this request. '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: Generate World /marble/v1/worlds:list: post: description: "List worlds with optional filters.\n\nReturns worlds created through\ \ the API with optional filtering and pagination.\n\nArgs:\n request: List\ \ request with optional filters:\n - page_size: Number of results per\ \ page (default: 10)\n - page_token: Pagination token from previous\ \ response\n - status: Filter by status (e.g., \"COMPLETED\")\n \ \ - model: Filter by model name (e.g., \"marble-1.0-plus\")\n -\ \ tags: Filter by tags (matches worlds with any tag)\n - is_public:\ \ Filter by visibility (true=public, false=private, null=all)\n - created_after:\ \ Filter by creation time (after timestamp)\n - created_before: Filter\ \ by creation time (before timestamp)\n - sort_by: Sort order (\"created_at\"\ \ or \"updated_at\")\n\nReturns:\n ListWorldsResponse with worlds list\ \ and next_page_token for pagination.\n\nRaises:\n HTTPException: 400 if\ \ invalid parameters\n HTTPException: 500 if request fails" operationId: list_worlds_marble_v1_worlds_list_post requestBody: content: application/json: schema: $ref: '#/components/schemas/ListWorldsRequest' required: true responses: '200': content: application/json: schema: $ref: '#/components/schemas/ListWorldsResponse' description: Successful Response '422': content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' description: Validation Error summary: List Worlds security: - ApiKeyAuth: [] servers: - description: World API url: https://api.worldlabs.ai ```` # Get an operation Source: https://docs.worldlabs.ai/api/reference/operations/get GET /marble/v1/operations/{operation_id} Get an operation by ID. Poll this endpoint to check the status of a long-running operation. When done=true, the response field contains the generated world. Args: operation_id: The operation identifier from /worlds:generate. Returns: GetOperationResponse[World] with: - operation_id: Operation identifier - created_at: Creation timestamp - updated_at: Last update timestamp - expires_at: Expiration timestamp - done: true when complete, false while in progress - error: Error details if failed, null otherwise - metadata: Progress information and world_id - response: Generated World if done=true, null otherwise - cost: Settled credit cost breakdown; populated only when the operation completed successfully (done=true with no error) and a public price is known for its operation type. Failed or in-progress operations return null. Raises: HTTPException: 401 if unauthorized HTTPException: 404 if operation not found HTTPException: 500 if request fails # Operations Source: https://docs.worldlabs.ai/api/reference/operations/index Poll long-running API jobs until they complete. Long-running endpoints return an `operation_id`. Use the operations endpoint to check status, inspect progress metadata, and retrieve the final response. **GET** `/marble/v1/operations/{operation_id}` Poll a world-generation or pano operation until `done` is `true`. ## Response states * `done: false`: The job is still running. Check `metadata` for progress details when present. * `done: true` with `error: null`: The job completed. Read `response` for the generated result. * `done: true` with `error`: The job failed. Use the error code and message to decide whether to retry or adjust the request. # Depth to RGB Source: https://docs.worldlabs.ai/api/reference/pano/depth_to_rgb POST /marble/v1/pano:depth_to_rgb Generate an RGB panorama from a depth panorama. Provide a depth panorama and a text prompt describing the desired appearance. The depth map supplies the scene geometry; the model synthesizes textures that match that geometry and returns a panoramic RGB image. Accepted depth inputs: * **EXR**: float depth values. Omit ``z_min`` and ``z_max``. * **PNG**: depth values normalized to [0, 1]. Provide both ``z_min`` and ``z_max`` so the service can decode the PNG correctly. For a complete PNG-based workflow, see the [web-chisel-depth-png example](https://github.com/worldlabsai/worldlabs-api-examples/tree/main/web-chisel-depth-png). Returns a long-running ``Operation``. Poll ``GET /operations/{operation_id}`` until ``done`` is ``true``, then read the generated panorama URL from ``response.pano_url``. Raises: HTTPException: 400 if invalid request HTTPException: 402 if insufficient credits HTTPException: 500 if generation could not start `depth_pano_image` must be a full 360° equirectangular depth panorama with a 2:1 width-to-height aspect ratio. Inputs that are not 2:1 are center-cropped to that ratio before generation, which discards content at the edges. # Pano Source: https://docs.worldlabs.ai/api/reference/pano/index Generate RGB panoramas from depth panorama inputs. Pano endpoints work with panorama assets and return long-running operations. **POST** `/marble/v1/pano:depth_to_rgb` Generate an RGB panorama from a depth panorama and a text description. ## Flow The endpoint returns an operation. Poll [Get an operation](/api/reference/operations/get) until the generated panorama URL is available in the response. # Delete a world Source: https://docs.worldlabs.ai/api/reference/worlds/delete DELETE /marble/v1/worlds/{world_id} Delete a world by ID. Permanently deletes a world and its associated assets. Only the world owner can delete a world. Args: world_id: The unique identifier of the world to delete. Returns: DeleteWorldResponse confirming the deletion. Raises: HTTPException: 403 if user is not the world owner HTTPException: 404 if world not found HTTPException: 500 if deletion fails # Export a world Source: https://docs.worldlabs.ai/api/reference/worlds/export POST /marble/v1/worlds/{world_id}:export Export a generated world asset. PLY splat exports are converted synchronously, cached in GCS, and returned as completed operations. HQ mesh exports reuse the existing async mesh export service and return an in-progress operation. # Generate a world Source: https://docs.worldlabs.ai/api/reference/worlds/generate POST /marble/v1/worlds:generate Start world generation. Creates a new world generation job and returns a long-running operation. Poll the /operations/{operation_id} endpoint to check generation status and retrieve the generated world when complete. Args: request: The world generation request containing world_prompt, display_name, tags, model, seed, and permission settings. Returns: GenerateWorldResponse with operation_id and timestamps. Use the operation_id to poll for completion. Raises: HTTPException: 400 if invalid request or content violates policies HTTPException: 402 if insufficient credits HTTPException: 500 if generation could not be started # Get a world Source: https://docs.worldlabs.ai/api/reference/worlds/get GET /marble/v1/worlds/{world_id} Get a world by ID. Retrieves a world's details including generated assets if available. Only the world owner or users with access to public worlds can retrieve them. Args: world_id: The unique identifier of the world. Returns: World object with world_id, display_name, tags, assets, created_at, updated_at, permission, model, world_prompt, and world_marble_url. Raises: HTTPException: 404 if world not found or access denied # Worlds Source: https://docs.worldlabs.ai/api/reference/worlds/index Create worlds and manage API-generated world resources. World endpoints start generation jobs and manage worlds created through the API. World generation returns an operation. Poll the [operations endpoint](/api/reference/operations/get) until `done` is `true`, then read the generated world from the operation response or fetch it by ID. **POST** `/marble/v1/worlds:generate` Start a world generation job. **POST** `/marble/v1/worlds:list` List API-generated worlds. **GET** `/marble/v1/worlds/{world_id}` Retrieve a world, including generated asset URLs when they are available. **POST** `/marble/v1/worlds/{world_id}:export` Download PLY splats or start a high-quality mesh export. **DELETE** `/marble/v1/worlds/{world_id}` Permanently delete a world and its associated assets. ## Generation flow For local image or video files, first create a [media asset upload](/api/reference/media-assets/prepare-upload). Call [Generate a world](/api/reference/worlds/generate). Save the returned `operation_id`. Call [Get an operation](/api/reference/operations/get) until the operation completes. Read the world response for Marble links, thumbnails, panorama imagery, Gaussian splat files, and mesh assets. Use [Export a world](/api/reference/worlds/export) for cached `.ply` splat downloads or high-quality mesh exports. If you render `.spz` files in a third-party engine, see [Rendering Marble SPZ files in third-party engines](/api/rendering-spz). # List worlds Source: https://docs.worldlabs.ai/api/reference/worlds/list POST /marble/v1/worlds:list List worlds with optional filters. Returns worlds created through the API with optional filtering and pagination. Args: request: List request with optional filters: - page_size: Number of results per page (default: 10) - page_token: Pagination token from previous response - status: Filter by status (e.g., "COMPLETED") - model: Filter by model name (e.g., "marble-1.0-plus") - tags: Filter by tags (matches worlds with any tag) - is_public: Filter by visibility (true=public, false=private, null=all) - created_after: Filter by creation time (after timestamp) - created_before: Filter by creation time (before timestamp) - sort_by: Sort order ("created_at" or "updated_at") Returns: ListWorldsResponse with worlds list and next_page_token for pagination. Raises: HTTPException: 400 if invalid parameters HTTPException: 500 if request fails # Rendering Marble SPZ files in third-party engines Source: https://docs.worldlabs.ai/api/rendering-spz Apply Marble splat scale metadata when rendering SPZ assets outside Marble. Marble SPZ files are exported in the generated asset coordinate frame. When you render them outside Marble, use `assets.splats.semantics_metadata` to convert Gaussian centers and Gaussian sizes into metric, ground-aligned coordinates. ## Read the scale metadata Completed world responses include SPZ URLs and scale metadata together: ```json theme={null} { "assets": { "splats": { "spz_urls": { "500k": "<500k_spz_url>", "100k": "<100k_spz_url>", "full_res": "" }, "semantics_metadata": { "metric_scale_factor": 1.23, "ground_plane_offset": 0.42 } } } } ``` The numeric values in this example are representative, not fixed constants. Use the values returned with the world you are rendering. `metric_scale_factor` converts raw generated asset units to meters. `ground_plane_offset` places the metric ground plane at `y = 0`. ## Apply center and size scale Apply scale to Gaussian centers and Gaussian sizes: ```python theme={null} def to_metric_gaussian(center, linear_scale, semantics): scale = semantics["metric_scale_factor"] metric_center = center * scale metric_center[..., 1] -= semantics["ground_plane_offset"] metric_linear_scale = linear_scale * scale return metric_center, metric_linear_scale ``` The ground-plane offset applies only to Gaussian centers. Do not apply `ground_plane_offset` to Gaussian sizes. ## Handle log-scale fields Some Gaussian splat formats expose `scale_0`, `scale_1`, and `scale_2` as log-space values. If your decoder gives you log scales, add the logarithm of the metric scale instead of multiplying the log values directly: ```python theme={null} import math def to_metric_log_scale(log_scale, semantics): return log_scale + math.log(semantics["metric_scale_factor"]) ``` Equivalently, convert to linear scale first: ```python theme={null} import numpy as np linear_scale = np.exp(log_scale) metric_linear_scale = linear_scale * semantics["metric_scale_factor"] metric_log_scale = np.log(metric_linear_scale) ``` Multiplying `scale_0`, `scale_1`, and `scale_2` directly is only correct if your decoder exposes those fields as linear sizes. ## Convert renderer axes separately `semantics_metadata` converts raw Marble SPZ coordinates to a metric, ground-aligned frame. It does not include renderer-specific axis conversion. Generated Marble SPZ assets use the `marble_raw_opencv` convention. If your engine uses an OpenGL or Three.js-style frame, apply your engine's axis conversion after applying metric scale and ground alignment. Marble's web viewer applies a 180 degree rotation around the X axis for generated SPZ assets. # World generation examples Source: https://docs.worldlabs.ai/api/world-generation-examples Example-based request patterns for generating worlds from text, images, panoramas, multiple images, and video. Use these examples when you already know the kind of input you want to send. For the full request schema, see [`POST /marble/v1/worlds:generate`](/api/reference/worlds/generate). ## Text prompt Text prompts are the smallest request. The API generates the panorama first, then generates the world from that panorama. ```bash theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "Sunlit Atrium", "model": "marble-1.1", "world_prompt": { "type": "text", "text_prompt": "A sunlit indoor atrium with marble floors, plants, and tall glass windows" } }' ``` ## Single image For a regular photo or illustration, omit `is_pano` or set it to `false`. The API uses the image to generate a panorama, then generates the world. ```bash theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "Greenhouse Workshop", "model": "marble-1.1", "world_prompt": { "type": "image", "image_prompt": { "source": "uri", "uri": "https://example.com/greenhouse.jpg" }, "text_prompt": "A peaceful greenhouse workshop with warm light and dense plants" } }' ``` A `source: "uri"` URL must be fetchable by World Labs servers with no cookies, authentication, or referer. Hotlink-protected or login-gated hosts can block these fetches and return a `400`. If a URL won't fetch, upload the file as a media asset or inline it with `source: "data_base64"`. See [Errors and troubleshooting](/api/errors). ## Existing panorama If your image is a full 360-degree equirectangular panorama, send it as an image prompt. `is_pano` defaults to `auto`, so the API checks whether the image looks like a valid equirectangular panorama before skipping pano generation. ```bash theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "Captured Apartment Pano", "model": "marble-1.1", "world_prompt": { "type": "image", "image_prompt": { "source": "uri", "uri": "https://example.com/apartment-equirect-pano.jpg" }, "is_pano": "auto" } }' ``` Set `is_pano` to `true` when your integration has already validated the image as a pano. Set it to `false` for wide 2:1 images that are not 360-degree equirectangular panoramas, such as banners, crops, or collages. ## Multiple images Use multi-image input when you have several views of the same scene. Add `azimuth` values when you know where each image faces. ```bash theme={null} curl -X POST 'https://api.worldlabs.ai/marble/v1/worlds:generate' \ -H 'Content-Type: application/json' \ -H 'WLT-Api-Key: YOUR_API_KEY' \ -d '{ "display_name": "Living Room From Two Views", "model": "marble-1.1", "world_prompt": { "type": "multi-image", "multi_image_prompt": [ { "azimuth": 0, "content": { "source": "uri", "uri": "https://example.com/living-room-front.jpg" } }, { "azimuth": 180, "content": { "source": "uri", "uri": "https://example.com/living-room-back.jpg" } } ], "text_prompt": "A cozy living room with a fireplace and bookshelves" } }' ``` ## Local media For local files, create a media asset, upload to the signed URL, then reference the returned `media_asset_id` in `world_prompt`. The same flow works for local images, local panoramas, and local videos. ```json theme={null} { "display_name": "Uploaded Pano", "model": "marble-1.1", "world_prompt": { "type": "image", "image_prompt": { "source": "media_asset", "media_asset_id": "550e8400-e29b-41d4-a716-446655440000" }, "is_pano": "auto" } } ``` See the [Quickstart upload flow](/api#image-input) for the signed upload steps. # Welcome to Marble Source: https://docs.worldlabs.ai/index Marble helps you create high-fidelity, persistent 3D worlds.