Documentación para desarrolladores

English

La API de trámites de Managora

Dé de alta trámites para sus clientes desde su propio software. Usted nos manda los datos; su cliente revisa, firma y paga en nuestro dominio; nosotros preparamos y presentamos el expediente.

Qué automatiza esta API y qué no

La presentación ante la Administración la realiza un profesional de Managora. La API automatiza la preparación del expediente, no la presentación. Cuando el expediente está completo, el pedido queda en siguiente_paso: "presentacion_por_gestor" con su plazo en días hábiles. No lo disfrazamos de processing: hay una persona detrás y conviene que su producto lo cuente igual.

Pruébela aquí mismo

Llamadas reales contra managora.net, con su clave de pruebas. Sin instalar nada.

El correo sale de verdad. Se crea un expediente real en modo de pruebas y su destinatario recibe un aviso de Managora invitándole a completar el trámite. No se cobra nada, no se emite factura y no presentamos nada ante la Administración.

Empezar

Necesita una clave. Se emiten dos, en momentos distintos: primero la de pruebas (mgk_sandbox_…) y, cuando haya recorrido el flujo entero, la de producción (mgk_live_…). Escríbanos a tramites@managora.net.

1

Compruebe la clave y en qué entorno está

curl https://managora.net/api/partner/v1/me \
  -H "Authorization: Bearer $MANAGORA_API_KEY"

La respuesta trae cobra_de_verdad. Es el campo que conviene mirar antes de nada: dice si esa clave cobra o si es la de pruebas.

2

Lea el catálogo y el contrato de campos

curl https://managora.net/api/partner/v1/services \
  -H "Authorization: Bearer $MANAGORA_API_KEY"

curl https://managora.net/api/partner/v1/services/titulo_nautico_per \
  -H "Authorization: Bearer $MANAGORA_API_KEY"

El segundo devuelve los campos que hay que mandar, con su tipo, sus valores admitidos (opciones), sus condiciones de visibilidad (visible_si) y las causas por las que el trámite no procedería (no_elegible_si). Esa llamada es la lista viva: no copie los campos a mano, porque cambian.

3

Cree el pedido

curl -X POST https://managora.net/api/partner/v1/orders \
  -H "Authorization: Bearer $MANAGORA_API_KEY" \
  -H "Idempotency-Key: pedido-4417" \
  -H "Content-Type: application/json" \
  -d '{
    "tramite": "titulo_nautico_per",
    "referencia": "4417",
    "datos": { "...": "GET /services/titulo_nautico_per" }
  }'

El ejemplo completo y verificado, con todos los campos obligatorios reales y valores válidos, está en la referencia y en la colección de Postman.

A partir de ahí, el enlace sale por correo a su cliente y usted sigue el pedido con GET /orders/{id} o esperando los webhooks.

El entorno de pruebas

Una clave mgk_sandbox_ recorre el mismo código que una real: mismas comprobaciones, mismo expediente, misma pantalla y mismo escrito. Lo único que cambia es que no hay cobro.

Es real en pruebas

  • El expediente y todas las validaciones.
  • El correo a su cliente. Sale de verdad.
  • La pantalla del cliente y la firma del mandato.
  • La subida de documentos.
  • Los webhooks, firmados igual.
  • El escrito que se genera.

No es real en pruebas

  • El cobro: no se abre ninguna pasarela y no se pide ninguna tarjeta.
  • La factura: no se emite ninguna.
  • La presentación ante la Administración: no presentamos nada.

Los correos salen de verdad, y esto le obliga a algo

El destinatario es la dirección que usted declare en datos. Si pone la de un cliente suyo, ese cliente recibe un aviso real de Managora invitándole a firmar un trámite. Pruebe con buzones que controle usted.

El expediente que se crea es real, y por eso el importe que se registra es el que habría cobrado la pasarela, no cero: así puede comprobar que los números cuadran. Cuando termine sus pruebas, dígalo y los purgamos.

En la pantalla de su cliente, donde iría el botón de pago, sale uno que dice Simular el pago y continuar, con el aviso de que es una prueba. A partir de ahí el trámite sigue igual: se genera el escrito y salen los webhooks.

El flujo, y por qué es así

  1. Usted crea el pedido con los datos de su cliente. Comprobamos que el trámite procede, que los valores son de los admitidos y que el generador del escrito puede redactar con ellos. Si algo falla, se rechaza antes de crear nada.
  2. Nosotros le mandamos el enlace a su cliente, por correo. Es personal, de un solo uso y caduca (de 24 a 168 horas, a su elección).
  3. Su cliente revisa, firma y paga en managora.net, con su navegador.
  4. Un profesional de Managora prepara y presenta el expediente y escribe la referencia real que devuelve el organismo.

El enlace de firma no se le devuelve a usted, y no va a existir esa opción

Ese enlace acuña la credencial de acceso al expediente de su cliente. Si se lo diéramos, su servidor podría canjearlo y firmar el mandato en su nombre, y el documento que acredita quién firmó llevaría grabados los metadatos de su servidor en vez de los del titular. La firma vale precisamente porque el trazo es de quien firma.

Consecuencia comercial, dicha claramente: si su cliente no abre el enlace, no hay trámite. Con enlace_enviado_en, enlace_caduca_en y enlace_abierto_en puede perseguirlo desde su propio sistema, y reenviárselo con POST /orders/{id}/hosted-link.

Webhooks

Cinco eventos, y solo cinco. Cada uno se emite desde el sitio que escribe ese hecho.

EventoCuándo
order.signedSu cliente ha firmado el mandato.
order.paidHa entrado el pago.
order.documents_completeSu cliente ha subido el último documento obligatorio.
order.submittedUn profesional lo ha presentado y ha escrito la referencia real del organismo.
order.completedLa Administración ha resuelto y el expediente se ha cerrado.

Se registran desde la propia API (POST /webhooks) y se pueden editar, rotar el secreto, apagar y volver a encender sin escribirnos.

La firma

Cada aviso lleva Managora-Signature: t=<epoch>,v1=<hex>. El valor es un HMAC-SHA256 de <t>.<cuerpo crudo> con el secreto del endpoint.

  • Hexadecimal en minúsculas.
  • El secreto se usa como bytes UTF-8, con el prefijo whsec_ incluido y sin decodificar.
  • Firme el cuerpo crudo que recibe, nunca el JSON reserializado: es el fallo número uno al validar un HMAC.
  • Rechace lo que llegue con más de 5 minutos de antigüedad.
  • Durante una rotación van dos v1 y le basta con validar una.
Node
const crypto = require("crypto");

function firmaValida(cuerpoCrudo, cabecera, secreto) {
  const partes = String(cabecera).split(",").map((p) => p.trim());
  const t = Number(partes.find((p) => p.startsWith("t="))?.slice(2));
  if (!Number.isFinite(t)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false;

  const esperada = crypto
    .createHmac("sha256", secreto)
    .update(`${t}.${cuerpoCrudo}`)
    .digest("hex");

  return partes
    .filter((p) => p.startsWith("v1="))
    .some((p) => {
      const a = Buffer.from(esperada, "utf8");
      const b = Buffer.from(p.slice(3), "utf8");
      return a.length === b.length && crypto.timingSafeEqual(a, b);
    });
}
PHP
<?php
function firma_valida(string $cuerpoCrudo, string $cabecera, string $secreto): bool {
    $partes = array_map('trim', explode(',', $cabecera));
    $t = null;
    foreach ($partes as $p) {
        if (str_starts_with($p, 't=')) { $t = (int) substr($p, 2); }
    }
    if ($t === null || abs(time() - $t) > 300) { return false; }

    $esperada = hash_hmac('sha256', $t . '.' . $cuerpoCrudo, $secreto);

    foreach ($partes as $p) {
        if (str_starts_with($p, 'v1=') && hash_equals($esperada, substr($p, 3))) {
            return true;
        }
    }
    return false;
}

Pruebe su validación el primer día, no en producción

POST /webhooks/{id}/test manda un aviso firmado a su endpoint en ese momento y le devuelve lo que contestó su servidor, más el cuerpo exacto que hemos firmado para que lo compare byte a byte.

Con {"evento": "order.submitted", "pedido_id": "…"} sobre un pedido de pruebas recibe una muestra de ese evento concreto, con la forma del real. Sirve para los dos que no puede provocar usted (order.submitted y order.completed), porque los escribimos nosotros al presentar y al cerrar.

Entrega

Conteste 2xx en cuanto reciba y procese después. Cortamos a los 10 segundos y no seguimos redirecciones: un 3xx lo contamos como fallo. Reintentamos a 1 minuto, 5, 30, 2 horas y 6 horas. La entrega es at-least-once: deduplique por la cabecera Managora-Entrega-Id header, que es la misma en todos los reintentos del mismo aviso.

Si su endpoint falla 20 veces seguidas lo apagamos y se lo decimos por correo. Lo vuelve a encender con POST /webhooks/{id}/reactivate, que además reencola lo que no llegó. Y GET /events es el historial completo, con el cuerpo de cada aviso.

Errores

Todos los errores tienen la misma forma. Ramifique por error.codigo, que es estable; el mensaje está escrito para que lo lea una persona y puede cambiar.

{
  "error": {
    "codigo": "datos_invalidos",
    "mensaje": "Hay campos con valores que no admitimos.",
    "campo": "datos",
    "detalles": [
      {
        "campo": "lista",
        "etiqueta": "Lista del registro",
        "motivo": "\"Lista 7\" no es uno de los valores admitidos.",
        "opciones": ["Lista 3 (pesca)", "Lista 6 (alquiler)", "Lista 7 (recreo)"]
      }
    ]
  }
}

Los más habituales. La tabla completa, con los 52, está en la referencia.

HTTPCódigoQué ha pasado
400campos_desconocidosManda claves que no existen en la ficha. La lista sobrante va en `detalles`.
400idempotency_key_requeridaFalta la cabecera, o no tiene entre 8 y 200 caracteres.
400servicio_fuera_de_su_catalogoEse trámite no está abierto a su cuenta. Los que sí, en `detalles`.
400url_no_validaLa URL del webhook no es https, lleva credenciales o apunta a un host interno.
401clave_desconocidaLa clave no existe o el secreto no casa. Revise que la copió entera.
403scope_insuficienteSu clave no tiene el permiso de esa ruta. Los que sí tiene van en el mensaje.
404pedido_no_encontradoEse pedido no es suyo o no existe. Respondemos 404 y no 403 a propósito.
405metodo_no_permitidoMétodo equivocado en una ruta que existe. La cabecera Allow dice cuáles valen.
409cliente_no_elegiblePara esos datos el trámite no procede. Se puede anticipar con `no_elegible_si`.
409idempotency_key_reutilizadaEsa clave ya se usó con OTRO cuerpo. Use una nueva para el pedido nuevo.
409referencia_duplicadaYa tiene un pedido con esa referencia. Es única por cuenta y no se reutiliza.
413cuerpo_demasiado_grandeEl cuerpo pasa de 256 KB. Los ficheros no van en `datos`.
422datos_incompletosFalta un campo obligatorio y visible. `detalles` trae campo, etiqueta y motivo.
422datos_invalidosUn valor no es de los admitidos. `detalles` trae las `opciones` válidas.
429cuota_excedidaMás de 120 peticiones en el último minuto con esa clave. Espere el Retry-After.
500error_internoFallo nuestro. Reintente con espera creciente y escríbanos con la hora exacta.

Límites y buenas prácticas

  • 120 peticiones por minuto y clave. Cada respuesta trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset, para que frene antes de chocar.
  • Idempotency-Key obligatoria en cada pedido. Un reintento con el mismo cuerpo devuelve el mismo pedido con reintento: true. Con un cuerpo distinto devuelve 409: eso no es un reintento, y darle por bueno el pedido viejo le haría creer que se guardó lo nuevo.
  • La referencia es única por cuenta y no se reutiliza, ni siquiera para el mismo cliente.
  • Los ficheros no van en la API. Los sube su cliente desde su pantalla, después de pagar, y usted ve lo que falta en documentos_pendientes.
  • Todo es JSON, también los errores. Una ruta mal escrita o un método equivocado devuelven el mismo sobre, nunca una página HTML.

Para integrar

La referencia completa se genera del propio contrato, así que no se desincroniza de lo que la API hace de verdad.