Ucotron Cortex
Guías por flujo

Trabajos, entrega y errores

Cómo funciona el API de integradores por dentro — credenciales, idempotencia, ciclo de un trabajo, descarga y la tabla completa de códigos.

Los tres productos —actividad eléctrica, edificación y peritaje de daños— tienen la misma forma. Esta guía explica esa forma una sola vez; las guías de cada producto se ocupan de lo que cambia.

Los dos entornos

Tu organización recibe dos claves, y no son intercambiables:

ClaveEntornoQué devuelveCuota
uco_sbox_…https://sbox.ucotron.comFixtures deterministasNo consume
uco_live_…https://api.ucotron.comDatos satelitales realesConsume

Construí tu integración contra el sandbox y cambiá una variable para pasar a producción: las rutas y los cuerpos son idénticos, sólo cambia el host y la clave.

Presentar una clave contra el entorno equivocado responde 401 con código environment_mismatch. Tiene un motivo propio a propósito: una clave desconocida manda a revisar si se copió mal, y una del entorno equivocado manda a revisar qué variable se desplegó. Son investigaciones distintas.

bash
curl -s -X POST "$BASE/v1/lightning/evaluations" \
  -H "Authorization: Bearer $UCOTRON_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"lat":-2.76,"lng":-60.52,"eventAt":"2026-06-26T17:22:00Z","radiusKm":20,"windowMin":15}'

El secreto se muestra una sola vez al acuñarlo. Se guarda su hash, no el secreto, así que "no la vamos a volver a mostrar" es literal: si se pierde, se revoca y se acuña otra.

El sandbox no guarda estado

sbox.ucotron.com no tiene base de datos, y eso es una decisión: el entorno declara databaseMutation: false. Todo lo que responde es un fixture, así que cada respuesta es una función pura de su pedido y el identificador lleva el pedido adentro. Cualquier instancia lo reconstruye idéntico sin guardar nada.

Eso tiene dos consecuencias visibles. No son errores de tu integración:

Qué vesPor qué
GET /v1/jobs y GET /v1/reports devuelven data: []Listar es preguntar «¿qué hice?», y eso es exactamente el estado que el sandbox no guarda. Devolver un catálogo inventado te mostraría trabajos que nunca encolaste
Los trabajos nacen succeededNo hay cola ni worker que esperar. Tu bucle de poleo funciona igual —la respuesta trae pollUrl y Retry-After— pero cierra en la primera vuelta

Y una tercera sobre el PDF: el archivo es real y abre, con su mapa satelital, su logo y su bloque de firma, pero su contenido no refleja los parámetros de tu pedido. Pedir un radio de 5 km o de 80 km devuelve el mismo documento. El sha256 que informa el API sí es el de los bytes que descargás, así que verificar la descarga funciona.

Para ejercitar el ciclo asincrónico completo, el listado y un PDF que refleje tu consulta, está producción.

Escenarios deterministas

Como el sandbox no guarda estado, los caminos negativos se piden por convención del pedido. Están para que puedas escribir y probar el else de tu integración sin salir a producción: un entorno que sólo sabe decir que sí no sirve para eso.

Qué querés ensayarCómo pedirloQué responde
Rayos: no hubo actividadlat y lng enteros (p. ej. -31, -68)detected: false con coverage: "ok"
Rayos: fuera de coberturaeventAt anterior a 2018coverage: "out_of_coverage", sin procedencia
Edificación: lote vacíoun anillo de 5 hectáreas o máshasBuilding: false
Peritaje: lote inexistenteaoi_ref fuera del catálogo de demostración (demo:lote-1, -2, -3)trabajo failed con aoi_not_found
Conflicto de idempotenciaIdempotency-Key: 00000000-0000-0000-0000-000000000409409 idempotency_conflict, siempre
Anillo mal armadoun ring cuyo último punto no repite el primero422 invalid_ring
Fecha imposibleeventAt en el futuro422 event_in_future
Informe inexistenteun reportId inventado404 not_found

La clave del 409 es reservada porque el sandbox no puede recordar que una clave ya se usó: sin ella, el conflicto sería el único código del contrato que no se puede ensayar antes de salir a vivo.

Idempotencia

Toda operación que gasta cuota exige el header Idempotency-Key. Sin él la respuesta es 428, no 400: es una precondición que falta, no un cuerpo mal armado.

Con la misma clave y el mismo cuerpo, la segunda llamada devuelve el resultado guardado sin volver a calcular ni a cobrar. Con la misma clave y otro cuerpo, la respuesta es 409 idempotency_conflict: es casi seguro un error de tu lado, y contestar la pregunta nueva bajo la clave vieja te devolvería la respuesta equivocada sin que nadie lo note.

Esto vale igual para los trabajos y para las evaluaciones. Si tu integración se escribió contra una versión anterior de este API, conviene revisarla: antes las evaluaciones exigían la clave y no la leían, así que un reintento con el cuerpo cambiado respondía 200 en vez de 409.

El identificador de un trabajo sale del hash de su contenido. Pedir dos veces exactamente el mismo análisis —aun con claves de idempotencia distintas— devuelve el trabajo que ya existe, con su resultado intacto. Eso también significa que reemitir el mismo informe no produce un documento nuevo: produce el mismo, con el mismo sha256.

El ciclo de un trabajo

Las evaluaciones responden en el momento. Las emisiones de PDF son asincrónicas, porque renderizar un documento tarda segundos y mantenerte esperando una respuesta HTTP sería una promesa que no siempre se puede cumplir.

Diagrama: Trabajos, entrega y erroresCiclo de vida de un trabajo asíncrono: la emisión responde 202 con la URL de poleo, el cliente consulta el estado respetando el Retry-After hasta que queda en succeeded, y entonces la descarga redirige al PDF.APITu sistemaAPITu sistemaloop[cada Retry-After segundos]POST /v1/lightning/reports { evaluationId }202 · { id, status: "queued", pollUrl }GET /v1/jobs/{id}200 · { status: "running" }GET /v1/jobs/{id}200 · { status: "succeeded", resultUrl }GET /v1/reports/{id}/download302 · hacia el PDF
Diagrama: Trabajos, entrega y erroresCiclo de vida de un trabajo asíncrono: la emisión responde 202 con la URL de poleo, el cliente consulta el estado respetando el Retry-After hasta que queda en succeeded, y entonces la descarga redirige al PDF.APITu sistemaAPITu sistemaloop[cada Retry-After segundos]POST /v1/lightning/reports { evaluationId }202 · { id, status: "queued", pollUrl }GET /v1/jobs/{id}200 · { status: "running" }GET /v1/jobs/{id}200 · { status: "succeeded", resultUrl }GET /v1/reports/{id}/download302 · hacia el PDF
Fuente del diagrama (mermaid)
sequenceDiagram
    participant C as Tu sistema
    participant A as API
    C->>A: POST /v1/lightning/reports { evaluationId }
    A-->>C: 202 · { id, status: "queued", pollUrl }
    loop cada Retry-After segundos
        C->>A: GET /v1/jobs/{id}
        A-->>C: 200 · { status: "running" }
    end
    C->>A: GET /v1/jobs/{id}
    A-->>C: 200 · { status: "succeeded", resultUrl }
    C->>A: GET /v1/reports/{id}/download
    A-->>C: 302 · hacia el PDF

Estados: queued → running → succeeded | failed | cancelled. Los tres últimos son terminales: un trabajo que llegó ahí no vuelve a cambiar.

Respetá el Retry-After que viene en el 202. Poletear más rápido no acelera nada y te acerca al 429.

GET /v1/jobs lista tus trabajos, filtrables por status y operation. La paginación es por cursor: pasá el nextCursor de la página anterior. No uses offsets — la lista crece por arriba, y un trabajo encolado entre dos páginas haría que una fila se repita o se saltee.

La descarga

GET /v1/reports/{id}/download devuelve un 302 hacia el PDF. Esa dirección la podés guardar: no vence. Lo que vence es la URL a la que redirige, que se acuña en el momento del click y dura minutos.

Es deliberado y es mejor que repartir un enlace firmado directo: el acceso se corta solo cuando revocás la clave, cosa que un enlace de S3 ya entregado no permite. Seguí el redirect con curl -L.

GET /v1/reports lista lo emitido, con el sha256 de cada documento y su firma —lo que te permite comprobar más tarde que el archivo que tenés es el que emitimos—.

Los códigos, y qué hacer con cada uno

Todos los errores son RFC 7807 y traen un campo retryable: no necesitás mantener tu propia tabla de qué conviene reintentar.

CódigoCuándoretryableQué hacer
400Cuerpo que no es JSONnoArreglar el request
401Falta la clave, es inválida, fue revocada, venció, o es de otro entornonoRevisar la credencial. El code distingue el caso
402Se agotó el presupuesto de la organizaciónnoEscribirnos: es un tope comercial, no un error técnico
403La clave no tiene el permiso que la operación pidenoEl cuerpo trae requiredPermission
404La ruta no existe o el recurso es de otra organizaciónnoVer abajo
409Misma Idempotency-Key, distinto cuerpo. O resultado pedido antes de tiemponoUsar una clave nueva, o esperar a succeeded
422Los parámetros no tienen la forma esperadanoEl cuerpo lista los campos en issues
422 invalid_ringEl anillo del lote no está cerradonoRepetir el primer punto al final. No lo cerramos por vos: sería peritar otra superficie
422 event_in_futureLa fecha del evento es posterior a hoynoCorregir la fecha. No hay imagen satelital de mañana
428Falta Idempotency-Key en una operación que gasta cuotanoAgregar el header
429Demasiadas peticionessíEsperar el Retry-After
503 engine_not_configuredEl motor satelital de ese producto no está disponible en el entornonoEscribirnos. Preferimos decirlo antes que emitir un documento firmado sin el dato que lo sostiene
5xxAlgo nuestrosíReintentar con backoff

Por qué un recurso ajeno contesta 404 y no 403. Un 403 confirmaría que ese identificador existe en algún lado. El 404 no distingue "no existe" de "no es tuyo" —ni en el status ni en el cuerpo, que es idéntico en los dos casos— y por eso este endpoint no sirve para averiguar qué ids son reales.

Qué afirma un informe

Cada PDF trae al pie el hash del análisis y una firma Ed25519. Eso permite comprobar que el archivo no fue alterado y que el análisis se puede rehacer: la misma consulta sobre las mismas escenas produce el mismo resultado.

La firma se verifica con una clave pública, no con un secreto compartido. La diferencia importa cuando el documento va a un expediente: quien verifica no puede fabricar otro. La clave está publicada, sin credenciales, en https://app.ucotron.com/v1/verify/keys, y el propio PDF dice cuál usó y sobre qué bytes se firmó. Está desarrollado en Reportes firmados.

Lo que no hace es reemplazar una inspección en campo. Cada informe declara sus propias limitaciones en una sección dedicada, antes de la procedencia, para que quien llega al final ya sepa qué puede y qué no puede sostener el documento.

On this page