Revisión humana

La tercera respuesta, y un sitio donde resolverla

Si solo puedes publicar o borrar, un umbral estricto se come comentarios legítimos y uno permisivo deja pasar los ataques. ToxicFilter responde review cuando no lo tiene claro, deja ese veredicto abierto en una cola para tus propios moderadores y le cuenta a tu web por webhook lo que han decidido.

Cómo funciona

  1. El veredicto dice review

    Tu política fija, categoría a categoría, dónde empieza review y dónde empieza block. Lo que cae entre las dos líneas vuelve como review, con todas las señales y el motivo en una frase.

  2. Espera en la cola

    Solo un review abre una entrada. Tus moderadores la trabajan en el panel o la leen con GET /v1/records, y un webhook moderation.review puede avisar a tus herramientas en cuanto llega.

  3. Decide una persona

    Aprobado o rechazado, con el nombre del moderador tal como lo llames tú. También pueden decirnos si el veredicto acertó, que es la única forma honesta de saber si tus umbrales están bien puestos.

  4. Tu web se entera

    Un webhook moderation.resolved, firmado como los de Stripe, lleva la decisión a tu web para que el comentario retenido se publique o se retire allí, y no solo en nuestra pantalla.

Míralo en acción

  1. 01 Un taco dicho con cariño
  2. 02 Alguien que quizá pide ayuda
  3. 03 Tres contra uno
  4. 04 Una amenaza
  5. 05 Un comentario normal

Un taco dicho con cariño POST /v1/text

Joder, qué parche más bueno, gracias por subirlo.

review 1 ms
  • Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.

Un taco y nada dirigido contra nadie. La plantilla de comunidad lo retiene en vez de rechazarlo, porque solo alguien que conoce el ambiente distingue un elogio de un insulto.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "toxicity"
  ],
  "scores": {
    "toxicity": 0.35
  },
  "signals": [
    {
      "category": "toxicity",
      "score": 0.35,
      "reason": "Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.",
      "evidence": [
        "joder"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 1
}

Alguien que quizá pide ayuda POST /v1/text

Hay días que me quiero morir. Nadie se daría cuenta si no estuviera.

review 7 ms
  • Contains 1 phrase(s) about self-harm. This may be someone asking for help rather than breaking a rule.

Las autolesiones nunca se bloquean, tenga la puntuación que tenga. Borrarlo quita la única señal visible de que alguien lo está pasando mal; retenerlo lo pone delante de una persona.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "self_harm"
  ],
  "scores": {
    "self_harm": 0.7
  },
  "signals": [
    {
      "category": "self_harm",
      "score": 0.7,
      "reason": "Contains 1 phrase(s) about self-harm. This may be someone asking for help rather than breaking a rule.",
      "evidence": [
        "me quiero morir"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Tres contra uno POST /v1/conversation

ana Aquí va mi primer mod para el juego, se aceptan comentarios

leo eres idiota, esto no sirve para nada

ana Es el primero que hago, estoy aprendiendo

max bórralo ya, eres un imbécil

ana Vale, lo quito

sam joder, ya era hora, te lo digo yo

review 19 ms
  • Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.
  • 3 different people in this conversation are being hostile. Each message on its own is ordinary bad temper; together they are a pile-on, and that is not visible in any one of them.

El último mensaje, solo, es mal humor. Tres personas distintas contra la misma novata es un linchamiento, y eso solo se ve en el hilo.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "harassment",
    "toxicity"
  ],
  "scores": {
    "harassment": 0.575,
    "toxicity": 0.35
  },
  "signals": [
    {
      "category": "toxicity",
      "score": 0.35,
      "reason": "Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.",
      "evidence": [
        "joder"
      ]
    },
    {
      "category": "harassment",
      "score": 0.575,
      "reason": "3 different people in this conversation are being hostile. Each message on its own is ordinary bad temper; together they are a pile-on, and that is not visible in any one of them.",
      "evidence": []
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 19
}

Una amenaza POST /v1/text

Sé dónde vives y te voy a matar.

block 1 ms
  • Contains 2 phrase(s) threatening harm, aimed at the reader.

Hay cosas que no necesitan una segunda opinión. Una amenaza dirigida al lector se rechaza, y un webhook moderation.blocked lo comunica.

La respuesta, resumida
{
  "decision": "block",
  "flagged": [
    "violence"
  ],
  "scores": {
    "violence": 0.95
  },
  "signals": [
    {
      "category": "violence",
      "score": 0.95,
      "reason": "Contains 2 phrase(s) threatening harm, aimed at the reader.",
      "evidence": [
        "se donde vives",
        "te voy a matar"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 1
}

Un comentario normal POST /v1/text

Buen artículo, lo de las claves de caché me ha ahorrado una tarde.

allow 7 ms

Casi todo el tráfico es así. Se permite, no abre ninguna entrada y no envía ningún webhook, así que en la cola solo queda lo que necesita a una persona.

La respuesta, resumida
{
  "decision": "allow",
  "flagged": [],
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Míralo en acción

Un taco dicho con cariño POST /v1/text

Joder, qué parche más bueno, gracias por subirlo.

review 1 ms
  • Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.

Un taco y nada dirigido contra nadie. La plantilla de comunidad lo retiene en vez de rechazarlo, porque solo alguien que conoce el ambiente distingue un elogio de un insulto.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "toxicity"
  ],
  "scores": {
    "toxicity": 0.35
  },
  "signals": [
    {
      "category": "toxicity",
      "score": 0.35,
      "reason": "Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.",
      "evidence": [
        "joder"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 1
}

Alguien que quizá pide ayuda POST /v1/text

Hay días que me quiero morir. Nadie se daría cuenta si no estuviera.

review 7 ms
  • Contains 1 phrase(s) about self-harm. This may be someone asking for help rather than breaking a rule.

Las autolesiones nunca se bloquean, tenga la puntuación que tenga. Borrarlo quita la única señal visible de que alguien lo está pasando mal; retenerlo lo pone delante de una persona.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "self_harm"
  ],
  "scores": {
    "self_harm": 0.7
  },
  "signals": [
    {
      "category": "self_harm",
      "score": 0.7,
      "reason": "Contains 1 phrase(s) about self-harm. This may be someone asking for help rather than breaking a rule.",
      "evidence": [
        "me quiero morir"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Tres contra uno POST /v1/conversation

ana Aquí va mi primer mod para el juego, se aceptan comentarios

leo eres idiota, esto no sirve para nada

ana Es el primero que hago, estoy aprendiendo

max bórralo ya, eres un imbécil

ana Vale, lo quito

sam joder, ya era hora, te lo digo yo

review 19 ms
  • Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.
  • 3 different people in this conversation are being hostile. Each message on its own is ordinary bad temper; together they are a pile-on, and that is not visible in any one of them.

El último mensaje, solo, es mal humor. Tres personas distintas contra la misma novata es un linchamiento, y eso solo se ve en el hilo.

La respuesta, resumida
{
  "decision": "review",
  "flagged": [
    "harassment",
    "toxicity"
  ],
  "scores": {
    "harassment": 0.575,
    "toxicity": 0.35
  },
  "signals": [
    {
      "category": "toxicity",
      "score": 0.35,
      "reason": "Contains 1 profanity. On its own this says the tone is casual, not that the content is abusive.",
      "evidence": [
        "joder"
      ]
    },
    {
      "category": "harassment",
      "score": 0.575,
      "reason": "3 different people in this conversation are being hostile. Each message on its own is ordinary bad temper; together they are a pile-on, and that is not visible in any one of them.",
      "evidence": []
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 19
}

Una amenaza POST /v1/text

Sé dónde vives y te voy a matar.

block 1 ms
  • Contains 2 phrase(s) threatening harm, aimed at the reader.

Hay cosas que no necesitan una segunda opinión. Una amenaza dirigida al lector se rechaza, y un webhook moderation.blocked lo comunica.

La respuesta, resumida
{
  "decision": "block",
  "flagged": [
    "violence"
  ],
  "scores": {
    "violence": 0.95
  },
  "signals": [
    {
      "category": "violence",
      "score": 0.95,
      "reason": "Contains 2 phrase(s) threatening harm, aimed at the reader.",
      "evidence": [
        "se donde vives",
        "te voy a matar"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 1
}

Un comentario normal POST /v1/text

Buen artículo, lo de las claves de caché me ha ahorrado una tarde.

allow 7 ms

Casi todo el tráfico es así. Se permite, no abre ninguna entrada y no envía ningún webhook, así que en la cola solo queda lo que necesita a una persona.

La respuesta, resumida
{
  "decision": "allow",
  "flagged": [],
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

La revisión humana con una API, explicada

Cómo funciona la tercera respuesta, dónde está la cola y cómo vuelve la decisión a tu web.

Por qué una API de moderación necesita tres respuestas

Con un solo umbral siempre te equivocas hacia un lado. Si es estricto, desaparecen comentarios legítimos; si es permisivo, se publican ataques. Review es donde cabe la duda: el comentario con un taco que en realidad es un elogio, la respuesta que solo parece agresiva dentro de su hilo, el mensaje de alguien que puede necesitar ayuda. Las autolesiones, en concreto, nunca se bloquean con las reglas por defecto, porque borrarlas quita la única señal de que alguien lo está pasando mal.

La cola de revisión por la API

Cada veredicto tiene un id (mod_ y un ULID) y te devuelve tu propia reference. Solo un review abre una entrada en la cola; un block es una decisión ya tomada, y llenar la cola con todo lo rechazado enterraría lo que de verdad necesita a una persona. GET /v1/records lista por defecto las entradas abiertas, de la más reciente a la más antigua, con filtros por proyecto, decisión, tu referencia o valoración, y se pagina por cursor para no saltarse nada mientras la cola sigue creciendo. POST /v1/records/{id}/resolve recibe approved o rejected y el nombre de tu moderador, con tus propios términos: no conocemos a tu equipo y no nos inventamos identidades.

Moderar desde el panel

La misma cola está en el panel, en Review, con el número de entradas abiertas junto al enlace en todas las pantallas. El moderador lee los motivos, la evidencia y, si tu política lo conserva, el contenido, y lo aprueba o lo rechaza. Pueden compartirla varias personas, porque la cuenta es de una organización con miembros y no de un solo usuario.

Valoraciones: ¿acertó el veredicto?

POST /v1/records/{id}/feedback recibe correct, false_positive o false_negative. Es gratis, una valoración por veredicto, y es la única forma honesta de saber si tus líneas están en su sitio: si tus moderadores aprueban casi todo lo que retiene una categoría, esa línea está demasiado baja.

Conservar el contenido, poco tiempo

Por defecto ToxicFilter guarda el veredicto y un hash del contenido, nunca el contenido. Una cola sin nada que leer cuesta de trabajar, así que cada política puede conservarlo un número de horas: cifrado, solo para review y block, nunca para allow, con la fecha de caducidad fijada en el momento de guardarlo. Pasado ese plazo el contenido ya no se devuelve y una tarea horaria lo vacía, así que "seis horas" son seis. Las imágenes enviadas como bytes no se guardan nunca.

Webhooks que cierran el círculo

moderation.review y moderation.blocked se envían al registrar el veredicto, moderation.resolved cuando decide una persona y appeal.resolved cuando se responde una reclamación. Para allow no se envía nada. El contenido del envío es el veredicto, nunca el texto: lo relacionas con tu reference. Los envíos van firmados como los de Stripe y salen de una cola, para que un receptor lento no frene la llamada de moderación; se reintentan seis veces en unas dos horas y media, y un endpoint que falla veinte veces seguidas se desactiva.

Cómo se reparte el trabajo

Las comprobaciones instantáneas resuelven los casos claros en torno a un milisegundo, el modelo lee lo que depende del contexto, y tus reglas y tu equipo tienen la última palabra.

  • La última palabra es de una persona

    Review es una palabra en la respuesta para tu código y tu gente. Deciden tus moderadores, con su propio nombre, y nadie en ToxicFilter lee tu cola ni decide por ti.

  • No se guarda nada si no lo pides

    Por defecto la cola guarda el veredicto y sus motivos, nunca el contenido. Cuando una política conserva el texto para que lo lea un moderador, va cifrado y desaparece pasadas las horas que hayas elegido.

  • Webhooks que insisten

    La decisión llega a tu web con webhooks firmados, que se reintentan seis veces en unas dos horas y media, y un endpoint que falla una y otra vez aparece así en el panel, nunca falla en silencio.

Preguntas frecuentes

¿Qué diferencia hay entre review y block?

Block es una decisión ya tomada: el contenido no debe publicarse. Review significa que hay indicios reales pero no bastan para rechazarlo, así que conviene que lo mire una persona. Cada categoría tiene sus dos líneas en tu política, y una puntuación entre ambas vuelve como review.

¿Leer la cola o resolver gasta créditos?

No. Listar registros, leer uno, resolverlo y enviar tu valoración es gratis y queda fuera del control de créditos, así que la cola se puede seguir trabajando aunque se haya acabado la asignación del mes.

¿Puedo ver el contenido en la cola de revisión?

Solo si tu política lo conserva. La retención se fija por política en horas, cero por defecto y hasta una semana desde el panel. El contenido se guarda cifrado, solo para review y block, nunca para allow, y una tarea que se ejecuta cada hora lo vacía cuando vence el plazo.

¿Cómo sé que un webhook viene de verdad de ToxicFilter?

Cada envío lleva la cabecera X-ToxicFilter-Signature con una marca de tiempo y un HMAC-SHA256 de esa marca y el cuerpo, firmado con el secreto de tu endpoint, igual que hace Stripe. Como la marca de tiempo va firmada junto al cuerpo, un envío capturado no se puede reutilizar más tarde.

¿Qué pasa si mi endpoint de webhooks está caído?

El veredicto se responde igual: un envío nunca retrasa ni hace fallar la llamada de moderación. Se reintenta con esperas cada vez más largas, seis intentos en unas dos horas y media, y tras veinte fallos seguidos el endpoint se desactiva.

¿Puede un usuario reclamar una decisión?

Sí, si el proyecto redacta declaraciones de motivos. La reclamación contra una restricción vigente se presenta por la API en los seis meses siguientes a la decisión y la resuelve un moderador que tiene que explicar su respuesta, como exige el artículo 20 de la DSA. Un webhook appeal.resolved le dice a tu web si debe restaurar el contenido.

Sigue leyendo

Pruébalo con tu tráfico real

2.000 créditos al mes en el plan gratuito, sin tarjeta. Suficiente para pasarle una semana de tu propio contenido y ver qué opina de él.