Skip to main content

API Overview

The Splat3D API lets you create 3D Gaussian Splat scenes programmatically. All endpoint documentation on this tab is auto-generated from the OpenAPI specification.

Base URL

All endpoints are prefixed with /v1/.

Authentication

Every request requires a Bearer token in the Authorization header:
See Authentication for details on creating and managing API keys.

Request format

Request bodies must be JSON sent with Content-Type: application/json (curl -d sends form encoding unless you add that header), and create, /process and /retrain answer a body of any other type with 415 invalid_input, before anything is created or charged. The body of /process and /retrain is optional: none, or an empty one, means {}.

Response format

Success responses

All successful responses use the same envelope:
List endpoints include pagination metadata:

Error responses

Errors use a consistent structure:
A request that fails the endpoint’s schema also lists each problem in details, one {field, message} per field:
meta can hold more than request_id: a 429 names its limit there (see the error codes below). Treat a code you don’t recognize by its HTTP status; new codes may be added.

Request IDs

Every response includes a unique request_id in the meta object and as an X-Request-Id header. Include this ID when contacting support about a specific request.

Retrying scene processing

POST /v1/scenes/{id}/process reserves at most one API launch for a scene. Repeat the same body to replay its saved result without another credit deduction, processing-quota increment, or GPU dispatch. You can supply an Idempotency-Key of 1–128 printable ASCII characters without spaces. If supplied, send the same key on every retry. Keys belong to the user, so changing API keys does not create another launch. A different body or key for an already reserved scene returns 409 conflict. Reusing a key on another scene also returns 409. Without a key, the scene ID and effective request body identify the launch. The meta.request_id remains unique to each HTTP request. A successful response means the launch was accepted. If Modal times out or returns an uncertain error, the reservation stays in place and retries replay acceptance. Poll GET /v1/scenes/{id} for the authoritative outcome; do not create another scene to retry an uncertain launch. The webhook and scheduled reconciler resolve the original job. A worker interruption before dispatch can leave a reservation pending until reconciliation fails and refunds it. Configuration, source-file, and signing failures before reservation do not charge credits. Definite dispatch rejection or capture-data storage failure records a failed result and refunds the captured charge once. Repeating that request returns the saved failure; it does not start a new attempt. Successful acceptance is also replayed after the scene finishes, so use GET to inspect its current status. These guarantees apply to the public /process route. Scene creation and /retrain are not retry-safe yet: /retrain creates a new scene on each request. Do not automatically retry those endpoints or submit the same scene simultaneously through the dashboard and API.

Error codes

Pagination

List endpoints use cursor-based pagination: The cursor is opaque: pass it back unchanged. It is URL-safe, so it needs no encoding. A cursor the API can’t read answers 400 invalid_input. Scenes come newest first, and each list item has the shape GET /v1/scenes/{id} returns.

Endpoints summary

Create answers scene_id and upload_url, /process answers scene_id, and /retrain answers the new scene as scene_id and the scene it came from as source_scene_id. Each also carries id, the same as scene_id. Create and /process still send the older sceneId and uploadUrl, which are deprecated: read the snake_case keys. A /retrain refused before any charge keeps no new scene; after a 502 the new scene stays, failed and refunded.

Processing lifecycle

A scene moves through these statuses: While processing, status reflects the current pipeline stage. (Some older scenes report running_sfm, a legacy alias of estimating_poses, or one of the retired preview stages preview_extracting, preview_generating and preview_compressing.) A launch reports extracting_frames first, at 0% while it waits for a GPU, which can take minutes. At any point before complete, you can cancel processing. A cancel before the job starts, or within 10 seconds after, refunds the scene’s credits. The job starts around when /process returns. A later cancel keeps the processing charge; only an AI Enhance charge reserved at upload comes back. The cancel response says what came back, in refunded and refunded_credits. If something goes wrong, the status becomes failed with an error message and the scene’s credits are refunded automatically. The server fails and refunds a stuck job itself: one still queued after 90 minutes, or still processing after 150 (checked every 10 minutes). You will always reach a terminal status (complete, failed, or cancelled) without intervention.

Tracking progress

Poll GET /v1/scenes/{id} to track progress. The response is a flat SceneDetail object (sceneDetailSchema in api/src/routes/schemas.ts) regardless of state — SDKs can decode it with a single type. The list and PATCH return the same object. Key fields: Poll at 5–10 second intervals and stop once status reaches complete, failed, or cancelled. A 503 while polling is transient: keep polling after its Retry-After.

Webhooks

Create a scene with a webhook_url (https:// only) to get a POST when it finishes: scene.completed or scene.failed, whose pages list every field. A cancel sends nothing, nor does a retrain’s new version or a launch that /process answers with an error. Answer any 2xx; anything else is retried, 3 attempts in all. Redirects are followed, and a 301, 302 or 303 arrives as a GET without the body, so register the final URL. Add a webhook_secret and each request carries X-Splat-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body> keyed with your secret. Check it against the body bytes as received: