API

Put QueryWin into your own pipeline

QueryWin finds the searches your site already gets impressions for but has no page written for, drafts the missing article, and prepares your product for submission to directories, launch platforms, communities and the sites AI answers already cite. This API hands both pipelines to your scripts and to AI assistants. Publishing and submitting still happen with your own credentials and accounts — QueryWin never connects to your CMS and never submits anywhere itself.

Base URLhttps://www.querywin.com/apiCreate an API keyOpenAPI spec (JSON)

Quickstart

Three steps. Everything below is a plain REST call with one header.

1

Create a key

In the QueryWin dashboard under API. The plaintext key is shown once, at creation. Grant only the scopes you need — a key with no boxes ticked is read-only.

2

Send it as a bearer token

Authorization: Bearer qw_live_… on every request. X-API-Key works too, for tools that only let you set one header.

3

Read what to write, then take the draft

The topic list is free and costs no credits. Generating an outline or a draft deducts credits, and requires the spend scope.

bash
# 1. Which sites this key can act on
curl "https://www.querywin.com/api/v1/sites" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 2. What to write this week (free, no credits)
curl "https://www.querywin.com/api/v1/topics?siteId=YOUR_SITE_ID" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
bash
# 3. Get the draft (Markdown + JSON-LD with the data filled in)
curl "https://www.querywin.com/api/v1/topics/article?siteId=YOUR_SITE_ID&key=standard%20wardrobe%20depth" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"

# 4. Your script publishes it to your own blog, then reports the URL back
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"
  }'

Every response is wrapped: {"success": true, "data": …}. Errors are machine-readable codes, never prose — QueryWin is bilingual and a hardcoded English sentence would end up printed on a Chinese page.

The content pipeline

Five steps, and only two of them cost anything. Step 4 is yours: QueryWin hands you the markdown and the structured data, and you publish it.

StepEndpointCost
Find what has impressions but no pageGET /v1/topicsFree
Generate an evidence-backed outlinePOST /v1/topics/outlineCredits
Turn it into a publishable draftPOST /v1/topics/articleCredits
Publish it to your own blogyour own CMS
Report the URL backPOST /v1/topics/publishedFree

QueryWin never connects to your CMS, never holds your site credentials, and never presses publish for you. This API hands you the content; the write happens on your machine with your own credentials. That is the boundary, not a loophole in it.

The distribution pipeline

Seven steps, and only one of them costs anything. Step 6 is yours: QueryWin hands you the submission material for each channel, and you — or your agent, with your own accounts — submit it. QueryWin then re-checks every published listing itself.

StepEndpointCost
List your products and how complete each profile isGET /v1/productsFree
List where to submit, sorted by fit — including the sites AI answers citeGET /v1/channels?productId=Free
Create a campaign from the channels you chosePOST /v1/campaignsFree
Take the prepared material for a taskGET /v1/tasks/{id}Free
Rewrite it for that channelPOST /v1/tasks/{id}/materialsCredits
Submit ityour own accounts
Report submitted, then published with the listing URLPOST /v1/tasks/{id}/statusFree

published is what you reported; verified is what QueryWin saw when it re-checked the listing about 72 hours later — a link to your product on directories and AI tool lists, a mention everywhere else. They are separate fields. And a site that AI answers cite (citedByAi) is a site worth asking; it is not a promise that it will list you.

Scopes

Each key carries the permissions granted when it was created. GET /v1/usage reports them, so you never have to discover them by hitting a 403.

read

Always on

Every GET endpoint: sites, content gaps, outlines, drafts, products, channels, campaigns, tasks and their material, usage.

publish

Off by default

Record what happened: a published article URL (also submitted to Bing, Yandex, Seznam and Naver — not Google), a new campaign, a task marked submitted or published. Free, but each one is a record with consequences: campaigns count against your plan, and a published listing gets re-checked.

spend

Off by default

Generate outlines and drafts, and rewrite submission material for a channel. These deduct credits. Grant it only if you want the caller — a script, or an AI assistant — to spend on its own.

There is no hierarchy: publish does not imply spend, and spend does not imply publish. They are different kinds of risk. A call your key lacks the scope for returns 403 with missing_scope_<name> and the scopes you do have.

Spending credits

Two endpoints deduct credits: POST /v1/topics/outline and POST /v1/topics/article. Three gates stand in front of them.

GateWhat it stops
confirmSpendA script that keeps charging after the price goes up.
Daily limitA runaway loop burning through the balance overnight. Returns 429 with the reset time.
Input fingerprintCharging twice for the same topic. Identical inputs return the cached result for free, so retrying a timed-out request is safe.

confirmSpend is an authorization ceiling, not an exact amount. Send a value greater than or equal to the current price and you are charged what it actually costs — often zero, on a cache hit. If the price ever rises above your ceiling the call fails with confirm_spend_too_low instead of quietly charging more.

Conventions

Three things that hold across every endpoint.

Response envelope

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

Errors are codes, never sentences. Read them, do not display them raw — they are meant to be mapped to your own wording.

Business outcomes are not errors

A generation call that could not produce a valid result returns HTTP 200 with data.ok = false and a data.failure code, so you can tell it apart from an auth failure or a dropped connection. Nothing is charged for those.

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

Rate limit

120 requests per minute per key. Over that you get 429 with the reset time. This is separate from the daily generation limit above.

MCP for AI agents

Both pipelines are available over the Model Context Protocol, authenticated with the same key and the same header. Add it to Claude Code, Cursor, n8n, or anything else that speaks MCP over 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"

Fifteen tools. Content: list_sites, get_usage, list_content_gaps, get_outline, get_article_draft, generate_outline, generate_article_draft, mark_published. Distribution: list_products, list_channels, create_campaign, list_tasks, get_task, write_task_materials, report_task_status. Ask your assistant what to write this week, or where to submit your product next, and it will go find out.

prompt
Using the QueryWin MCP server, find the three biggest content gaps on my site,
show me the searches behind each one, and tell me what it would cost to draft the top one.

Tools are filtered by scope. With a read-only key the generation tools do not appear in the assistant’s tool list at all — an agent cannot call a tool it cannot see. Give an agent its own key, so you can revoke it without touching your other integrations.

Authentication is a bearer token, which the MCP specification allows (authorization is optional there). Clients that let you set a header — Claude Code, Cursor, n8n — connect directly. Hosts that require an OAuth consent screen may not.

Account

GET/v1/sites

List the sites this key can act on

Start here. Every other endpoint takes the siteId returned by this call. Omitting siteId elsewhere falls back to the earliest connected site, which is fine for single-site accounts and a bug waiting to happen for everyone else.

Response200

FieldTypeDescription
successenumValues: true
dataSiteList
sitesarray<Site>
siteIdstring
domainstring
gscPropertystringThe Search Console property, verbatim: sc-domain:example.com or https://example.com/.
syncStatusenumValues: pendingsyncingdonefailed
syncedThroughnullablestringSearch Console lags 2-3 days. Every metric on this site is "as of" this date — say so if you surface the numbers anywhere.
Possible errors
401Missing, malformed, revoked, or expired key

Example

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

Current prices, free allowance, credit balance, and daily limits

Read this before generating anything. It is the same source of truth the web UI uses to decide what to print on the button — availability is decided server-side, not by trying and failing.

Response200

FieldTypeDescription
successenumValues: true
dataUsage
scopesarray<enum>What this key may do. Read it once at startup rather than discovering your permissions by hitting a 403.Values: readpublishspend
creditsobject
balanceinteger
outlineStepUsage
availablebooleanFalse when the generation engine is not configured. Do not call the POST endpoint.
pricePerOutlineintegerCredits per outline (present on outline only).
pricePerArticleintegerCredits per draft (present on article only).
pricePerTaskintegerCredits per channel rewrite (present on materials only).
freeRemainingintegerFree generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
dailyDailyLimitA backstop against a runaway script, counted in the database across every channel (the web UI counts toward it too). It resets at local midnight, not on a sliding window.
usedinteger
limitinteger
remaininginteger
resetAtstring
articleStepUsage
availablebooleanFalse when the generation engine is not configured. Do not call the POST endpoint.
pricePerOutlineintegerCredits per outline (present on outline only).
pricePerArticleintegerCredits per draft (present on article only).
pricePerTaskintegerCredits per channel rewrite (present on materials only).
freeRemainingintegerFree generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
dailyDailyLimitA backstop against a runaway script, counted in the database across every channel (the web UI counts toward it too). It resets at local midnight, not on a sliding window.
usedinteger
limitinteger
remaininginteger
resetAtstring
materialsStepUsage
availablebooleanFalse when the generation engine is not configured. Do not call the POST endpoint.
pricePerOutlineintegerCredits per outline (present on outline only).
pricePerArticleintegerCredits per draft (present on article only).
pricePerTaskintegerCredits per channel rewrite (present on materials only).
freeRemainingintegerFree generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
dailyDailyLimitA backstop against a runaway script, counted in the database across every channel (the web UI counts toward it too). It resets at local midnight, not on a sliding window.
usedinteger
limitinteger
remaininginteger
resetAtstring
distributionDistributionQuotaThe plan's distribution limits and current usage. A null limit means unlimited.
planstring
limitsobject
channelsnullableintegerDistinct channels one product may have tasks for, cumulative.
tasksPerMonthnullableintegerTasks that may be created per calendar month (UTC), across products.
activeCampaignsnullableintegerCampaigns that may be active at once.
usageobject
activeCampaignsinteger
tasksThisMonthinteger
channelsnullableintegerOnly when the request named a product.
Possible errors
401Authentication failed

Example

bash
curl "https://www.querywin.com/api/v1/usage" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — response
{
  "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,
        "tasksPerMonth": 0,
        "activeCampaigns": 0
      },
      "usage": {
        "activeCampaigns": 0,
        "tasksThisMonth": 0,
        "channels": 0
      }
    }
  }
}

Topics

GET/v1/topics

Searches with impressions but no page written for them

Free, no external billing (the API itself requires a paid plan). Computed fresh on each call from your own Search Console data — there is no external keyword database involved, and that is the point: "you already have impressions and no page for it" is not something a keyword tool can tell you.

Results are ordered by opportunity. Topics the user dismissed in the UI are excluded.

Query parameters

FieldTypeDescription
siteIdstringFrom GET /v1/sites. Defaults to the earliest connected site.

Response200

FieldTypeDescription
successenumValues: true
dataTopicList
siteIdnullablestring
topicsarray<Topic>
keystringThe cluster key — pass it back as key to every other topic endpoint. It is the normalized text of the representative search, so it can contain spaces, slashes and non-Latin characters. Always send it in the query string or body, never in a URL path.
titlestringThe highest-impression search in the cluster, verbatim. This is not a generated headline — that comes with the outline.
shapeenumcomparison means this cluster hit your competitor list. The article must contrast, not explain the competitor — otherwise you are writing content for them.Values: comparisonroundupguide
intentstring
membersarray<TopicMember>
textstringThe search, as typed.
impressionsinteger
clicksinteger
positionnullablenumber
landingUrlnullablestringThe page Search Console currently records for this search, if any.
impressionsintegerMeasured, from Search Console.
clicksintegerMeasured, from Search Console.
positionnullablenumberMeasured: impression-weighted average position across the cluster.
competitorboolean
scorenumber
rankinteger
upsideClicksintegerAn estimate, not a measurement: extra monthly clicks if a dedicated page reached position 3. It is deliberately a separate field from clicks and impressions, and it must stay visually separate wherever you display it. Presenting a projection as measured data is the standard failure of this product category.
statusenumValues: newdismissedplannedpublished
outlineAtnullablestring
articleAtnullablestringDo not infer this from outlineAt. Having an outline does not mean a draft exists — they are two separate paid steps.
totalQueriesintegerHow many distinct searches these topics cover in total.
Possible errors
401Authentication failed
404site_not_found — the siteId does not belong to this account

Example

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

Read an already-generated draft

Never triggers generation and never costs anything. article is null if none exists yet.

Query parameters

FieldTypeDescription
keyrequiredstringThe cluster key from GET /v1/topics.
siteIdstringFrom GET /v1/sites. Defaults to the earliest connected site.

Response200

FieldTypeDescription
successenumValues: true
dataArticleResult
articlenullableArticle
titlestring
descriptionstringMeta description.
markdownstringThe body to publish.
jsonLdstringStructured data for this article, already filled in. Valid JSON — put it inside a script tag of type application/ld+json on the published page.
wordCountinteger
warningsarray<ArticleWarning>Phrases that read as AI-generated. The draft is still usable — these are reported rather than silently rewritten. Log them. In an automated pipeline this is the only moment anyone could notice.
kindenumValues: banned_phrasebanned_wordem_dash_overuseno_short_sentence
hitstringThe offending text.
articleAtnullablestring
modelnullablestring
Possible errors
400key_required
401Authentication failed

Example

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

Turn the outline into a publishable draft (costs credits)

The most expensive call in the product. Synchronous; can take one to two minutes.

An outline must exist first — without one you get failure: "no_outline". The outline is what carries the evidence (the real searches behind the cluster, the pages AI cites today, deduplication against your existing pages). Skipping it would reduce this to an AI writing tool with nothing behind it.

article.markdown is the body to publish. article.jsonLd is structured data already filled in with this article's content. **article.warnings should be logged, not dropped** — each one points at a specific phrase that reads as AI-generated, and in an automated pipeline nobody re-reads the draft before it goes out.

Drafts that fail structural validation (missing sections, unanswered required questions, invented links, broken JSON-LD) are discarded and not charged.

Request body

FieldTypeDescription
keyrequiredstringThe cluster key from GET /v1/topics.
siteIdstringDefaults to the earliest connected site.
confirmSpendrequiredintegerAn authorization ceiling in credits, not an exact amount. Send a value >= the current price from GET /v1/usage; you are charged what it actually costs, which is often zero on a cache hit. If the price ever exceeds your ceiling the call is refused rather than silently charging more. Required even when you have free allowance left — the allowance runs out, and that should not be the moment your script first discovers this endpoint costs money. (min 0)

Response200

FieldTypeDescription
successenumValues: true
dataArticleOutcome
okboolean
failureenumPresent only when ok is false. HTTP is still 200.Values: topic_not_foundno_outlineengine_unavailableengine_failedno_valid_article
articlenullableArticle
titlestring
descriptionstringMeta description.
markdownstringThe body to publish.
jsonLdstringStructured data for this article, already filled in. Valid JSON — put it inside a script tag of type application/ld+json on the published page.
wordCountinteger
warningsarray<ArticleWarning>Phrases that read as AI-generated. The draft is still usable — these are reported rather than silently rewritten. Log them. In an automated pipeline this is the only moment anyone could notice.
kindenumValues: banned_phrasebanned_wordem_dash_overuseno_short_sentence
hitstringThe offending text.
generatedbooleanFalse when a cached result was returned — nothing was charged.
creditsSpentinteger
freeUsedboolean
rejectedarray<string>Structural failures that caused a draft to be discarded (and not charged): missing_sections, missing_faq, invented_link, invalid_json_ld, comparison_without_contrast, body_too_short.
Possible errors
400key_required, confirm_spend_required, or confirm_spend_too_low (the body carries the current price)
401Authentication failed
402insufficient_credits — body carries requiredCredits, currentBalance, shortfall
403missing_scope_spend — this key was not granted the spend scope
429rate_limited or daily_limit_reached (body carries resetAt)

Example

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 — response
{
  "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

Read an already-generated outline

Never triggers generation and never costs anything. outline is null if none exists yet.

Query parameters

FieldTypeDescription
keyrequiredstringThe cluster key from GET /v1/topics.
siteIdstringFrom GET /v1/sites. Defaults to the earliest connected site.

Response200

FieldTypeDescription
successenumValues: true
dataOutlineResult
outlinenullableOutline
titlestring
slugstring
anglestringThe argument this page should make.
sectionsarray<object>
headingstring
pointsarray<string>
faqarray<object>Questions the page must answer. These are the hooks AI answers quote.
questionstring
answerstring
schemaTypestringWhich JSON-LD type suits this page.
internalLinksarray<string>Pages on your own site worth linking to. Chosen from real URLs, never invented.
outlineAtnullablestring
modelnullablestring
Possible errors
400key_required
401Authentication failed

Example

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

Generate an outline (costs credits)

Synchronous; expect roughly 10-20 seconds.

Identical inputs return the cached outline without charging again — the fingerprint covers the topic cluster and its competitor classification, so retrying a failed HTTP request is safe.

Returns HTTP 200 with ok: false and a failure code for business outcomes (engine unavailable, output failed validation). Insufficient credits is a real 402.

Request body

FieldTypeDescription
keyrequiredstringThe cluster key from GET /v1/topics.
siteIdstringDefaults to the earliest connected site.
confirmSpendrequiredintegerAn authorization ceiling in credits, not an exact amount. Send a value >= the current price from GET /v1/usage; you are charged what it actually costs, which is often zero on a cache hit. If the price ever exceeds your ceiling the call is refused rather than silently charging more. Required even when you have free allowance left — the allowance runs out, and that should not be the moment your script first discovers this endpoint costs money. (min 0)

Response200

FieldTypeDescription
successenumValues: true
dataOutlineOutcome
okboolean
failureenumPresent only when ok is false. HTTP is still 200 — this is an outcome, not an error.Values: topic_not_foundengine_unavailableengine_failedno_valid_outline
outlinenullableOutline
titlestring
slugstring
anglestringThe argument this page should make.
sectionsarray<object>
headingstring
pointsarray<string>
faqarray<object>Questions the page must answer. These are the hooks AI answers quote.
questionstring
answerstring
schemaTypestringWhich JSON-LD type suits this page.
internalLinksarray<string>Pages on your own site worth linking to. Chosen from real URLs, never invented.
generatedbooleanFalse when a cached result was returned — nothing was charged.
creditsSpentinteger
freeUsedboolean
rejectedarray<string>Validation rules the model output tripped. Worth logging as a quality signal.
Possible errors
400key_required, confirm_spend_required, or confirm_spend_too_low (the body carries the current price)
401Authentication failed
402insufficient_credits — body carries requiredCredits, currentBalance, shortfall
403missing_scope_spend — this key was not granted the spend scope
429rate_limited or daily_limit_reached (body carries resetAt)

Example

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 — response
{
  "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

Report where you published it

Closes the loop. Marks the topic as published, stores the URL, and submits it to IndexNow on your behalf.

IndexNow covers Bing, Yandex, Seznam and Naver — not Google. Google has no equivalent instant-indexing endpoint; it finds the page through your sitemap.

The IndexNow submission never fails the request: your article is already published, and that is the fact this call records. Check the indexnow field for what actually happened. Submission requires the IndexNow key file to be verified for the site (set that up once in the dashboard).

Recording the URL is also what lets QueryWin re-measure the searches this article targets once it has had time to land.

Request body

FieldTypeDescription
keyrequiredstring
siteIdstring
urlrequiredstringWhere you published it. http/https only. Not fetched at this point.

Response200

FieldTypeDescription
successenumValues: true
dataPublishedResult
clusterKeystring
statusenumValues: published
publishedUrlstring
publishedAtstring
indexnowobjectBing, Yandex, Seznam, Naver. Not Google.
pushedboolean
outcomestringskipped usually means the key file is not verified yet.
enginesstring
Possible errors
400key_required, url_required, or invalid_url (http/https only)
401Authentication failed
403missing_scope_publish — this key was not granted the publish scope
404site_not_found

Example

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 — response
{
  "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

Campaigns and your distribution quota

Campaigns (archived ones excluded unless includeArchived=true) plus the plan's distribution limits and current usage. Free.

Query parameters

FieldTypeDescription
productIdstringOnly this product's campaigns.
includeArchivedboolean

Response200

FieldTypeDescription
successenumValues: true
dataCampaignList
campaignsarray<Campaign>
campaignIdstring
productIdstring
namestring
statusenumcompleted is computed: every task is published, verified, failed or skipped.Values: activecompletedarchived
quotaintegerHow many channels the campaign was created with.
startsAtstring
endsAtnullablestring
countsobject
totalinteger
submittedintegersubmitted + published + verified.
liveintegerpublished + verified.
blockedinteger
doneinteger
byStatusobject
createdAtstring
quotaDistributionQuotaThe plan's distribution limits and current usage. A null limit means unlimited.
planstring
limitsobject
channelsnullableintegerDistinct channels one product may have tasks for, cumulative.
tasksPerMonthnullableintegerTasks that may be created per calendar month (UTC), across products.
activeCampaignsnullableintegerCampaigns that may be active at once.
usageobject
activeCampaignsinteger
tasksThisMonthinteger
channelsnullableintegerOnly when the request named a product.
Possible errors
401Authentication failed

Example

bash
curl "https://www.querywin.com/api/v1/campaigns" \
  -H "Authorization: Bearer $QUERYWIN_API_KEY"
json — response
{
  "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,
        "tasksPerMonth": 0,
        "activeCampaigns": 0
      },
      "usage": {
        "activeCampaigns": 0,
        "tasksThisMonth": 0,
        "channels": 0
      }
    }
  }
}
POST/v1/campaigns

Create a campaign from an explicit list of channels

One task per channel id, with its submission material prepared from the product profile at once, at no cost. Pass the channels that were actually chosen — a campaign is the record of where you decided to submit, not a filter the server expands.

Channels the product already has an open or submitted task for are skipped and listed in skipped with a reason (already_open, already_submitted, not_found, broken, inactive, other_product). If nothing is left, the call returns HTTP 200 with ok: false and failure: "no_valid_targets". Exceeding the plan's distribution quota is a refusal: **409 quota_exceeded** with dimension, limit, used and requested — nothing is created, not even the part that would have fit.

Request body

FieldTypeDescription
productIdrequiredstring
namerequiredstring
targetIdsrequiredarray<string>Channel ids from GET /v1/channels. Only the channels that were chosen.
endsAtstringOptional deadline shown in the dashboard. Nothing closes automatically.

Response200

FieldTypeDescription
successenumValues: true
dataCreateCampaignOutcome
okboolean
failureenumPresent when ok is false.Values: no_valid_targets
campaignCampaign
campaignIdstring
productIdstring
namestring
statusenumcompleted is computed: every task is published, verified, failed or skipped.Values: activecompletedarchived
quotaintegerHow many channels the campaign was created with.
startsAtstring
endsAtnullablestring
countsobject
totalinteger
submittedintegersubmitted + published + verified.
liveintegerpublished + verified.
blockedinteger
doneinteger
byStatusobject
createdAtstring
tasksarray<Task>
taskIdstring
campaignIdstring
productIdstring
statusenumpublished is what you reported. verified is what QueryWin saw on the listing page (a link for directories and AI tool lists, a mention elsewhere). Keep them apart when you report.Values: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
blockedReasonnullableenumValues: logincaptchapaymentmissing_materialothernull
missingarray<string>Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
listingUrlnullablestring
markedByenumWho made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
hasGeneratedbooleanA channel-specific rewrite exists.
reviewDueAtnullablestringWhen to check back after submitting (submittedAt + the channel's review days).
submittedAtnullablestring
publishedAtnullablestring
verifiedAtnullablestring
nextarray<string>Statuses you may set from the current one via POST /v1/tasks/{id}/status.
targetobject
targetIdstring
namestring
urlstring
submitUrlstring
kindstring
sourceenumValues: seeduserrivals
languagestring
requiresBacklinkboolean
checknullableobjectThe re-check QueryWin runs on the listing after publication. Null until the task is published.
kindenumWhat is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
statenullableenumconfirmed = seen. unconfirmed = not found in one round of checks (status unchanged; check the URL). lost = it was there and is gone (task failed).Values: confirmedunconfirmedlostnull
checkedAtnullablestring
dueAtnullablestring
updatedAtstring
skippedarray<object>
targetIdstring
reasonenumother_product = an AI-cited candidate that belongs to a different product; inactive = a channel that is disabled or was ignored.Values: not_foundbrokeninactiveother_productalready_openalready_submitted
Possible errors
400product_id_required, name_required / name_too_long (80), target_ids_required / too_many_targets (100), or invalid_date
401Authentication failed
403missing_scope_publish — this key was not granted the publish scope
404product_not_found — the productId does not belong to this account
409quota_exceeded — body carries dimension (active_campaigns / tasks_per_month / channels), limit, used, requested

Example

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 — response
{
  "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}

One campaign with its tasks

The campaign and every task in it. Free.

Response200

FieldTypeDescription
successenumValues: true
dataCampaignDetail
campaignCampaign
campaignIdstring
productIdstring
namestring
statusenumcompleted is computed: every task is published, verified, failed or skipped.Values: activecompletedarchived
quotaintegerHow many channels the campaign was created with.
startsAtstring
endsAtnullablestring
countsobject
totalinteger
submittedintegersubmitted + published + verified.
liveintegerpublished + verified.
blockedinteger
doneinteger
byStatusobject
createdAtstring
tasksarray<Task>
taskIdstring
campaignIdstring
productIdstring
statusenumpublished is what you reported. verified is what QueryWin saw on the listing page (a link for directories and AI tool lists, a mention elsewhere). Keep them apart when you report.Values: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
blockedReasonnullableenumValues: logincaptchapaymentmissing_materialothernull
missingarray<string>Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
listingUrlnullablestring
markedByenumWho made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
hasGeneratedbooleanA channel-specific rewrite exists.
reviewDueAtnullablestringWhen to check back after submitting (submittedAt + the channel's review days).
submittedAtnullablestring
publishedAtnullablestring
verifiedAtnullablestring
nextarray<string>Statuses you may set from the current one via POST /v1/tasks/{id}/status.
targetobject
targetIdstring
namestring
urlstring
submitUrlstring
kindstring
sourceenumValues: seeduserrivals
languagestring
requiresBacklinkboolean
checknullableobjectThe re-check QueryWin runs on the listing after publication. Null until the task is published.
kindenumWhat is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
statenullableenumconfirmed = seen. unconfirmed = not found in one round of checks (status unchanged; check the URL). lost = it was there and is gone (task failed).Values: confirmedunconfirmedlostnull
checkedAtnullablestring
dueAtnullablestring
updatedAtstring
Possible errors
401Authentication failed
404campaign_not_found

Example

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

Where a product can be submitted, sorted by fit

The channel library (directories, launch platforms, AI tool lists, communities, publishing platforms), your own entries, and — when productId is given — the sites AI answers already cite for that product's searches (source: "rivals"). With productId the list is sorted by relevance and each channel carries taskStatus (non-null when the product already has a task there). Free.

**citedByAi means AI answers cited that site for your searches. It does not mean the site will list your product** — reaching out to ask is what the task is for.

Query parameters

FieldTypeDescription
productIdstringSort by fit for this product, attach taskStatus, and include its AI-cited candidates.
kindstringChannel type.Values: directorylaunchai_directorycommunitycontentother
sourcestringseed = the library, user = added by you, rivals = AI-cited sites for this product.Values: seeduserrivals
pricingstringCost to submit.Values: freeconditionalpaidunknown
qstringSearch name, domain and topics.
hideSubmittedbooleanLeave out channels this product already has an open or submitted task for. Needs productId.
pageintegerDefaults to 1.
pageSizeintegerDefaults to 30.

Response200

FieldTypeDescription
successenumValues: true
dataChannelList
productIdnullablestring
itemsarray<Channel>
targetIdstringPass these as targetIds to POST /v1/campaigns.
namestring
urlstring
submitUrlstringThe submission form or posting page; for AI-cited sites, the page AI answers cite most.
kindenumValues: directorylaunchai_directorycommunitycontentother
sourceenumseed = the library, user = added by you, rivals = a site AI answers cite for this product's searches.Values: seeduserrivals
pricingTypeenumValues: freeconditionalpaidunknown
priceNotenullablestring
languagestringen, zh, multi, or a language code detected from the searches for AI-cited sites.
topicsarray<string>
requiresAccountboolean
requiresBacklinkboolean
reviewDaysnullableintegerTypical review time. The task reminds you to check back after it.
hasFormSpecbooleanThe channel's form fields are registered, so material is cut to its exact limits.
relevancenullableintegerFit score for the product named in the request. Only for sorting.
taskStatusnullablestringThe product's open or completed task for this channel, if any. null = nothing yet.
citedByAinullableobjectOnly for source: "rivals". AI answers cited this site for the listed searches. This is not a promise that the site will list your product — asking is what the task is for.
queriesintegerHow many distinct searches it was cited for.
samplesintegerHow many AI answer samples cited it.
searchesarray<string>
pagesarray<string>The cited pages, most-cited first. Empty for older samples that only recorded the domain.
totalinteger
pageinteger
pageSizeinteger
Possible errors
401Authentication failed

Example

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

Your products and how complete each profile is

Start here for the distribution pipeline; every other distribution endpoint takes a productId. completeness and missing come from the product profile in the dashboard: an empty field there is an empty field in every submission, and required gaps stop a task as blocked / missing_material until the profile is completed. Free.

Response200

FieldTypeDescription
successenumValues: true
dataProductList
productsarray<Product>
productIdstring
namestring
urlstring
domainstring
primaryLanguageenumValues: enzh
topicsarray<string>
completenessintegerProfile completeness, 0–100. Filled in the dashboard.
missingarray<string>Profile fields that are empty. Each one is a gap in every submission.
sitesarray<object>
siteIdstring
domainstring
syncedThroughnullablestring
Possible errors
401Authentication failed

Example

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

Tasks across campaigns

The submission record, newest activity first. Filter by product, campaign or a comma-separated list of statuses. byStatus counts every task in scope before the status filter is applied. Free.

Query parameters

FieldTypeDescription
productIdstringOnly this product's tasks.
campaignIdstring
statusstringComma-separated, e.g. prepared,in_progress.
pageintegerDefaults to 1.
pageSizeintegerDefaults to 30.

Response200

FieldTypeDescription
successenumValues: true
dataTaskList
itemsarray<Task>
taskIdstring
campaignIdstring
productIdstring
statusenumpublished is what you reported. verified is what QueryWin saw on the listing page (a link for directories and AI tool lists, a mention elsewhere). Keep them apart when you report.Values: plannedpreparedin_progressblockedsubmittedpublishedverifiedfailedskipped
blockedReasonnullableenumValues: logincaptchapaymentmissing_materialothernull
missingarray<string>Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
listingUrlnullablestring
markedByenumWho made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
hasGeneratedbooleanA channel-specific rewrite exists.
reviewDueAtnullablestringWhen to check back after submitting (submittedAt + the channel's review days).
submittedAtnullablestring
publishedAtnullablestring
verifiedAtnullablestring
nextarray<string>Statuses you may set from the current one via POST /v1/tasks/{id}/status.
targetobject
targetIdstring
namestring
urlstring
submitUrlstring
kindstring
sourceenumValues: seeduserrivals
languagestring
requiresBacklinkboolean
checknullableobjectThe re-check QueryWin runs on the listing after publication. Null until the task is published.
kindenumWhat is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
statenullableenumconfirmed = seen. unconfirmed = not found in one round of checks (status unchanged; check the URL). lost = it was there and is gone (task failed).Values: confirmedunconfirmedlostnull
checkedAtnullablestring
dueAtnullablestring
updatedAtstring
totalinteger
pageinteger
pageSizeinteger
byStatusobject
Possible errors
401Authentication failed

Example

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

One task with the material to submit

Every field the channel's form asks for, cut from the product profile to the channel's limits (source: "profile"), plus the channel-specific rewrite if one exists (source: "ai") and any edits made in the dashboard (source: "override"). source: "none" means the profile has nothing for that field — do not invent it. Never triggers a rewrite and never costs anything.

Response200

FieldTypeDescription
successenumValues: true
dataTaskDetailResult
taskTaskDetail
Possible errors
401Authentication failed
404task_not_found

Example

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

Rewrite the material for this channel (costs credits)

One AI pass that adapts the profile for this specific channel: a tighter directory description, a maker's first comment, a community post, a pitch to an editor, or — for AI-cited sites — a pitch plus a paragraph the page owner could add. Synchronous, a few seconds. Uses only facts in the profile; any part containing a link that is not in the profile is discarded and not charged.

Identical inputs return the previous version without charging (cached: true); force: true rewrites anyway and is charged. The profile material from GET /v1/tasks/{id} is usually enough for directories; rewrite when the channel wants a different voice.

Returns HTTP 200 with ok: false and failure: "engine_failed" when nothing survived validation. Insufficient credits is a real 402.

Request body

FieldTypeDescription
confirmSpendrequiredintegerAuthorization ceiling in credits, same semantics as for outlines and drafts. Read the price from GET /v1/usage (materials.pricePerTask). Required even while free allowance remains. (min 0)
forcebooleanRewrite even if nothing changed since the last version. Charged.

Response200

FieldTypeDescription
successenumValues: true
dataWriteMaterialsOutcome
okboolean
failureenumPresent when ok is false. Nothing is charged.Values: engine_failedengine_unavailable
cachedbooleanInputs were unchanged; the previous version was returned and nothing was charged.
freeUsedboolean
creditsSpentinteger
taskTaskDetail
Possible errors
400confirm_spend_required or confirm_spend_too_low (the body carries the current price)
401Authentication failed
402insufficient_credits — body carries requiredCredits, currentBalance, shortfall
403missing_scope_spend — this key was not granted the spend scope
404task_not_found
429rate_limited or daily_limit_reached (body carries resetAt)

Example

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

Report what happened with the submission

Record the outcome of a submission you made with your own accounts. QueryWin never submits anywhere itself. Set submitted once the form went in, then published with the listing URL (the entry or post itself, not the site home page) once it is live; QueryWin re-checks that page about 72 hours later for a link to your product (directories, AI tool lists) or a mention (everything else) and sets verified itself — you cannot set it.

blocked is "needs a person": pass reason (login, captcha, payment, missing_material, other). failed / skipped close the task; prepared puts it back. Transitions the state machine does not allow are **409 transition_not_allowed**; the task's next field lists what is allowed from its current status.

Request body

FieldTypeDescription
statusrequiredenumverified cannot be set; QueryWin sets it after re-checking the listing.Values: preparedin_progressblockedsubmittedpublishedfailedskipped
listingUrlstringRequired for published: the live entry or post, not the site home page. http/https only. Optional with submitted if you already know it.
notestring
reasonenumRequired for blocked.Values: logincaptchapaymentmissing_materialother

Response200

FieldTypeDescription
successenumValues: true
dataTaskDetailResult
taskTaskDetail
Possible errors
400invalid_status, listing_url_required (published needs listingUrl), invalid_listing_url, or reason_required (blocked needs reason)
401Authentication failed
403missing_scope_publish — this key was not granted the publish scope
404task_not_found
409transition_not_allowed — read the task; next lists the allowed statuses

Example

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 — response
{
  "success": true,
  "data": {
    "task": null
  }
}
QueryWin API — content and distribution pipelines for scripts and AI agents