API

Intégrez QueryWin à vos propres automatisations

QueryWin repère les requêtes pour lesquelles votre site obtient déjà des impressions sans qu’aucune page n’y réponde, rédige l’article manquant et prépare votre produit pour sa soumission aux annuaires, plateformes de lancement, communautés et sites déjà cités dans les réponses des IA. Cette API confie ces deux chaînes à vos scripts et à vos assistants IA. La publication et la soumission se font toujours avec vos propres identifiants et comptes : QueryWin ne se connecte jamais à votre CMS et ne soumet jamais rien lui-même.

Base URLhttps://www.querywin.com/apiCréer une clé APISpécification OpenAPI (JSON) →

Démarrage rapide

Trois étapes. Tout ce qui suit est un simple appel REST avec un seul en-tête.

1

Créer une clé

Dans le tableau de bord QueryWin, rubrique « API ». La clé en clair ne s’affiche qu’une fois, à sa création. N’accordez que les scopes nécessaires : une clé sans aucune case cochée est en lecture seule.

2

Transmettre la clé en jeton Bearer

Ajoutez l’en-tête Authorization (valeur Bearer qw_live_…) à chaque requête. X-API-Key fonctionne aussi, pour les outils qui ne permettent de définir qu’un seul en-tête.

3

Savoir quoi écrire, puis récupérer le brouillon

La liste des sujets est gratuite et ne consomme aucun crédit. Générer un plan ou un brouillon débite des crédits et nécessite le scope spend.

bash
# 1. Sites sur lesquels cette clé peut agir
curl "https://www.querywin.com/api/v1/sites" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 2. Sujets à écrire cette semaine (gratuit, aucun crédit)
curl "https://www.querywin.com/api/v1/topics?siteId=YOUR_SITE_ID" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
bash
# 3. Récupérer le brouillon (Markdown + JSON-LD avec les données renseignées)
curl "https://www.querywin.com/api/v1/topics/article?siteId=YOUR_SITE_ID&key=standard%20wardrobe%20depth" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 4. Votre script le publie sur votre propre blog, puis renvoie l’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"
  }'

Chaque réponse est encapsulée : {"success": true, "data": …}. Les erreurs sont des codes lisibles par une machine, jamais des phrases : QueryWin est multilingue, et une phrase en anglais codée en dur finirait affichée telle quelle sur une page en français.

La chaîne de contenu

Cinq étapes, dont deux seulement sont payantes. L’étape 4 vous revient : QueryWin vous remet le Markdown et les données structurées, et c’est vous qui publiez.

ÉtapeEndpointCoût
Trouver les requêtes avec des impressions mais sans pageGET /v1/topicsGratuit
Générer un plan étayé par des sourcesPOST /v1/topics/outlineCrédits
En faire un brouillon publiablePOST /v1/topics/articleCrédits
Le publier sur votre propre blogvotre propre CMS—
Renvoyer l’URL à QueryWinPOST /v1/topics/publishedGratuit

QueryWin ne se connecte jamais à votre CMS, ne détient jamais les identifiants de votre site et ne publie jamais à votre place. Cette API vous remet le contenu ; l’écriture se fait sur votre machine, avec vos propres identifiants. C’est la limite elle-même, pas une façon de la contourner.

La chaîne de diffusion

Sept étapes, dont une seule est payante. L’étape 6 vous revient : QueryWin vous remet les contenus de soumission de chaque canal, et c’est vous (ou votre agent, avec vos propres comptes) qui les soumettez. QueryWin revérifie ensuite lui-même chaque fiche publiée.

ÉtapeEndpointCoût
Lister vos produits et le taux de complétion de chaque ficheGET /v1/productsGratuit
Lister les canaux où soumettre, triés par pertinence, y compris les sites que citent les réponses des IAGET /v1/channels?productId=Gratuit
Créer une campagne à partir des canaux choisisPOST /v1/campaignsGratuit
Récupérer les contenus préparés d’une tâcheGET /v1/tasks/{id}Gratuit
Les réécrire pour ce canalPOST /v1/tasks/{id}/materialsCrédits
Les soumettrevos propres comptes—
Signaler la soumission, puis la publication avec l’URL de la fichePOST /v1/tasks/{id}/statusGratuit

published correspond à ce que vous avez déclaré ; verified, à ce que QueryWin a constaté en revérifiant la fiche environ 72 heures plus tard : un lien vers votre produit sur les annuaires (y compris d’outils IA), une mention partout ailleurs. Ce sont deux champs distincts. Et un site cité dans les réponses des IA (citedByAi) vaut la peine d’être sollicité ; ce n’est pas une garantie qu’il vous référencera.

Scopes

Chaque clé porte les permissions accordées à sa création. GET /v1/usage les renvoie : inutile de les découvrir en tombant sur une erreur 403.

read

Toujours actif

Tous les endpoints GET : sites, lacunes de contenu, plans, brouillons, produits, canaux, campagnes, tâches et leurs contenus, consommation.

publish

Désactivé par défaut

Créer et mettre à jour des fiches produit, importer des images et enregistrer ce qui s’est passé : l’URL d’un article publié (également soumise à Bing, Yandex, Seznam et Naver, pas à Google), une nouvelle campagne, une tâche marquée comme soumise ou publiée. Gratuit, mais chaque appel crée un enregistrement qui a des conséquences : les campagnes sont décomptées de votre offre, et une fiche publiée est revérifiée.

spend

Désactivé par défaut

Générer des plans et des brouillons, et réécrire des contenus de soumission pour un canal. Ces actions débitent des crédits. N’accordez ce scope que si vous voulez que l’appelant (un script ou un assistant IA) puisse dépenser de lui-même.

Il n’y a pas de hiérarchie : publish n’implique pas spend, et spend n’implique pas publish. Ce sont deux types de risque différents. Un appel pour lequel votre clé n’a pas le scope requis renvoie une erreur 403 avec missing_scope_<name> et la liste des scopes dont vous disposez.

Consommation de crédits

Deux endpoints débitent des crédits : POST /v1/topics/outline et POST /v1/topics/article. Trois garde-fous les protègent.

Garde-fouCe qu’il empêche
confirmSpendUn script qui continue d’être débité après une hausse de prix.
Limite quotidienneUne boucle incontrôlée qui épuise le solde en une nuit. Renvoie une erreur 429 avec l’heure de réinitialisation.
Empreinte des entréesUn double débit pour le même sujet. Des entrées identiques renvoient gratuitement le résultat en cache : vous pouvez relancer sans risque une requête expirée.

confirmSpend est un plafond d’autorisation, pas un montant exact. Envoyez une valeur supérieure ou égale au prix actuel : seul le coût réel est débité, souvent zéro quand le résultat est en cache. Si le prix dépasse un jour votre plafond, l’appel échoue avec confirm_spend_too_low au lieu de débiter davantage en silence.

Conventions

Trois règles valables pour tous les endpoints.

Enveloppe de réponse

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

Les erreurs sont des codes, jamais des phrases. Interprétez ces codes au lieu de les afficher tels quels : ils sont faits pour être convertis en vos propres libellés.

Un résultat métier n’est pas une erreur

Un appel de génération qui n’a pas pu produire de résultat valide renvoie HTTP 200 avec data.ok = false et un code data.failure : vous pouvez ainsi le distinguer d’un échec d’authentification ou d’une connexion interrompue. Ces appels ne sont pas facturés.

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

Limite de requêtes

120 requêtes par minute et par clé. Au-delà, vous recevez une erreur 429 avec l’heure de réinitialisation. Cette limite est distincte de la limite quotidienne de génération ci-dessus.

MCP pour les agents IA

Les deux chaînes sont disponibles via le Model Context Protocol, avec la même clé et le même en-tête d’authentification. Ajoutez le serveur à Claude Code, Cursor, n8n ou tout autre outil compatible MCP sur 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"

Dix-neuf outils. Fiches produit : create_product, get_product, update_product, import_product_image. Contenu : list_sites, get_usage, list_content_gaps, get_outline, get_article_draft, generate_outline, generate_article_draft, mark_published. Diffusion : list_products, list_channels, create_campaign, list_tasks, get_task, write_task_materials, report_task_status. Demandez à votre assistant quoi écrire cette semaine ou où soumettre votre produit ensuite : il ira chercher la réponse lui-même.

prompt
À l’aide du serveur MCP QueryWin, trouvez les trois plus grosses lacunes de contenu de mon site,
montrez-moi les requêtes derrière chacune et dites-moi combien coûterait le brouillon de la première.

Les outils sont filtrés par scope. Avec une clé en lecture seule, les outils de génération n’apparaissent même pas dans la liste d’outils de l’assistant : un agent ne peut pas appeler un outil qu’il ne voit pas. Donnez à chaque agent sa propre clé, pour pouvoir la révoquer sans toucher à vos autres intégrations.

L’authentification se fait par jeton Bearer, ce que la spécification MCP autorise (l’autorisation y est facultative). Les clients qui permettent de définir un en-tête (Claude Code, Cursor, n8n) se connectent directement. Les hôtes qui exigent un écran de consentement OAuth risquent de ne pas y parvenir.

Account

GET/v1/sites

Lister les sites sur lesquels cette clé peut agir

Commencez ici. Tous les autres endpoints prennent le siteId renvoyé par cet appel. Sans siteId, ils se rabattent sur le premier site connecté : sans conséquence pour un compte à site unique, mais un bug en puissance pour tous les autres.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataSiteList
└sitesarray<Site>
└siteIdstring
└domainstring
└gscPropertystringLa propriété Search Console, telle quelle : sc-domain:example.com ou https://example.com/.
└syncStatusenumValeurs : pendingsyncingdonefailed
└syncedThroughpeut être nullstringLes données Search Console ont 2 à 3 jours de décalage. Chaque indicateur de ce site est arrêté à cette date : précisez-le si vous affichez ces chiffres, où que ce soit.
Erreurs possibles
401Clé absente, mal formée, révoquée ou expirée

Exemple

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

Prix actuels, générations gratuites, solde de crédits et limites quotidiennes

À lire avant toute génération. C’est la même source de vérité que celle dont se sert l’interface web pour décider de ce qu’affiche chaque bouton : la disponibilité est décidée côté serveur, et non à force d’essais et d’erreurs.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataUsage
└scopesarray<enum>Ce que cette clé a le droit de faire. Lisez-le une fois au démarrage, plutôt que de découvrir vos droits en tombant sur une erreur 403.Valeurs : readpublishspend
└creditsobject
└balanceinteger
└outlineStepUsage
└availablebooleanVaut false lorsque le moteur de génération n’est pas configuré. N’appelez pas alors l’endpoint POST correspondant.
└pricePerOutlineintegerCrédits par plan (présent uniquement sur outline).
└pricePerArticleintegerCrédits par brouillon (présent uniquement sur article).
└pricePerTaskintegerCrédits par réécriture pour un canal (présent uniquement sur materials).
└freeRemainingintegerGénérations gratuites restantes sur ce compte, comptées par sujet distinct (plans, brouillons) ou par tâche distincte (contenus), et non par clic sur un bouton. Les générations gratuites exigent elles aussi confirmSpend.
└dailyDailyLimitGarde-fou contre un script qui s’emballe, comptabilisé en base de données sur tous les points d’entrée (les actions dans l’interface web comptent aussi). Remise à zéro à minuit, heure locale, et non sur une fenêtre glissante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└articleStepUsage
└availablebooleanVaut false lorsque le moteur de génération n’est pas configuré. N’appelez pas alors l’endpoint POST correspondant.
└pricePerOutlineintegerCrédits par plan (présent uniquement sur outline).
└pricePerArticleintegerCrédits par brouillon (présent uniquement sur article).
└pricePerTaskintegerCrédits par réécriture pour un canal (présent uniquement sur materials).
└freeRemainingintegerGénérations gratuites restantes sur ce compte, comptées par sujet distinct (plans, brouillons) ou par tâche distincte (contenus), et non par clic sur un bouton. Les générations gratuites exigent elles aussi confirmSpend.
└dailyDailyLimitGarde-fou contre un script qui s’emballe, comptabilisé en base de données sur tous les points d’entrée (les actions dans l’interface web comptent aussi). Remise à zéro à minuit, heure locale, et non sur une fenêtre glissante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└materialsStepUsage
└availablebooleanVaut false lorsque le moteur de génération n’est pas configuré. N’appelez pas alors l’endpoint POST correspondant.
└pricePerOutlineintegerCrédits par plan (présent uniquement sur outline).
└pricePerArticleintegerCrédits par brouillon (présent uniquement sur article).
└pricePerTaskintegerCrédits par réécriture pour un canal (présent uniquement sur materials).
└freeRemainingintegerGénérations gratuites restantes sur ce compte, comptées par sujet distinct (plans, brouillons) ou par tâche distincte (contenus), et non par clic sur un bouton. Les générations gratuites exigent elles aussi confirmSpend.
└dailyDailyLimitGarde-fou contre un script qui s’emballe, comptabilisé en base de données sur tous les points d’entrée (les actions dans l’interface web comptent aussi). Remise à zéro à minuit, heure locale, et non sur une fenêtre glissante.
└usedinteger
└limitinteger
└remaininginteger
└resetAtstring
└distributionDistributionQuotaLes limites de diffusion de l’offre et leur consommation actuelle. Une limite à null signifie qu’il n’y a pas de limite.
└planstring
└limitsobject
└channelspeut être nullintegerNombre de canaux distincts sur lesquels un produit peut avoir des tâches, compté sur la période channelsPeriod.
└channelsPeriodenummonth (offres payantes) : décompte par mois calendaire (UTC), chaque mois permet donc un nouveau lot de canaux. total (offre gratuite) : décompte sur toute la durée de vie du produit.Valeurs : monthtotal
└tasksPerMonthpeut être nullintegerNombre de tâches pouvant être créées par mois calendaire (UTC), tous produits confondus.
└activeCampaignspeut être nullintegerNombre de campagnes pouvant être actives simultanément.
└usageobject
└activeCampaignsinteger
└tasksThisMonthinteger
└channelspeut être nullintegerUniquement lorsque l’appel précise un produit.
Erreurs possibles
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/usage" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Requêtes avec des impressions mais sans page dédiée

Gratuit, sans facturation externe (l’API elle-même nécessite une offre payante). Recalculé à chaque appel à partir de vos propres données Search Console, sans aucune base de mots-clés externe — et c’est tout l’intérêt : « vous avez déjà des impressions et aucune page pour y répondre », aucun outil de mots-clés ne peut vous le dire.

Les résultats sont triés par opportunité. Les sujets écartés par l’utilisateur dans l’interface sont exclus.

Paramètres de requête

ChampTypeDescription
siteIdstringIssu de GET /v1/sites. Par défaut, le premier site connecté.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataTopicList
└siteIdpeut être nullstring
└topicsarray<Topic>
└keystringL’identifiant du cluster : renvoyez-le tel quel dans key à tous les autres endpoints de sujets. C’est le texte normalisé de la requête représentative ; il peut donc contenir des espaces, des barres obliques et des caractères non latins. Envoyez-le toujours en paramètre de requête ou dans le corps, jamais dans le chemin de l’URL.
└titlestringLa requête du cluster qui génère le plus d’impressions, telle quelle. Ce n’est pas un titre généré : celui-ci est fourni avec le plan.
└shapeenumcomparison signifie que ce cluster recoupe votre liste de concurrents. L’article doit établir une comparaison, pas présenter le concurrent : sinon, c’est pour lui que vous écrivez.Valeurs : comparisonroundupguide
└intentstring
└membersarray<TopicMember>
└textstringLa requête, telle que saisie.
└impressionsinteger
└clicksinteger
└positionpeut être nullnumber
└landingUrlpeut être nullstringLa page que Search Console associe actuellement à cette requête, s’il y en a une.
└impressionsintegerValeur mesurée, issue de Search Console.
└clicksintegerValeur mesurée, issue de Search Console.
└positionpeut être nullnumberValeur mesurée : position moyenne sur le cluster, pondérée par les impressions.
└competitorboolean
└scorenumber
└rankinteger
└upsideClicksintegerIl s’agit d’une estimation, et non d’une mesure : clics supplémentaires par mois si une page dédiée atteignait la position 3. Ce champ est volontairement séparé de clicks et impressions, et doit rester visuellement distinct partout où vous l’affichez. Présenter une projection comme une donnée mesurée est l’écueil classique de cette catégorie de produits.
└statusenumValeurs : newdismissedplannedpublished
└outlineAtpeut être nullstring
└articleAtpeut être nullstringNe le déduisez pas de outlineAt. Avoir un plan ne signifie pas qu’un brouillon existe : ce sont deux étapes payantes distinctes.
└totalQueriesintegerNombre total de requêtes distinctes couvertes par ces sujets.
Erreurs possibles
401Authentification refusée
404site_not_found — ce siteId n’appartient pas à ce compte

Exemple

bash
curl "https://www.querywin.com/api/v1/topics" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Lire un brouillon déjà généré

Ne déclenche jamais de génération et ne coûte jamais rien. article vaut null tant qu’aucun brouillon n’existe.

Paramètres de requête

ChampTypeDescription
keyobligatoirestringL’identifiant du cluster, issu de GET /v1/topics.
siteIdstringIssu de GET /v1/sites. Par défaut, le premier site connecté.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataArticleResult
└articlepeut être nullArticle
└titlestring
└descriptionstringMéta-description.
└markdownstringLe corps à publier.
└jsonLdstringLes données structurées de cet article, déjà remplies. JSON valide : placez-le dans une balise script de type application/ld+json sur la page publiée.
└wordCountinteger
└warningsarray<ArticleWarning>Les formules qui sonnent comme écrites par une IA. Le brouillon reste utilisable : elles sont signalées plutôt que réécrites en silence. Consignez-les dans vos logs. Dans un pipeline automatisé, c’est le seul moment où quelqu’un pourrait s’en apercevoir.
└kindenumValeurs : banned_phrasebanned_wordem_dash_overuseno_short_sentence
└hitstringLe passage en cause.
└articleAtpeut être nullstring
└modelpeut être nullstring
Erreurs possibles
400key_required
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/topics/article?key=..." \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Transformer le plan en brouillon prêt à publier (consomme des crédits)

L’appel le plus cher du produit. Synchrone : peut prendre une à deux minutes.

Un plan doit exister au préalable : sans plan, vous obtenez failure: "no_outline". C’est le plan qui porte les éléments factuels (les vraies requêtes derrière le cluster, les pages que l’IA cite aujourd’hui, le dédoublonnage avec vos pages existantes). S’en passer ramènerait cet appel à un simple outil de rédaction par IA, sans rien derrière.

article.markdown est le corps à publier. article.jsonLd contient les données structurées, déjà remplies avec le contenu de cet article. **article.warnings doit être consigné dans vos logs, pas ignoré** : chaque avertissement pointe une formule précise qui sonne comme écrite par une IA, et dans un pipeline automatisé, personne ne relit le brouillon avant sa mise en ligne.

Les brouillons qui échouent à la validation structurelle (sections manquantes, questions obligatoires sans réponse, liens inventés, JSON-LD invalide) sont écartés et ne sont pas facturés.

Corps de la requête

ChampTypeDescription
keyobligatoirestringL’identifiant du cluster, issu de GET /v1/topics.
siteIdstringPar défaut, le premier site connecté.
confirmSpendobligatoireintegerUn plafond d’autorisation en crédits, et non un montant exact. Envoyez une valeur supérieure ou égale au prix actuel indiqué par GET /v1/usage ; seul le coût réel est débité, souvent zéro lorsque le résultat est en cache. Si le prix dépasse un jour votre plafond, l’appel est refusé au lieu de débiter davantage en silence. Obligatoire même s’il vous reste des générations gratuites : elles finiront par s’épuiser, et ce ne doit pas être à ce moment-là que votre script découvre que cet endpoint est payant. (min 0)

Réponse200

ChampTypeDescription
successenumValeurs : true
dataArticleOutcome
└okboolean
└failureenumPrésent uniquement lorsque ok vaut false. Le statut HTTP reste 200.Valeurs : topic_not_foundno_outlineengine_unavailableengine_failedno_valid_article
└articlepeut être nullArticle
└titlestring
└descriptionstringMéta-description.
└markdownstringLe corps à publier.
└jsonLdstringLes données structurées de cet article, déjà remplies. JSON valide : placez-le dans une balise script de type application/ld+json sur la page publiée.
└wordCountinteger
└warningsarray<ArticleWarning>Les formules qui sonnent comme écrites par une IA. Le brouillon reste utilisable : elles sont signalées plutôt que réécrites en silence. Consignez-les dans vos logs. Dans un pipeline automatisé, c’est le seul moment où quelqu’un pourrait s’en apercevoir.
└kindenumValeurs : banned_phrasebanned_wordem_dash_overuseno_short_sentence
└hitstringLe passage en cause.
└generatedbooleanVaut false lorsqu’un résultat en cache a été renvoyé : rien n’a été débité.
└creditsSpentinteger
└freeUsedboolean
└rejectedarray<string>Défauts structurels qui ont fait écarter le brouillon (sans facturation) : missing_sections, missing_faq, invented_link, invalid_json_ld, comparison_without_contrast, body_too_short.
Erreurs possibles
400key_required, confirm_spend_required ou confirm_spend_too_low (le corps contient le price actuel)
401Authentification refusée
402insufficient_credits — le corps contient requiredCredits, currentBalance, shortfall
403missing_scope_spend — cette clé ne dispose pas du droit spend
429rate_limited ou daily_limit_reached (le corps contient resetAt)

Exemple

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 — réponse
{
  "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

Lire un plan déjà généré

Ne déclenche jamais de génération et ne coûte jamais rien. outline vaut null tant qu’aucun plan n’existe.

Paramètres de requête

ChampTypeDescription
keyobligatoirestringL’identifiant du cluster, issu de GET /v1/topics.
siteIdstringIssu de GET /v1/sites. Par défaut, le premier site connecté.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataOutlineResult
└outlinepeut être nullOutline
└titlestring
└slugstring
└anglestringLa thèse que cette page doit défendre.
└sectionsarray<object>
└headingstring
└pointsarray<string>
└faqarray<object>Les questions auxquelles la page doit répondre. Ce sont les passages que reprennent les réponses des IA.
└questionstring
└answerstring
└schemaTypestringLe type JSON-LD adapté à cette page.
└internalLinksarray<string>Les pages de votre propre site vers lesquelles un lien est pertinent. Choisies parmi de vraies URL, jamais inventées.
└outlineAtpeut être nullstring
└modelpeut être nullstring
Erreurs possibles
400key_required
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/topics/outline?key=..." \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Générer un plan (consomme des crédits)

Synchrone : comptez environ 10 à 20 secondes.

À entrées identiques, le plan en cache est renvoyé sans nouveau débit : l’empreinte couvre le cluster et son statut vis-à-vis de vos concurrents, vous pouvez donc relancer sans risque une requête HTTP qui a échoué.

Les résultats métier (moteur indisponible, sortie refusée à la validation) renvoient HTTP 200 avec ok: false et un code failure. Un solde de crédits insuffisant, lui, renvoie une vraie erreur 402.

Corps de la requête

ChampTypeDescription
keyobligatoirestringL’identifiant du cluster, issu de GET /v1/topics.
siteIdstringPar défaut, le premier site connecté.
confirmSpendobligatoireintegerUn plafond d’autorisation en crédits, et non un montant exact. Envoyez une valeur supérieure ou égale au prix actuel indiqué par GET /v1/usage ; seul le coût réel est débité, souvent zéro lorsque le résultat est en cache. Si le prix dépasse un jour votre plafond, l’appel est refusé au lieu de débiter davantage en silence. Obligatoire même s’il vous reste des générations gratuites : elles finiront par s’épuiser, et ce ne doit pas être à ce moment-là que votre script découvre que cet endpoint est payant. (min 0)

Réponse200

ChampTypeDescription
successenumValeurs : true
dataOutlineOutcome
└okboolean
└failureenumPrésent uniquement lorsque ok vaut false. Le statut HTTP reste 200 : c’est un résultat, pas une erreur.Valeurs : topic_not_foundengine_unavailableengine_failedno_valid_outline
└outlinepeut être nullOutline
└titlestring
└slugstring
└anglestringLa thèse que cette page doit défendre.
└sectionsarray<object>
└headingstring
└pointsarray<string>
└faqarray<object>Les questions auxquelles la page doit répondre. Ce sont les passages que reprennent les réponses des IA.
└questionstring
└answerstring
└schemaTypestringLe type JSON-LD adapté à cette page.
└internalLinksarray<string>Les pages de votre propre site vers lesquelles un lien est pertinent. Choisies parmi de vraies URL, jamais inventées.
└generatedbooleanVaut false lorsqu’un résultat en cache a été renvoyé : rien n’a été débité.
└creditsSpentinteger
└freeUsedboolean
└rejectedarray<string>Règles de validation enfreintes par la sortie du modèle. Utile à consigner comme signal de qualité.
Erreurs possibles
400key_required, confirm_spend_required ou confirm_spend_too_low (le corps contient le price actuel)
401Authentification refusée
402insufficient_credits — le corps contient requiredCredits, currentBalance, shortfall
403missing_scope_spend — cette clé ne dispose pas du droit spend
429rate_limited ou daily_limit_reached (le corps contient resetAt)

Exemple

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 — réponse
{
  "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

Indiquer l’URL de publication

Cet appel boucle la boucle : il marque le sujet comme publié, enregistre l’URL et la soumet à IndexNow en votre nom.

IndexNow couvre Bing, Yandex, Seznam et Naver — pas Google. Google n’a pas d’endpoint d’indexation instantanée équivalent ; il découvre la page via votre sitemap.

La soumission IndexNow ne fait jamais échouer l’appel : votre article est déjà publié, et c’est ce fait que l’appel enregistre. Consultez le champ indexnow pour savoir ce qui s’est réellement passé. La soumission exige que le fichier de clé IndexNow soit vérifié pour le site (à configurer une fois dans le tableau de bord).

Enregistrer l’URL permet aussi à QueryWin de mesurer à nouveau les requêtes visées par cet article, une fois qu’il a eu le temps de faire effet.

Corps de la requête

ChampTypeDescription
keyobligatoirestring
siteIdstring
urlobligatoirestringL’URL où vous l’avez publié. http/https uniquement. La page n’est pas récupérée à ce stade.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataPublishedResult
└clusterKeystring
└statusenumValeurs : published
└publishedUrlstring
└publishedAtstring
└indexnowobjectBing, Yandex, Seznam et Naver. Pas Google.
└pushedboolean
└outcomestringskipped signifie généralement que le fichier de clé n’est pas encore vérifié.
└enginesstring
Erreurs possibles
400key_required, url_required ou invalid_url (http/https uniquement)
401Authentification refusée
403missing_scope_publish — cette clé ne dispose pas du droit publish
404site_not_found

Exemple

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 — réponse
{
  "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

Campagnes et quota de diffusion

Les campagnes (hors archivées, sauf avec includeArchived=true), ainsi que les limites de diffusion de l’offre et leur consommation actuelle. Gratuit.

Paramètres de requête

ChampTypeDescription
productIdstringUniquement les campagnes de ce produit.
includeArchivedboolean

Réponse200

ChampTypeDescription
successenumValeurs : true
dataCampaignList
└campaignsarray<Campaign>
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted est calculé : toutes les tâches sont publiées, vérifiées, en échec ou ignorées.Valeurs : activecompletedarchived
└quotaintegerNombre de canaux retenus à la création de la campagne.
└startsAtstring
└endsAtpeut être nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└quotaDistributionQuotaLes limites de diffusion de l’offre et leur consommation actuelle. Une limite à null signifie qu’il n’y a pas de limite.
└planstring
└limitsobject
└channelspeut être nullintegerNombre de canaux distincts sur lesquels un produit peut avoir des tâches, compté sur la période channelsPeriod.
└channelsPeriodenummonth (offres payantes) : décompte par mois calendaire (UTC), chaque mois permet donc un nouveau lot de canaux. total (offre gratuite) : décompte sur toute la durée de vie du produit.Valeurs : monthtotal
└tasksPerMonthpeut être nullintegerNombre de tâches pouvant être créées par mois calendaire (UTC), tous produits confondus.
└activeCampaignspeut être nullintegerNombre de campagnes pouvant être actives simultanément.
└usageobject
└activeCampaignsinteger
└tasksThisMonthinteger
└channelspeut être nullintegerUniquement lorsque l’appel précise un produit.
Erreurs possibles
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/campaigns" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Créer une campagne à partir d’une liste explicite de canaux

Une tâche par identifiant de canal, avec ses contenus de soumission préparés immédiatement à partir de la fiche produit, sans frais. Transmettez les canaux réellement choisis : une campagne enregistre où vous avez décidé de soumettre, ce n’est pas un filtre que le serveur se charge d’étendre.

Les canaux sur lesquels le produit a déjà une tâche en cours ou soumise sont ignorés et listés dans skipped, avec un motif (already_open, already_submitted, not_found, broken, inactive, other_product, locked). S’il ne reste rien, l’appel renvoie HTTP 200 avec ok: false et failure: "no_valid_targets". Dépasser le quota de diffusion de l’offre entraîne un refus : **409 quota_exceeded**, avec dimension, limit, used et requested. Rien n’est créé, pas même la partie qui aurait tenu dans le quota.

Corps de la requête

ChampTypeDescription
productIdobligatoirestring
nameobligatoirestring
targetIdsobligatoirearray<string>Identifiants de canaux issus de GET /v1/channels. Uniquement les canaux choisis.
endsAtstringÉchéance facultative, affichée dans le tableau de bord. Rien n’est clôturé automatiquement.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataCreateCampaignOutcome
└okboolean
└failureenumPrésent lorsque ok vaut false.Valeurs : no_valid_targets
└campaignCampaign
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted est calculé : toutes les tâches sont publiées, vérifiées, en échec ou ignorées.Valeurs : activecompletedarchived
└quotaintegerNombre de canaux retenus à la création de la campagne.
└startsAtstring
└endsAtpeut être nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└tasksarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished correspond à ce que vous avez déclaré. verified correspond à ce que QueryWin a constaté sur la page de la fiche (un lien pour les annuaires et les annuaires d’outils IA, une mention ailleurs). Distinguez-les lorsque vous en rendez compte.Valeurs : plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonpeut être nullenumValeurs : logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenus obligatoires absents de la fiche. Complétez la fiche dans le tableau de bord : la tâche se prépare à nouveau d’elle-même.
└listingUrlpeut être nullstring
└markedByenumAuteur du dernier changement de statut : une personne (ou cette API), l’extension de navigateur ou QueryWin lui-même.Valeurs : userdevicesystem
└hasGeneratedbooleanUne version réécrite pour ce canal existe.
└reviewDueAtpeut être nullstringDate à laquelle revenir vérifier après la soumission (submittedAt + délai de validation du canal).
└submittedAtpeut être nullstring
└publishedAtpeut être nullstring
└verifiedAtpeut être nullstring
└nextarray<string>Statuts que vous pouvez définir depuis le statut actuel via POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValeurs : seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkpeut être nullobjectLa vérification que QueryWin effectue sur la fiche après sa mise en ligne. Vaut null tant que la tâche n’est pas publiée.
└kindenumCe qui est recherché : un lien vers le produit (annuaires, annuaires d’outils IA) ou une mention de la marque (tous les autres types).Valeurs : link_livemention_seen
└statepeut être nullenumconfirmed = constaté. unconfirmed = introuvable lors d’une série de vérifications (statut inchangé ; vérifiez l’URL). lost = présent auparavant, disparu depuis (tâche passée en échec).Valeurs : confirmedunconfirmedlostnull
└checkedAtpeut être nullstring
└dueAtpeut être nullstring
└updatedAtstring
└skippedarray<object>
└targetIdstring
└reasonenumother_product = un site candidat cité par l’IA qui appartient à un autre produit ; inactive = un canal désactivé ou ignoré ; locked = hors de la fenêtre de la bibliothèque de canaux de l’offre gratuite (les window premiers canaux par pertinence, tels que renvoyés par GET /v1/channels ; vos propres canaux, vos favoris et les sites cités par l’IA ne sont pas limités).Valeurs : not_foundbrokeninactiveother_productalready_openalready_submittedlocked
Erreurs possibles
400product_id_required, name_required / name_too_long (80 caractères), target_ids_required / too_many_targets (100 canaux) ou invalid_date
401Authentification refusée
403missing_scope_publish — cette clé ne dispose pas du droit publish
404product_not_found — ce productId n’appartient pas à ce compte
409quota_exceeded — le corps contient dimension (active_campaigns / tasks_per_month / channels), limit, used, requested ; pour channels, également period (month / total, comme DistributionQuota.limits.channelsPeriod)

Exemple

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 — réponse
{
  "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}

Une campagne et ses tâches

La campagne et toutes ses tâches. Gratuit.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataCampaignDetail
└campaignCampaign
└campaignIdstring
└productIdstring
└namestring
└statusenumcompleted est calculé : toutes les tâches sont publiées, vérifiées, en échec ou ignorées.Valeurs : activecompletedarchived
└quotaintegerNombre de canaux retenus à la création de la campagne.
└startsAtstring
└endsAtpeut être nullstring
└countsobject
└totalinteger
└submittedintegersubmitted + published + verified.
└liveintegerpublished + verified.
└blockedinteger
└doneinteger
└byStatusobject
└createdAtstring
└tasksarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished correspond à ce que vous avez déclaré. verified correspond à ce que QueryWin a constaté sur la page de la fiche (un lien pour les annuaires et les annuaires d’outils IA, une mention ailleurs). Distinguez-les lorsque vous en rendez compte.Valeurs : plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonpeut être nullenumValeurs : logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenus obligatoires absents de la fiche. Complétez la fiche dans le tableau de bord : la tâche se prépare à nouveau d’elle-même.
└listingUrlpeut être nullstring
└markedByenumAuteur du dernier changement de statut : une personne (ou cette API), l’extension de navigateur ou QueryWin lui-même.Valeurs : userdevicesystem
└hasGeneratedbooleanUne version réécrite pour ce canal existe.
└reviewDueAtpeut être nullstringDate à laquelle revenir vérifier après la soumission (submittedAt + délai de validation du canal).
└submittedAtpeut être nullstring
└publishedAtpeut être nullstring
└verifiedAtpeut être nullstring
└nextarray<string>Statuts que vous pouvez définir depuis le statut actuel via POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValeurs : seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkpeut être nullobjectLa vérification que QueryWin effectue sur la fiche après sa mise en ligne. Vaut null tant que la tâche n’est pas publiée.
└kindenumCe qui est recherché : un lien vers le produit (annuaires, annuaires d’outils IA) ou une mention de la marque (tous les autres types).Valeurs : link_livemention_seen
└statepeut être nullenumconfirmed = constaté. unconfirmed = introuvable lors d’une série de vérifications (statut inchangé ; vérifiez l’URL). lost = présent auparavant, disparu depuis (tâche passée en échec).Valeurs : confirmedunconfirmedlostnull
└checkedAtpeut être nullstring
└dueAtpeut être nullstring
└updatedAtstring
Erreurs possibles
401Authentification refusée
404campaign_not_found

Exemple

bash
curl "https://www.querywin.com/api/v1/campaigns/{id}" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Où soumettre un produit, par ordre de pertinence

La bibliothèque de canaux (annuaires, plateformes de lancement, annuaires d’outils IA, communautés, plateformes de publication), les canaux que vous avez ajoutés et, si productId est fourni, les sites que les réponses des IA citent déjà sur les requêtes de ce produit (source: "rivals"). Avec productId, la liste est triée par pertinence et chaque canal porte taskStatus (non nul lorsque le produit a déjà une tâche sur ce canal). Gratuit.

**citedByAi signifie que des réponses d’IA ont cité ce site sur vos requêtes. Cela ne signifie pas que ce site référencera votre produit** : c’est justement pour le lui demander que la tâche existe.

Paramètres de requête

ChampTypeDescription
productIdstringTrie par pertinence pour ce produit, ajoute taskStatus et inclut ses sites candidats cités par l’IA.
kindstringType de canal.Valeurs : directorylaunchai_directorycommunitycontentother
sourcestringseed = la bibliothèque, user = ajouté par vous, rivals = sites cités par l’IA pour ce produit.Valeurs : seeduserrivals
pricingstringCoût de la soumission.Valeurs : freeconditionalpaidunknown
submitMethodstringMode de soumission : remplir un formulaire, publier dans une communauté ou envoyer un pitch par e-mail.Valeurs : formpostemail
qstringRecherche dans le nom, le domaine et les thématiques.
hideSubmittedbooleanExclut les canaux sur lesquels ce produit a déjà une tâche en cours ou soumise. Nécessite productId.
pageintegerPar défaut : 1.
pageSizeintegerPar défaut : 30.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataChannelList
└productIdpeut être nullstring
└itemsarray<Channel>
└targetIdstringÀ transmettre dans targetIds à POST /v1/campaigns.
└namestring
└urlstring
└submitUrlstringLe formulaire de soumission ou la page de publication ; pour les sites cités par l’IA, la page la plus citée par les réponses des IA.
└kindenumValeurs : directorylaunchai_directorycommunitycontentother
└submitMethodenumform = remplir son formulaire de soumission, post = publier vous-même dans la communauté, email = envoyer un pitch aux rédacteurs par e-mail (l’adresse est lue sur la page au moment où vous l’ouvrez ; elle n’est pas stockée).Valeurs : formpostemail
└sourceenumseed = la bibliothèque, user = ajouté par vous, rivals = un site que les réponses des IA citent sur les requêtes de ce produit.Valeurs : seeduserrivals
└pricingTypeenumValeurs : freeconditionalpaidunknown
└priceNotepeut être nullstring
└languagestringen, zh, multi ou, pour les sites cités par l’IA, un code de langue déduit des requêtes.
└topicsarray<string>
└requiresAccountboolean
└requiresBacklinkboolean
└reviewDayspeut être nullintegerDélai de validation habituel. La tâche vous rappelle de revenir vérifier une fois ce délai écoulé.
└siteRankpeut être nullintegerRang mondial dans la liste publique Tranco (plus il est bas, plus le site est visité). null = hors du premier million. Ce n’est pas le Domain Rating.
└hasFormSpecbooleanLes champs du formulaire de ce canal sont répertoriés : les contenus sont donc tronqués à ses limites exactes.
└relevancepeut être nullintegerScore de pertinence pour le produit indiqué dans l’appel. Sert uniquement au tri.
└taskStatuspeut être nullstringLa tâche en cours ou terminée du produit sur ce canal, s’il y en a une. null = aucune pour l’instant.
└citedByAipeut être nullobjectUniquement pour source: "rivals". Des réponses d’IA ont cité ce site sur les requêtes listées. Ce n’est pas une promesse que ce site référencera votre produit : c’est justement pour le lui demander que la tâche existe.
└queriesintegerNombre de requêtes distinctes sur lesquelles il a été cité.
└samplesintegerNombre d’échantillons de réponses d’IA qui l’ont cité.
└searchesarray<string>
└pagesarray<string>Les pages citées, les plus citées en premier. Vide pour les échantillons plus anciens, qui n’enregistraient que le domaine.
└totalinteger
└pageinteger
└pageSizeinteger
└limitedbooleanOffre gratuite : seuls les window premiers canaux dans l’ordre par défaut (pertinence pour productId) sont renvoyés, et les paramètres de filtre, de recherche et de tri sont refusés avec 403 library_locked. total reste la taille réelle de la bibliothèque.
└windowpeut être nullintegerTaille de la fenêtre de l’offre gratuite : 20 canaux, plus 20 par personne inscrite avec votre lien de parrainage (et 20 de plus si votre propre inscription est passée par un tel lien). null lorsque la liste n’est pas limitée.
Erreurs possibles
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/channels" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Vos produits et la complétude de chaque fiche

Point de départ du pipeline de diffusion : tous les autres endpoints de diffusion prennent un productId. completeness et missing proviennent de la fiche produit enregistrée : un champ vide dans la fiche est un champ vide dans chaque soumission, et un champ obligatoire manquant bloque la tâche en blocked / missing_material jusqu’à ce que la fiche soit complétée. Lisez-la avec GET /v1/products/{id} et remplissez-la avec PATCH /v1/products/{id}. Gratuit.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataProductList
└productsarray<Product>
└productIdstring
└namestring
└urlstring
└domainstring
└primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
└topicsarray<string>
└completenessintegerComplétude de la fiche, de 0 à 100. Complétez les champs manquants dans le tableau de bord ou avec PATCH /v1/products/{id}.
└missingarray<string>Champs vides de la fiche. Chacun se retrouve vide dans toutes les soumissions.
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughpeut être nullstring
Erreurs possibles
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/products" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Créer un produit

Nécessite le droit publish et une offre payante. Crée un produit à partir de l’URL publique de sa page d’accueil et d’un nom facultatif ; occupe une place de votre quota de produits, sans débiter de crédits. Renvoie la fiche complète enregistrée, à remplir ensuite avec PATCH /v1/products/{id}. N’explore pas le site et ne fait appel à aucune IA. Un domaine déjà enregistré renvoie 409 product_exists avec le productId existant ; réutilisez-le si la réponse s’est perdue. Les valeurs par défaut du produit sont modifiables : ce ne sont pas des faits vérifiés sur le site.

Corps de la requête

ChampTypeDescription
urlobligatoirestring
namestring

Réponse201

ChampTypeDescription
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValeurs : saastoolecommercecontentserviceother
└launchStatusenumValeurs : livebeta
└pricingModelenumValeurs : freefreemiumpaidtrial
└taglinepeut être nullobject
└shortDescpeut être nullobject
└longDescpeut être nullobject
└firstCommentpeut être nullobject
└topicsarray<string>
└promoCodepeut être nullstring
└videoUrlpeut être nullstring
└demoUrlpeut être nullstring
└linkspeut être nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialspeut être nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNamepeut être nullstring
└contactEmailpeut être nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrlpeut être nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughpeut être nullstring
Erreurs possibles
400invalid_product_input, invalid_url ou url_not_public
401Authentification refusée
403plan_required, ou missing_scope_publish pour les opérations d’écriture
409product_exists (avec le productId existant) ou product_limit_reached
429rate_limited — 120 requêtes par minute et par clé

Exemple

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 — réponse
{
  "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}

Remplir ou modifier une fiche produit

Nécessite le droit publish. Enregistre immédiatement les seuls champs envoyés ; les objets (textes localisés compris) et les tableaux remplacent le champ entier. Lisez d’abord la fiche pour conserver les autres langues. Aucun crédit débité, aucune génération par IA. Les contenus de soumission en attente sont actualisés de manière asynchrone. N’utilisez que des faits connus : n’inventez pas les informations produit manquantes.

Corps de la requête

ChampTypeDescription
namestring
urlstring
primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
businessTypeenumValeurs : saastoolecommercecontentserviceother
launchStatusenumValeurs : livebeta
pricingModelenumValeurs : 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>

Réponse200

ChampTypeDescription
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValeurs : saastoolecommercecontentserviceother
└launchStatusenumValeurs : livebeta
└pricingModelenumValeurs : freefreemiumpaidtrial
└taglinepeut être nullobject
└shortDescpeut être nullobject
└longDescpeut être nullobject
└firstCommentpeut être nullobject
└topicsarray<string>
└promoCodepeut être nullstring
└videoUrlpeut être nullstring
└demoUrlpeut être nullstring
└linkspeut être nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialspeut être nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNamepeut être nullstring
└contactEmailpeut être nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrlpeut être nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughpeut être nullstring
Erreurs possibles
400invalid_product_input, invalid_url, url_not_public, invalid_email ou invalid_gallery
401Authentification refusée
403plan_required, ou missing_scope_publish pour les opérations d’écriture
404product_not_found
409product_exists (avec le productId existant)
429rate_limited — 120 requêtes par minute et par clé

Exemple

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 — réponse
{
  "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}

Lire une fiche produit complète

Renvoie les textes localisés enregistrés, les liens, les coordonnées, les images, la complétude et les champs manquants. Droit read ; aucun crédit débité. Un produit qui n’appartient pas à cet espace de travail renvoie lui aussi product_not_found.

Réponse200

ChampTypeDescription
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValeurs : saastoolecommercecontentserviceother
└launchStatusenumValeurs : livebeta
└pricingModelenumValeurs : freefreemiumpaidtrial
└taglinepeut être nullobject
└shortDescpeut être nullobject
└longDescpeut être nullobject
└firstCommentpeut être nullobject
└topicsarray<string>
└promoCodepeut être nullstring
└videoUrlpeut être nullstring
└demoUrlpeut être nullstring
└linkspeut être nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialspeut être nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNamepeut être nullstring
└contactEmailpeut être nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrlpeut être nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughpeut être nullstring
Erreurs possibles
401Authentification refusée
403plan_required, ou missing_scope_publish pour les opérations d’écriture
404product_not_found
429rate_limited — 120 requêtes par minute et par clé

Exemple

bash
curl "https://www.querywin.com/api/v1/products/{id}" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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

Importer une image produit depuis une URL

Nécessite le droit publish. Récupère une image publique en HTTP(S) et en stocke une copie comme miniature (elle remplace l’actuelle) ou dans la galerie (ajoutée à la suite, six au maximum). JPEG, PNG, WebP et SVG, 4 Mo au maximum ; les SVG sont convertis en PNG. Les réseaux privés et les redirections non sûres sont bloqués par le module de récupération d’images existant. Aucun crédit débité. Des imports répétés dans la galerie peuvent créer des doublons : si la réponse s’est perdue, relisez le produit avec GET avant de réessayer.

Corps de la requête

ChampTypeDescription
urlobligatoirestring
kindenumValeurs : thumbnailgalleryPar défaut gallery

Réponse200

ChampTypeDescription
successboolean
dataProductDetail
└namestring
└urlstring
└primaryLanguageenumValeurs : enzhjakodefresptitrunlpltrarthviid
└businessTypeenumValeurs : saastoolecommercecontentserviceother
└launchStatusenumValeurs : livebeta
└pricingModelenumValeurs : freefreemiumpaidtrial
└taglinepeut être nullobject
└shortDescpeut être nullobject
└longDescpeut être nullobject
└firstCommentpeut être nullobject
└topicsarray<string>
└promoCodepeut être nullstring
└videoUrlpeut être nullstring
└demoUrlpeut être nullstring
└linkspeut être nullobject
└webAppstring
└appStorestring
└playStorestring
└chromeExtensionstring
└macosstring
└windowsstring
└socialspeut être nullobject
└xstring
└linkedinstring
└githubstring
└instagramstring
└youtubestring
└facebookstring
└contactNamepeut être nullstring
└contactEmailpeut être nullstring
└galleryarray<string>
└productIdstring
└domainstring
└thumbnailUrlpeut être nullstring
└completenessinteger (0–100)
└missingarray<string>
└createdAtstring
└updatedAtstring
└sitesarray<object>
└siteIdstring
└domainstring
└syncedThroughpeut être nullstring
Erreurs possibles
400invalid_product_input ou invalid_url
401Authentification refusée
403plan_required, ou missing_scope_publish pour les opérations d’écriture
404product_not_found
409gallery_full
413image_too_large
415image_type
429rate_limited — 120 requêtes par minute et par clé
502image_unreachable
503storage_unavailable

Exemple

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 — réponse
{
  "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

Tâches de toutes les campagnes

L’historique des soumissions, activité la plus récente en premier. Filtrez par produit, par campagne ou par une liste de statuts séparés par des virgules. byStatus compte toutes les tâches du périmètre avant application du filtre de statut. Gratuit.

Paramètres de requête

ChampTypeDescription
productIdstringUniquement les tâches de ce produit.
campaignIdstring
statusstringSéparés par des virgules, par ex. prepared,in_progress.
pageintegerPar défaut : 1.
pageSizeintegerPar défaut : 30.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataTaskList
└itemsarray<Task>
└taskIdstring
└campaignIdstring
└productIdstring
└statusenumpublished correspond à ce que vous avez déclaré. verified correspond à ce que QueryWin a constaté sur la page de la fiche (un lien pour les annuaires et les annuaires d’outils IA, une mention ailleurs). Distinguez-les lorsque vous en rendez compte.Valeurs : plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
└blockedReasonpeut être nullenumValeurs : logincaptchapaymentmissing_materialothernull
└missingarray<string>Contenus obligatoires absents de la fiche. Complétez la fiche dans le tableau de bord : la tâche se prépare à nouveau d’elle-même.
└listingUrlpeut être nullstring
└markedByenumAuteur du dernier changement de statut : une personne (ou cette API), l’extension de navigateur ou QueryWin lui-même.Valeurs : userdevicesystem
└hasGeneratedbooleanUne version réécrite pour ce canal existe.
└reviewDueAtpeut être nullstringDate à laquelle revenir vérifier après la soumission (submittedAt + délai de validation du canal).
└submittedAtpeut être nullstring
└publishedAtpeut être nullstring
└verifiedAtpeut être nullstring
└nextarray<string>Statuts que vous pouvez définir depuis le statut actuel via POST /v1/tasks/{id}/status.
└targetobject
└targetIdstring
└namestring
└urlstring
└submitUrlstring
└kindstring
└sourceenumValeurs : seeduserrivals
└languagestring
└requiresBacklinkboolean
└checkpeut être nullobjectLa vérification que QueryWin effectue sur la fiche après sa mise en ligne. Vaut null tant que la tâche n’est pas publiée.
└kindenumCe qui est recherché : un lien vers le produit (annuaires, annuaires d’outils IA) ou une mention de la marque (tous les autres types).Valeurs : link_livemention_seen
└statepeut être nullenumconfirmed = constaté. unconfirmed = introuvable lors d’une série de vérifications (statut inchangé ; vérifiez l’URL). lost = présent auparavant, disparu depuis (tâche passée en échec).Valeurs : confirmedunconfirmedlostnull
└checkedAtpeut être nullstring
└dueAtpeut être nullstring
└updatedAtstring
└totalinteger
└pageinteger
└pageSizeinteger
└byStatusobject
Erreurs possibles
401Authentification refusée

Exemple

bash
curl "https://www.querywin.com/api/v1/tasks" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — réponse
{
  "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}

Une tâche et ses contenus à soumettre

Chaque champ demandé par le formulaire du canal, tiré de la fiche produit et tronqué aux limites du canal (source: "profile"), plus la version réécrite pour ce canal s’il en existe une (source: "ai") et les modifications faites dans le tableau de bord (source: "override"). source: "none" signifie que la fiche ne contient rien pour ce champ : ne l’inventez pas. Ne déclenche jamais de réécriture et ne coûte jamais rien.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataTaskDetailResult
└taskTaskDetail
Erreurs possibles
401Authentification refusée
404task_not_found

Exemple

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

Réécrire les contenus pour ce canal (consomme des crédits)

Un passage par l’IA qui adapte la fiche à ce canal précis : une description d’annuaire plus resserrée, le premier commentaire du créateur, une publication pour une communauté, un pitch pour un rédacteur ou, pour les sites cités par l’IA, un pitch accompagné d’un paragraphe que le responsable de la page pourrait ajouter. Synchrone, quelques secondes. N’utilise que les faits présents dans la fiche ; toute partie contenant un lien absent de la fiche est écartée et non facturée.

À entrées identiques, la version précédente est renvoyée sans débit (cached: true) ; force: true réécrit malgré tout et est facturé. Les contenus tirés de la fiche par GET /v1/tasks/{id} suffisent généralement pour les annuaires ; réécrivez lorsque le canal attend un autre ton.

Renvoie HTTP 200 avec ok: false et failure: "engine_failed" lorsque rien n’a passé la validation. Un solde de crédits insuffisant, lui, renvoie une vraie erreur 402.

Corps de la requête

ChampTypeDescription
confirmSpendobligatoireintegerPlafond d’autorisation en crédits, même sémantique que pour les plans et les brouillons. Lisez le prix dans GET /v1/usage (materials.pricePerTask). Obligatoire même s’il vous reste des générations gratuites. (min 0)
forcebooleanRéécrit même si rien n’a changé depuis la dernière version. Facturé.

Réponse200

ChampTypeDescription
successenumValeurs : true
dataWriteMaterialsOutcome
└okboolean
└failureenumPrésent lorsque ok vaut false. Rien n’est débité.Valeurs : engine_failedengine_unavailable
└cachedbooleanEntrées inchangées : la version précédente a été renvoyée et rien n’a été débité.
└freeUsedboolean
└creditsSpentinteger
└taskTaskDetail
Erreurs possibles
400confirm_spend_required ou confirm_spend_too_low (le corps contient le price actuel)
401Authentification refusée
402insufficient_credits — le corps contient requiredCredits, currentBalance, shortfall
403missing_scope_spend — cette clé ne dispose pas du droit spend
404task_not_found
429rate_limited ou daily_limit_reached (le corps contient resetAt)

Exemple

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 — réponse
{
  "success": true,
  "data": {
    "ok": true,
    "failure": "engine_failed",
    "cached": true,
    "freeUsed": true,
    "creditsSpent": 0,
    "task": null
  }
}
POST/v1/tasks/{id}/status

Déclarer le résultat de la soumission

Enregistrez l’issue d’une soumission que vous avez effectuée avec vos propres comptes. QueryWin ne soumet jamais rien de lui-même. Passez à submitted une fois le formulaire envoyé, puis à published avec l’URL de la fiche (la fiche ou la publication elle-même, pas la page d’accueil du site) une fois en ligne ; environ 72 heures plus tard, QueryWin revérifie cette page à la recherche d’un lien vers votre produit (annuaires, annuaires d’outils IA) ou d’une mention (tous les autres types), et passe lui-même la tâche en verified — vous ne pouvez pas définir ce statut.

blocked signifie « intervention humaine requise » : transmettez reason (login, captcha, payment, missing_material, other). failed / skipped clôturent la tâche ; prepared la remet en attente. Les transitions que la machine à états n’autorise pas renvoient **409 transition_not_allowed** ; le champ next de la tâche liste les statuts autorisés depuis le statut actuel.

Corps de la requête

ChampTypeDescription
statusobligatoireenumverified ne peut pas être défini : QueryWin l’attribue après avoir revérifié la fiche.Valeurs : preparedin_progressblockedsubmittedpublishedfailedskipped
listingUrlstringObligatoire pour published : la fiche ou la publication en ligne, pas la page d’accueil du site. http/https uniquement. Facultatif avec submitted si vous la connaissez déjà.
notestring
reasonenumObligatoire pour blocked.Valeurs : logincaptchapaymentmissing_materialother

Réponse200

ChampTypeDescription
successenumValeurs : true
dataTaskDetailResult
└taskTaskDetail
Erreurs possibles
400invalid_status, listing_url_required (published exige listingUrl), invalid_listing_url ou reason_required (blocked exige reason)
401Authentification refusée
403missing_scope_publish — cette clé ne dispose pas du droit publish
404task_not_found
409transition_not_allowed — lisez la tâche : next liste les statuts autorisés

Exemple

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 — réponse
{
  "success": true,
  "data": {
    "task": null
  }
}
API QueryWin — chaînes de contenu et de diffusion pour vos scripts et agents IA