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:
| Clave | Entorno | Qué devuelve | Cuota |
|---|---|---|---|
uco_sbox_… | https://sbox.ucotron.com | Fixtures deterministas | No consume |
uco_live_… | https://api.ucotron.com | Datos satelitales reales | Consume |
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.
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é ves | Por 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 succeeded | No 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.
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 mismo
trabajo sin volver a cobrar. Con la misma clave y otro cuerpo, la respuesta
es 409: 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.
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.
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ódigo | Cuándo | retryable | Qué hacer |
|---|---|---|---|
400 | Cuerpo que no es JSON | no | Arreglar el request |
401 | Falta la clave, es inválida, fue revocada, venció, o es de otro entorno | no | Revisar la credencial. El code distingue el caso |
402 | Se agotó el presupuesto de la organización | no | Escribirnos: es un tope comercial, no un error técnico |
403 | La clave no tiene el permiso que la operación pide | no | El cuerpo trae requiredPermission |
404 | La ruta no existe o el recurso es de otra organización | no | Ver abajo |
409 | Misma Idempotency-Key, distinto cuerpo. O resultado pedido antes de tiempo | no | Usar una clave nueva, o esperar a succeeded |
422 | Los parámetros no tienen la forma esperada | no | El cuerpo lista los campos en issues |
428 | Falta Idempotency-Key en una operación que gasta cuota | no | Agregar el header |
429 | Demasiadas peticiones | sí | Esperar el Retry-After |
5xx | Algo nuestro | sí | 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. 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.
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.