Asset Uploads
The public asset workflow has two steps:
- Ask Portals for a temporary upload destination.
- Upload the reviewed file bytes to that destination, then use the returned location only in the supported follow-up workflow.
Temporary upload destinations expire after 60 seconds. They are sensitive operational data: do not log or publish them.
All three request routes use the x-api-key header. Their successful response has the same field names:
{
"signedUploadURL": "temporary credential-bearing upload destination",
"assetURL": "returned asset location",
"objectKey": "storage identifier"
}
Use assetURL only in the documented follow-up workflow. It is not a promise of permanent storage, a reusable cross-project identifier, or a public API identifier. Do not assume objectKey is stable or usable outside the response that returned it.
JSON room-data files
POST /api/v2/utils/generate-json-upload-url
Required header: x-api-key
The handler has no required request-body field. It generates the storage name itself.
The resulting upload expects JSON bytes with this header:
Content-Type: application/json
Use this route when preparing a room-data snapshot for the room import workflow. The import handler can delete a JSON object served from the service-managed CDN after a successful import, so keep your original backup elsewhere.
GLB models
POST /api/v2/utils/generate-gltf-upload-url
Required header: x-api-key
Request body:
{
"fileType": "model/gltf-binary",
"fileName": "optional-name.glb",
"enableDraco": false
}
fileType must be exactly model/gltf-binary. The route accepts only GLB binary uploads; it does not accept a generic GLTF JSON document.
Upload the returned destination with the model’s raw bytes and:
Content-Type: model/gltf-binary
The route accepts enableDraco as a boolean, but it does not transform the uploaded file. Prepare any compression before you upload. At URL issuance, the handler validates fileType; it does not add a GLB Content-Type constraint to the signed upload request. Send the matching header as good upload hygiene, but do not rely on the signing step to reject a mismatched GLB upload header.
Images
POST /api/v2/utils/generate-image-upload-url
Required header: x-api-key
Request body:
{
"fileType": "image/png"
}
The accepted fileType values are:
| Type | MIME value |
|---|---|
| JPEG | image/jpeg or image/jpg |
| PNG | image/png |
| GIF | image/gif |
| WebP | image/webp |
Upload the returned destination with the raw image bytes and the same MIME value you supplied in fileType. At URL issuance, the handler validates fileType; it does not add an image Content-Type constraint to the signed upload request. Use the matching header, but validate the file before requesting a destination.
The public contract does not require a room identifier for this route. Do not send undocumented folder overrides.
Safe upload checklist
- Validate the file type before requesting a destination.
- Request the destination only when the file is ready; it expires after 60 seconds.
- Use the matching Content-Type on the upload request.
- Check the upload response before using
assetURLin the immediately intended workflow. - Keep the temporary upload destination out of logs, source control, and screenshots.
- Test the final asset in a disposable game before referencing it from a live game.
Common failure causes
| Symptom | Likely cause |
|---|---|
| 400 type-not-allowed response | The MIME value is not on the route’s allowlist. |
| 403 response | The personal credential is missing or invalid. |
| Upload request fails after issuance | The temporary destination expired or the upload Content-Type did not match the file workflow. |
| 429 response for a model or image request | The creator’s current daily upload allowance has been reached. |
For connected asset workflows, begin with MCP setup. For placing an asset in a game, use Build Your Game.
