API

Integra QueryWin en tus propios flujos

QueryWin encuentra las búsquedas por las que tu sitio ya obtiene impresiones sin tener ninguna página que las responda, redacta el artículo que falta y prepara tu producto para enviarlo a directorios, plataformas de lanzamiento, comunidades y los sitios que las respuestas de IA ya citan. Esta API pone ambos flujos en manos de tus scripts y asistentes de IA. La publicación y los envíos se siguen haciendo con tus propias credenciales y cuentas: QueryWin nunca se conecta a tu CMS ni envía nada por su cuenta.

Inicio rápido

Tres pasos. Todo lo que sigue es una llamada REST normal con un solo encabezado.

1

Crear una clave

En el panel de QueryWin, en “API”. La clave en texto plano solo se muestra una vez, al crearla. Otorga solo los permisos que necesites: una clave sin ninguna casilla marcada es de solo lectura.

2

Enviarla como token Bearer

Agrega Authorization: Bearer qw_live_… a cada solicitud. X-API-Key también funciona, para las herramientas que solo permiten definir un encabezado.

3

Consultar qué escribir y obtener el borrador

La lista de temas es gratuita y no consume créditos. Generar un esquema o un borrador descuenta créditos y requiere el permiso spend.

bash
# 1. Sitios sobre los que puede actuar esta clave
curl "https://www.querywin.com/api/v1/sites" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 2. Qué escribir esta semana (gratis, sin créditos)
curl "https://www.querywin.com/api/v1/topics?siteId=YOUR_SITE_ID" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
bash
# 3. Obtener el borrador (Markdown + JSON-LD con los datos ya completados)
curl "https://www.querywin.com/api/v1/topics/article?siteId=YOUR_SITE_ID&key=standard%20wardrobe%20depth" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 4. Tu script lo publica en tu propio blog y luego devuelve la URL
curl -X POST https://www.querywin.com/api/v1/topics/published \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": "YOUR_SITE_ID",
    "key": "standard wardrobe depth",
    "url": "https://example.com/blog/standard-wardrobe-depth"
  }'

Todas las respuestas van envueltas: {"success": true, "data": …}. Los errores son códigos legibles por máquina, nunca frases: QueryWin es multilingüe, y una frase en inglés escrita en el código acabaría mostrándose tal cual en una página en español.

El flujo de contenido

Cinco pasos, y solo dos tienen costo. El paso 4 es tuyo: QueryWin te entrega el Markdown y los datos estructurados, y la publicación la haces tú.

PasoEndpointCosto
Encontrar búsquedas con impresiones pero sin páginaGET /v1/topicsGratis
Generar un esquema respaldado por datosPOST /v1/topics/outlineCréditos
Convertirlo en un borrador listo para publicarPOST /v1/topics/articleCréditos
Publicarlo en tu propio blogtu propio CMS—
Devolver la URL a QueryWinPOST /v1/topics/publishedGratis

QueryWin nunca se conecta a tu CMS, nunca guarda las credenciales de tu sitio y nunca hace clic en “Publicar” por ti. Esta API te entrega el contenido; la escritura se hace en tu máquina, con tus propias credenciales. Ese es el límite, no una forma de eludirlo.

El flujo de difusión

Siete pasos, y solo uno tiene costo. El paso 6 es tuyo: QueryWin te entrega los contenidos de envío de cada canal, y el envío lo haces tú (o tu agente, con tus propias cuentas). Después, QueryWin vuelve a verificar por su cuenta cada ficha publicada.

PasoEndpointCosto
Listar tus productos y el grado de completitud de cada perfilGET /v1/productsGratis
Listar dónde enviar, por orden de relevancia, incluidos los sitios que citan las respuestas de IAGET /v1/channels?productId=Gratis
Crear una campaña con los canales elegidosPOST /v1/campaignsGratis
Obtener los contenidos preparados de una tareaGET /v1/tasks/{id}Gratis
Reescribirlos para ese canalPOST /v1/tasks/{id}/materialsCréditos
Enviarlostus propias cuentas—
Reportar el envío y, luego, la publicación con la URL de la fichaPOST /v1/tasks/{id}/statusGratis

published es lo que tú reportaste; verified es lo que QueryWin vio al volver a verificar la ficha unas 72 horas después: un enlace a tu producto en los directorios (también los de herramientas de IA) y una mención en el resto. Son campos distintos. Y un sitio que citan las respuestas de IA (citedByAi) es un sitio al que vale la pena dirigirse; no es una promesa de que vaya a incluirte.

Permisos

Cada clave lleva los permisos (scopes) otorgados al crearla. GET /v1/usage los devuelve, así que no tienes que descubrirlos al recibir un 403.

read

Siempre activo

Todos los endpoints GET: sitios, brechas de contenido, esquemas, borradores, productos, canales, campañas, tareas y sus contenidos, uso.

publish

Desactivado de forma predeterminada

Crear y actualizar perfiles de producto, importar imágenes y registrar lo que pasó: la URL de un artículo publicado (que también se envía a Bing, Yandex, Seznam y Naver, no a Google), una campaña nueva, una tarea marcada como enviada o publicada. Es gratis, pero cada llamada crea un registro con consecuencias: las campañas cuentan para el límite de tu plan y una ficha publicada se vuelve a verificar.

spend

Desactivado de forma predeterminada

Generar esquemas y borradores, y reescribir contenidos de envío para un canal. Estas acciones descuentan créditos. Otorga este permiso solo si quieres que quien hace la llamada (un script o un asistente de IA) pueda gastar por su cuenta.

No hay jerarquía: publish no implica spend, y spend no implica publish. Son tipos de riesgo distintos. Una llamada para la que tu clave no tiene permiso devuelve un 403 con missing_scope_<name> y los permisos que sí tienes.

Consumo de créditos

Dos endpoints descuentan créditos: POST /v1/topics/outline y POST /v1/topics/article. Los protegen tres salvaguardas.

SalvaguardaQué evita
confirmSpendUn script que sigue gastando créditos después de un aumento de precio.
Límite diarioUn bucle descontrolado que agota el saldo en una noche. Devuelve un 429 con la hora de reinicio.
Huella de la entradaCobrar dos veces por el mismo tema. Las entradas idénticas devuelven gratis el resultado en caché, así que puedes reintentar sin riesgo una solicitud que agotó el tiempo de espera.

confirmSpend es un límite de autorización, no un monto exacto. Envía un valor mayor o igual al precio actual y se te cobrará lo que cueste realmente, a menudo cero si el resultado está en caché. Si el precio llega a superar tu límite, la llamada falla con confirm_spend_too_low en lugar de cobrar más sin avisar.

Convenciones

Tres reglas que se cumplen en todos los endpoints.

Envoltorio de respuesta

json
{ "success": true,  "data": { "...": "..." } }
{ "success": false, "error": "missing_scope_spend" }

Los errores son códigos, nunca frases. Léelos, pero no los muestres sin procesar: están pensados para que los conviertas en tus propios textos.

Un resultado de negocio no es un error

Una llamada de generación que no pudo producir un resultado válido devuelve HTTP 200 con data.ok = false y un código data.failure, para que puedas distinguirla de una falla de autenticación o de una conexión caída. Estas llamadas no se cobran.

json
{
  "success": true,
  "data": { "ok": false, "failure": "no_outline", "creditsSpent": 0 }
}

Límite de solicitudes

120 solicitudes por minuto y por clave. Si superas esa cifra, recibes un 429 con la hora de reinicio. Es independiente del límite diario de generación descrito arriba.

MCP para agentes de IA

Ambos flujos están disponibles a través del Model Context Protocol, con la misma clave y el mismo encabezado de autenticación. Agrega el servidor a Claude Code, Cursor, n8n o cualquier otra herramienta compatible con MCP sobre HTTP.

MCPhttps://www.querywin.com/api/mcp
bash
claude mcp add --transport http querywin https://www.querywin.com/api/mcp \
  --header "Authorization: Bearer $QUERYWIN_API_KEY"

Diecinueve herramientas. Perfiles de producto: create_product, get_product, update_product, import_product_image. Contenido: list_sites, get_usage, list_content_gaps, get_outline, get_article_draft, generate_outline, generate_article_draft, mark_published. Difusión: list_products, list_channels, create_campaign, list_tasks, get_task, write_task_materials, report_task_status. Pregúntale a tu asistente qué escribir esta semana o dónde enviar tu producto a continuación, y lo averiguará por su cuenta.

prompt
Con el servidor MCP de QueryWin, encuentra las tres brechas de contenido más grandes de mi sitio,
muéstrame las búsquedas que hay detrás de cada una y dime cuánto costaría redactar el borrador de la primera.

Las herramientas se filtran por permiso. Con una clave de solo lectura, las herramientas de generación ni siquiera aparecen en la lista de herramientas del asistente: un agente no puede llamar a una herramienta que no ve. Dale a cada agente su propia clave para poder revocarla sin tocar tus otras integraciones.

La autenticación se hace con un token Bearer, algo que la especificación MCP permite (en ella la autorización es opcional). Los clientes que permiten definir un encabezado (Claude Code, Cursor, n8n) se conectan directamente. Es posible que los hosts que exigen una pantalla de consentimiento de OAuth no puedan conectarse.

Account

GET/v1/sites

Listar los sitios sobre los que puede actuar esta clave

Comienza por aquí. Todos los demás endpoints reciben el siteId que devuelve esta llamada. Si lo omites, recurren al primer sitio conectado: no pasa nada en las cuentas con un solo sitio, pero en todas las demás es un error latente.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataSiteList
└sitesarray<Site>
└siteIdstring
└domainstring
└gscPropertystringLa propiedad de Search Console, tal cual: sc-domain:example.com o https://example.com/.
└syncStatusenumValores: pendingsyncingdonefailed
└syncedThroughadmite nullstringLos datos de Search Console llevan entre 2 y 3 días de retraso. Todas las métricas de este sitio están actualizadas hasta esta fecha: indícalo si muestras estas cifras en cualquier parte.
Errores posibles
401Clave ausente, mal formada, revocada o vencida

Ejemplo

bash
curl "https://www.querywin.com/api/v1/sites" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "sites": [
      {
        "siteId": "string",
        "domain": "example.com",
        "gscProperty": "string",
        "syncStatus": "pending",
        "syncedThrough": "2026-09-01T00:00:00.000Z"
      }
    ]
  }
}
GET/v1/usage

Precios actuales, generaciones gratuitas, saldo de créditos y límites diarios

Consúltalo antes de generar nada. Es la misma fuente de verdad que usa la interfaz web para decidir qué muestra cada botón: la disponibilidad se decide en el servidor, no por prueba y error.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataUsage
└scopesarray<enum>Lo que puede hacer esta clave. Consúltalo una vez al iniciar, en lugar de descubrir tus permisos topándote con un 403.Valores: readpublishspend
└creditsobject
└balanceinteger
└outlineStepUsage
└availablebooleanEs false cuando el motor de generación no está configurado. En ese caso, no llames al endpoint POST correspondiente.
└pricePerOutlineintegerCréditos por esquema (solo presente en outline).
└pricePerArticleintegerCréditos por borrador (solo presente en article).
└pricePerTaskintegerCréditos por cada reescritura para un canal (solo presente en materials).
└freeRemainingintegerGeneraciones gratuitas que le quedan a esta cuenta, contadas por tema distinto (esquemas, borradores) o por tarea distinta (contenidos), no por cada clic en un botón. Las generaciones gratuitas también requieren confirmSpend.
└dailyDailyLimitProtección contra un script desbocado, contabilizada en la base de datos en todos los puntos de entrada (las acciones en la interfaz web también cuentan). Se reinicia a medianoche, hora local, no en una ventana deslizante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└articleStepUsage
└availablebooleanEs false cuando el motor de generación no está configurado. En ese caso, no llames al endpoint POST correspondiente.
└pricePerOutlineintegerCréditos por esquema (solo presente en outline).
└pricePerArticleintegerCréditos por borrador (solo presente en article).
└pricePerTaskintegerCréditos por cada reescritura para un canal (solo presente en materials).
└freeRemainingintegerGeneraciones gratuitas que le quedan a esta cuenta, contadas por tema distinto (esquemas, borradores) o por tarea distinta (contenidos), no por cada clic en un botón. Las generaciones gratuitas también requieren confirmSpend.
└dailyDailyLimitProtección contra un script desbocado, contabilizada en la base de datos en todos los puntos de entrada (las acciones en la interfaz web también cuentan). Se reinicia a medianoche, hora local, no en una ventana deslizante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└materialsStepUsage
└availablebooleanEs false cuando el motor de generación no está configurado. En ese caso, no llames al endpoint POST correspondiente.
└pricePerOutlineintegerCréditos por esquema (solo presente en outline).
└pricePerArticleintegerCréditos por borrador (solo presente en article).
└pricePerTaskintegerCréditos por cada reescritura para un canal (solo presente en materials).
└freeRemainingintegerGeneraciones gratuitas que le quedan a esta cuenta, contadas por tema distinto (esquemas, borradores) o por tarea distinta (contenidos), no por cada clic en un botón. Las generaciones gratuitas también requieren confirmSpend.
└dailyDailyLimitProtección contra un script desbocado, contabilizada en la base de datos en todos los puntos de entrada (las acciones en la interfaz web también cuentan). Se reinicia a medianoche, hora local, no en una ventana deslizante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└distributionDistributionQuotaLos límites de difusión del plan y su consumo actual. Un límite con valor null significa que no hay límite.
└planstring
└limitsobject
└channelsadmite nullintegerNúmero de canales distintos en los que un producto puede tener tareas, contados en el periodo channelsPeriod.
└channelsPeriodenummonth (planes de pago): se cuenta por mes calendario (UTC), así que cada mes permite un nuevo lote de canales. total (plan gratuito): se cuenta durante toda la vida del producto.Valores: monthtotal
└tasksPerMonthadmite nullintegerNúmero de tareas que se pueden crear por mes calendario (UTC), entre todos los productos.
└activeCampaignsadmite nullintegerNúmero de campañas que pueden estar activas a la vez.
└usageobject
└activeCampaignsinteger
└tasksThisMonthinteger
└channelsadmite nullintegerSolo cuando la solicitud indica un producto.
Errores posibles
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/usage" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "scopes": [
      "read"
    ],
    "credits": {
      "balance": 0
    },
    "outline": {
      "available": true,
      "pricePerOutline": 0,
      "pricePerArticle": 0,
      "pricePerTask": 0,
      "freeRemaining": 0,
      "daily": {
        "used": 0,
        "limit": 0,
        "remaining": 0,
        "resetAt": "2026-09-01T00:00:00.000Z"
      }
    },
    "article": {
      "available": true,
      "pricePerOutline": 0,
      "pricePerArticle": 0,
      "pricePerTask": 0,
      "freeRemaining": 0,
      "daily": {
        "used": 0,
        "limit": 0,
        "remaining": 0,
        "resetAt": "2026-09-01T00:00:00.000Z"
      }
    },
    "materials": {
      "available": true,
      "pricePerOutline": 0,
      "pricePerArticle": 0,
      "pricePerTask": 0,
      "freeRemaining": 0,
      "daily": {
        "used": 0,
        "limit": 0,
        "remaining": 0,
        "resetAt": "2026-09-01T00:00:00.000Z"
      }
    },
    "distribution": {
      "plan": "string",
      "limits": {
        "channels": 0,
        "channelsPeriod": "month",
        "tasksPerMonth": 0,
        "activeCampaigns": 0
      },
      "usage": {
        "activeCampaigns": 0,
        "tasksThisMonth": 0,
        "channels": 0
      }
    }
  }
}

Topics

GET/v1/topics

Búsquedas con impresiones pero sin una página que las cubra

Gratis y sin facturación externa (la API en sí requiere un plan de pago). Se recalcula en cada llamada a partir de tus propios datos de Search Console, sin ninguna base de palabras clave externa, y de eso se trata precisamente: “ya tienes impresiones y ninguna página que las atienda” no es algo que pueda decirte una herramienta de palabras clave.

Los resultados se ordenan por oportunidad. Se excluyen los temas que el usuario descartó en la interfaz.

Parámetros de consulta

CampoTipoDescripción
siteIdstringDe GET /v1/sites. Si se omite, se usa el primer sitio conectado.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataTopicList
└siteIdadmite nullstring
└topicsarray<Topic>
└keystringEl identificador del clúster: devuélvelo tal cual como key a todos los demás endpoints de temas. Es el texto normalizado de la búsqueda representativa, así que puede contener espacios, barras y caracteres no latinos. Envíalo siempre como parámetro de consulta o en el cuerpo, nunca en la ruta de la URL.
└titlestringLa búsqueda del clúster con más impresiones, tal cual. No es un título generado: ese llega con el esquema.
└shapeenumcomparison significa que este clúster coincide con tu lista de competidores. El artículo tiene que contrastar, no explicar al competidor: de lo contrario, estarás escribiendo contenido para él.Valores: comparisonroundupguide
└intentstring
└membersarray<TopicMember>
└textstringLa búsqueda, tal como se escribió.
└impressionsinteger
└clicksinteger
└positionadmite nullnumber
└landingUrladmite nullstringLa página que Search Console asocia actualmente a esta búsqueda, si la hay.
└impressionsintegerValor medido, procedente de Search Console.
└clicksintegerValor medido, procedente de Search Console.
└positionadmite nullnumberValor medido: posición promedio en el clúster, ponderada por impresiones.
└competitorboolean
└scorenumber
└rankinteger
└upsideClicksintegerEs una estimación, no una medición: clics adicionales al mes si una página dedicada alcanzara la posición 3. Es a propósito un campo separado de clicks e impressions, y debe seguir visualmente separado allí donde lo muestres. Presentar una proyección como si fuera un dato medido es el error típico de esta categoría de productos.
└statusenumValores: newdismissedplannedpublished
└outlineAtadmite nullstring
└articleAtadmite nullstringNo lo deduzcas de outlineAt. Tener un esquema no significa que exista un borrador: son dos pasos de pago distintos.
└totalQueriesintegerNúmero total de búsquedas distintas que cubren estos temas.
Errores posibles
401Error de autenticación
404site_not_found — este siteId no pertenece a esta cuenta

Ejemplo

bash
curl "https://www.querywin.com/api/v1/topics" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "siteId": "string",
    "topics": [
      {
        "key": "string",
        "title": "string",
        "shape": "comparison",
        "intent": "string",
        "members": [
          {
            "text": null,
            "impressions": null,
            "clicks": null,
            "position": null,
            "landingUrl": null
          }
        ],
        "impressions": 0,
        "clicks": 0,
        "position": 0,
        "competitor": true,
        "score": 0,
        "rank": 0,
        "upsideClicks": 0,
        "status": "new",
        "outlineAt": "2026-09-01T00:00:00.000Z",
        "articleAt": "2026-09-01T00:00:00.000Z"
      }
    ],
    "totalQueries": 0
  }
}
GET/v1/topics/article

Leer un borrador ya generado

Nunca lanza una generación y nunca cuesta nada. article es null mientras no exista ningún borrador.

Parámetros de consulta

CampoTipoDescripción
keyobligatoriostringEl identificador del clúster, de GET /v1/topics.
siteIdstringDe GET /v1/sites. Si se omite, se usa el primer sitio conectado.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataArticleResult
└articleadmite nullArticle
└titlestring
└descriptionstringMetadescripción.
└markdownstringEl cuerpo que se publica.
└jsonLdstringLos datos estructurados de este artículo, ya completados. JSON válido: colócalo dentro de una etiqueta script de tipo application/ld+json en la página publicada.
└wordCountinteger
└warningsarray<ArticleWarning>Frases que suenan a escritas por una IA. El borrador sigue siendo utilizable: se señalan en lugar de reescribirse sin avisar. Regístralas en tus logs. En un flujo automatizado, este es el único momento en que alguien podría darse cuenta.
└kindenumValores: banned_phrasebanned_wordem_dash_overuseno_short_sentence
└hitstringEl texto problemático.
└articleAtadmite nullstring
└modeladmite nullstring
Errores posibles
400key_required
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/topics/article?key=..." \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "article": {
      "title": "string",
      "description": "string",
      "markdown": "string",
      "jsonLd": "string",
      "wordCount": 0,
      "warnings": [
        {
          "kind": "banned_phrase",
          "hit": "string"
        }
      ]
    },
    "articleAt": "2026-09-01T00:00:00.000Z",
    "model": "string"
  }
}
POST/v1/topics/article

Convertir el esquema en un borrador listo para publicar (consume créditos)

La llamada más cara del producto. Síncrona: puede tardar entre uno y dos minutos.

Antes tiene que existir un esquema: sin él, obtienes failure: "no_outline". El esquema es lo que aporta las pruebas (las búsquedas reales detrás del clúster, las páginas que la IA cita hoy, la deduplicación frente a tus páginas existentes). Prescindir de él reduciría esta llamada a una simple herramienta de redacción con IA, sin nada detrás.

article.markdown es el cuerpo que se publica. article.jsonLd contiene los datos estructurados, ya completados con el contenido de este artículo. **article.warnings debe registrarse en tus logs, no descartarse**: cada aviso señala una frase concreta que suena a escrita por una IA, y en un flujo automatizado nadie vuelve a leer el borrador antes de que se publique.

Los borradores que no pasan la validación estructural (secciones que faltan, preguntas obligatorias sin responder, enlaces inventados, JSON-LD no válido) se descartan y no se cobran.

Cuerpo de la solicitud

CampoTipoDescripción
keyobligatoriostringEl identificador del clúster, de GET /v1/topics.
siteIdstringSi se omite, se usa el primer sitio conectado.
confirmSpendobligatoriointegerUn tope de autorización en créditos, no un monto exacto. Envía un valor mayor o igual al precio actual que indica GET /v1/usage; solo se cobra lo que cueste realmente, que a menudo es cero si el resultado está en caché. Si algún día el precio supera tu tope, la llamada se rechaza en lugar de cobrarte más sin avisar. Es obligatorio aunque te queden generaciones gratuitas: se acabarán, y ese no debería ser el momento en que tu script descubra que este endpoint cuesta dinero. (min 0)

Respuesta200

CampoTipoDescripción
successenumValores: true
dataArticleOutcome
└okboolean
└failureenumSolo presente cuando ok es false. El código HTTP sigue siendo 200.Valores: topic_not_foundno_outlineengine_unavailableengine_failedno_valid_article
└articleadmite nullArticle
└titlestring
└descriptionstringMetadescripción.
└markdownstringEl cuerpo que se publica.
└jsonLdstringLos datos estructurados de este artículo, ya completados. JSON válido: colócalo dentro de una etiqueta script de tipo application/ld+json en la página publicada.
└wordCountinteger
└warningsarray<ArticleWarning>Frases que suenan a escritas por una IA. El borrador sigue siendo utilizable: se señalan en lugar de reescribirse sin avisar. Regístralas en tus logs. En un flujo automatizado, este es el único momento en que alguien podría darse cuenta.
└kindenumValores: banned_phrasebanned_wordem_dash_overuseno_short_sentence
└hitstringEl texto problemático.
└generatedbooleanEs false cuando se devolvió un resultado en caché: no se cobró nada.
└creditsSpentinteger
└freeUsedboolean
└rejectedarray<string>Defectos estructurales por los que se descartó el borrador (sin cobrarlo): missing_sections, missing_faq, invented_link, invalid_json_ld, comparison_without_contrast, body_too_short.
Errores posibles
400key_required, confirm_spend_required o confirm_spend_too_low (el cuerpo incluye el price actual)
401Error de autenticación
402insufficient_credits — el cuerpo incluye requiredCredits, currentBalance, shortfall
403missing_scope_spend — esta clave no tiene el permiso spend
429rate_limited o daily_limit_reached (el cuerpo incluye resetAt)

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/topics/article \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": "clx1site000",
    "key": "standard wardrobe depth",
    "confirmSpend": 80
  }'
json — respuesta
{
  "success": true,
  "data": {
    "ok": true,
    "failure": "topic_not_found",
    "article": {
      "title": "string",
      "description": "string",
      "markdown": "string",
      "jsonLd": "string",
      "wordCount": 0,
      "warnings": [
        {
          "kind": "banned_phrase",
          "hit": "string"
        }
      ]
    },
    "generated": true,
    "creditsSpent": 0,
    "freeUsed": true,
    "rejected": [
      "string"
    ]
  }
}
GET/v1/topics/outline

Leer un esquema ya generado

Nunca lanza una generación y nunca cuesta nada. outline es null mientras no exista ningún esquema.

Parámetros de consulta

CampoTipoDescripción
keyobligatoriostringEl identificador del clúster, de GET /v1/topics.
siteIdstringDe GET /v1/sites. Si se omite, se usa el primer sitio conectado.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataOutlineResult
└outlineadmite nullOutline
└titlestring
└slugstring
└anglestringLa tesis que debe defender esta página.
└sectionsarray<object>
└headingstring
└pointsarray<string>
└faqarray<object>Las preguntas que debe responder la página. Son los fragmentos que citan las respuestas de IA.
└questionstring
└answerstring
└schemaTypestringEl tipo de JSON-LD adecuado para esta página.
└internalLinksarray<string>Páginas de tu propio sitio a las que conviene enlazar. Elegidas entre URL reales, nunca inventadas.
└outlineAtadmite nullstring
└modeladmite nullstring
Errores posibles
400key_required
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/topics/outline?key=..." \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "outline": {
      "title": "string",
      "slug": "string",
      "angle": "string",
      "sections": [
        {
          "heading": "string",
          "points": []
        }
      ],
      "faq": [
        {
          "question": "string",
          "answer": "string"
        }
      ],
      "schemaType": "string",
      "internalLinks": [
        "string"
      ]
    },
    "outlineAt": "2026-09-01T00:00:00.000Z",
    "model": "string"
  }
}
POST/v1/topics/outline

Generar un esquema (consume créditos)

Llamada síncrona: tarda entre 10 y 20 segundos aproximadamente.

Con entradas idénticas se devuelve el esquema en caché sin volver a cobrar: la huella cubre el clúster de temas y su clasificación frente a tus competidores, así que puedes reintentar sin riesgo una solicitud HTTP que haya fallado.

Los resultados de negocio (motor no disponible, salida que no pasa la validación) devuelven HTTP 200 con ok: false y un código failure. La falta de créditos, en cambio, sí es un error 402 real.

Cuerpo de la solicitud

CampoTipoDescripción
keyobligatoriostringEl identificador del clúster, de GET /v1/topics.
siteIdstringSi se omite, se usa el primer sitio conectado.
confirmSpendobligatoriointegerUn tope de autorización en créditos, no un monto exacto. Envía un valor mayor o igual al precio actual que indica GET /v1/usage; solo se cobra lo que cueste realmente, que a menudo es cero si el resultado está en caché. Si algún día el precio supera tu tope, la llamada se rechaza en lugar de cobrarte más sin avisar. Es obligatorio aunque te queden generaciones gratuitas: se acabarán, y ese no debería ser el momento en que tu script descubra que este endpoint cuesta dinero. (min 0)

Respuesta200

CampoTipoDescripción
successenumValores: true
dataOutlineOutcome
└okboolean
└failureenumSolo presente cuando ok es false. El código HTTP sigue siendo 200: es un resultado, no un error.Valores: topic_not_foundengine_unavailableengine_failedno_valid_outline
└outlineadmite nullOutline
└titlestring
└slugstring
└anglestringLa tesis que debe defender esta página.
└sectionsarray<object>
└headingstring
└pointsarray<string>
└faqarray<object>Las preguntas que debe responder la página. Son los fragmentos que citan las respuestas de IA.
└questionstring
└answerstring
└schemaTypestringEl tipo de JSON-LD adecuado para esta página.
└internalLinksarray<string>Páginas de tu propio sitio a las que conviene enlazar. Elegidas entre URL reales, nunca inventadas.
└generatedbooleanEs false cuando se devolvió un resultado en caché: no se cobró nada.
└creditsSpentinteger
└freeUsedboolean
└rejectedarray<string>Reglas de validación que incumplió la salida del modelo. Conviene registrarlas como señal de calidad.
Errores posibles
400key_required, confirm_spend_required o confirm_spend_too_low (el cuerpo incluye el price actual)
401Error de autenticación
402insufficient_credits — el cuerpo incluye requiredCredits, currentBalance, shortfall
403missing_scope_spend — esta clave no tiene el permiso spend
429rate_limited o daily_limit_reached (el cuerpo incluye resetAt)

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/topics/outline \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": "clx1site000",
    "key": "standard wardrobe depth",
    "confirmSpend": 80
  }'
json — respuesta
{
  "success": true,
  "data": {
    "ok": true,
    "failure": "topic_not_found",
    "outline": {
      "title": "string",
      "slug": "string",
      "angle": "string",
      "sections": [
        {
          "heading": "string",
          "points": []
        }
      ],
      "faq": [
        {
          "question": "string",
          "answer": "string"
        }
      ],
      "schemaType": "string",
      "internalLinks": [
        "string"
      ]
    },
    "generated": true,
    "creditsSpent": 0,
    "freeUsed": true,
    "rejected": [
      "string"
    ]
  }
}
POST/v1/topics/published

Indicar dónde lo publicaste

Esta llamada cierra el ciclo: marca el tema como publicado, guarda la URL y la envía a IndexNow en tu nombre.

IndexNow cubre Bing, Yandex, Seznam y Naver, pero no Google. Google no tiene un endpoint equivalente de indexación instantánea: encuentra la página a través de tu sitemap.

El envío a IndexNow nunca hace fallar la llamada: tu artículo ya está publicado, y ese es el hecho que registra esta llamada. Consulta el campo indexnow para saber qué pasó realmente. El envío requiere que el archivo de clave de IndexNow esté verificado para el sitio (se configura una sola vez en el panel).

Registrar la URL es también lo que permite a QueryWin volver a medir las búsquedas a las que apunta este artículo, cuando haya tenido tiempo de surtir efecto.

Cuerpo de la solicitud

CampoTipoDescripción
keyobligatoriostring
siteIdstring
urlobligatoriostringLa URL donde lo publicaste. Solo http/https. En este momento no se descarga la página.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataPublishedResult
└clusterKeystring
└statusenumValores: published
└publishedUrlstring
└publishedAtstring
└indexnowobjectBing, Yandex, Seznam y Naver. No incluye Google.
└pushedboolean
└outcomestringskipped suele significar que el archivo de clave aún no está verificado.
└enginesstring
Errores posibles
400key_required, url_required o invalid_url (solo http/https)
401Error de autenticación
403missing_scope_publish — esta clave no tiene el permiso publish
404site_not_found

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/topics/published \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": "clx1site000",
    "key": "standard wardrobe depth",
    "url": "https://example.com/blog/standard-wardrobe-depth"
  }'
json — respuesta
{
  "success": true,
  "data": {
    "clusterKey": "string",
    "status": "published",
    "publishedUrl": "string",
    "publishedAt": "2026-09-01T00:00:00.000Z",
    "indexnow": {
      "pushed": true,
      "outcome": "string",
      "engines": "string"
    }
  }
}

Distribution

GET/v1/campaigns

Campañas y cuota de difusión

Las campañas (sin las archivadas, salvo con includeArchived=true), además de los límites de difusión del plan y su consumo actual. Gratis.

Parámetros de consulta

CampoTipoDescripción
productIdstringSolo las campañas de este producto.
includeArchivedboolean

Respuesta200

CampoTipoDescripción
successenumValores: true
dataCampaignList
└campaignsarray<Campaign>
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted se calcula: todas las tareas están publicadas, verificadas, fallidas u omitidas.Valores: activecompletedarchived
└quotaintegerNúmero de canales con los que se creó la campaña.
└startsAtstring
└endsAtadmite nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└quotaDistributionQuotaLos límites de difusión del plan y su consumo actual. Un límite con valor null significa que no hay límite.
└planstring
└limitsobject
└channelsadmite nullintegerNúmero de canales distintos en los que un producto puede tener tareas, contados en el periodo channelsPeriod.
└channelsPeriodenummonth (planes de pago): se cuenta por mes calendario (UTC), así que cada mes permite un nuevo lote de canales. total (plan gratuito): se cuenta durante toda la vida del producto.Valores: monthtotal
└tasksPerMonthadmite nullintegerNúmero de tareas que se pueden crear por mes calendario (UTC), entre todos los productos.
└activeCampaignsadmite nullintegerNúmero de campañas que pueden estar activas a la vez.
└usageobject
└activeCampaignsinteger
└tasksThisMonthinteger
└channelsadmite nullintegerSolo cuando la solicitud indica un producto.
Errores posibles
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/campaigns" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "campaigns": [
      {
        "campaignId": "string",
        "productId": "string",
        "name": "string",
        "status": "active",
        "quota": 0,
        "startsAt": "2026-09-01T00:00:00.000Z",
        "endsAt": "2026-09-01T00:00:00.000Z",
        "counts": {
          "total": 0,
          "submitted": 0,
          "live": 0,
          "blocked": 0,
          "done": 0,
          "byStatus": null
        },
        "createdAt": "2026-09-01T00:00:00.000Z"
      }
    ],
    "quota": {
      "plan": "string",
      "limits": {
        "channels": 0,
        "channelsPeriod": "month",
        "tasksPerMonth": 0,
        "activeCampaigns": 0
      },
      "usage": {
        "activeCampaigns": 0,
        "tasksThisMonth": 0,
        "channels": 0
      }
    }
  }
}
POST/v1/campaigns

Crear una campaña a partir de una lista explícita de canales

Una tarea por cada ID de canal, con sus contenidos de envío preparados al momento a partir del perfil de producto, sin costo. Pasa los canales que realmente se eligieron: una campaña es el registro de dónde decidiste enviar, no un filtro que el servidor se encarga de ampliar.

Los canales en los que el producto ya tiene una tarea abierta o enviada se omiten y aparecen en skipped con un motivo (already_open, already_submitted, not_found, broken, inactive, other_product, locked). Si no queda ninguno, la llamada devuelve HTTP 200 con ok: false y failure: "no_valid_targets". Superar la cuota de difusión del plan supone un rechazo: **409 quota_exceeded**, con dimension, limit, used y requested. No se crea nada, ni siquiera la parte que habría cabido.

Cuerpo de la solicitud

CampoTipoDescripción
productIdobligatoriostring
nameobligatoriostring
targetIdsobligatorioarray<string>Los ID de canal que devuelve GET /v1/channels. Solo los canales elegidos.
endsAtstringFecha límite opcional, que se muestra en el panel. Nada se cierra automáticamente.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataCreateCampaignOutcome
└okboolean
└failureenumPresente cuando ok es false.Valores: no_valid_targets
└campaignCampaign
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted se calcula: todas las tareas están publicadas, verificadas, fallidas u omitidas.Valores: activecompletedarchived
└quotaintegerNúmero de canales con los que se creó la campaña.
└startsAtstring
└endsAtadmite nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└tasksarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished es lo que tú declaraste. verified es lo que QueryWin vio en la página de la ficha (un enlace en los directorios y los directorios de herramientas de IA, una mención en el resto). Mantenlos separados cuando los reportes.Valores: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonadmite nullenumValores: logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenidos obligatorios que faltan en el perfil. Completa el perfil en el panel: la tarea se vuelve a preparar sola.
└listingUrladmite nullstring
└markedByenumQuién hizo el último cambio de estado: una persona (o esta API), la extensión del navegador o el propio QueryWin.Valores: userdevicesystem
└hasGeneratedbooleanExiste una versión adaptada a este canal.
└reviewDueAtadmite nullstringCuándo volver a revisar después del envío (submittedAt + el plazo de revisión del canal).
└submittedAtadmite nullstring
└publishedAtadmite nullstring
└verifiedAtadmite nullstring
└nextarray<string>Estados que puedes asignar desde el actual mediante POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValores: seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkadmite nullobjectLa verificación que QueryWin hace en la ficha una vez publicada. Es null hasta que la tarea se publica.
└kindenumQué se busca: un enlace al producto (directorios, directorios de herramientas de IA) o una mención de la marca (todos los demás tipos).Valores: link_livemention_seen
└stateadmite nullenumconfirmed = encontrado. unconfirmed = no se encontró en una ronda de verificaciones (el estado no cambia; revisa la URL). lost = estaba y desapareció (la tarea pasa a fallida).Valores: confirmedunconfirmedlostnull
└checkedAtadmite nullstring
└dueAtadmite nullstring
└updatedAtstring
└skippedarray<object>
└targetIdstring
└reasonenumother_product = un sitio candidato citado por la IA que pertenece a otro producto; inactive = un canal desactivado o ignorado; locked = fuera de la ventana de la biblioteca de canales del plan gratuito (los primeros window canales por relevancia, tal como los devuelve GET /v1/channels; tus propios canales, tus favoritos y los sitios citados por la IA no tienen límite).Valores: not_foundbrokeninactiveother_productalready_openalready_submittedlocked
Errores posibles
400product_id_required, name_required / name_too_long (80 caracteres), target_ids_required / too_many_targets (100 canales) o invalid_date
401Error de autenticación
403missing_scope_publish — esta clave no tiene el permiso publish
404product_not_found — este productId no pertenece a esta cuenta
409quota_exceeded — el cuerpo incluye dimension (active_campaigns / tasks_per_month / channels), limit, used, requested; para channels, también period (month / total, igual que DistributionQuota.limits.channelsPeriod)

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/campaigns \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "clx1prod000",
    "name": "Directories, September",
    "targetIds": [
      "clx1tgt001",
      "clx1tgt002"
    ]
  }'
json — respuesta
{
  "success": true,
  "data": {
    "ok": true,
    "failure": "no_valid_targets",
    "campaign": {
      "campaignId": "string",
      "productId": "string",
      "name": "string",
      "status": "active",
      "quota": 0,
      "startsAt": "2026-09-01T00:00:00.000Z",
      "endsAt": "2026-09-01T00:00:00.000Z",
      "counts": {
        "total": 0,
        "submitted": 0,
        "live": 0,
        "blocked": 0,
        "done": 0,
        "byStatus": null
      },
      "createdAt": "2026-09-01T00:00:00.000Z"
    },
    "tasks": [
      {
        "taskId": "string",
        "campaignId": "string",
        "productId": "string",
        "status": "planned",
        "blockedReason": "login",
        "missing": [
          "string"
        ],
        "listingUrl": "string",
        "markedBy": "user",
        "hasGenerated": true,
        "reviewDueAt": "2026-09-01T00:00:00.000Z",
        "submittedAt": "2026-09-01T00:00:00.000Z",
        "publishedAt": "2026-09-01T00:00:00.000Z",
        "verifiedAt": "2026-09-01T00:00:00.000Z",
        "next": [
          "string"
        ],
        "target": {
          "targetId": "string",
          "name": "string",
          "url": "string",
          "submitUrl": "string",
          "kind": "string",
          "source": "seed",
          "language": "string",
          "requiresBacklink": true
        },
        "check": {
          "kind": "link_live",
          "state": "confirmed",
          "checkedAt": "2026-09-01T00:00:00.000Z",
          "dueAt": "2026-09-01T00:00:00.000Z"
        },
        "updatedAt": "2026-09-01T00:00:00.000Z"
      }
    ],
    "skipped": [
      {
        "targetId": "string",
        "reason": "not_found"
      }
    ]
  }
}
GET/v1/campaigns/{id}

Una campaña con sus tareas

La campaña y todas sus tareas. Gratis.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataCampaignDetail
└campaignCampaign
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted se calcula: todas las tareas están publicadas, verificadas, fallidas u omitidas.Valores: activecompletedarchived
└quotaintegerNúmero de canales con los que se creó la campaña.
└startsAtstring
└endsAtadmite nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└tasksarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished es lo que tú declaraste. verified es lo que QueryWin vio en la página de la ficha (un enlace en los directorios y los directorios de herramientas de IA, una mención en el resto). Mantenlos separados cuando los reportes.Valores: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonadmite nullenumValores: logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenidos obligatorios que faltan en el perfil. Completa el perfil en el panel: la tarea se vuelve a preparar sola.
└listingUrladmite nullstring
└markedByenumQuién hizo el último cambio de estado: una persona (o esta API), la extensión del navegador o el propio QueryWin.Valores: userdevicesystem
└hasGeneratedbooleanExiste una versión adaptada a este canal.
└reviewDueAtadmite nullstringCuándo volver a revisar después del envío (submittedAt + el plazo de revisión del canal).
└submittedAtadmite nullstring
└publishedAtadmite nullstring
└verifiedAtadmite nullstring
└nextarray<string>Estados que puedes asignar desde el actual mediante POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValores: seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkadmite nullobjectLa verificación que QueryWin hace en la ficha una vez publicada. Es null hasta que la tarea se publica.
└kindenumQué se busca: un enlace al producto (directorios, directorios de herramientas de IA) o una mención de la marca (todos los demás tipos).Valores: link_livemention_seen
└stateadmite nullenumconfirmed = encontrado. unconfirmed = no se encontró en una ronda de verificaciones (el estado no cambia; revisa la URL). lost = estaba y desapareció (la tarea pasa a fallida).Valores: confirmedunconfirmedlostnull
└checkedAtadmite nullstring
└dueAtadmite nullstring
└updatedAtstring
Errores posibles
401Error de autenticación
404campaign_not_found

Ejemplo

bash
curl "https://www.querywin.com/api/v1/campaigns/{id}" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "campaign": {
      "campaignId": "string",
      "productId": "string",
      "name": "string",
      "status": "active",
      "quota": 0,
      "startsAt": "2026-09-01T00:00:00.000Z",
      "endsAt": "2026-09-01T00:00:00.000Z",
      "counts": {
        "total": 0,
        "submitted": 0,
        "live": 0,
        "blocked": 0,
        "done": 0,
        "byStatus": null
      },
      "createdAt": "2026-09-01T00:00:00.000Z"
    },
    "tasks": [
      {
        "taskId": "string",
        "campaignId": "string",
        "productId": "string",
        "status": "planned",
        "blockedReason": "login",
        "missing": [
          "string"
        ],
        "listingUrl": "string",
        "markedBy": "user",
        "hasGenerated": true,
        "reviewDueAt": "2026-09-01T00:00:00.000Z",
        "submittedAt": "2026-09-01T00:00:00.000Z",
        "publishedAt": "2026-09-01T00:00:00.000Z",
        "verifiedAt": "2026-09-01T00:00:00.000Z",
        "next": [
          "string"
        ],
        "target": {
          "targetId": "string",
          "name": "string",
          "url": "string",
          "submitUrl": "string",
          "kind": "string",
          "source": "seed",
          "language": "string",
          "requiresBacklink": true
        },
        "check": {
          "kind": "link_live",
          "state": "confirmed",
          "checkedAt": "2026-09-01T00:00:00.000Z",
          "dueAt": "2026-09-01T00:00:00.000Z"
        },
        "updatedAt": "2026-09-01T00:00:00.000Z"
      }
    ]
  }
}
GET/v1/channels

Dónde se puede enviar un producto, por orden de relevancia

La biblioteca de canales (directorios, plataformas de lanzamiento, directorios de herramientas de IA, comunidades y plataformas de publicación), los canales que agregaste tú y, si se indica productId, los sitios que las respuestas de IA ya citan en las búsquedas de ese producto (source: "rivals"). Con productId, la lista se ordena por relevancia y cada canal incluye taskStatus (no nulo cuando el producto ya tiene una tarea en ese canal). Gratis.

**citedByAi significa que las respuestas de IA citaron ese sitio en tus búsquedas. No significa que ese sitio vaya a incluir tu producto**: la tarea existe precisamente para pedírselo.

Parámetros de consulta

CampoTipoDescripción
productIdstringOrdena por relevancia para este producto, agrega taskStatus e incluye sus sitios candidatos citados por la IA.
kindstringTipo de canal.Valores: directorylaunchai_directorycommunitycontentother
sourcestringseed = la biblioteca, user = agregado por ti, rivals = sitios citados por la IA para este producto.Valores: seeduserrivals
pricingstringCosto del envío.Valores: freeconditionalpaidunknown
submitMethodstringCómo se envía: llenar un formulario, publicar en una comunidad o mandar un correo de presentación.Valores: formpostemail
qstringBusca en el nombre, el dominio y las temáticas.
hideSubmittedbooleanExcluye los canales en los que este producto ya tiene una tarea abierta o enviada. Requiere productId.
pageintegerValor predeterminado: 1.
pageSizeintegerValor predeterminado: 30.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataChannelList
└productIdadmite nullstring
└itemsarray<Channel>
└targetIdstringPásalos como targetIds a POST /v1/campaigns.
└namestring
└urlstring
└submitUrlstringEl formulario de envío o la página de publicación; en los sitios citados por la IA, la página que más citan las respuestas de IA.
└kindenumValores: directorylaunchai_directorycommunitycontentother
└submitMethodenumform = llenar su formulario de envío, post = publicar por tu cuenta en la comunidad, email = mandar un correo de presentación a los editores (la dirección se lee de la página cuando la abres; no se guarda).Valores: formpostemail
└sourceenumseed = la biblioteca, user = agregado por ti, rivals = un sitio que las respuestas de IA citan en las búsquedas de este producto.Valores: seeduserrivals
└pricingTypeenumValores: freeconditionalpaidunknown
└priceNoteadmite nullstring
└languagestringen, zh, multi o, en los sitios citados por la IA, un código de idioma detectado a partir de las búsquedas.
└topicsarray<string>
└requiresAccountboolean
└requiresBacklinkboolean
└reviewDaysadmite nullintegerPlazo de revisión habitual. La tarea te avisa para que vuelvas a revisarla cuando haya pasado.
└siteRankadmite nullintegerPosición mundial en la lista pública Tranco (cuanto más baja, más visitado es el sitio). null = fuera del primer millón. No es el Domain Rating.
└hasFormSpecbooleanLos campos del formulario de este canal están registrados, así que los contenidos se recortan a sus límites exactos.
└relevanceadmite nullintegerPuntaje de relevancia para el producto indicado en la solicitud. Solo sirve para ordenar.
└taskStatusadmite nullstringLa tarea abierta o completada del producto en este canal, si la hay. null = aún no hay ninguna.
└citedByAiadmite nullobjectSolo para source: "rivals". Las respuestas de IA citaron este sitio en las búsquedas indicadas. No es una promesa de que el sitio vaya a incluir tu producto: la tarea existe precisamente para pedírselo.
└queriesintegerNúmero de búsquedas distintas en las que se citó.
└samplesintegerNúmero de muestras de respuestas de IA que lo citaron.
└searchesarray<string>
└pagesarray<string>Las páginas citadas, de más a menos citada. Queda vacío en las muestras más antiguas, que solo registraban el dominio.
└totalinteger
└pageinteger
└pageSizeinteger
└limitedbooleanPlan gratuito: solo se devuelven los primeros window canales en el orden predeterminado (relevancia para productId), y los parámetros de filtro, búsqueda y ordenamiento se rechazan con 403 library_locked. total sigue siendo el tamaño real de la biblioteca.
└windowadmite nullintegerTamaño de la ventana del plan gratuito: 20 canales, más 20 por cada persona que se registre con tu enlace de referido (y 20 más si tú te registraste con uno). null cuando la lista no está limitada.
Errores posibles
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/channels" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "productId": "string",
    "items": [
      {
        "targetId": "string",
        "name": "string",
        "url": "string",
        "submitUrl": "string",
        "kind": "directory",
        "submitMethod": "form",
        "source": "seed",
        "pricingType": "free",
        "priceNote": "string",
        "language": "string",
        "topics": [
          "string"
        ],
        "requiresAccount": true,
        "requiresBacklink": true,
        "reviewDays": 0,
        "siteRank": 0,
        "hasFormSpec": true,
        "relevance": 0,
        "taskStatus": "string",
        "citedByAi": {
          "queries": 0,
          "samples": 0,
          "searches": [],
          "pages": []
        }
      }
    ],
    "total": 0,
    "page": 0,
    "pageSize": 0,
    "limited": true,
    "window": 0
  }
}
GET/v1/products

Tus productos y lo completo que está cada perfil

Punto de partida del flujo de difusión: todos los demás endpoints de difusión reciben un productId. completeness y missing salen del perfil de producto guardado: un campo vacío en el perfil es un campo vacío en cada envío, y si falta un campo obligatorio, la tarea se queda en blocked / missing_material hasta que se complete el perfil. Consúltalo con GET /v1/products/{id} y complétalo con PATCH /v1/products/{id}. Gratis.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataProductList
└productsarray<Product>
└productIdstring
└namestring
└urlstring
└domainstring
└primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
└topicsarray<string>
└completenessintegerGrado de completitud del perfil, de 0 a 100. Completa los campos que faltan en el panel o con PATCH /v1/products/{id}.
└missingarray<string>Campos del perfil que están vacíos. Cada uno queda vacío en todos los envíos.
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughadmite nullstring
Errores posibles
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/products" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "products": [
      {
        "productId": "string",
        "name": "string",
        "url": "string",
        "domain": "string",
        "primaryLanguage": "en",
        "topics": [
          "string"
        ],
        "completeness": 0,
        "missing": [
          "string"
        ],
        "sites": [
          {
            "siteId": null,
            "domain": null,
            "syncedThrough": null
          }
        ]
      }
    ]
  }
}
POST/v1/products

Crear un producto

Requiere el permiso publish y un plan de pago. Crea un producto a partir de la URL pública de su página de inicio y de un nombre opcional; ocupa un lugar de tu cuota de productos y no consume créditos. Devuelve el perfil completo tal como se guardó, que después puedes completar con PATCH /v1/products/{id}. No rastrea el sitio ni usa IA. Un dominio ya registrado devuelve 409 product_exists con el productId existente; reutilízalo si se perdió la respuesta. Los valores predeterminados del producto son editables: no son datos verificados del sitio web.

Cuerpo de la solicitud

CampoTipoDescripción
urlobligatoriostring
namestring

Respuesta201

CampoTipoDescripción
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValores: saastoolecommercecontentserviceother
└launchStatusenumValores: livebeta
└pricingModelenumValores: freefreemiumpaidtrial
└taglineadmite nullobject
└shortDescadmite nullobject
└longDescadmite nullobject
└firstCommentadmite nullobject
└topicsarray<string>
└promoCodeadmite nullstring
└videoUrladmite nullstring
└demoUrladmite nullstring
└linksadmite nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialsadmite nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNameadmite nullstring
└contactEmailadmite nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrladmite nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughadmite nullstring
Errores posibles
400invalid_product_input, invalid_url o url_not_public
401Error de autenticación
403plan_required, o missing_scope_publish en las operaciones de escritura
409product_exists (con el productId existente) o product_limit_reached
429rate_limited — 120 solicitudes por minuto y por clave

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/products \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "name": "Example"
  }'
json — respuesta
{
  "success": true,
  "data": {
    "name": "string",
    "url": "string",
    "primaryLanguage": "en",
    "businessType": "saas",
    "launchStatus": "live",
    "pricingModel": "free",
    "tagline": null,
    "shortDesc": null,
    "longDesc": null,
    "firstComment": null,
    "topics": [
      "string"
    ],
    "promoCode": "string",
    "videoUrl": "string",
    "demoUrl": "string",
    "links": {
      "webApp": "string",
      "appStore": "string",
      "playStore": "string",
      "chromeExtension": "string",
      "macos": "string",
      "windows": "string"
    },
    "socials": {
      "x": "string",
      "linkedin": "string",
      "github": "string",
      "instagram": "string",
      "youtube": "string",
      "facebook": "string"
    },
    "contactName": "string",
    "contactEmail": "string",
    "gallery": [
      "string"
    ],
    "productId": "string",
    "domain": "string",
    "thumbnailUrl": "string",
    "completeness": 0,
    "missing": [
      "string"
    ],
    "createdAt": "2026-09-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z",
    "sites": [
      {
        "siteId": "string",
        "domain": "string",
        "syncedThrough": "string"
      }
    ]
  }
}
PATCH/v1/products/{id}

Completar o modificar un perfil de producto

Requiere el permiso publish. Guarda al instante solo los campos enviados; los objetos (incluidos los textos localizados) y los arrays reemplazan el campo entero. Consulta antes el perfil para conservar los demás idiomas. No consume créditos ni genera nada con IA. Los contenidos de envío pendientes se actualizan de forma asíncrona. Usa solo datos conocidos: no inventes la información del producto que falte.

Cuerpo de la solicitud

CampoTipoDescripción
namestring
urlstring
primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
businessTypeenumValores: saastoolecommercecontentserviceother
launchStatusenumValores: livebeta
pricingModelenumValores: freefreemiumpaidtrial
taglineobject
shortDescobject
longDescobject
firstCommentobject
topicsarray<string>
promoCodestring
videoUrlstring
demoUrlstring
linksobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
socialsobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
contactNamestring
contactEmailstring
galleryarray<string>

Respuesta200

CampoTipoDescripción
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValores: saastoolecommercecontentserviceother
└launchStatusenumValores: livebeta
└pricingModelenumValores: freefreemiumpaidtrial
└taglineadmite nullobject
└shortDescadmite nullobject
└longDescadmite nullobject
└firstCommentadmite nullobject
└topicsarray<string>
└promoCodeadmite nullstring
└videoUrladmite nullstring
└demoUrladmite nullstring
└linksadmite nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialsadmite nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNameadmite nullstring
└contactEmailadmite nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrladmite nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughadmite nullstring
Errores posibles
400invalid_product_input, invalid_url, url_not_public, invalid_email o invalid_gallery
401Error de autenticación
403plan_required, o missing_scope_publish en las operaciones de escritura
404product_not_found
409product_exists (con el productId existente)
429rate_limited — 120 solicitudes por minuto y por clave

Ejemplo

bash
curl -X PATCH https://www.querywin.com/api/v1/products/{id} \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tagline": {
      "en": "Your product tagline",
      "zh": "你的产品标语"
    },
    "shortDesc": {
      "en": "A factual description of the product."
    },
    "topics": [
      "productivity",
      "developer-tools"
    ],
    "contactEmail": "hello@example.com"
  }'
json — respuesta
{
  "success": true,
  "data": {
    "name": "string",
    "url": "string",
    "primaryLanguage": "en",
    "businessType": "saas",
    "launchStatus": "live",
    "pricingModel": "free",
    "tagline": null,
    "shortDesc": null,
    "longDesc": null,
    "firstComment": null,
    "topics": [
      "string"
    ],
    "promoCode": "string",
    "videoUrl": "string",
    "demoUrl": "string",
    "links": {
      "webApp": "string",
      "appStore": "string",
      "playStore": "string",
      "chromeExtension": "string",
      "macos": "string",
      "windows": "string"
    },
    "socials": {
      "x": "string",
      "linkedin": "string",
      "github": "string",
      "instagram": "string",
      "youtube": "string",
      "facebook": "string"
    },
    "contactName": "string",
    "contactEmail": "string",
    "gallery": [
      "string"
    ],
    "productId": "string",
    "domain": "string",
    "thumbnailUrl": "string",
    "completeness": 0,
    "missing": [
      "string"
    ],
    "createdAt": "2026-09-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z",
    "sites": [
      {
        "siteId": "string",
        "domain": "string",
        "syncedThrough": "string"
      }
    ]
  }
}
GET/v1/products/{id}

Leer un perfil de producto completo

Devuelve los textos localizados guardados, los enlaces, los datos de contacto, las imágenes, el grado de completitud y los campos que faltan. Permiso read; no consume créditos. Un producto que no pertenece a este espacio de trabajo también devuelve product_not_found.

Respuesta200

CampoTipoDescripción
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValores: saastoolecommercecontentserviceother
└launchStatusenumValores: livebeta
└pricingModelenumValores: freefreemiumpaidtrial
└taglineadmite nullobject
└shortDescadmite nullobject
└longDescadmite nullobject
└firstCommentadmite nullobject
└topicsarray<string>
└promoCodeadmite nullstring
└videoUrladmite nullstring
└demoUrladmite nullstring
└linksadmite nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialsadmite nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNameadmite nullstring
└contactEmailadmite nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrladmite nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughadmite nullstring
Errores posibles
401Error de autenticación
403plan_required, o missing_scope_publish en las operaciones de escritura
404product_not_found
429rate_limited — 120 solicitudes por minuto y por clave

Ejemplo

bash
curl "https://www.querywin.com/api/v1/products/{id}" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "name": "string",
    "url": "string",
    "primaryLanguage": "en",
    "businessType": "saas",
    "launchStatus": "live",
    "pricingModel": "free",
    "tagline": null,
    "shortDesc": null,
    "longDesc": null,
    "firstComment": null,
    "topics": [
      "string"
    ],
    "promoCode": "string",
    "videoUrl": "string",
    "demoUrl": "string",
    "links": {
      "webApp": "string",
      "appStore": "string",
      "playStore": "string",
      "chromeExtension": "string",
      "macos": "string",
      "windows": "string"
    },
    "socials": {
      "x": "string",
      "linkedin": "string",
      "github": "string",
      "instagram": "string",
      "youtube": "string",
      "facebook": "string"
    },
    "contactName": "string",
    "contactEmail": "string",
    "gallery": [
      "string"
    ],
    "productId": "string",
    "domain": "string",
    "thumbnailUrl": "string",
    "completeness": 0,
    "missing": [
      "string"
    ],
    "createdAt": "2026-09-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z",
    "sites": [
      {
        "siteId": "string",
        "domain": "string",
        "syncedThrough": "string"
      }
    ]
  }
}
POST/v1/products/{id}/images/from-url

Importar una imagen de producto desde una URL

Requiere el permiso publish. Descarga una imagen pública por HTTP(S) y guarda una copia como miniatura (reemplaza la actual) o en la galería (se agrega al final, seis como máximo). JPEG, PNG, WebP y SVG, 4 MB como máximo; los SVG se convierten a PNG. El módulo de descarga de imágenes existente bloquea las redes privadas y las redirecciones no seguras. No consume créditos. Importar varias veces a la galería puede crear duplicados: si se perdió la respuesta, consulta el producto con GET antes de reintentar.

Cuerpo de la solicitud

CampoTipoDescripción
urlobligatoriostring
kindenumValores: thumbnailgalleryPredeterminado gallery

Respuesta200

CampoTipoDescripción
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValores: enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValores: saastoolecommercecontentserviceother
└launchStatusenumValores: livebeta
└pricingModelenumValores: freefreemiumpaidtrial
└taglineadmite nullobject
└shortDescadmite nullobject
└longDescadmite nullobject
└firstCommentadmite nullobject
└topicsarray<string>
└promoCodeadmite nullstring
└videoUrladmite nullstring
└demoUrladmite nullstring
└linksadmite nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialsadmite nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNameadmite nullstring
└contactEmailadmite nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrladmite nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughadmite nullstring
Errores posibles
400invalid_product_input o invalid_url
401Error de autenticación
403plan_required, o missing_scope_publish en las operaciones de escritura
404product_not_found
409gallery_full
413image_too_large
415image_type
429rate_limited — 120 solicitudes por minuto y por clave
502image_unreachable
503storage_unavailable

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/products/{id}/images/from-url \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/logo.png",
    "kind": "thumbnail"
  }'
json — respuesta
{
  "success": true,
  "data": {
    "name": "string",
    "url": "string",
    "primaryLanguage": "en",
    "businessType": "saas",
    "launchStatus": "live",
    "pricingModel": "free",
    "tagline": null,
    "shortDesc": null,
    "longDesc": null,
    "firstComment": null,
    "topics": [
      "string"
    ],
    "promoCode": "string",
    "videoUrl": "string",
    "demoUrl": "string",
    "links": {
      "webApp": "string",
      "appStore": "string",
      "playStore": "string",
      "chromeExtension": "string",
      "macos": "string",
      "windows": "string"
    },
    "socials": {
      "x": "string",
      "linkedin": "string",
      "github": "string",
      "instagram": "string",
      "youtube": "string",
      "facebook": "string"
    },
    "contactName": "string",
    "contactEmail": "string",
    "gallery": [
      "string"
    ],
    "productId": "string",
    "domain": "string",
    "thumbnailUrl": "string",
    "completeness": 0,
    "missing": [
      "string"
    ],
    "createdAt": "2026-09-01T00:00:00.000Z",
    "updatedAt": "2026-09-01T00:00:00.000Z",
    "sites": [
      {
        "siteId": "string",
        "domain": "string",
        "syncedThrough": "string"
      }
    ]
  }
}
GET/v1/tasks

Tareas de todas las campañas

El historial de envíos, con la actividad más reciente primero. Filtra por producto, por campaña o por una lista de estados separados por comas. byStatus cuenta todas las tareas incluidas en la consulta antes de aplicar el filtro de estado. Gratis.

Parámetros de consulta

CampoTipoDescripción
productIdstringSolo las tareas de este producto.
campaignIdstring
statusstringSeparados por comas; por ejemplo, prepared,in_progress.
pageintegerValor predeterminado: 1.
pageSizeintegerValor predeterminado: 30.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataTaskList
└itemsarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished es lo que tú declaraste. verified es lo que QueryWin vio en la página de la ficha (un enlace en los directorios y los directorios de herramientas de IA, una mención en el resto). Mantenlos separados cuando los reportes.Valores: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonadmite nullenumValores: logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenidos obligatorios que faltan en el perfil. Completa el perfil en el panel: la tarea se vuelve a preparar sola.
└listingUrladmite nullstring
└markedByenumQuién hizo el último cambio de estado: una persona (o esta API), la extensión del navegador o el propio QueryWin.Valores: userdevicesystem
└hasGeneratedbooleanExiste una versión adaptada a este canal.
└reviewDueAtadmite nullstringCuándo volver a revisar después del envío (submittedAt + el plazo de revisión del canal).
└submittedAtadmite nullstring
└publishedAtadmite nullstring
└verifiedAtadmite nullstring
└nextarray<string>Estados que puedes asignar desde el actual mediante POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValores: seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkadmite nullobjectLa verificación que QueryWin hace en la ficha una vez publicada. Es null hasta que la tarea se publica.
└kindenumQué se busca: un enlace al producto (directorios, directorios de herramientas de IA) o una mención de la marca (todos los demás tipos).Valores: link_livemention_seen
└stateadmite nullenumconfirmed = encontrado. unconfirmed = no se encontró en una ronda de verificaciones (el estado no cambia; revisa la URL). lost = estaba y desapareció (la tarea pasa a fallida).Valores: confirmedunconfirmedlostnull
└checkedAtadmite nullstring
└dueAtadmite nullstring
└updatedAtstring
└totalinteger
└pageinteger
└pageSizeinteger
└byStatusobject
Errores posibles
401Error de autenticación

Ejemplo

bash
curl "https://www.querywin.com/api/v1/tasks" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "items": [
      {
        "taskId": "string",
        "campaignId": "string",
        "productId": "string",
        "status": "planned",
        "blockedReason": "login",
        "missing": [
          "string"
        ],
        "listingUrl": "string",
        "markedBy": "user",
        "hasGenerated": true,
        "reviewDueAt": "2026-09-01T00:00:00.000Z",
        "submittedAt": "2026-09-01T00:00:00.000Z",
        "publishedAt": "2026-09-01T00:00:00.000Z",
        "verifiedAt": "2026-09-01T00:00:00.000Z",
        "next": [
          "string"
        ],
        "target": {
          "targetId": "string",
          "name": "string",
          "url": "string",
          "submitUrl": "string",
          "kind": "string",
          "source": "seed",
          "language": "string",
          "requiresBacklink": true
        },
        "check": {
          "kind": "link_live",
          "state": "confirmed",
          "checkedAt": "2026-09-01T00:00:00.000Z",
          "dueAt": "2026-09-01T00:00:00.000Z"
        },
        "updatedAt": "2026-09-01T00:00:00.000Z"
      }
    ],
    "total": 0,
    "page": 0,
    "pageSize": 0,
    "byStatus": null
  }
}
GET/v1/tasks/{id}

Una tarea con los contenidos que hay que enviar

Cada campo que pide el formulario del canal, tomado del perfil de producto y recortado a los límites del canal (source: "profile"), más la versión adaptada a ese canal si existe (source: "ai") y las modificaciones hechas en el panel (source: "override"). source: "none" significa que el perfil no tiene nada para ese campo: no lo inventes. Nunca lanza una reescritura y nunca cuesta nada.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataTaskDetailResult
└taskTaskDetail
Errores posibles
401Error de autenticación
404task_not_found

Ejemplo

bash
curl "https://www.querywin.com/api/v1/tasks/{id}" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — respuesta
{
  "success": true,
  "data": {
    "task": null
  }
}
POST/v1/tasks/{id}/materials

Reescribir los contenidos para este canal (consume créditos)

Una pasada de IA que adapta el perfil a este canal concreto: una descripción de directorio más ajustada, el primer comentario del creador, una publicación para una comunidad, un correo de presentación para un editor o, en los sitios citados por la IA, un correo de presentación junto con un párrafo que el responsable de la página podría agregar. Síncrona, unos segundos. Solo usa datos que estén en el perfil; cualquier parte que contenga un enlace que no figure en el perfil se descarta y no se cobra.

Con entradas idénticas se devuelve la versión anterior sin cobrar (cached: true); force: true reescribe igualmente y se cobra. Los contenidos del perfil que devuelve GET /v1/tasks/{id} suelen bastar para los directorios; reescribe cuando el canal pida otro tono.

Devuelve HTTP 200 con ok: false y failure: "engine_failed" cuando nada pasa la validación. La falta de créditos, en cambio, sí es un error 402 real.

Cuerpo de la solicitud

CampoTipoDescripción
confirmSpendobligatoriointegerTope de autorización en créditos, con la misma semántica que en los esquemas y los borradores. Consulta el precio en GET /v1/usage (materials.pricePerTask). Obligatorio aunque te queden generaciones gratuitas. (min 0)
forcebooleanReescribe aunque no haya cambiado nada desde la última versión. Se cobra.

Respuesta200

CampoTipoDescripción
successenumValores: true
dataWriteMaterialsOutcome
└okboolean
└failureenumPresente cuando ok es false. No se cobra nada.Valores: engine_failedengine_unavailable
└cachedbooleanLas entradas no cambiaron: se devolvió la versión anterior y no se cobró nada.
└freeUsedboolean
└creditsSpentinteger
└taskTaskDetail
Errores posibles
400confirm_spend_required o confirm_spend_too_low (el cuerpo incluye el price actual)
401Error de autenticación
402insufficient_credits — el cuerpo incluye requiredCredits, currentBalance, shortfall
403missing_scope_spend — esta clave no tiene el permiso spend
404task_not_found
429rate_limited o daily_limit_reached (el cuerpo incluye resetAt)

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/tasks/{id}/materials \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "confirmSpend": 10
  }'
json — respuesta
{
  "success": true,
  "data": {
    "ok": true,
    "failure": "engine_failed",
    "cached": true,
    "freeUsed": true,
    "creditsSpent": 0,
    "task": null
  }
}
POST/v1/tasks/{id}/status

Indicar el resultado del envío

Registra el resultado de un envío que hiciste con tus propias cuentas. QueryWin nunca envía nada por su cuenta. Marca submitted cuando hayas enviado el formulario y, después, published con la URL de la ficha (la ficha o la publicación en sí, no la página de inicio del sitio) cuando ya esté publicada; unas 72 horas después, QueryWin vuelve a verificar esa página en busca de un enlace a tu producto (directorios, directorios de herramientas de IA) o de una mención (todos los demás tipos) y marca verified por su cuenta. Tú no puedes asignar ese estado.

blocked significa “necesita a una persona”: indica reason (login, captcha, payment, missing_material, other). failed / skipped cierran la tarea; prepared la vuelve a dejar pendiente. Las transiciones que la máquina de estados no permite devuelven **409 transition_not_allowed**; el campo next de la tarea indica qué estados se permiten desde el actual.

Cuerpo de la solicitud

CampoTipoDescripción
statusobligatorioenumverified no se puede asignar: lo asigna QueryWin después de volver a verificar la ficha.Valores: preparedin_progressblockedsubmittedpublishedfailedskipped
listingUrlstringObligatorio para published: la ficha o la publicación tal como está en línea, no la página de inicio del sitio. Solo http/https. Opcional con submitted si ya la conoces.
notestring
reasonenumObligatorio para blocked.Valores: logincaptchapaymentmissing_materialother

Respuesta200

CampoTipoDescripción
successenumValores: true
dataTaskDetailResult
└taskTaskDetail
Errores posibles
400invalid_status, listing_url_required (published requiere listingUrl), invalid_listing_url o reason_required (blocked requiere reason)
401Error de autenticación
403missing_scope_publish — esta clave no tiene el permiso publish
404task_not_found
409transition_not_allowed — consulta la tarea: next indica los estados permitidos

Ejemplo

bash
curl -X POST https://www.querywin.com/api/v1/tasks/{id}/status \
  -H "Authorization: Bearer $QUERYWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "published",
    "listingUrl": "https://example-directory.com/tools/your-product"
  }'
json — respuesta
{
  "success": true,
  "data": {
    "task": null
  }
}
API de QueryWin — flujos de contenido y difusión para scripts y agentes de IA