{
  "info": {
    "_postman_id": "8c1f9d24-5b30-4a7e-9f61-3a2d7c0e5b48",
    "name": "Managora - API de trámites (canal partner v1)",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Colección de trabajo para integrar el canal de trámites de Managora.\n\nCÓMO EMPEZAR\n\n1. Abra las variables de la colección y pegue su clave en `api_key`. Una clave que empieza por `mgk_sandbox_` es del entorno de PRUEBAS; una que empieza por `mgk_live_` cobra de verdad.\n2. Lance `Identidad / GET me`. Si responde 200, ya está autenticado. Mire `cobra_de_verdad`.\n3. Lance `Catálogo / GET services/{tramiteId}`: esa respuesta es la lista viva de campos y manda sobre cualquier ejemplo de esta colección.\n4. Lance `Pedidos / POST orders`. El identificador del pedido se guarda solo en la variable `order_id`.\n\nEL ENTORNO DE PRUEBAS MANDA CORREOS DE VERDAD. Con una clave `mgk_sandbox_` el expediente, la validación, el correo a su cliente, la pantalla del cliente, la firma del mandato, los documentos, los webhooks y el escrito son reales. No lo son el cobro, la factura ni la presentación. Ponga en `datos` un buzón suyo, nunca el de un cliente.\n\nLA PRESENTACIÓN ANTE LA ADMINISTRACIÓN LA REALIZA UN PROFESIONAL DE MANAGORA; LA API AUTOMATIZA LA PREPARACIÓN DEL EXPEDIENTE, NO LA PRESENTACIÓN.\n\nEL ENLACE DE FIRMA NO SE DEVUELVE NUNCA. Sale por correo al cliente final, es de un solo uso y caduca.\n\nFIRMA DE LOS WEBHOOKS. Cabecera `Managora-Signature: t=<epoch>,v1=<hex>`. Es el HMAC-SHA256 de la cadena `<t>.<cuerpo crudo>` con el secreto del endpoint (el `whsec_...` completo, tal cual, como bytes UTF-8), en hexadecimal minúsculas. Firme el cuerpo CRUDO que reciba, nunca el reserializado. Rechace lo que llegue con más de 5 minutos. Durante una rotación vienen dos `v1` y basta con validar uno."
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://managora.net",
      "type": "string",
      "description": "El mismo servidor para pruebas y para producción. El entorno lo decide la clave."
    },
    {
      "key": "api_key",
      "value": "mgk_sandbox_PEGUE_AQUI_SU_CLAVE",
      "type": "string",
      "description": "Su clave completa: mgk_<entorno>_<prefijo>_<secreto>."
    },
    {
      "key": "tramite_id",
      "value": "titulo_nautico_per",
      "type": "string"
    },
    {
      "key": "order_id",
      "value": "",
      "type": "string",
      "description": "Lo rellena solo POST orders."
    },
    {
      "key": "endpoint_id",
      "value": "",
      "type": "string",
      "description": "Lo rellena solo POST webhooks."
    },
    {
      "key": "entrega_id",
      "value": "",
      "type": "string",
      "description": "Lo rellena solo GET events."
    }
  ],
  "item": [
    {
      "name": "Identidad",
      "description": "Quién es su clave y qué puede hacer.",
      "item": [
        {
          "name": "GET me",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/me",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "me"]
            },
            "description": "Primera llamada de toda integración. Contesta si la clave vale, en qué entorno está, qué permisos tiene, qué cuota y si cobra de verdad."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Catálogo",
      "description": "Qué puede vender y con qué contrato de campos.",
      "item": [
        {
          "name": "GET services",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/services?category=nautica&lang=es",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "services"],
              "query": [
                {
                  "key": "category",
                  "value": "nautica",
                  "description": "Opcional. Sin este parámetro se devuelve todo su catálogo."
                },
                {
                  "key": "lang",
                  "value": "es",
                  "description": "es o en. Solo cambia lo que se lee; los valores a mandar van siempre en español."
                }
              ]
            },
            "description": "Los trámites abiertos a su cuenta, con el precio desglosado y sin los campos."
          },
          "response": []
        },
        {
          "name": "GET services/{tramiteId}",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/services/{{tramite_id}}?lang=es",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "services", "{{tramite_id}}"],
              "query": [
                {
                  "key": "lang",
                  "value": "es"
                }
              ]
            },
            "description": "LA LISTA VIVA DE CAMPOS. Manda sobre cualquier ejemplo de esta colección. Coja los campos con `obligatorio: true`, respete `visible_si`, y mande en cada `select` una de sus `opciones` literales. Los campos con `subida: true` no van en `datos`, y los que traen `lo_fijamos_nosotros: true`, tampoco."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Pedidos",
      "description": "Crear el expediente, seguirlo, reenviar el enlace y cancelar.",
      "item": [
        {
          "name": "POST orders",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "  const cuerpo = pm.response.json();",
                  "  if (cuerpo.pedido && cuerpo.pedido.id) {",
                  "    pm.collectionVariables.set('order_id', cuerpo.pedido.id);",
                  "    console.log('order_id =', cuerpo.pedido.id);",
                  "  }",
                  "  if (cuerpo.enlace_enviado === false) {",
                  "    console.warn('El correo con el enlace NO ha salido. Reintente con POST orders/{id}/hosted-link.');",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "pedido-{{$guid}}",
                "description": "OBLIGATORIA. Única por pedido, de 8 a 200 caracteres. Reintentar con la misma clave y el mismo cuerpo devuelve el mismo 201 con reintento: true; con un cuerpo distinto devuelve 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tramite\": \"titulo_nautico_per\",\n  \"referencia\": \"PED-2026-4417\",\n  \"idioma\": \"es\",\n  \"datos\": {\n    \"solicitante_nombre\": \"Ana\",\n    \"solicitante_apellido1\": \"Ruiz\",\n    \"solicitante_apellido2\": \"Serrano\",\n    \"solicitante_dni_nie\": \"12345678Z\",\n    \"solicitante_domicilio\": \"Calle del Puerto 14, 3 B, 07015 Palma\",\n    \"solicitante_email\": \"pruebas@sudominio.example\",\n    \"solicitante_telefono\": \"+34600111222\",\n    \"solicitante_fecha_nacimiento\": \"1988-04-12\",\n    \"ccaa_o_dgmm_competente\": \"Illes Balears\",\n    \"clase_tramite\": \"expedicion_inicial\",\n    \"titulo_solicitado\": \"per_patron_embarcaciones_recreo\",\n    \"ha_superado_examen_teorico\": \"Sí\",\n    \"fecha_examen_teorico\": \"2026-06-21\",\n    \"ha_realizado_practicas_basicas_seguridad_navegacion\": \"Sí\",\n    \"ha_realizado_practicas_radiocomunicaciones\": \"Sí\",\n    \"ha_certificado_aptitud_psicofisica\": \"Sí\",\n    \"ha_pagado_tasa\": \"No\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/orders",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "orders"]
            },
            "description": "Crea el expediente y manda a su cliente, POR CORREO, el enlace donde revisa, firma y paga. El enlace no viaja en la respuesta.\n\nCAMBIE `solicitante_email` POR UN BUZÓN SUYO ANTES DE LANZARLO: en pruebas el correo sale de verdad.\n\n`referencia` es única dentro de su cuenta: repetirla devuelve 409 `referencia_duplicada`. Cámbiela en cada prueba.\n\n`fecha_presentacion_prevista` no va en el cuerpo: la fijamos nosotros."
          },
          "response": []
        },
        {
          "name": "GET orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/orders?limit=25",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "orders"],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Máximo 25."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "El siguiente_cursor de la página anterior.",
                  "disabled": true
                },
                {
                  "key": "referencia",
                  "value": "PED-2026-4417",
                  "description": "Su referencia, exacta.",
                  "disabled": true
                },
                {
                  "key": "estado",
                  "value": "pendiente_cliente",
                  "description": "pendiente_cliente, firmado_pendiente_pago, pagado_pendiente_firma, pendiente_documentos, listo_para_presentar, presentado, finalizado, cancelado, en_curso.",
                  "disabled": true
                },
                {
                  "key": "desde",
                  "value": "2026-09-01",
                  "description": "Fecha de creación, ISO 8601.",
                  "disabled": true
                },
                {
                  "key": "hasta",
                  "value": "2026-09-30T23:59:59Z",
                  "description": "Fecha de creación, ISO 8601.",
                  "disabled": true
                }
              ]
            },
            "description": "Paginado por cursor, del más reciente al más antiguo. El filtro `estado` se aplica sobre la página ya leída: una página puede venir más corta y traer cursor igualmente."
          },
          "response": []
        },
        {
          "name": "GET orders/{id}",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/orders/{{order_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "orders", "{{order_id}}"]
            },
            "description": "Dónde está el pedido, qué falta y quién tiene que mover ficha. Mire `estado`, `siguiente_paso` y `documentos_pendientes`.\n\nUn pedido que no es suyo responde 404, no 403."
          },
          "response": []
        },
        {
          "name": "POST orders/{id}/hosted-link",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ttl_horas\": 72\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/orders/{{order_id}}/hosted-link",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "orders", "{{order_id}}", "hosted-link"]
            },
            "description": "Reenvía el enlace a su cliente, por correo. El anterior deja de servir.\n\n`ttl_horas` entre 24 y 168; por defecto 24. El cuerpo puede ir vacío ({}).\n\nNo hay parámetro de entrega: mandar `entrega` devuelve 400."
          },
          "response": []
        },
        {
          "name": "POST orders/{id}/cancel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"motivo\": \"Prueba de integración\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/orders/{{order_id}}/cancel",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "orders", "{{order_id}}", "cancel"]
            },
            "description": "Cancela el pedido y MATA el enlace de su cliente: no se puede reemitir.\n\nEs idempotente: cancelar dos veces devuelve 200 con `ya_estaba_cancelado: true`.\n\nNo se puede cancelar lo ya pagado (409 `pedido_ya_pagado`) ni lo ya presentado (409 `pedido_ya_presentado`)."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Alta, cambio, prueba, rotación de secreto y reactivación de sus endpoints.",
      "item": [
        {
          "name": "GET webhooks",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks"]
            },
            "description": "Sus endpoints, con su estado y sus fallos seguidos. El secreto de firma no viaja aquí."
          },
          "response": []
        },
        {
          "name": "POST webhooks",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "  const cuerpo = pm.response.json();",
                  "  if (cuerpo.endpoint && cuerpo.endpoint.id) {",
                  "    pm.collectionVariables.set('endpoint_id', cuerpo.endpoint.id);",
                  "  }",
                  "  if (cuerpo.secreto) {",
                  "    console.log('SECRETO (solo se enseña ahora, guárdelo):', cuerpo.secreto);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://api.sudominio.example/managora/webhooks\",\n  \"eventos\": [\n    \"order.signed\",\n    \"order.paid\",\n    \"order.documents_complete\",\n    \"order.submitted\",\n    \"order.completed\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks"]
            },
            "description": "Da de alta un endpoint y devuelve el SECRETO una sola vez. Guárdelo en ese momento.\n\nLa URL tiene que ser https y de un host público: nada de localhost ni de direcciones de red interna.\n\n`eventos` vacío o ausente significa TODOS."
          },
          "response": []
        },
        {
          "name": "PATCH webhooks/{id}",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://api.sudominio.example/managora/webhooks/v2\",\n  \"eventos\": [\n    \"order.paid\",\n    \"order.submitted\",\n    \"order.completed\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks/{{endpoint_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks", "{{endpoint_id}}"]
            },
            "description": "Cambia la URL, los eventos o las dos cosas. Mover la URL NO cambia el secreto de firma.\n\nMande al menos una de las dos claves o recibirá 400 `nada_que_cambiar`."
          },
          "response": []
        },
        {
          "name": "POST webhooks/{id}/test",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks/{{endpoint_id}}/test",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks", "{{endpoint_id}}", "test"]
            },
            "description": "Manda AHORA un aviso `ping` firmado y le devuelve lo que contestó su servidor, más el `cuerpo_firmado` exacto.\n\nCompare ese `cuerpo_firmado` byte a byte con el que recibió: el fallo número uno al validar un HMAC es firmar el JSON reserializado en vez del crudo.\n\nFunciona también con el endpoint apagado y no consume la escalera de reintentos."
          },
          "response": []
        },
        {
          "name": "POST webhooks/{id}/rotate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"horas_gracia\": 48\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks/{{endpoint_id}}/rotate",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks", "{{endpoint_id}}", "rotate"]
            },
            "description": "Cambia el secreto y devuelve el nuevo, una sola vez.\n\nDurante la ventana de gracia (de 1 a 168 horas; por defecto 48) cada aviso lleva DOS firmas `v1` en la misma cabecera y le basta con validar una, así que despliega cuando quiera dentro de esa ventana."
          },
          "response": []
        },
        {
          "name": "POST webhooks/{id}/reactivate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reencolar\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks/{{endpoint_id}}/reactivate",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks", "{{endpoint_id}}", "reactivate"]
            },
            "description": "Vuelve a encender un endpoint que se apagó solo tras 20 fallos seguidos, y devuelve a la cola lo que quedó sin entregar.\n\nAntes de encender le mandamos un ping: si su servidor sigue sin contestar 2xx, la respuesta es 409 y el endpoint sigue apagado.\n\n`reencolar: false` si solo quiere los avisos NUEVOS."
          },
          "response": []
        },
        {
          "name": "DELETE webhooks/{id}",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/webhooks/{{endpoint_id}}",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "webhooks", "{{endpoint_id}}"]
            },
            "description": "Da de baja el endpoint y cancela lo que le quedara encolado. El histórico de entregas se conserva y se sigue consultando por GET events."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Avisos",
      "description": "Historial de los webhooks que le hemos mandado, y reenvío de uno concreto.",
      "item": [
        {
          "name": "GET events",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const cuerpo = pm.response.json();",
                  "  if (cuerpo.eventos && cuerpo.eventos.length) {",
                  "    pm.collectionVariables.set('entrega_id', cuerpo.eventos[0].entrega_id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/partner/v1/events?limit=50",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "events"],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Máximo 50."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "evento",
                  "value": "order.paid",
                  "description": "Uno de los cinco eventos, o ping.",
                  "disabled": true
                },
                {
                  "key": "estado",
                  "value": "fallido",
                  "description": "pendiente, entregando, entregado, fallido, cancelado.",
                  "disabled": true
                },
                {
                  "key": "pedido",
                  "value": "{{order_id}}",
                  "disabled": true
                },
                {
                  "key": "desde",
                  "value": "2026-09-01",
                  "disabled": true
                },
                {
                  "key": "hasta",
                  "value": "2026-09-30T23:59:59Z",
                  "disabled": true
                }
              ]
            },
            "description": "Todo lo que le hemos mandado o intentado mandar, con el cuerpo EXACTO que firmamos y el resultado de cada entrega.\n\n`entrega_id` es el mismo valor que viajó en la cabecera `Managora-Entrega-Id`: es por donde casa su log con el nuestro.\n\n`cuerpo` se publica como CADENA y no como objeto, a propósito: reserializarlo daría otra firma."
          },
          "response": []
        },
        {
          "name": "POST events",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entrega_id\": \"{{entrega_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/partner/v1/events",
              "host": ["{{base_url}}"],
              "path": ["api", "partner", "v1", "events"]
            },
            "description": "Vuelve a meter en la cola un aviso concreto. Sale por el camino normal, con su escalera de reintentos, no de forma inmediata.\n\nSi el aviso ya estaba esperando, 409 `entrega_ya_en_cola`. Si el endpoint está apagado, 409 `endpoint_desactivado`: reactívelo antes."
          },
          "response": []
        }
      ]
    }
  ]
}
