Processing and Monitoring
After input files are uploaded, the platform can generate processed outputs (standardized point clouds, mesh HLODs, photospheres, and so on). This guide explains how to list processing options, launch processing, and poll progress until every output reaches a terminal state.
It assumes you have a bundle with at least one completed upload session and hold read:bundle and write:bundle scopes.
How it works
Section titled “How it works”Processing is fire-and-forget: the API enqueues a processing job and returns immediately.
sequenceDiagram
participant Client as Client
participant RCAPI as RCAPI
Client->>RCAPI: GET processing/options
RCAPI-->>Client: options + metadata
Client->>RCAPI: POST processing
RCAPI-->>Client: accepted or typed error
loop until all terminal
Client->>RCAPI: GET bundle/processings
RCAPI-->>Client: processing records (status, progress)
end
Endpoints
Section titled “Endpoints”| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /v1/bundles/{bundleId}/processing/options | read:bundle | List possible output types and their metadata |
POST | /v1/bundles/{bundleId}/processing | write:bundle | Launch processing for requested outputs |
GET | /v1/bundles/{bundleId}/processings | read:bundle | List the bundle’s processing records to poll progress |
GET | /v1/site/{siteId}/bundles/{bundleId} | read:hierarchy | Get the bundle with its processed output components (signed links) |
Step 1: list processing options
Section titled “Step 1: list processing options”Before launching, discover which outputs the bundle can produce and how much each one costs.
GET {api_url}/v1/bundles/{bundleId}/processing/options?page=1&limit=20&sortBy=type&sortDir=ASCAuthorization: Bearer {access_token}Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
page | number | 1-based page index (default 1) |
limit | number | Page size (default 20, maximum 20) |
sortBy | string | type or cost (default type) |
sortDir | string | ASC or DESC (default ASC) |
The response is a paginated list ({ items, total }). Each option describes one output type:
| Field | Meaning |
|---|---|
type | Output component type to pass when starting processing (e.g. StandardizedPointCloud, OgcPointCloudHlod) |
hasProcessing | true when a processing for this output has already been launched |
cost | Processing cost in bytes for paid outputs; 0 for free-tier outputs |
Example:
{ "items": [ { "type": "StandardizedPointCloud", "hasProcessing": false, "cost": 734003200 }, { "type": "OgcPointCloudHlod", "hasProcessing": false, "cost": 734003200 } ], "total": 2}Skip outputs where hasProcessing is true unless you intend to reprocess.
Step 2: start processing
Section titled “Step 2: start processing”Pass the list of output component types you want the pipeline to produce, plus a cost field that acknowledges the total processing charge. The server sums the cost values from Step 1 for every type in requestedComponents and rejects the request when your acknowledgment does not match.
Processing starts asynchronously once the cost is accepted.
POST {api_url}/v1/bundles/{bundleId}/processingAuthorization: Bearer {access_token}Content-Type: application/json
{ "requestedComponents": [ "StandardizedPointCloud", "OgcPointCloudHlod" ], "cost": 1468006400}In the example above, cost is 734003200 + 734003200 from the Step 1 options list.
Success response
Section titled “Success response”204 No Content: Processing was accepted and enqueued.
Error response
Section titled “Error response”When processing cannot be launched, the API returns 409 Conflict with a typed error:
error value | Meaning |
|---|---|
ProcessingCostMismatch | The submitted cost does not match the computed total for the requested outputs |
InsufficientProcessingCapacity | Organization processing limit would be exceeded |
NoInputDataFoundForProcessing | No finalized input files are available on the bundle |
FailedToLaunchProcessing | Pipeline submission failed for an unexpected reason |
Example:
{ "error": "ProcessingCostMismatch"}Structural validation of the uploaded batch (mandatory types, extensions, dependencies) is enforced by the processing pipeline after launch. If your upload session completed but violated the requirements contract, processing may fail later even though every file uploaded successfully and the start call returned 204.
Step 3: poll for progress
Section titled “Step 3: poll for progress”After a successful start, poll the bundle’s processing records until every one reaches a terminal state.
GET {api_url}/v1/bundles/{bundleId}/processingsAuthorization: Bearer {access_token}The response is a list ({ items, total }) with every processing record for the bundle. There are no query parameters; all records are returned in a single call.
Each item tracks one processing job:
| Field | Meaning |
|---|---|
id | Identifier of the processing record |
status | Pipeline status (see table below) |
progress | Completion percentage (0–100) while active |
createdAt | When the job was created |
updatedAt | When the job last changed state |
launchedById | Id of the user who launched the job, or null |
knownErrors | Structured failures, each with an errorCode and the affected errorFilepaths (null when not file-specific) |
Processing states
Section titled “Processing states”| Status | Terminal? | Meaning |
|---|---|---|
Pending | no | Queued, not yet started |
Processing | no | Pipeline is running |
Restarted | no | Job was restarted and is running again |
Ready | yes | Output produced successfully |
Failed | yes | Unstructured pipeline failure |
FailedToLaunch | yes | Job never entered the pipeline |
KnownError | yes | Structured validation or format error |
Poll until every record is in a terminal state (Ready, Failed, FailedToLaunch, or KnownError).
Example mid-flight response:
{ "items": [ { "id": "3f2b1c4d-8e7a-4b2c-9d1e-0a1b3c4d4e6f", "status": "Processing", "progress": 42, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:12:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-9c0d-1e2f3a2b2c1d", "knownErrors": [] }, { "id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b2c3d3e4f", "status": "Pending", "progress": 0, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:05:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-9c0d-1e2f3a3b1c1d", "knownErrors": [] } ], "total": 2}Example with a known error:
{ "items": [ { "id": "3f2b1c4d-8e7a-4b2c-9d1e-0a2b3c4d4e5f", "status": "KnownError", "progress": 100, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:48:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-1c0d-2e6f3a4b5c6d", "knownErrors": [ { "errorCode": "MissingTrajectory", "errorFilepaths": ["scan.las"] } ] } ], "total": 1}The processing list does not include the output type per record. To map finished outputs back to their component types and signed links, fetch the bundle’s components (see Retrieving processed outputs).
Polling guidance
Section titled “Polling guidance”- Interval: 1-5 minutes is a reasonable default. Processing can run for multiple minutes to several hours depending on input size.
- Rate limits: the bundle endpoints share a rate limit bucket with multiple other endpoints. Avoid sub-minute polling.
Retrieving processed outputs
Section titled “Retrieving processed outputs”Once a processing record reaches Ready, fetch the bundle to get its output components and their signed download links. Bundle details live on the hierarchy endpoint:
GET {api_url}/v1/site/{siteId}/bundles/{bundleId}Authorization: Bearer {access_token}This endpoint requires the read:hierarchy scope and returns the bundle with its components array:
| Field | Meaning |
|---|---|
id | Bundle id |
name | Bundle name |
components | One entry per available component (inputs and processed outputs) |
Each component carries:
| Field | Meaning |
|---|---|
id | Component id |
type | Component type, e.g. StandardizedPointCloud, OgcPointCloudHlod |
version | Component version |
signedLink | Signed URL to download the component; no auth header required |
roots | Entry-point paths within the component, when applicable |
Example:
{ "id": "a6f75b3c-f261-4b27-9a2a-9a6cc4178a13", "name": "ZF capture 2026-06-01", "components": [ { "id": "5d6e7f8a-9b0c-1d2e-3f4a-1b3c7d4e8f0a", "type": "StandardizedPointCloud", "version": "1", "signedLink": "https://cdn…/components/…?X-Amz-…" } ]}A RealityPlan project exposes the same bundle through GET /v1/reality-plan/{projectId}/bundles/{bundleId} (scope read:twin).
Full example
Section titled “Full example”import timeimport requests
API_URL = "https://<your-regional-api-url>"TOKEN = "<access_token>"BUNDLE_ID = "<bundleId>"
headers = {"Authorization": f"Bearer {TOKEN}"}base = f"{API_URL}/v1/bundles/{BUNDLE_ID}"
# 1. Discover outputs.options = requests.get( f"{base}/processing/options", headers=headers, params={"page": 1, "limit": 20},).json()
options_by_type = {item["type"]: item for item in options["items"]}to_process = [ item["type"] for item in options["items"] if not item["hasProcessing"]]total_cost = sum(options_by_type[t]["cost"] for t in to_process)print(f"Launching: {to_process} (cost: {total_cost})")
# 2. Start processing.response = requests.post( f"{base}/processing", headers=headers, json={"requestedComponents": to_process, "cost": total_cost},)
if response.status_code == 409: raise RuntimeError( f"Processing failed to start: {response.json().get('error')}" )response.raise_for_status() # expect 204
# 3. Poll until every job is terminal.TERMINAL = {"Ready", "Failed", "FailedToLaunch", "KnownError"}POLL_SECONDS = 15TIMEOUT_SECONDS = 3600deadline = time.time() + TIMEOUT_SECONDS
while time.time() < deadline: listing = requests.get( f"{base}/processings", headers=headers, ).json()
processings = listing.get("items", []) for p in processings: print(f" {p['id']}: {p['status']} ({p['progress']}%)")
if processings and all(p["status"] in TERMINAL for p in processings): break
time.sleep(POLL_SECONDS)else: raise TimeoutError("Processing did not finish within the timeout")Next step
Section titled “Next step”To retrieve the raw input files that were uploaded (for archival, reprocessing, or audit), see Downloading Input Files.