Procesamiento y seguimiento
Una vez subidos los archivos de entrada, la plataforma puede generar salidas procesadas (nubes de puntos estandarizadas, HLODs de malla, fotosferas, etc.). Esta guía explica cómo listar las opciones de procesamiento, iniciar el procesamiento y sondear el progreso hasta que cada salida alcance un estado terminal.
Se da por supuesto que tienes un bundle con al menos una sesión de subida completada y que dispones de los ámbitos read:bundle y write:bundle.
Cómo funciona
Sección titulada «Cómo funciona»El procesamiento es de tipo fire-and-forget (dispara y olvida): la API pone en cola un trabajo de procesamiento y devuelve el control de inmediato.
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
Sección titulada «Endpoints»| Método | Ruta | Ámbito | Propósito |
|---|---|---|---|
GET | /v1/bundles/{bundleId}/processing/options | read:bundle | Listar los posibles tipos de salida y sus metadatos |
POST | /v1/bundles/{bundleId}/processing | write:bundle | Iniciar el procesamiento de las salidas solicitadas |
GET | /v1/bundles/{bundleId}/processings | read:bundle | Listar los registros de procesamiento del bundle para sondear el progreso |
GET | /v1/site/{siteId}/bundles/{bundleId} | read:hierarchy | Obtener el bundle con sus componentes de salida procesados (enlaces firmados) |
Paso 1: listar las opciones de procesamiento
Sección titulada «Paso 1: listar las opciones de procesamiento»Antes de iniciar, descubre qué salidas puede producir el bundle y cuánto cuesta cada una.
GET {api_url}/v1/bundles/{bundleId}/processing/options?page=1&limit=20&sortBy=type&sortDir=ASCAuthorization: Bearer {access_token}Parámetros de consulta
Sección titulada «Parámetros de consulta»| Parámetro | Tipo | Descripción |
|---|---|---|
page | number | Índice de página en base 1 (predeterminado 1) |
limit | number | Tamaño de página (predeterminado 20, máximo 20) |
sortBy | string | type o cost (predeterminado type) |
sortDir | string | ASC o DESC (predeterminado ASC) |
La respuesta es una lista paginada ({ items, total }). Cada opción describe un tipo de salida:
| Campo | Significado |
|---|---|
type | Tipo de componente de salida que se pasa al iniciar el procesamiento (p. ej., StandardizedPointCloud, OgcPointCloudHlod) |
hasProcessing | true cuando ya se ha iniciado un procesamiento para esta salida |
cost | Coste de procesamiento en bytes para las salidas de pago; 0 para las salidas del nivel gratuito |
Ejemplo:
{ "items": [ { "type": "StandardizedPointCloud", "hasProcessing": false, "cost": 734003200 }, { "type": "OgcPointCloudHlod", "hasProcessing": false, "cost": 734003200 } ], "total": 2}Omite las salidas en las que hasProcessing sea true a menos que tengas intención de reprocesar.
Paso 2: iniciar el procesamiento
Sección titulada «Paso 2: iniciar el procesamiento»Pasa la lista de tipos de componentes de salida que quieres que produzca el pipeline, además de un campo cost que confirme el cargo total de procesamiento. El servidor suma los valores cost del paso 1 para cada tipo de requestedComponents y rechaza la solicitud cuando tu confirmación no coincide.
El procesamiento comienza de forma asíncrona una vez aceptado el coste.
POST {api_url}/v1/bundles/{bundleId}/processingAuthorization: Bearer {access_token}Content-Type: application/json
{ "requestedComponents": [ "StandardizedPointCloud", "OgcPointCloudHlod" ], "cost": 1468006400}En el ejemplo anterior, cost es 734003200 + 734003200 de la lista de opciones del paso 1.
Respuesta de éxito
Sección titulada «Respuesta de éxito»204 No Content: el procesamiento se aceptó y se puso en cola.
Respuesta de error
Sección titulada «Respuesta de error»Cuando el procesamiento no puede iniciarse, la API devuelve 409 Conflict con un error tipado:
Valor de error | Significado |
|---|---|
ProcessingCostMismatch | El cost enviado no coincide con el total calculado para las salidas solicitadas |
InsufficientProcessingCapacity | Se superaría el límite de procesamiento de la organización |
NoInputDataFoundForProcessing | No hay archivos de entrada finalizados disponibles en el bundle |
FailedToLaunchProcessing | El envío al pipeline no se completó por un motivo inesperado |
Ejemplo:
{ "error": "ProcessingCostMismatch"}La validación estructural del lote subido (tipos obligatorios, extensiones, dependencias) la aplica el pipeline de procesamiento después del inicio. Si tu sesión de subida se completó pero infringió el contrato de requirements, el procesamiento puede fallar más tarde aunque todos los archivos se hayan subido correctamente y la llamada de inicio haya devuelto 204.
Paso 3: sondear el progreso
Sección titulada «Paso 3: sondear el progreso»Tras un inicio correcto, sondea los registros de procesamiento del bundle hasta que cada uno alcance un estado terminal.
GET {api_url}/v1/bundles/{bundleId}/processingsAuthorization: Bearer {access_token}La respuesta es una lista ({ items, total }) con todos los registros de procesamiento del bundle. No hay parámetros de consulta; todos los registros se devuelven en una sola llamada.
Cada elemento sigue un trabajo de procesamiento:
| Campo | Significado |
|---|---|
id | Identificador del registro de procesamiento |
status | Estado del pipeline (ver la tabla siguiente) |
progress | Porcentaje de finalización (0–100) mientras está activo |
createdAt | Cuándo se creó el trabajo |
updatedAt | Cuándo cambió de estado el trabajo por última vez |
launchedById | Id del usuario que inició el trabajo, o null |
knownErrors | Fallos estructurados, cada uno con un errorCode y las errorFilepaths afectadas (null cuando no es específico de un archivo) |
Estados de procesamiento
Sección titulada «Estados de procesamiento»| Estado | ¿Terminal? | Significado |
|---|---|---|
Pending | no | En cola, aún no iniciado |
Processing | no | El pipeline se está ejecutando |
Restarted | no | El trabajo se reinició y se está ejecutando de nuevo |
Ready | sí | Salida producida correctamente |
Failed | sí | Fallo no estructurado del pipeline |
FailedToLaunch | sí | El trabajo nunca entró en el pipeline |
KnownError | sí | Error estructurado de validación o de formato |
Sondea hasta que cada registro esté en un estado terminal (Ready, Failed, FailedToLaunch o KnownError).
Ejemplo de respuesta en pleno proceso:
{ "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}Ejemplo con un error conocido:
{ "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}La lista de procesamientos no incluye el type de salida por registro. Para asignar las salidas terminadas a sus tipos de componente y enlaces firmados, obtén los componentes del bundle (consulta Recuperar las salidas procesadas).
Recomendaciones de sondeo
Sección titulada «Recomendaciones de sondeo»- Intervalo: de 1 a 5 minutos es un valor predeterminado razonable. El procesamiento puede durar de varios minutos a varias horas según el tamaño de la entrada.
- Límites de tasa: los endpoints de bundle comparten un mismo grupo de límite de tasa con varios otros endpoints. Evita sondear con una frecuencia inferior al minuto.
Recuperar las salidas procesadas
Sección titulada «Recuperar las salidas procesadas»Una vez que un registro de procesamiento alcanza Ready, obtén el bundle para conseguir sus componentes de salida y sus enlaces de descarga firmados. Los detalles del bundle residen en el endpoint de jerarquía:
GET {api_url}/v1/site/{siteId}/bundles/{bundleId}Authorization: Bearer {access_token}Este endpoint requiere el ámbito read:hierarchy y devuelve el bundle con su array components:
| Campo | Significado |
|---|---|
id | Id del bundle |
name | Nombre del bundle |
components | Una entrada por cada componente disponible (entradas y salidas procesadas) |
Cada componente contiene:
| Campo | Significado |
|---|---|
id | Id del componente |
type | Tipo de componente, p. ej., StandardizedPointCloud, OgcPointCloudHlod |
version | Versión del componente |
signedLink | URL firmada para descargar el componente; no se requiere cabecera de autenticación |
roots | Rutas de punto de entrada dentro del componente, cuando corresponde |
Ejemplo:
{ "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-…" } ]}Un proyecto de RealityPlan expone el mismo bundle a través de GET /v1/reality-plan/{projectId}/bundles/{bundleId} (ámbito read:twin).
Ejemplo completo
Sección titulada «Ejemplo completo»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")Paso siguiente
Sección titulada «Paso siguiente»Para recuperar los archivos de entrada sin procesar que se subieron (para archivado, reprocesamiento o auditoría), consulta Descargar archivos de entrada.