Consentimientos

Una solicitud de consentimiento le pregunta a quien tiene la autoridad — el propio titular o su apoderado, según la banda etaria — si acepta una actividad de tratamiento concreta. Edugoverna arma el aviso desde la versión publicada de la actividad, despacha la entrega por email o WhatsApp, captura la decisión con verificación de identidad y conserva la evidencia. En esta página revisamos los endpoints para crear solicitudes una a una, consultarlas de forma incremental y lanzar campañas sobre tu propia audiencia.

El modelo de solicitud

Una solicitud (consentRequest) referencia a un titular objetivo (por su referenceCode del padrón) y a una versión de actividad de tratamiento. De ella cuelgan las entregas (deliveries, una por canal y destinatario) y, cuando alguien decide, la decisión (latestDecision) con su evidencia.

Ciclo de vida (status)

Mientras la solicitud sigue abierta, su status es pending; el avance del envío (queued, sent, delivered, opened, failed) se lee en cada entrega (deliveries[].status), no en la solicitud. Los estados terminales de la solicitud son:

  • granted — el consentimiento fue otorgado.
  • denied — fue denegado.
  • revoked — fue otorgado y luego revocado.
  • expired — venció sin decisión.
  • invalidated — la solicitud fue anulada (por ejemplo, porque se publicó una nueva versión de la actividad que invalida los consentimientos previos, porque una campaña posterior reemplazó una pendiente o porque su campaña se cerró). El campo invalidationReason explica el motivo, y toda anulación emite consent.invalidated con context.invalidationReason (ver Webhooks).

Qué cambia un consentimiento otorgado

Una campaña es una herramienta de envío: crearla, cerrarla, cambiar su cierre o reenviar sus sobres nunca cambia un granted. El expiresAt de una solicitud es el plazo para decidir y sólo vence una pending. Un granted cambia únicamente por:

  • El titular, que lo retira o lo ajusta en el portal o en la página de retiro, o pide al colegio o a ti que lo registren (consola o POST …/revoke). Emite consent.revoked.
  • La decisión del propio titular cuando ya tiene edad para decidir. Cumplir años no anula el «sí» del apoderado; pero si una campaña nueva sobre la misma actividad le pide la decisión al titular (según la ageTransitionPolicy de la versión), cuando él decide —sí o no— su decisión pasa a ser la vigente y la del apoderado se cierra. Emite consent.invalidated ("superseded_by_titular"). Mientras no responde, GET …/consents/status sigue informando el granted del apoderado.
  • Una versión nueva de la actividad publicada con invalidatesPreviousConsents: true. Emite consent.invalidated ("version_superseded").
  • El rechazo de un registro público en la revisión del colegio. Emite consent.invalidated ("registration_rejected").

Autorización del apoderado (requestRole)

Cuando la versión de la actividad tiene requiresGuardianAuthorization (salidas, giras) y la solicitud la decide el propio titular por su edad, el colegio abre una segunda solicitud para que el apoderado autorice la actividad. Son dos actos distintos: el titular consiente el tratamiento de sus datos; el apoderado autoriza su participación.

  • requestRole — consent en toda solicitud corriente; guardian_authorization en la del apoderado.
  • companionOfRequestId — en la del apoderado, el id de la solicitud del titular; null en las demás.
  • guardianAuthorization — en la solicitud del titular, { requestId, status, decidedAt } de la autorización vigente del apoderado; null si no hay.

Las solicitudes del apoderado sólo las abre el colegio desde su consola: las que creas por esta API siempre vuelven con requestRole: "consent", companionOfRequestId: null y guardianAuthorization: null. Tampoco aparecen en los listados, en GET …/consents/status ni en los webhooks; sólo se ven pidiéndolas por su id.

Idempotencia

Los endpoints de escritura aceptan idempotencyKey (entre 8 y 120 caracteres, único por credencial). Reintentar una llamada con la misma llave devuelve la solicitud original con idempotentReplay: true y sin tokens de entrega nuevos, al estilo de los reintentos seguros de pago.


POST/v1/organizations/:organizationId/consents/requests

Crear una solicitud de consentimiento

Crea una solicitud para un titular del padrón y encola de inmediato sus entregas. Responde 201 con el paquete completo de la solicitud y emite el webhook consent.requested.

El token del portal viaja en deliveryAccessLinks solo en esta respuesta: ni la repetición idempotente (idempotentReplay: true, con deliveryAccessLinks: []) ni el detalle lo devuelven. Si lo pierdes, o el apoderado vuelve pasados los siete días de vigencia, reemite el enlace en vez de crear otra solicitud.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write.

La actividad de tratamiento se referencia de una de dos formas: por processingActivityVersionId (UUID exacto de la versión) o por processingActivityCode (más processingActivityVersionNumber opcional; si se omite, se usa la versión vigente). Omitir ambas responde 422; si ambas vienen y no resuelven a la misma versión, 422 PROCESSING_ACTIVITY_REFERENCE_MISMATCH.

Las URLs de redirección deben estar registradas en la lista de destinos permitidos de tu conexión; una URL no registrada rechaza la llamada antes de escribir nada.

Atributos requeridos

  • Nombre
    targetSubjectReferenceCode
    Tipo
    string
    Descripción

    Código de referencia del titular objetivo (típicamente el estudiante). Si no existe, 404 TARGET_SUBJECT_NOT_FOUND.

Atributos opcionales

  • Nombre
    processingActivityVersionId
    Tipo
    string
    Descripción

    UUID de la versión de la actividad. Requerido si no viene processingActivityCode.

  • Nombre
    processingActivityCode
    Tipo
    string
    Descripción

    Código de la actividad. Requerido si no viene processingActivityVersionId.

  • Nombre
    processingActivityVersionNumber
    Tipo
    integer
    Descripción

    Número de versión a usar junto con processingActivityCode. Por defecto, la versión vigente.

  • Nombre
    successRedirectUrl
    Tipo
    string
    Descripción

    URL a la que se envía al firmante tras otorgar. Máximo 2048 caracteres.

  • Nombre
    declineRedirectUrl
    Tipo
    string
    Descripción

    URL a la que se envía al firmante tras denegar.

  • Nombre
    redirectMode
    Tipo
    string
    Descripción

    auto (redirección inmediata) o bridge (pantalla intermedia de Edugoverna antes de redirigir).

  • Nombre
    partnerExternalId
    Tipo
    string
    Descripción

    Identificador tuyo para correlacionar la solicitud (1–160 caracteres). Filtrable en el listado.

  • Nombre
    idempotencyKey
    Tipo
    string
    Descripción

    Llave de idempotencia (8–120 caracteres).

  • Nombre
    channels
    Tipo
    string[]
    Descripción

    Canales por los que Edugoverna envía la solicitud: email, whatsapp o ambos. whatsapp exige que la organización tenga su número de WhatsApp conectado en Ajustes → WhatsApp y los mensajes aprobados por Meta; si no, la llamada responde 409 WHATSAPP_CHANNEL_UNAVAILABLE con details.reason (not_licensed, not_connected, setup_pending, setup_failed, disconnected, account_restricted, not_funded, templates_pending o spend_limit_reached). Ausente, se usa el contacto primario de cada apoderado. Un arreglo vacío ([]) significa «solo enlace»: se acuña el token y no se envía nada — la entrega queda con canal partner_link y estado handed_off, y tú muestras el enlace dentro de tu app. El colegio la ve como «Enlace entregado», no como una entrega manual que imprimir. El código de verificación del portal va al contacto registrado del decisor, correo primero.

  • Nombre
    expiresAt
    Tipo
    timestamp
    Descripción

    Vencimiento de la solicitud (ISO 8601, en el futuro). Pasado ese instante sin decisión, el barrido la marca expired y emite consent.expired.

Request
POST
/v1/.../consents/requests
curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/requests \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -H "Content-Type: application/json" \
  -d '{
    "targetSubjectReferenceCode": "EST-2026-0142",
    "processingActivityCode": "plataforma-lectura",
    "successRedirectUrl": "https://app.milectura.cl/bienvenida",
    "redirectMode": "auto",
    "partnerExternalId": "lic-88231",
    "idempotencyKey": "lic-88231-consent-v1"
  }'
Response
{
  "id": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "organizationId": "org_2f7a",
  "processingActivityVersionId": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
  "targetSubjectId": "9c1f6d1e-1c9d-4f6a-9a44-1f2ab5c0d9e1",
  "requestedForAgeBand": "under_14",
  "decisionActorType": "guardian",
  "status": "pending",
  "requestedAt": "2026-08-25T12:30:00.000Z",
  "expiresAt": null,
  "successRedirectUrl": "https://app.milectura.cl/bienvenida",
  "declineRedirectUrl": null,
  "redirectMode": "auto",
  "partnerExternalId": "lic-88231",
  "idempotencyKey": "lic-88231-consent-v1",
  "deliveries": [
    {
      "id": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6",
      "channel": "email",
      "status": "queued",
      "destinationMasked": "c***a.pino@familia.cl",
      "recipientSummary": null
    }
  ],
  "latestDecision": null,
  "deliveryAccessLinks": [
    {
      "consentDeliveryId": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6",
      "recipientSubjectId": "5d2a8c3b-77e1-4b0e-9f1d-3c4e5f6a7b8c",
      "channel": "email",
      "token": "cdt_..."
    }
  ],
  "idempotentReplay": false
  // ...
}

GET/v1/organizations/:organizationId/consents/requests

Listar solicitudes de consentimiento

Devuelve una página de solicitudes de la organización, ordenadas de la más reciente a la más antigua. A diferencia del detalle, el listado devuelve las filas base de cada solicitud (sin entregas ni decisión); usa el detalle para el paquete completo.

Permiso requerido: partner.consents.read · Alcance de conexión: consents:read.

since acota a las solicitudes creadas después del instante dado (requestedAt). Para una conciliación incremental usa updatedSince: devuelve sólo las que cambiaron desde ese instante (una decisión, un retiro, un vencimiento, una anulación), ordenadas de la más antigua a la más nueva por updatedAt. Guarda el mayor updatedAt que leíste y úsalo como el próximo updatedSince. El filtro es inclusivo, así que la última fila puede repetirse en la lectura siguiente: deduplica por id + updatedAt. Una solicitud que cambia mientras recorres las páginas pasa al final del recorrido, así que no se pierde.

Combina cualquiera de los dos con cursor para recorrer páginas. El cursor está firmado contra la organización y el juego de filtros con que se emitió: reutilizarlo con filtros distintos responde 422 INVALID_CURSOR. Las solicitudes cuyo titular fue suprimido en origen se omiten de la respuesta. El listado trae las solicitudes que viven en la organización de la URL. Las de una campaña compartida viven en el colegio o en el registrador: léelas con consentimientos de un grupo compartido o síguelas por webhooks.

Cada fila trae exactamente los campos del ejemplo: id, organizationId, processingActivityVersionId, targetSubjectId, requestedForAgeBand, decisionActorType, status, requestedAt, expiresAt, invalidatedAt, invalidationReason, partnerExternalId, idempotencyKey, consentCampaignId, redirectMode, successRedirectUrl, declineRedirectUrl, isSandbox, createdAt y updatedAt.

Parámetros de consulta opcionales

  • Nombre
    status
    Tipo
    string
    Descripción

    Filtra por estado (por ejemplo granted, pending).

  • Nombre
    processingActivityCode
    Tipo
    string
    Descripción

    Filtra por actividad de tratamiento (todas sus versiones).

  • Nombre
    partnerExternalId
    Tipo
    string
    Descripción

    Filtra por tu identificador externo.

  • Nombre
    referenceCode
    Tipo
    string
    Descripción

    Filtra por el referenceCode del titular objetivo. Un código que no existe devuelve una página vacía. Para saber solo si hay una decisión vigente, usa el estado por titular.

  • Nombre
    since
    Tipo
    timestamp
    Descripción

    Sólo solicitudes creadas después de este instante (ISO 8601).

  • Nombre
    updatedSince
    Tipo
    timestamp
    Descripción

    Sólo solicitudes cuyo updatedAt sea igual o posterior a este instante (ISO 8601). Cambia el orden a updatedAt ascendente.

  • Nombre
    limit
    Tipo
    integer
    Descripción

    Tamaño de página, entre 1 y 200. Por defecto 50.

  • Nombre
    cursor
    Tipo
    string
    Descripción

    Cursor de la página anterior (nextCursor). Ver Paginación.

Request
GET
/v1/.../consents/requests
curl -G https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/requests \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -d status=granted \
  -d since=2026-08-01T00:00:00Z \
  -d limit=50
Response
{
  "consentRequests": [
    {
      "id": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "organizationId": "org_2f7a",
      "processingActivityVersionId": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
      "targetSubjectId": "9c1f6d1e-1c9d-4f6a-9a44-1f2ab5c0d9e1",
      "requestedForAgeBand": "under_14",
      "decisionActorType": "guardian",
      "status": "granted",
      "requestedAt": "2026-08-20T15:02:11.000Z",
      "expiresAt": null,
      "invalidatedAt": null,
      "invalidationReason": null,
      "partnerExternalId": "lic-88231",
      "idempotencyKey": null,
      "consentCampaignId": null,
      "redirectMode": "auto",
      "successRedirectUrl": null,
      "declineRedirectUrl": null,
      "isSandbox": false,
      "createdAt": "2026-08-20T15:02:11.000Z",
      "updatedAt": "2026-08-21T09:40:03.000Z"
    }
  ],
  "nextCursor": "eyJ2IjoxLCJzY29wZSI6InBhcnRuZXItY29uc2VudC1yZXF1ZXN0cyJ9"
}

GET/v1/organizations/:organizationId/consents/requests/:consentRequestId

Obtener una solicitud de consentimiento

Devuelve el paquete completo de una solicitud: sus entregas con resumen del destinatario, la última decisión con su desglose por permiso (cuando la actividad usa consentimiento granular) y su evidencia, y el último evento del historial.

Permiso requerido: partner.consents.read · Alcance de conexión: consents:read.

Si la solicitud no existe — o si su titular fue suprimido en la organización de origen — la respuesta es 404 CONSENT_REQUEST_NOT_FOUND en ambos casos, deliberadamente indistinguibles. El resumen de un apoderado suprimido vuelve en null aunque la solicitud siga visible.

Desde tu workspace también puedes leer las solicitudes de tus campañas compartidas, aunque vivan en el colegio o en el registrador. Esas no traen el paquete completo sino la proyección shared_partner, la misma del webhook: sin nombre, RUT, contactos ni entregas. En una campaña segmentada por RUT, una solicitud sin decisión responde 404 CONSENT_REQUEST_NOT_FOUND, igual que una inexistente, para no confirmar qué RUT están en el padrón. Para leer muchas a la vez está consentimientos de un grupo compartido.

Campos destacados del paquete:

  • Nombre
    targetSubjectSummary
    Tipo
    object
    Descripción

    Resumen de visualización del titular objetivo.

  • Nombre
    deliveries
    Tipo
    array
    Descripción

    Entregas con canal, estado, destino enmascarado y recipientSummary.

  • Nombre
    latestDecision
    Tipo
    object
    Descripción

    Última decisión: decision, options (desglose por permiso, null si el consentimiento es en bloque), optionsSummary, evidence y missingEvidenceTypes.

  • Nombre
    latestEvent
    Tipo
    object
    Descripción

    Último evento del ciclo de vida, con su payload.

  • Nombre
    requestRole / companionOfRequestId / guardianAuthorization
    Tipo
    varios
    Descripción

    La autorización del apoderado que el colegio abre junto a la solicitud del titular. En las solicitudes creadas por esta API: consent, null y null.

Request
GET
/v1/.../consents/requests/c4a1b2d3...
curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/requests/c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d \
  -H "Authorization: Bearer {keyId}.{secret}"
Response
{
  "id": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "status": "granted",
  "requestedForAgeBand": "under_14",
  "decisionActorType": "guardian",
  "requestRole": "consent",
  "companionOfRequestId": null,
  "guardianAuthorization": null,
  "targetSubjectSummary": {
    "displayName": "Martina Rojas Pino",
    "subjectType": "student"
  },
  "deliveries": [
    {
      "id": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6",
      "channel": "email",
      "status": "delivered",
      "destinationMasked": "c***a.pino@familia.cl",
      "deliveredAt": "2026-08-20T15:04:40.000Z",
      "recipientSummary": { "displayName": "Carolina Pino Soto" }
    }
  ],
  "latestDecision": {
    "decision": "granted",
    "decidedAt": "2026-08-20T16:12:03.000Z",
    "options": null,
    "optionsSummary": null,
    "decisionMakerSummary": { "displayName": "Carolina Pino Soto" },
    "evidence": [
      {
        "id": "ev_01",
        "artifactId": "art_7c2d",
        "evidenceType": "signed_manifest",
        "checksum": "sha256:…",
        "capturedAt": "2026-08-20T16:12:03.000Z"
      }
    ],
    "missingEvidenceTypes": []
  },
  "latestEvent": {
    "eventType": "granted",
    "payload": { }
  }
  // ...
}

GET/v1/organizations/:organizationId/consents/status

Consultar el estado vigente

La pregunta que una plataforma se hace antes de mostrar una funcionalidad — «¿este titular tiene consentimiento para esta actividad?» — respondida en una llamada, sin recorrer el listado.

Permiso requerido: partner.consents.read · Alcance de conexión: consents:read.

«Vigente» es la solicitud más reciente del titular sobre cualquier versión de la actividad que no haya sido invalidada (una recaptura o una reemisión invalida a la anterior). expiresAt es el plazo para decidir: una pending que lo pasa se informa como expired, pero un granted sigue granted aunque su campaña haya terminado. Un «sí» vale hasta que el titular lo retire o una regla lo reemplace (ver qué cambia un consentimiento otorgado). isCurrentVersion distingue una decisión sobre la versión publicada de una sobre un aviso anterior: sigue siendo válida, pero no es la versión vigente.

Titular inexistente (o suprimido en origen): 404 TARGET_SUBJECT_NOT_FOUND. Actividad inexistente: 404 PROCESSING_ACTIVITY_NOT_FOUND.

Parámetros de consulta requeridos

  • Nombre
    referenceCode
    Tipo
    string
    Descripción

    Código de referencia del titular.

  • Nombre
    processingActivityCode
    Tipo
    string
    Descripción

    Código de la actividad de tratamiento.

Campos de la respuesta

  • Nombre
    status
    Tipo
    string
    Descripción

    granted, denied, pending, revoked, expired o none (nunca se pidió, o todas las solicitudes fueron invalidadas).

  • Nombre
    consentRequestId
    Tipo
    string | null
    Descripción

    La solicitud que sostiene el estado; null con none.

  • Nombre
    processingActivityVersionNumber
    Tipo
    integer | null
    Descripción

    Versión sobre la que se decidió, junto a currentVersionNumber e isCurrentVersion.

Request
GET
/v1/.../consents/status
curl "https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/status?referenceCode=EST-2026-0142&processingActivityCode=plataforma-lectura" \
  -H "Authorization: Bearer {keyId}.{secret}"
Response
{
  "referenceCode": "EST-2026-0142",
  "processingActivityCode": "plataforma-lectura",
  "status": "granted",
  "consentRequestId": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "processingActivityVersionId": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
  "processingActivityVersionNumber": 2,
  "currentVersionNumber": 2,
  "isCurrentVersion": true,
  "requestedAt": "2026-08-25T12:30:00.000Z",
  "expiresAt": null
}

POST/v1/organizations/:organizationId/consents/requests/:consentRequestId/deliveries/:consentDeliveryId/reissue

Reemitir el enlace de una entrega

Acuña un token nuevo para una entrega de una solicitud pendiente. El anterior deja de servir en el mismo instante: una plataforma que ofrece «copiar enlace» tiene siempre un único enlace vivo. Responde 201 con el token y su vencimiento (siete días), y deja el evento de auditoría consent_delivery · portal_token_reissued con tu cuenta de partner como actor.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write.

Una solicitud ya decidida, revocada, invalidada o vencida responde 409 CONSENT_REQUEST_NOT_OPEN: reemitir su enlace abriría un portal sin nada que decidir. Una entrega que no pertenece a la solicitud, 404 CONSENT_DELIVERY_NOT_FOUND; una solicitud cuyo titular fue suprimido en origen, 404 CONSENT_REQUEST_NOT_FOUND.

Esta llamada no reenvía nada: si quieres que Edugoverna vuelva a escribir al apoderado, el canal sigue siendo el de la solicitud. Sirve para el enlace que muestras tú.

Request
POST
/v1/.../deliveries/d1e2f3a4.../reissue
curl -X POST https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/requests/c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d/deliveries/d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6/reissue \
  -H "Authorization: Bearer {keyId}.{secret}"
Response
{
  "consentRequestId": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "consentDeliveryId": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6",
  "recipientSubjectId": "5d2a8c3b-77e1-4b0e-9f1d-3c4e5f6a7b8c",
  "channel": "email",
  "token": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6.…",
  "tokenExpiresAt": "2026-09-01T12:30:00.000Z"
}

POST/v1/organizations/:organizationId/consents/requests/:consentRequestId/revoke

Revocar una solicitud

Registra el retiro de un consentimiento otorgado cuando el titular te lo pidió a ti. Es la misma revocación que hace el colegio desde la Consola: la solicitud pasa a revoked, queda sellada la evidencia del retiro y se emite consent.revoked con context.revocationSource = "partner". La respuesta es la solicitud, con la misma forma que GET …/requests/:consentRequestId.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write.

Qué solicitudes puedes revocar:

  • En tu workspace: cualquiera de las suyas y las de tus campañas compartidas, aunque vivan en el colegio o en el registrador. La actividad es tuya y eres el responsable nombrado en el aviso.
  • En la organización de un colegio: sólo las que emitió tu credencial, directas o de una campaña en línea tuya. Las del colegio responden 404 CONSENT_REQUEST_NOT_FOUND, igual que una solicitud inexistente.

Una solicitud que no está otorgada (pendiente, rechazada, vencida o anulada) responde 409 ACTIVE_CONSENT_NOT_FOUND. Revocar una ya revocada responde 200 con la solicitud tal cual y no emite otro evento, así que puedes reintentar sin miedo después de un timeout. Si el titular vuelve a otorgar después (por ejemplo en el portal de transparencia) y retira de nuevo, ese segundo retiro trae su propio consent.revoked.

El cuerpo es opcional.

  • Nombre
    reason
    Tipo
    string
    Descripción

    Opcional (1–500). Motivo del retiro; queda en el historial de la solicitud. Nunca se le muestra al titular.

Request
POST
/v1/.../consents/requests/c4a1b2d3.../revoke
curl -X POST https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/requests/c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d/revoke \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "El titular lo pidió por correo" }'
Response
{
  "id": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "status": "revoked",
  "latestDecision": {
    "decision": "granted",
    "revokedAt": "2026-10-02T14:03:22.511Z"
    // ...
  }
  // ...
}

POST/v1/organizations/:organizationId/consents/campaigns

Crear una campaña con audiencia en línea

Crea y publica en una sola llamada una campaña de consentimiento sobre una lista de estudiantes que viaja en el propio cuerpo (audiencia "en línea"), creando una solicitud por estudiante. De pasada puede hacer upsert del estudiante, del apoderado y del vínculo de cada entrada, con los mismos esquemas del padrón.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write.

La audiencia en línea acepta hasta 100 estudiantes por llamada — la creación es síncrona y alguien espera la respuesta. Para lotes mayores, pagina: cada página es idempotente por su idempotencyKey, así que reenviar una página no duplica nada. Para campañas sobre padrones compartidos por un colegio, usa Campañas compartidas.

El procesamiento es por estudiante y tolerante a fallos: un error en una entrada no detiene el resto. Cada resultado vuelve con status created, idempotent_replay o error (con error.code y error.message); las entradas con error quedan también visibles en el embudo de la campaña. Si el correo o teléfono de una entrada ya es de otro titular de la organización, el código es SUBJECT_CONTACT_CONFLICT; en tu propio workspace el error trae además error.holderReferenceCode, el referenceCode del titular que ya lo tiene (ver padrón). Por cada solicitud creada se encolan las entregas y se emite el webhook consent.requested.

Quien ya otorgó no recibe otra solicitud. Si el titular ya tiene un granted para la misma versión de la actividad —de otra campaña, aunque esa ya haya terminado—, la entrada vuelve idempotent_replay con el consentRequestId de ese consentimiento, no se envía nada y el embudo lo muestra como «Ya otorgado». Una pending de otra campaña sí se reemplaza por la nueva, y la reemplazada emite consent.invalidated con context.invalidationReason = "superseded_by_campaign".

La actividad tiene que exigir consentimiento: si su base de licitud es otra, la llamada responde 409 CONSENT_NOT_REQUIRED antes de crear la campaña.

Usar URLs de redirección exige una conexión de partner (403 PARTNER_CONNECTION_REQUIRED en su ausencia) y que todas las URLs del lote estén en la lista de destinos permitidos — se validan completas antes de escribir nada.

Retiro sin RUT. El portal de transparencia de la organización de la campaña (/transparencia/{slug}/retirar) encuentra al titular por RUT o, si no tiene RUT registrado, por el correo de contacto que le diste: el código llega sólo a esa dirección. Un titular que creas con un seudónimo en referenceCode y un correo puede revisar y retirar su consentimiento sin pedírselo a nadie, y el comprobante de cada decisión trae el enlace a ese portal. Sin RUT ni correo no hay forma de identificarlo ahí: para ese caso, registra tú el retiro con POST …/revoke.

Atributos requeridos

  • Nombre
    students
    Tipo
    array
    Descripción

    Entre 1 y 100 entradas. Cada una acepta: referenceCode (requerido), partnerExternalId, successRedirectUrl, declineRedirectUrl, idempotencyKey propio, student (upsert de estudiante), guardian (upsert de apoderado, con referenceCode requerido) y guardianship (upsert del vínculo entre ambos).

Además debe venir processingActivityVersionId o processingActivityCode, igual que en la creación individual.

Atributos opcionales

  • Nombre
    name
    Tipo
    string
    Descripción

    Nombre de la campaña (2–160). Por defecto, un nombre generado con la fecha.

  • Nombre
    description
    Tipo
    string
    Descripción

    Descripción (máx. 2000).

  • Nombre
    channels
    Tipo
    array
    Descripción

    Canales de despacho: ["email"], ["whatsapp"] o ambos. whatsapp requiere el número de WhatsApp conectado de la organización y mensajes aprobados por Meta; si no, 409 WHATSAPP_CHANNEL_UNAVAILABLE (ver details.reason). Si se omite, cada solicitud usa el canal del contacto primario del destinatario. Un arreglo vacío ([]) es «solo enlace», igual que en una solicitud suelta: no se envía nada y cada resultado trae portalAccessLinks para que entregues el enlace tú. El código de verificación del portal va al contacto registrado del decisor (correo primero), así que alcanza con que tenga un correo. Un estudiante de 16 años o más con birthDate decide por sí mismo; el payload mínimo es referenceCode + birthDate

    • un contacto.
  • Nombre
    endsAt
    Tipo
    timestamp
    Descripción

    Cierre de la campaña (ISO 8601, debe ser futuro). Se copia como expiresAt de cada solicitud: es el plazo para decidir, y no vence un consentimiento ya otorgado. Puedes extenderlo o adelantarlo después, ya publicada, con PATCH /consents/campaigns/:campaignId.

  • Nombre
    reminderDayOffsets
    Tipo
    array
    Descripción

    Hasta 3 recordatorios, en días desde el envío (1–30, estrictamente crecientes). Por ejemplo [3, 7].

  • Nombre
    redirectMode
    Tipo
    string
    Descripción

    auto o bridge, para toda la campaña.

  • Nombre
    defaultSuccessRedirectUrl
    Tipo
    string
    Descripción

    URL de éxito por defecto; cada estudiante puede traer la suya.

  • Nombre
    defaultDeclineRedirectUrl
    Tipo
    string
    Descripción

    URL de rechazo por defecto.

  • Nombre
    idempotencyKey
    Tipo
    string
    Descripción

    Llave de idempotencia de la campaña (8–120). Reintentar con la misma llave reutiliza la campaña ya creada y deduplica por estudiante ({llave}:{referenceCode} cuando el estudiante no trae la suya).

Request
POST
/v1/.../consents/campaigns
curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/campaigns \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -H "Content-Type: application/json" \
  -d '{
    "processingActivityCode": "plataforma-lectura",
    "name": "Alta plataforma de lectura 2026",
    "channels": ["email"],
    "reminderDayOffsets": [3, 7],
    "idempotencyKey": "alta-lectura-2026-p1",
    "students": [
      {
        "referenceCode": "EST-2026-0142",
        "partnerExternalId": "lic-88231",
        "student": { "legalGivenNames": "Martina", "legalFamilyNames": "Rojas Pino" },
        "guardian": {
          "referenceCode": "APO-889",
          "legalGivenNames": "Carolina",
          "legalFamilyNames": "Pino Soto",
          "contact": { "contactType": "email", "value": "carolina.pino@familia.cl" }
        },
        "guardianship": { "relationshipType": "madre" }
      }
    ]
  }'
Response
{
  "campaignId": "a9b8c7d6-e5f4-4321-a0b1-c2d3e4f5a6b7",
  "processingActivityVersionId": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
  "results": [
    {
      "referenceCode": "EST-2026-0142",
      "partnerExternalId": "lic-88231",
      "consentRequestId": "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "portalAccessLinks": [
        {
          "consentDeliveryId": "d1e2f3a4-b5c6-4d7e-8f90-a1b2c3d4e5f6",
          "channel": "email",
          "token": "cdt_..."
        }
      ],
      "status": "created"
    },
    {
      "referenceCode": "EST-2026-0198",
      "partnerExternalId": null,
      "consentRequestId": null,
      "portalAccessLinks": [],
      "status": "error",
      "error": {
        "code": "PARTNER_SUBJECT_NOT_FOUND",
        "message": "student subject not found for reference code EST-2026-0198."
      }
    }
  ]
}

PATCH/v1/organizations/:organizationId/consents/campaigns/:campaignId

Cambiar el cierre de una campaña

Extiende o adelanta el endsAt de una campaña con audiencia en línea, también ya publicada. Las solicitudes pendientes pasan a vencer en la fecha nueva; las que ya vencieron no reviven. Al pasar la fecha nueva, el barrido marca las pendientes expired (con su consent.expired), el portal deja de aceptar decisiones y los recordatorios no se envían. La campaña sigue published: para cerrarla de inmediato usa …/close.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write.

Actúa sobre las campañas creadas con POST …/consents/campaigns: en tu workspace, cualquiera de las tuyas; en la organización de un colegio conectado, solo las que creó tu cuenta de partner — las demás responden 404 CONSENT_CAMPAIGN_NOT_FOUND. Una campaña sobre padrón compartido responde 409 PARTNER_CAMPAIGN_FAMILY_MISMATCH: se gestiona en /shared-campaigns/:campaignId.

La fecha debe ser futura (422 VALIDATION_ERROR) y la campaña no puede estar cerrada (409 CONSENT_CAMPAIGN_CLOSED).

  • Nombre
    endsAt
    Tipo
    timestamp
    Descripción

    Requerido. ISO 8601 con zona horaria.

Request
PATCH
/v1/.../consents/campaigns/a9b8c7d6...
curl -X PATCH https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/campaigns/a9b8c7d6-e5f4-4321-a0b1-c2d3e4f5a6b7 \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -H "Content-Type: application/json" \
  -d '{ "endsAt": "2026-11-15T03:00:00Z" }'
Response
{
  "campaignId": "a9b8c7d6-e5f4-4321-a0b1-c2d3e4f5a6b7",
  "status": "published",
  "endsAt": "2026-11-15T03:00:00.000Z"
}

POST/v1/organizations/:organizationId/consents/campaigns/:campaignId/close

Cerrar una campaña

Cierra la campaña de inmediato, con la misma cascada que el botón «Cerrar campaña» de la Consola:

  • las solicitudes pendientes pasan a invalidated (invalidationReason: "campaign_closed") y cada una emite consent.invalidated;
  • sus enlaces dejan de abrir el formulario y no salen más recordatorios;
  • la campaña queda closed y ya no se puede editar (409 CONSENT_CAMPAIGN_CLOSED).

Las decisiones ya registradas no se tocan: quien otorgó o rechazó conserva su comprobante, y un consentimiento otorgado sigue vigente hasta que se revoque. Cerrar no se deshace.

Permiso requerido: partner.consents.write · Alcance de conexión: consents:write. Mismas reglas de alcance que el PATCH. Cerrar una campaña ya cerrada responde 409 CONSENT_CAMPAIGN_ALREADY_CLOSED, así que un reintento después de un timeout se reconoce por ese código.

El cuerpo es opcional. La respuesta no trae conteos: cada solicitud anulada te llega por su propio consent.invalidated.

  • Nombre
    reason
    Tipo
    string
    Descripción

    Opcional (1–500). Motivo del cierre; queda en el evento de auditoría closed. Nunca se le muestra al titular.

Request
POST
/v1/.../consents/campaigns/a9b8c7d6.../close
curl -X POST https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/consents/campaigns/a9b8c7d6-e5f4-4321-a0b1-c2d3e4f5a6b7/close \
  -H "Authorization: Bearer {keyId}.{secret}" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Proceso de admisión cerrado" }'
Response
{
  "campaignId": "a9b8c7d6-e5f4-4321-a0b1-c2d3e4f5a6b7",
  "status": "closed",
  "closedAt": "2026-10-02T14:03:22.511Z"
}

¿Te sirvió esta página?