Twinbly for Developers
Turn photos, video, and capture data into hosted, streamable Gaussian splats through one API.
Build capture processing into your application without operating a reconstruction pipeline. Upload a capture, start processing, follow its progress, and retrieve the resulting assets. Processing runs asynchronously; your application can keep working while the job runs.
This guide covers the processing API. It does not require a Twinbly CLI, SDK, or MCP server. Those tools are not currently part of this guide's supported distribution.
Start here
The processing API base URL is https://rg-splats.web.app/v1. The service name in the address is historical; this is the Twinbly processing service.
- Developer console — signed-in processing history and diagnostics
- Processing API schema
- Capture guide
- Twinbly pricing
The schema includes some fields and operations marked x-status: planned. Do not build a production dependency on those entries. Examples in this guide use the existing upload-and-start workflow.
New integration? Request processing access with your use case and expected capture volume. Processing onboarding is assisted while we verify account and billing linkage; the public API is available to provisioned integrations.
Authentication
Backend integrations send a processing key in the X-API-Key header. Store the key on your server or in a local environment variable; do not put it in a browser bundle, native application binary, repository, or shared prompt.
Processing keys and scene access tokens are different credentials. The tbly_ read-only tokens in account settings do not authorize processing. Processing keys use the rgs_ family. Existing partners should use their provisioned processing credential.
Processing-key management is available through /v1/me/keys using a signed-in user's Firebase ID token as Authorization: Bearer …. An API key cannot create other API keys. A new key's secret is returned once. The dashboard's read-only token controls are not a processing-key onboarding screen.
First-party native integrations use their existing Firebase identity flow. Refresh the ID token for each request rather than capturing one token before a long polling loop. Third-party keys act for their own account; omit userId rather than choosing another user's identity.
Before starting paid work, confirm the credential's billing account with your integration contact. This guide does not promise a hard per-job spending cap. Quotes estimate cost; a null balance or affordability field means unknown, not free processing or permission to spend.
Quote a capture
Set TWINBLY_API_KEY in your environment without committing its value. These examples use curl and jq. Do not enable shell tracing around credentials or upload/download URLs.
curl --fail-with-body --silent --show-error \
'https://rg-splats.web.app/v1/jobs/quote' \
-H "X-API-Key: ${TWINBLY_API_KEY}" \
-H 'Content-Type: application/json' \
--data '{"frames":120,"width":1920,"height":1080,"inputType":"zip","recipe":{"engine":"lichtfeld","iterations":30000,"targetMegapixels":"preserve","useGut":false}}'
Use your actual image count and dimensions. For video, quote the intended extracted keyframes rather than every encoded video frame. Check feasible and reported limits first, then estimate.cost, any estimate range, and adjustments. A capture beyond a processing limit will not become feasible by adding money.
You can also validate a creation request by sending the same body to POST /v1/jobs with the boolean dryRun: true. This does not create or start a job. An estimate is not a guaranteed final charge or processing time.
Create and upload
Prepare a ZIP of overlapping photos from one capture. This first example assumes you have already checked the estimate and agreed to the intended processing cost. The recipe below is illustrative; inspect the dry-run response for routing and adjustments before accepting it.
Choose a captureId unique to this intended job and save it with your application record. Keep it stable during recovery. Do not automatically retry a creation request with a new identifier.
umask 077
curl --fail-with-body --silent --show-error \
'https://rg-splats.web.app/v1/jobs' \
-H "X-API-Key: ${TWINBLY_API_KEY}" \
-H 'Content-Type: application/json' \
--data '{"captureId":"replace-with-your-unique-capture-id","inputType":"zip","recipe":{"engine":"lichtfeld","iterations":30000,"targetMegapixels":"preserve","useGut":false},"analysis":{"schemaVersion":2,"capture":{"actualFrameCount":120}}}' \
--output twinbly-job.json
Use the actual frame count, consistent with your quote. Check the request succeeded before reading the response. Save jobId durably before proceeding. Treat the response file as private: it contains an upload capability. It can also include adjustments; review these before starting.
The response's upload describes the destination, method, content type, and required headers. Upload the file bytes to that destination, not to /v1/jobs. Do not send your API key to the storage URL. Prefer the returned resumable session for large uploads. Only call /start after storage confirms the complete upload.
For a single PUT, use the method and all headers returned by the service, including its exact Content-Type. Do not substitute multipart/form-data or guess the MIME type from your local filename. If the target expires, follow upload recovery for the existing job with your integration contact; creating and starting another job is not an upload-resume strategy.
Recover a lost creation response
Look up your original capture before attempting another create:
curl --fail-with-body --silent --show-error --get \
'https://rg-splats.web.app/v1/jobs/list' \
-H "X-API-Key: ${TWINBLY_API_KEY}" \
--data-urlencode 'captureId=replace-with-your-unique-capture-id'
The current creation contract does not guarantee replay of the same job throughout its lifecycle. In particular, a repeat create after processing starts can produce another job. If recovery is ambiguous, stop and reconcile the existing job rather than automatically creating billable work. Do not treat the Idempotency-Key header alone as a universal retry guarantee.
Start and monitor
Starting processing is the paid-work boundary. Persist the job ID and the caller's spending authorization before this step.
TWINBLY_JOB_ID=$(jq -er '.jobId | select(type == "string" and length > 0)' twinbly-job.json)
curl --fail-with-body --silent --show-error --request POST \
"https://rg-splats.web.app/v1/jobs/${TWINBLY_JOB_ID}/start" \
-H "X-API-Key: ${TWINBLY_API_KEY}"
Read status using GET /v1/jobs/{jobId}. Poll with backoff; honor Retry-After when supplied. If a start response is lost, inspect the same job before issuing another start. Starting a failed job can rerun processing and should be an intentional decision.
Training complete is not a guarantee that every converted delivery format is ready. Read the job's available status information and its downloads response for the format your application needs. Handle failure explicitly rather than polling forever. Treat unknown statuses as unsupported pending states and surface them for investigation; do not assume success.
Callbacks are available to configured integrations, but planned retry schedules and self-service signing-secret operations must not be assumed live. Keep polling as a recovery path. Do not treat an unverified webhook body as authority to fetch arbitrary URLs or update someone else's job.
Retrieve and use results
curl --fail-with-body --silent --show-error \
"https://rg-splats.web.app/v1/jobs/${TWINBLY_JOB_ID}/downloads" \
-H "X-API-Key: ${TWINBLY_API_KEY}" \
--output twinbly-downloads.json
Use this authenticated downloads endpoint to obtain fetchable asset URLs. A gs:// path in a job record is a storage identifier, not a public HTTP download. Signed URLs expire and should not be logged or committed. Request fresh download links when needed.
Some converted results include hosted delivery URLs in the receipt. Use the URLs returned by the service rather than constructing paths from a job ID. Formats and readiness vary; do not assume PLY, SOG/SOGS, and 3D Tiles are all present on every job. Preserve the relative sibling-file resolution of multi-file streaming assets.
A hosted asset is different from a published Twinbly scene. A job may have no editorUrl or viewerUrl; library ownership and publishing require the relevant integration workflow. Do not promise that raw API processing automatically creates a public Twinbly page. Publish only when the user intends to share.
Runtime clipping and editing do not inherently redact exported source assets. Confirm the export behavior for your integration before distributing files that contain sensitive geometry. Hosted asset links also have their own access and retention policy; API-key revocation is not a promise to invalidate already issued asset URLs.
Input choices
| Input | Use |
|---|---|
zip | A photo capture packaged as a ZIP |
video / mov | Video capture; processing extracts frames |
360 | Supported equirectangular 360 input; not a blanket promise for every camera's proprietary files |
colmap | Prepared imagery and reconstruction; follow the schema's bundle layout |
capture | Prepared device capture bundle; follow the schema's bundle layout |
Use the current schema for exact bundle requirements and processing constraints. Do not send the planned inputSource: manifest field expecting remote-file ingestion. Survey/megasplat workflows and proprietary camera preflight flows require their own integration guidance.
Activity and diagnostics
The developer console shows the latest 100 dated jobs associated with your Twinbly web, native, and self-service API identity. Search by job ID or status and filter by source. Copy details includes only the job ID, source, status, and input type. Custom partner tenants can use a separate identity and are not automatically joined to your account. This is job history, not a request-by-request access log.
For credential-scoped reporting, use GET /v1/usage?view=activity. It counts every job state, with separate estimated and recorded actual processing costs. Missing cost data is null, not zero; coverage counts tell you how many jobs contributed to a subtotal. These costs are not wallet charges. Use Twinbly account settings for billing history.
The default usage response retains its completed-only view. Both views now confine non-delegating credentials to their own qualified subject; requesting another user returns 403. Reports exceeding 5,000 matching records are refused rather than silently truncated. Date filtering happens after that cap; date-only end dates mean midnight UTC. Use project scope to narrow large reports.
Reference and troubleshooting
| Operation | Purpose |
|---|---|
GET /v1/me | Inspect the authenticated processing account |
GET /v1/gpu-catalog | Read available processing capabilities |
POST /v1/jobs/quote | Estimate feasibility and cost |
POST /v1/jobs | Validate with dryRun, or reserve a job and upload target |
GET /v1/jobs/list | List visible jobs or recover by captureId |
POST /v1/jobs/{jobId}/start | Start uploaded input |
GET /v1/jobs/{jobId} | Read job status |
GET /v1/jobs/{jobId}/downloads | Retrieve result links and delivery information |
POST /v1/jobs/{jobId}/cancel | Request cancellation; inspect the result rather than assuming immediate interruption or no charge |
POST /v1/jobs/{jobId}/convert | Request another delivery conversion; availability and limits apply |
GET /v1/usage | Read usage information |
For 401, check credential type and freshness. For 403, verify ownership instead of changing user IDs. For 429, honor the retry guidance. For a timeout during a write, reconcile the existing job before retrying. Retain the job ID, HTTP status, and machine-readable error code for support; redact credentials and signed URLs.
Using Twinbly from an agent
Give the agent this guide and the schema, with credentials supplied through its authorized runtime rather than prompt text. The agent should quote first, obtain the user's spending decision, persist the job handle, upload, start once, monitor, and return available output links. Creation, cancellation, reruns, conversion, and publication are separate decisions.
There is no required agent framework. A coding agent can use HTTP directly today. Do not invent a Twinbly package installation command or MCP connection address: supported distributions will be documented here when available.