Conectar un sistema propio con KOP
Tu sistema —la planificación, el ERP, un flujo de n8n o Make— puede crear órdenes de trabajo en
KOP con sus documentos y recibir lo que pasa en ellas. Esta página cuenta lo necesario para
empezar; el contrato completo está en openapi.yaml.
1. La clave
Todas las llamadas llevan una clave de API de tu organización:
Authorization: Bearer kop_…
La crea un administrador en la app, en Organización › Integraciones › Acceso por API. Se ve una sola vez: guárdala donde guardas tus secretos. Si se pierde, se revoca y se crea otra.
Sin copiar nada desde el móvil
Si tu herramienta puede esperar, pide ella la clave (es el device authorization grant, RFC 8628):
curl -s https://kopproject.com/api/v1/auth/device \
-H 'Content-Type: application/json' \
-d '{"clientName": "Planificación de Aeromedia"}'
La respuesta trae un userCode (BCDF-GHJK) y una verificationUriComplete. Enséñalos —el código
o un QR de la URL—, y un administrador los escanea o los teclea en Autorizar una herramienta.
Mientras tanto, pregunta cada interval segundos con el deviceCode:
curl -s https://kopproject.com/api/v1/auth/token \
-H 'Content-Type: application/json' \
-d '{"deviceCode": "…"}'
Hasta que contesten, 400 con authorization_pending; si preguntas más deprisa, slow_down.
Aprobada, la respuesta trae la clave una sola vez. El código caduca a los diez minutos.
2. Qué plantillas hay
Cada orden sale de una plantilla de la organización, que dice qué datos pide:
curl -s https://kopproject.com/api/v1/templates -H "Authorization: Bearer $KOP_KEY"
3. Crear una orden
curl -s https://kopproject.com/api/v1/workOrders \
-H "Authorization: Bearer $KOP_KEY" \
-H 'Content-Type: application/json' \
-d '{
"requestId": "8f14e45f-ceea-467f-a9f3-6b1f0c2a9d10",
"template": {
"id": "vuelo",
"parameters": {
"matricula": "EC-DRN",
"ubicacion": { "lat": 42.4336, "lng": -8.6475, "address": "Diana 3" }
}
},
"description": "Fotogrametría de la parcela",
"assignees": ["+34600111222"],
"documents": [{ "name": "restricciones.pdf", "url": "https://…/restricciones.pdf" }]
}'
requestIdes un UUID tuyo, y es el id de la orden. Si repites la llamada con el mismo —tras un corte, un reintento—, KOP devuelve la misma orden con200en vez de crear otra.assignees: los teléfonos de quienes la harán, que tienen que estar en el equipo. Sinassignees, la orden queda para quien la reparta.- Ubicaciones: una dirección (
"address": "Rúa da Ponte 4, Pontevedra"), que KOP comprueba en el callejero, o un punto (latylng) para un sitio sin calle. Si la dirección admite varias lecturas,422con los candidatos: elige uno y vuelve a mandarla. documents: PDFs que KOP descarga de su URL (https, pública) antes de crear la orden. El técnico los abre desde la orden y KOP los lee al acompañarle en el trabajo. Si uno no se puede descargar, la orden no se crea.
Un documento más, después:
curl -s "https://kopproject.com/api/v1/workOrders/$ORDER/documents?name=plano.pdf" \
-H "Authorization: Bearer $KOP_KEY" \
-H 'Content-Type: application/pdf' \
--data-binary @plano.pdf
Una orden lleva como mucho 20 documentos, de hasta 15 MB.
4. Recibir lo que pasa: el webhook
En Integraciones › Webhook, un administrador pone la URL donde quieres recibir los eventos. Al
conectar, KOP manda un ping y enseña una sola vez el secreto de firma (whsec_…).
Cada entrega es un POST con el evento en JSON —hoy, workorder.completed, con los datos de la
orden y, si tiene parte aprobado, workReport.url: un enlace a su PDF que vale siete días, así que
descárgalo al recibirlo— y dos cabeceras:
KOP-Event-Id: el mismo en cada reintento. Puede llegar más de una vez: ignora el que ya tengas.KOP-Signature: t=<segundos>,v1=<hex>: el HMAC-SHA256 de<t>.<cuerpo>con el secreto.
Compruébala sobre el cuerpo tal cual llega, antes de parsearlo, y descarta un t de hace más de
cinco minutos:
import { createHmac, timingSafeEqual } from 'node:crypto';
function signedByKop(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = Buffer.from(
createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
);
const given = Buffer.from(v1 ?? '');
return given.length === expected.length && timingSafeEqual(given, expected);
}
Contesta con un 2xx. Un 408, un 429 o un 5xx se reintentan; cualquier otra respuesta da la
entrega por fallida.
Límites y errores
- 600 llamadas por hora por clave. Pasado el límite,
429conRetry-After. - Los errores llegan como
{ "error": { "code", "message" } }. Elcodees estable (invalid_request,unauthorized,not_found,conflict,too_large,unprocessable,rate_limited,unavailable,internal); elmessageexplica el caso, en inglés. - Una clave revocada, mal escrita o ausente es siempre
401.