---
title: "Conexiones y partners"
description: "API del grafo de partners: cuentas, solicitudes de conexión, conexiones con sus scopes, contratos DPA y diagnóstico de autorización."
url: "https://docs.edugoverna.com/gestion/conexiones"
---
# Conexiones y partners

Una conexión es la relación formal entre tu establecimiento y un partner tecnológico: quién es, con qué propósito trata datos, a qué recursos puede llegar y bajo qué contrato. Esta página cubre la API con que se administra ese grafo — cuentas de partner, solicitudes de conexión, scopes, contratos DPA y orígenes de redirección — además del endpoint de diagnóstico para depurar rechazos `403`. {{ className: 'lead' }}

<Note>
  Estas rutas las llama **el colegio**, con su sesión de consola o su API key de
  organización en la cabecera `x-api-key`; tu credencial de partner no sirve
  aquí y responden `401 API_KEY_REQUIRED`. Las lecturas exigen
  `integrations.read`; las escrituras, `integrations.write`. Todo esto también
  se administra desde la Consola, en **Integraciones**. Si estás del lado del
  partner, el recorrido escrito para ti es
  [Cómo se habilita tu acceso](/gestion/acceso).
</Note>

## El modelo: conexión, scopes y DPA

Tres piezas gobiernan el acceso de un partner:

- **La conexión** (`organization_partner_connection`) une un colegio con una cuenta de partner. Registra el propósito (`purposeSummary`), la base legal, quién la autorizó y su estado (`active` o `revoked`; cualquier estado distinto de `active` cierra el paso al partner). Hay a lo más una conexión por par colegio–partner.
- **Los scopes** (`connection_scopes`) abren grupos de rutas de la API de partner por espacio de recursos (`resourceNamespace`, por ejemplo `subjects` o `consents`) y tipo (`read` o `write`). Sin el scope correspondiente, el grupo de rutas queda cerrado aunque la credencial tenga el permiso.
- **El contrato DPA** (Art. 15 bis) es una compuerta dura: sin un DPA vigente — estado `active` y dentro de su rango de vigencia — toda llamada del partner es rechazada con `PARTNER_DPA_NOT_ACTIVE`. Las conexiones de tipo `external_registry` no llevan DPA; para ellas la compuerta exige la declaración jurada firmada del registrador.

Cada llamada del partner atraviesa, en orden, cuatro compuertas: credencial válida y no vencida → conexión activa con ese colegio → cuenta del partner activa → DPA vigente (o declaración jurada, según el tipo). Recién entonces se evalúan scopes y permisos de la ruta. Puedes reproducir esa evaluación con [el diagnóstico de acceso](#diagnostico-de-acceso).

Las [credenciales de API del partner](/gestion/credenciales) y los [endpoints de webhook asociados a una conexión](/gestion/webhooks) se documentan aparte.

---

## Listar partners {{ tag: 'GET', label: '/api/integrations/partners' }}

<Row>
  <Col>

    Devuelve el estado completo del grafo para tu organización en una sola
    respuesta:

    <Properties>
      <Property name="partnerConnections" type="array">
        Las conexiones existentes, cada una con su cuenta de partner
        (`partner`), sus `scopes` y el resumen del último DPA (`dpa`, con
        `status`, vigencia y si tiene documento adjunto).
      </Property>
      <Property name="partnerConnectionRequests" type="array">
        Las solicitudes de conexión pendientes o decididas que involucran a tu
        organización.
      </Property>
      <Property name="orphanPartnerAccounts" type="array">
        Cuentas de partner creadas por tu organización que aún no tienen
        conexión, listadas para que puedas limpiarlas.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/integrations/partners">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/partners \
      -H "x-api-key: {api_key}"
    ```

    ```js
    const { partnerConnections, partnerConnectionRequests } = await fetch(
      'https://app.edugoverna.com/api/integrations/partners',
      { headers: { 'x-api-key': apiKey } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta' }}
    {
      "partnerConnections": [
        {
          "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
          "partnerAccountId": "16fd2706-8baf-433b-82eb-8c7fada847da",
          "connectionType": "api",
          "status": "active",
          "purposeSummary": "Sincronización de matrícula y consentimientos",
          "legalBasis": "contrato_encargo",
          "partner": {
            "id": "16fd2706-8baf-433b-82eb-8c7fada847da",
            "name": "Plataforma Escolar SpA",
            "slug": "plataforma-escolar"
          },
          "scopes": [
            { "resourceNamespace": "subjects", "scopeType": "read" },
            { "resourceNamespace": "consents", "scopeType": "write" }
          ],
          "dpa": {
            "id": "a3bb189e-8bf9-3888-9912-ace4e6543002",
            "versionNumber": 2,
            "status": "active",
            "effectiveFrom": "2026-08-12T00:00:00.000Z",
            "effectiveTo": null,
            "hasDocument": true
          }
        }
      ],
      "partnerConnectionRequests": [],
      "orphanPartnerAccounts": []
    }
    ```

  </Col>
</Row>

---

## Detalle de una conexión {{ tag: 'GET', label: '/api/integrations/partners/:organizationPartnerConnectionId' }}

<Row>
  <Col>

    Devuelve una conexión con todo lo que cuelga de ella: la cuenta de partner,
    sus `scopes` (con `id`, para poder quitarlos), el resumen del último DPA,
    los `redirectOrigins` aprobados y las `credentials` del partner en su forma
    segura (con `prefix`, nunca la llave). Una conexión de otra organización
    responde `404` con `ORGANIZATION_PARTNER_CONNECTION_NOT_FOUND`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/integrations/partners/:id">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/partners/d290f1ee-6c54-4b01-90e6-d701748f0851 \
      -H "x-api-key: {api_key}"
    ```

    ```js
    const detail = await fetch(
      'https://app.edugoverna.com/api/integrations/partners/d290f1ee-6c54-4b01-90e6-d701748f0851',
      { headers: { 'x-api-key': apiKey } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta' }}
    {
      "partnerConnection": {
        "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
        "status": "active",
        "partner": { "name": "Plataforma Escolar SpA" },
        "scopes": [
          {
            "id": "0b8e4c2a-5d1f-4a6b-8c3e-7f9d2a1b6c4e",
            "resourceNamespace": "subjects",
            "scopeType": "read"
          }
        ],
        "dpa": { "status": "active", "hasDocument": true },
        "redirectOrigins": [
          {
            "id": "b1946ac9-2d5a-4a3c-8f2e-0d9c7e6f5a4b",
            "origin": "https://app.plataformaescolar.cl",
            "allowSubpaths": true
          }
        ],
        "credentials": [
          {
            "id": "9e2a41d7-6c3b-4f0e-8a17-b5d4c2e8f901",
            "label": "Producción",
            "prefix": "9e2a41d7-6c3",
            "status": "active"
          }
        ]
      }
    }
    ```

  </Col>
</Row>

---

## Crear una cuenta de partner {{ tag: 'POST', label: '/api/integrations/partners' }}

<Row>
  <Col>

    Registra una cuenta de partner nueva (por ejemplo, un proveedor que aún no
    existe en la plataforma). La cuenta queda visible en tu listado como
    huérfana hasta que la conectes.

    ### Atributos obligatorios

    <Properties>
      <Property name="name" type="string">
        Nombre del partner (2–160 caracteres).
      </Property>
    </Properties>

    ### Atributos opcionales

    <Properties>
      <Property name="slug" type="string">
        Identificador legible, único en la plataforma (2–160 caracteres).
      </Property>
      <Property name="partnerType" type="string">
        Tipo de partner. Por defecto `software_vendor`.
      </Property>
      <Property name="websiteUrl" type="string">
        Sitio web del partner.
      </Property>
      <Property name="contactEmail" type="string">
        Correo de contacto general.
      </Property>
      <Property name="contactPhone" type="string">
        Teléfono de contacto.
      </Property>
      <Property name="privacyContactEmail" type="string">
        Correo del contacto de privacidad del partner.
      </Property>
      <Property name="status" type="string">
        Estado inicial. Por defecto `active`.
      </Property>
      <Property name="metadata" type="object">
        Metadatos libres.
      </Property>
    </Properties>

    Para eliminar una cuenta creada por tu organización que **no** tiene
    conexión, usa
    `DELETE /api/integrations/partner-accounts/:partnerAccountId`; si la
    cuenta ya tiene conexiones, solicitudes o credenciales, responde `409`
    con `PARTNER_ACCOUNT_IN_USE`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/partners">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/partners \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Plataforma Escolar SpA",
        "contactEmail": "soporte@plataformaescolar.cl",
        "privacyContactEmail": "privacidad@plataformaescolar.cl"
      }'
    ```

    ```js
    await fetch('https://app.edugoverna.com/api/integrations/partners', {
      method: 'POST',
      headers: {
        'x-api-key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'Plataforma Escolar SpA',
        contactEmail: 'soporte@plataformaescolar.cl',
        privacyContactEmail: 'privacidad@plataformaescolar.cl',
      }),
    })
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (201)' }}
    {
      "id": "16fd2706-8baf-433b-82eb-8c7fada847da",
      "name": "Plataforma Escolar SpA",
      "slug": "plataforma-escolar-spa",
      "partnerType": "software_vendor",
      "status": "active"
      // ... resto de la cuenta
    }
    ```

  </Col>
</Row>

---

## Solicitudes de conexión {{ tag: 'POST', label: '/api/integrations/connection-requests' }}

<Row>
  <Col>

    Una solicitud de conexión es la invitación formal entre las partes: el
    colegio invita a un partner (`school_invites_partner`) o el partner pide
    acceso a un colegio (`partner_requests_school`). Las pendientes se listan
    con `GET /api/integrations/connection-requests` y la contraparte las
    decide con
    `POST /api/integrations/connection-requests/:id/decide` enviando
    `{ "decision": "accepted" }` o `{ "decision": "rejected" }`. Al aceptarse,
    la plataforma crea la conexión con los scopes solicitados; si la solicitud
    no traía scopes, aplica un conjunto por defecto (`subjects` read,
    `consents` read y write, `rights_requests` read).

    ### Atributos opcionales

    <Properties>
      <Property name="direction" type="string">
        `school_invites_partner` (por defecto) o `partner_requests_school`.
      </Property>
      <Property name="targetPartnerAccountId" type="string">
        UUID de la cuenta de partner invitada.
      </Property>
      <Property name="targetOrganizationId" type="string">
        UUID de la organización objetivo, cuando quien solicita es el partner.
      </Property>
      <Property name="targetEmail" type="string">
        Correo del invitado cuando aún no tiene cuenta en la plataforma; la
        invitación le llega por correo.
      </Property>
      <Property name="requestedScopes" type="array">
        Hasta 50 scopes solicitados, cada uno con `resourceNamespace`
        (2–80 caracteres), `scopeType` y `conditions` opcionales.
      </Property>
      <Property name="message" type="string">
        Mensaje para la contraparte (hasta 2000 caracteres).
      </Property>
      <Property name="expiresAt" type="string (fecha)">
        Vencimiento de la invitación.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connection-requests">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/connection-requests \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{
        "direction": "school_invites_partner",
        "targetPartnerAccountId": "16fd2706-8baf-433b-82eb-8c7fada847da",
        "requestedScopes": [
          { "resourceNamespace": "subjects", "scopeType": "read" },
          { "resourceNamespace": "consents", "scopeType": "write" }
        ],
        "message": "Integración de consentimientos 2026"
      }'
    ```

    ```js
    await fetch(
      'https://app.edugoverna.com/api/integrations/connection-requests',
      {
        method: 'POST',
        headers: {
          'x-api-key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          direction: 'school_invites_partner',
          targetPartnerAccountId: '16fd2706-8baf-433b-82eb-8c7fada847da',
          requestedScopes: [
            { resourceNamespace: 'subjects', scopeType: 'read' },
            { resourceNamespace: 'consents', scopeType: 'write' },
          ],
          message: 'Integración de consentimientos 2026',
        }),
      },
    )
    ```

    </CodeGroup>

    ```json {{ title: 'Decidir (POST …/:id/decide)' }}
    {
      "decision": "accepted"
    }
    ```

  </Col>
</Row>

---

## Crear o reactivar una conexión {{ tag: 'POST', label: '/api/integrations/connections' }}

<Row>
  <Col>

    Crea la conexión directamente (sin pasar por una solicitud), o actualiza y
    reactiva la existente: hay a lo más una conexión por par colegio–partner,
    así que un segundo `POST` con el mismo `partnerAccountId` actualiza esa
    fila y limpia una revocación previa.

    ### Atributos obligatorios

    <Properties>
      <Property name="partnerAccountId" type="string">
        UUID de la cuenta de partner.
      </Property>
    </Properties>

    ### Atributos opcionales

    <Properties>
      <Property name="connectionType" type="string">
        Tipo de conexión. Por defecto `api`.
      </Property>
      <Property name="status" type="string">
        Estado inicial. Por defecto `active`.
      </Property>
      <Property name="purposeSummary" type="string">
        Propósito del tratamiento encargado (hasta 1000 caracteres).
      </Property>
      <Property name="legalBasis" type="string">
        Base legal registrada para la relación.
      </Property>
      <Property name="controllerEntityId" type="string">
        Entidad responsable bajo la cual se registra la conexión; si se omite,
        se usa la entidad principal de la organización.
      </Property>
      <Property name="authorizedByUserId" type="string">
        Usuario que autorizó la conexión.
      </Property>
      <Property name="approvedAt" type="string (fecha)">
        Fecha de aprobación. Por defecto, el momento de la llamada.
      </Property>
      <Property name="returnOrDeletionRequired" type="boolean">
        Si al término de la relación se exige devolución o supresión de los
        datos. Por defecto `true`.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connections">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/connections \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{
        "partnerAccountId": "16fd2706-8baf-433b-82eb-8c7fada847da",
        "purposeSummary": "Sincronización de matrícula y consentimientos",
        "legalBasis": "contrato_encargo"
      }'
    ```

    ```js
    await fetch('https://app.edugoverna.com/api/integrations/connections', {
      method: 'POST',
      headers: {
        'x-api-key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        partnerAccountId: '16fd2706-8baf-433b-82eb-8c7fada847da',
        purposeSummary: 'Sincronización de matrícula y consentimientos',
        legalBasis: 'contrato_encargo',
      }),
    })
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (201)' }}
    {
      "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "partnerAccountId": "16fd2706-8baf-433b-82eb-8c7fada847da",
      "connectionType": "api",
      "status": "active",
      "returnOrDeletionRequired": true
      // ... resto de la conexión
    }
    ```

  </Col>
</Row>

---

## Scopes de una conexión {{ tag: 'POST', label: '/api/integrations/connections/:id/scopes' }}

<Row>
  <Col>

    Agrega un scope a la conexión. Cada scope abre un grupo de rutas de la API
    de partner; quitarlo lo cierra en la llamada siguiente, sin caché de por
    medio.

    ### Atributos

    <Properties>
      <Property name="resourceNamespace" type="string">
        Espacio de recursos que abre el scope (2–80 caracteres), por ejemplo
        `subjects` o `consents`.
      </Property>
      <Property name="scopeType" type="string">
        `read` o `write`. Por defecto `read`.
      </Property>
      <Property name="conditions" type="object">
        Condiciones del scope, opcionales. Dos tienen lector hoy:
        `{ "onlyActive": true }` en `subjects:read` (el padrón que ve el
        partner excluye a los titulares no activos) y `{ "readAll": true }`
        en `rights_requests:read` (el partner ve toda la bandeja ARCO del
        colegio y no solo los casos atribuidos a él — ver
        [Derechos ARCO](/derechos-arco#listar-solicitudes-de-derechos)).
        Ambas se marcan también desde la Consola al agregar el scope.
      </Property>
    </Properties>

    Para quitar un scope:
    `DELETE /api/integrations/connections/:id/scopes/:connectionScopeId`.
    El `id` de cada scope viene en el
    [detalle de la conexión](#detalle-de-una-conexion); un id de otra
    conexión u otra organización responde `404` con
    `CONNECTION_SCOPE_NOT_FOUND`. La respuesta de la eliminación es
    `{ "ok": true }`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connections/:id/scopes">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/scopes \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{ "resourceNamespace": "consents", "scopeType": "write" }'
    ```

    ```js
    await fetch(
      'https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/scopes',
      {
        method: 'POST',
        headers: {
          'x-api-key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          resourceNamespace: 'consents',
          scopeType: 'write',
        }),
      },
    )
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (201)' }}
    {
      "id": "0b8e4c2a-5d1f-4a6b-8c3e-7f9d2a1b6c4e",
      "organizationPartnerConnectionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "resourceNamespace": "consents",
      "scopeType": "write",
      "conditions": null
    }
    ```

  </Col>
</Row>

---

## Revocar una conexión {{ tag: 'POST', label: '/api/integrations/connections/:id/revoke' }}

<Row>
  <Col>

    Marca la conexión como `revoked` y registra cuándo y quién la revocó. La
    revocación arrastra todo lo que cuelga de la relación: las comparticiones
    de padrón otorgadas por esta conexión se retiran y sus solicitudes de
    consentimiento pendientes quedan invalidadas (ver
    [Campañas compartidas](/campanas-compartidas)). Desde ese momento, toda
    llamada del partner a tu organización es rechazada con
    `PARTNER_CONNECTION_NOT_ACTIVE`.

    Un nuevo `POST /api/integrations/connections` con el mismo partner
    reactiva la relación.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connections/:id/revoke">

    ```bash {{ title: 'cURL' }}
    curl -X POST https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/revoke \
      -H "x-api-key: {api_key}"
    ```

    ```js
    await fetch(
      'https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/revoke',
      { method: 'POST', headers: { 'x-api-key': apiKey } },
    )
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta' }}
    {
      "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "status": "revoked",
      "revokedAt": "2026-08-25T14:03:22.511Z"
      // ... resto de la conexión
    }
    ```

  </Col>
</Row>

---

## Registrar un contrato DPA {{ tag: 'POST', label: '/api/integrations/connections/:id/dpa-contracts' }}

<Row>
  <Col>

    Registra una nueva versión del contrato de encargo (Art. 15 bis) del
    partner. El endpoint acepta dos formatos:

    **Multipart** — adjunta el documento firmado en el campo `file`
    (máximo 15 MB; `DPA_DOCUMENT_TOO_LARGE` si lo excede) y, opcionalmente, un
    campo `status`. El documento se almacena como evidencia y la nueva versión
    del contrato lo referencia.

    **JSON** — registra la versión con o sin documento externo:

    <Properties>
      <Property name="status" type="string">
        `draft` o `active`. Por defecto `active`. Solo un contrato `active`
        dentro de su vigencia abre la compuerta de la API de partner.
      </Property>
      <Property name="versionNumber" type="integer">
        Número de versión; si se omite, continúa la numeración existente.
      </Property>
      <Property name="executedAt" type="string (fecha)">
        Fecha de firma. Por defecto, el momento de la llamada.
      </Property>
      <Property name="effectiveFrom" type="string (fecha)">
        Inicio de vigencia. Por defecto, el momento de la llamada.
      </Property>
      <Property name="effectiveTo" type="string (fecha)">
        Fin de vigencia; debe ser posterior a `effectiveFrom`.
      </Property>
      <Property name="documentUrl" type="string">
        Referencia externa al documento (URL), como alternativa al adjunto.
      </Property>
      <Property name="returnOrDeletionClause" type="string">
        Texto de la cláusula de devolución o supresión (hasta 2000
        caracteres).
      </Property>
      <Property name="artifactId / artifact" type="string / object">
        Referencia a un artefacto de evidencia ya almacenado, o uno inline.
        Excluyentes entre sí.
      </Property>
    </Properties>

    Una versión nueva que no re-adjunta documento arrastra la referencia al
    documento de la versión anterior, de modo que el DPA vigente siga
    apuntando al último documento cargado.

    El documento del último contrato registrado se descarga con
    `GET /api/integrations/connections/:id/dpa-document` — la respuesta es el
    archivo mismo, con su `content-type` original y la cabecera
    `x-artifact-checksum`. Si la conexión no tiene documento cargado, responde
    `404` con `DPA_DOCUMENT_NOT_FOUND`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connections/:id/dpa-contracts">

    ```bash {{ title: 'cURL (multipart)' }}
    curl https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/dpa-contracts \
      -H "x-api-key: {api_key}" \
      -F "file=@dpa-firmado.pdf" \
      -F "status=active"
    ```

    ```js
    // Variante JSON con referencia externa al documento
    await fetch(
      'https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/dpa-contracts',
      {
        method: 'POST',
        headers: {
          'x-api-key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          status: 'active',
          effectiveFrom: '2026-03-01T00:00:00.000Z',
          documentUrl: 'https://contratos.micolegio.cl/dpa/plataforma-escolar-v2.pdf',
        }),
      },
    )
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (201)' }}
    {
      "id": "a3bb189e-8bf9-3888-9912-ace4e6543002",
      "versionNumber": 2,
      "status": "active",
      "executedAt": "2026-08-25T14:03:22.511Z",
      "effectiveFrom": "2026-08-25T14:03:22.511Z",
      "effectiveTo": null
      // ... resto del contrato
    }
    ```

  </Col>
</Row>

---

## Orígenes de redirección {{ tag: 'POST', label: '/api/integrations/connections/:id/redirect-origins' }}

<Row>
  <Col>

    Cuando el partner lanza flujos que terminan redirigiendo al titular de
    vuelta a su propia aplicación (por ejemplo, al cerrar un portal de
    consentimiento de una campaña), el destino debe estar en esta lista de
    orígenes aprobados por el colegio. Un origen fuera de la lista es
    rechazado.

    ### Atributos

    <Properties>
      <Property name="origin" type="string">
        El origen a aprobar, por ejemplo `https://app.plataformaescolar.cl`.
        Se normaliza antes de guardarse; un valor que no es un origen válido
        responde con `REDIRECT_ORIGIN_INVALID`.
      </Property>
      <Property name="allowSubpaths" type="boolean">
        Si se permiten rutas bajo el origen. Por defecto `true`.
      </Property>
    </Properties>

    La operación es idempotente: volver a agregar un origen existente devuelve
    la fila que ya estaba. Los orígenes se listan con
    `GET /api/integrations/connections/:id/redirect-origins` y se quitan con
    `DELETE /api/integrations/connections/:id/redirect-origins/:redirectOriginId`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/connections/:id/redirect-origins">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/redirect-origins \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{ "origin": "https://app.plataformaescolar.cl" }'
    ```

    ```js
    await fetch(
      'https://app.edugoverna.com/api/integrations/connections/d290f1ee-6c54-4b01-90e6-d701748f0851/redirect-origins',
      {
        method: 'POST',
        headers: {
          'x-api-key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ origin: 'https://app.plataformaescolar.cl' }),
      },
    )
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (201)' }}
    {
      "id": "b1946ac9-2d5a-4a3c-8f2e-0d9c7e6f5a4b",
      "organizationPartnerConnectionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "origin": "https://app.plataformaescolar.cl",
      "allowSubpaths": true
    }
    ```

  </Col>
</Row>

---

## Diagnóstico de acceso {{ tag: 'POST', label: '/api/integrations/authorize' }}

<Row>
  <Col>

    Resuelve una llave de partner contra tu organización pasando por las
    mismas cuatro compuertas que una llamada real de la API de partner. Es la
    herramienta para depurar un `403`: en lugar de adivinar cuál compuerta
    rechazó, la respuesta te lo dice. Aunque el método es `POST`, la ruta es
    de diagnóstico y exige `integrations.read`.

    ### Atributos

    <Properties>
      <Property name="providedKey" type="string">
        La llave del partner en texto plano (mínimo 10 caracteres).
      </Property>
    </Properties>

    Si todas las compuertas pasan, la respuesta es el contexto de acceso
    completo: credencial, cuenta, conexión, `scopes`, `permissions` y el
    resumen del `dpaContract` vigente. Si alguna falla, el error identifica la
    compuerta:

    - `PARTNER_CREDENTIAL_INVALID` (401) — la llave no existe o está revocada.
    - `PARTNER_CREDENTIAL_EXPIRED` (401) — la credencial venció.
    - `PARTNER_CONNECTION_NOT_ACTIVE` (403) — no hay conexión activa entre el
      partner y tu organización.
    - `PARTNER_WORKSPACE_NOT_ACTIVE` (403) — la cuenta del partner está
      suspendida o pendiente de activación.
    - `PARTNER_DPA_NOT_ACTIVE` (403) — no hay DPA vigente (o falta la
      declaración jurada, en conexiones de registro externo).

    El formato general de los errores está en [Errores](/errores).

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/integrations/authorize">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/integrations/authorize \
      -H "x-api-key: {api_key}" \
      -H "Content-Type: application/json" \
      -d '{ "providedKey": "9e2a41d7-6c3b-4f0e-8a17-b5d4c2e8f901.f47ac10b58cc4372a5670e02b2c3d479" }'
    ```

    ```js
    const context = await fetch(
      'https://app.edugoverna.com/api/integrations/authorize',
      {
        method: 'POST',
        headers: {
          'x-api-key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ providedKey: partnerKey }),
      },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta' }}
    {
      "partnerCredentialId": "0b8e4c2a-5d1f-4a6b-8c3e-7f9d2a1b6c4e",
      "partnerAccountId": "16fd2706-8baf-433b-82eb-8c7fada847da",
      "organizationPartnerConnectionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "scopes": [
        {
          "resourceNamespace": "subjects",
          "scopeType": "read",
          "conditions": null
        }
      ],
      "permissions": ["partner.consents.write", "partner.subjects.read"],
      "dpaContract": {
        "id": "a3bb189e-8bf9-3888-9912-ace4e6543002",
        "versionNumber": 2,
        "status": "active",
        "executedAt": "2026-08-12T00:00:00.000Z",
        "effectiveFrom": "2026-08-12T00:00:00.000Z",
        "effectiveTo": null
      }
    }
    ```

  </Col>
</Row>
