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.
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.
Step
Endpoint
Cost
Find what has impressions but no page
GET /v1/topics
Free
Generate an evidence-backed outline
POST /v1/topics/outline
Credits
Turn it into a publishable draft
POST /v1/topics/article
Credits
Publish it to your own blog
your own CMS
—
Report the URL back
POST /v1/topics/published
Free
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.
Step
Endpoint
Cost
List your products and how complete each profile is
GET /v1/products
Free
List where to submit, sorted by fit — including the sites AI answers cite
GET /v1/channels?productId=
Free
Create a campaign from the channels you chose
POST /v1/campaigns
Free
Take the prepared material for a task
GET /v1/tasks/{id}
Free
Rewrite it for that channel
POST /v1/tasks/{id}/materials
Credits
Submit it
your own accounts
—
Report submitted, then published with the listing URL
POST /v1/tasks/{id}/status
Free
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.
Gate
What it stops
confirmSpend
A script that keeps charging after the price goes up.
Daily limit
A runaway loop burning through the balance overnight. Returns 429 with the reset time.
Input fingerprint
Charging 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.
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.
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.
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
Field
Type
Description
success
enum
Values: true
data
SiteList
└sites
array<Site>
└siteId
string
└domain
string
└gscProperty
string
The Search Console property, verbatim: sc-domain:example.com or https://example.com/.
└syncStatus
enum
Values: pendingsyncingdonefailed
└syncedThroughnullable
string
Search Console lags 2-3 days. Every metric on this site is "as of" this date — say so if you surface the numbers anywhere.
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
Field
Type
Description
success
enum
Values: true
data
Usage
└scopes
array<enum>
What this key may do. Read it once at startup rather than discovering your permissions by hitting a 403.Values: readpublishspend
└credits
object
└balance
integer
└outline
StepUsage
└available
boolean
False when the generation engine is not configured. Do not call the POST endpoint.
└pricePerOutline
integer
Credits per outline (present on outline only).
└pricePerArticle
integer
Credits per draft (present on article only).
└pricePerTask
integer
Credits per channel rewrite (present on materials only).
└freeRemaining
integer
Free generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
└daily
DailyLimit
A 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.
└used
integer
└limit
integer
└remaining
integer
└resetAt
string
└article
StepUsage
└available
boolean
False when the generation engine is not configured. Do not call the POST endpoint.
└pricePerOutline
integer
Credits per outline (present on outline only).
└pricePerArticle
integer
Credits per draft (present on article only).
└pricePerTask
integer
Credits per channel rewrite (present on materials only).
└freeRemaining
integer
Free generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
└daily
DailyLimit
A 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.
└used
integer
└limit
integer
└remaining
integer
└resetAt
string
└materials
StepUsage
└available
boolean
False when the generation engine is not configured. Do not call the POST endpoint.
└pricePerOutline
integer
Credits per outline (present on outline only).
└pricePerArticle
integer
Credits per draft (present on article only).
└pricePerTask
integer
Credits per channel rewrite (present on materials only).
└freeRemaining
integer
Free generations left on this account, counted by distinct topic (outlines, drafts) or distinct task (materials) — not by button presses. Free generations still require confirmSpend.
└daily
DailyLimit
A 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.
└used
integer
└limit
integer
└remaining
integer
└resetAt
string
└distribution
DistributionQuota
The plan's distribution limits and current usage. A null limit means unlimited.
└plan
string
└limits
object
└channelsnullable
integer
Distinct channels one product may have tasks for, cumulative.
└tasksPerMonthnullable
integer
Tasks that may be created per calendar month (UTC), across products.
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
Field
Type
Description
siteId
string
From GET /v1/sites. Defaults to the earliest connected site.
Response200
Field
Type
Description
success
enum
Values: true
data
TopicList
└siteIdnullable
string
└topics
array<Topic>
└key
string
The 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.
└title
string
The highest-impression search in the cluster, verbatim. This is not a generated headline — that comes with the outline.
└shape
enum
comparison means this cluster hit your competitor list. The article must contrast, not explain the competitor — otherwise you are writing content for them.Values: comparisonroundupguide
└intent
string
└members
array<TopicMember>
└text
string
The search, as typed.
└impressions
integer
└clicks
integer
└positionnullable
number
└landingUrlnullable
string
The page Search Console currently records for this search, if any.
└impressions
integer
Measured, from Search Console.
└clicks
integer
Measured, from Search Console.
└positionnullable
number
Measured: impression-weighted average position across the cluster.
└competitor
boolean
└score
number
└rank
integer
└upsideClicks
integer
An 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.
└status
enum
Values: newdismissedplannedpublished
└outlineAtnullable
string
└articleAtnullable
string
Do not infer this from outlineAt. Having an outline does not mean a draft exists — they are two separate paid steps.
└totalQueries
integer
How many distinct searches these topics cover in total.
Possible errors
401Authentication failed
404site_not_found — the siteId does not belong to this account
Never triggers generation and never costs anything. article is null if none exists yet.
Query parameters
Field
Type
Description
keyrequired
string
The cluster key from GET /v1/topics.
siteId
string
From GET /v1/sites. Defaults to the earliest connected site.
Response200
Field
Type
Description
success
enum
Values: true
data
ArticleResult
└articlenullable
Article
└title
string
└description
string
Meta description.
└markdown
string
The body to publish.
└jsonLd
string
Structured data for this article, already filled in. Valid JSON — put it inside a script tag of type application/ld+json on the published page.
└wordCount
integer
└warnings
array<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.
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
Field
Type
Description
keyrequired
string
The cluster key from GET /v1/topics.
siteId
string
Defaults to the earliest connected site.
confirmSpendrequired
integer
An 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
Field
Type
Description
success
enum
Values: true
data
ArticleOutcome
└ok
boolean
└failure
enum
Present only when ok is false. HTTP is still 200.Values: topic_not_foundno_outlineengine_unavailableengine_failedno_valid_article
└articlenullable
Article
└title
string
└description
string
Meta description.
└markdown
string
The body to publish.
└jsonLd
string
Structured data for this article, already filled in. Valid JSON — put it inside a script tag of type application/ld+json on the published page.
└wordCount
integer
└warnings
array<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.
False when a cached result was returned — nothing was charged.
└creditsSpent
integer
└freeUsed
boolean
└rejected
array<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)
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
Field
Type
Description
keyrequired
string
The cluster key from GET /v1/topics.
siteId
string
Defaults to the earliest connected site.
confirmSpendrequired
integer
An 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
Field
Type
Description
success
enum
Values: true
data
OutlineOutcome
└ok
boolean
└failure
enum
Present only when ok is false. HTTP is still 200 — this is an outcome, not an error.Values: topic_not_foundengine_unavailableengine_failedno_valid_outline
└outlinenullable
Outline
└title
string
└slug
string
└angle
string
The argument this page should make.
└sections
array<object>
└heading
string
└points
array<string>
└faq
array<object>
Questions the page must answer. These are the hooks AI answers quote.
└question
string
└answer
string
└schemaType
string
Which JSON-LD type suits this page.
└internalLinks
array<string>
Pages on your own site worth linking to. Chosen from real URLs, never invented.
└generated
boolean
False when a cached result was returned — nothing was charged.
└creditsSpent
integer
└freeUsed
boolean
└rejected
array<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)
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
Field
Type
Description
keyrequired
string
siteId
string
urlrequired
string
Where you published it. http/https only. Not fetched at this point.
Response200
Field
Type
Description
success
enum
Values: true
data
PublishedResult
└clusterKey
string
└status
enum
Values: published
└publishedUrl
string
└publishedAt
string
└indexnow
object
Bing, Yandex, Seznam, Naver. Not Google.
└pushed
boolean
└outcome
string
skipped usually means the key file is not verified yet.
└engines
string
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
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
Field
Type
Description
productIdrequired
string
namerequired
string
targetIdsrequired
array<string>
Channel ids from GET /v1/channels. Only the channels that were chosen.
endsAt
string
Optional deadline shown in the dashboard. Nothing closes automatically.
Response200
Field
Type
Description
success
enum
Values: true
data
CreateCampaignOutcome
└ok
boolean
└failure
enum
Present when ok is false.Values: no_valid_targets
└campaign
Campaign
└campaignId
string
└productId
string
└name
string
└status
enum
completed is computed: every task is published, verified, failed or skipped.Values: activecompletedarchived
└quota
integer
How many channels the campaign was created with.
└startsAt
string
└endsAtnullable
string
└counts
object
└total
integer
└submitted
integer
submitted + published + verified.
└live
integer
published + verified.
└blocked
integer
└done
integer
└byStatus
object
└createdAt
string
└tasks
array<Task>
└taskId
string
└campaignId
string
└productId
string
└status
enum
published 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
Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
└listingUrlnullable
string
└markedBy
enum
Who made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
└hasGenerated
boolean
A channel-specific rewrite exists.
└reviewDueAtnullable
string
When to check back after submitting (submittedAt + the channel's review days).
└submittedAtnullable
string
└publishedAtnullable
string
└verifiedAtnullable
string
└next
array<string>
Statuses you may set from the current one via POST /v1/tasks/{id}/status.
└target
object
└targetId
string
└name
string
└url
string
└submitUrl
string
└kind
string
└source
enum
Values: seeduserrivals
└language
string
└requiresBacklink
boolean
└checknullable
object
The re-check QueryWin runs on the listing after publication. Null until the task is published.
└kind
enum
What is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
└statenullable
enum
confirmed = 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
└checkedAtnullable
string
└dueAtnullable
string
└updatedAt
string
└skipped
array<object>
└targetId
string
└reason
enum
other_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
completed is computed: every task is published, verified, failed or skipped.Values: activecompletedarchived
└quota
integer
How many channels the campaign was created with.
└startsAt
string
└endsAtnullable
string
└counts
object
└total
integer
└submitted
integer
submitted + published + verified.
└live
integer
published + verified.
└blocked
integer
└done
integer
└byStatus
object
└createdAt
string
└tasks
array<Task>
└taskId
string
└campaignId
string
└productId
string
└status
enum
published 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
Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
└listingUrlnullable
string
└markedBy
enum
Who made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
└hasGenerated
boolean
A channel-specific rewrite exists.
└reviewDueAtnullable
string
When to check back after submitting (submittedAt + the channel's review days).
└submittedAtnullable
string
└publishedAtnullable
string
└verifiedAtnullable
string
└next
array<string>
Statuses you may set from the current one via POST /v1/tasks/{id}/status.
└target
object
└targetId
string
└name
string
└url
string
└submitUrl
string
└kind
string
└source
enum
Values: seeduserrivals
└language
string
└requiresBacklink
boolean
└checknullable
object
The re-check QueryWin runs on the listing after publication. Null until the task is published.
└kind
enum
What is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
└statenullable
enum
confirmed = 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
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
Field
Type
Description
productId
string
Sort by fit for this product, attach taskStatus, and include its AI-cited candidates.
seed = the library, user = added by you, rivals = a site AI answers cite for this product's searches.Values: seeduserrivals
└pricingType
enum
Values: freeconditionalpaidunknown
└priceNotenullable
string
└language
string
en, zh, multi, or a language code detected from the searches for AI-cited sites.
└topics
array<string>
└requiresAccount
boolean
└requiresBacklink
boolean
└reviewDaysnullable
integer
Typical review time. The task reminds you to check back after it.
└hasFormSpec
boolean
The channel's form fields are registered, so material is cut to its exact limits.
└relevancenullable
integer
Fit score for the product named in the request. Only for sorting.
└taskStatusnullable
string
The product's open or completed task for this channel, if any. null = nothing yet.
└citedByAinullable
object
Only 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.
└queries
integer
How many distinct searches it was cited for.
└samples
integer
How many AI answer samples cited it.
└searches
array<string>
└pages
array<string>
The cited pages, most-cited first. Empty for older samples that only recorded the domain.
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
Field
Type
Description
success
enum
Values: true
data
ProductList
└products
array<Product>
└productId
string
└name
string
└url
string
└domain
string
└primaryLanguage
enum
Values: enzh
└topics
array<string>
└completeness
integer
Profile completeness, 0–100. Filled in the dashboard.
└missing
array<string>
Profile fields that are empty. Each one is a gap in every submission.
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
Field
Type
Description
productId
string
Only this product's tasks.
campaignId
string
status
string
Comma-separated, e.g. prepared,in_progress.
page
integer
Defaults to 1.
pageSize
integer
Defaults to 30.
Response200
Field
Type
Description
success
enum
Values: true
data
TaskList
└items
array<Task>
└taskId
string
└campaignId
string
└productId
string
└status
enum
published 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
Required material the profile lacks. Complete the profile in the dashboard; the task re-prepares itself.
└listingUrlnullable
string
└markedBy
enum
Who made the last status change: a person (or this API), the browser extension, or QueryWin itself.Values: userdevicesystem
└hasGenerated
boolean
A channel-specific rewrite exists.
└reviewDueAtnullable
string
When to check back after submitting (submittedAt + the channel's review days).
└submittedAtnullable
string
└publishedAtnullable
string
└verifiedAtnullable
string
└next
array<string>
Statuses you may set from the current one via POST /v1/tasks/{id}/status.
└target
object
└targetId
string
└name
string
└url
string
└submitUrl
string
└kind
string
└source
enum
Values: seeduserrivals
└language
string
└requiresBacklink
boolean
└checknullable
object
The re-check QueryWin runs on the listing after publication. Null until the task is published.
└kind
enum
What is looked for: a link to the product (directories, AI tool lists) or a mention of the brand (everything else).Values: link_livemention_seen
└statenullable
enum
confirmed = 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
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.
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
Field
Type
Description
confirmSpendrequired
integer
Authorization 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)
force
boolean
Rewrite even if nothing changed since the last version. Charged.
Response200
Field
Type
Description
success
enum
Values: true
data
WriteMaterialsOutcome
└ok
boolean
└failure
enum
Present when ok is false. Nothing is charged.Values: engine_failedengine_unavailable
└cached
boolean
Inputs were unchanged; the previous version was returned and nothing was charged.
└freeUsed
boolean
└creditsSpent
integer
└task
TaskDetail
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)
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
Field
Type
Description
statusrequired
enum
verified cannot be set; QueryWin sets it after re-checking the listing.Values: preparedin_progressblockedsubmittedpublishedfailedskipped
listingUrl
string
Required for published: the live entry or post, not the site home page. http/https only. Optional with submitted if you already know it.
note
string
reason
enum
Required for blocked.Values: logincaptchapaymentmissing_materialother