Self-serve billing, limits and receipts: API and MCP
For agents and scripts that use Linkio with an account API key (acc_…). Managed accounts are not billed this way.
Every number on this page is checked against the code on every build; the auth headers, scopes and 402 bodies
are in API for account callers, the routes a key can call in HTTP routes.
GET /api/pricing/self-serve (public, no key needed) returns the numbers every page states: Linkio's
per-job fees (fees, the meter's own prices), platformMarkupPercent on provider cost when a job runs on
Linkio keys, and publisher (median / quartile listed guest-post prices, refreshed hourly). An agent that
quotes a price to its user should read it from here, never from page copy.
Creating a key (POST /api/account/api-keys, or the API keys page) needs only an active account; there
are no plans or subscriptions to hold first. Money is checked per job: a paid call is refused with a 402
until the account has prepaid balance, the free start (its OpenAI and DataForSEO keys healthy) or a card on file.
How you are billed
- Every job pays provider cost on Linkio's keys + 20% margin + a compute fee for its job type (whole cents). Prices live in a settings row and are read with no deploy.
- Work on your own provider keys (OpenAI, DataForSEO, Hunter, MillionVerifier, QuickEnrich, Instantly) pays the compute fee only, for every feature the key passed the capability probe for (
featureson the key; the list is under Own keys). - Every API or MCP write call (create, update, run) costs 1c per API or MCP write call, counted per UTC day and charged once a day as one ledger line under the feature
requests. Reads are free. An MCP tool call that writes is counted once, not again for the HTTP call it makes. - A failed job pays no fee. Work you start is charged to your account, whatever your plan; work Linkio's team starts for you is not.
- Prepaid credits are used first, then the card on file (credit line $10, then $100 after the first successful charge; charged at thresholds and at month end). After 2 successful charges, the most you can set as your monthly budget rises to $1,000. A new account with its OpenAI and DataForSEO keys live gets a $10 free start for 90 days.
- A step re-run after a reset is a new job and is charged again; a retry inside the same run is charged once.
Limits
- Monthly budget (hard or warn), per-feature caps, per-key daily and monthly caps, approvals for one-off overruns, alerts at 50/80/100% of the budget.
- A refused start answers
402with a machine-readable body. Onecodefield names the reason: budget (budget_exceeded,budget_paused), approval (needs_approval: the owner, or a key withbilling:writethroughusage_approvals_decide, decides; then repeat the call, optionally with the headerX-Linkio-Approval), card (card_required,payment_failed,credit_line). Each body carriesestimateCentsand the route or page that lifts the block; the three shapes are quoted in API for account callers. - Routes:
GET/PUT /api/account/usage-limits,POST /api/account/usage-limits/increase-requests,/approvals,/features. MCP:usage_limits_get,usage_limits_update,usage_limit_increase_request,usage_limit_increase_withdraw,usage_approvals_list,usage_approvals_decide,usage_feature_cap_set,usage_api_key_caps_set. - Reads are free but capped: 5000 reads per key per UTC day over both channels, since an MCP tool call reaches the API with your key. Over the cap a read answers
429withcodeDAILY_READ_CAP,cap,used,resetsAtand aRetry-Afterheader. Writes are priced instead and not counted here. - The MCP server rate-limits each key: 60 calls per minute with a burst of 20 (token bucket). Every answer carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-RatePerMin; over the limit it answers429withcodeRATE_LIMIT_EXCEEDEDand aRetry-Afterheader. The HTTP API has no rate limit of its own beyond the read cap.
Receipt
After any paid call you can read where you stand:
GET /api/account/usage/receipt(needsbilling:read):accountId,period,asOf,spentCents,heldCents,budgetCents,budgetRemainingCents,resetsAt,prepaidCents,unbilledCents,cardStatus,cardHeadroomCents,canStartPaidWork,feesCents.- MCP: every write tool result carries
usageReceiptwith the same fields. - API: every 2xx answer of a route marked "receipt" in HTTP routes carries the same receipt (without
accountIdandasOf) in the headerX-Linkio-Usage-Receipt(JSON) plusX-Linkio-Usage-Receipt-Url. The header appears when the key holdsbilling:read; it is read at most once every 15 seconds per account. A 4xx or 5xx answer carries no receipt. GET /api/account/usagegives the full report by day, feature and key.
Own keys
Features a saved key can serve once a check grants it, by provider (the key panel has one card per provider and lights the features of a checked key):
OpenAI: Pre-screen and Find Sites discovery (pre-screen), Article generation (article), Topic generation (topic-generation), Market context (market-context), Anchor optimization (anchor-optimization), AI Mentions (ai-mentions), Target page AI (target-page-ai), Exchange page picker (exchange-osg), Outreach polish (outreach-polish), Domain classification (prospect-classify), Page checks (topical fit) (prospect-enhance);
DataForSEO: Pre-screen and Find Sites discovery (pre-screen), Topic generation (topic-generation), AI Mentions (ai-mentions), Site metrics (site-metrics), SERP search (serp), Backlink data (backlink-data);
Hunter: Contact discovery (contact-discovery);
MillionVerifier: Email verification (email-verify);
Instantly: Email verification (email-verify) — the optional second opinion on catch-alls, on your key only;
QuickEnrich: Contact discovery (contact-discovery).
The exchange page picker is opportunity_osg_run; site metrics is dataforseo_site_metrics; backlink data is target_page_fetch_backlinks and client_excluded_domains_auto_import; SERP search is campaign_serp_search_run (the probe grants it on the same four checks as the other DataForSEO features; the SERP endpoint itself is not tested, so a login without the SERP product fails its first search and the run stops or falls back as the key rule below says).
Keyword extraction (client_llm_extract_keywords and friends) and the search-query brief run on the AI Mentions and target page AI grants. When the work ran on your key the answer says onOwnKey: true and the provider cost is not charged; the sync citation routes (citation_check_trigger, the one-off check) stay on Linkio keys because their per-check reserve is priced for them — use client_llm_check_async to run checks on your own keys.
- Add a key of any of the six providers in Settings → AI & Data Keys (one card each), or
POST /api/account/provider-keyswith{ "provider": "openai", "apiKey": "…" }or{ "provider": "dataforseo", "login": "…", "password": "…" }(MCPprovider_key_save; the answer is{ credential, replaced }and never echoes the key);GET /api/account/provider-keys(provider_keys_list) lists them with their checked features;GET …/{id}/probe-estimate(provider_key_probe_estimate) is the probe's own price before you run it (one small paid call for OpenAI and DataForSEO; free for the four contact providers); probe withPOST /api/account/provider-keys/{id}/probe. The probe reportsfeatures(the ids above) and, for OpenAI, builds your private copy of the Semantic SEO store. - Hunter, MillionVerifier, QuickEnrich and Instantly keys: their cards on that page, or
POST /api/account/provider-keyswith{ "provider": "hunter" | "millionverifier" | "quickenrich" | "instantly", "apiKey": "…" }(MCP:provider_key_save). Their probe is one call: HunterGET /v2/account, MillionVerifierGET /api/v3/creditsand InstantlyGET /api/v2/workspaces/currentread the account and are free; QuickEnrich is onedataset-searchpage for a domain that has no results (QuickEnrich bills only a page that has results). It grants the feature when the provider accepts the key and, where the answer carries a credit count (Hunter, MillionVerifier), credits are left. An Instantly key that Instantly knows but whose workspace it does not show (HTTP 402 or 403) is saved active with the feature, and its first verification decides. - Remove a key with
DELETE /api/account/provider-keys/{id}(provider_key_remove): from the next start on, that provider's calls run on Linkio's key at provider cost plus the margin again; nothing already run is re-priced. A key of another account answers404. - When your key fails mid-run (invalid, quota, rate limit): a step falls back to Linkio's key and credits when the balance allows, else it stops with a plain message that says where to fix the key: the key page (Settings → AI & Data Keys); a prospecting message also names
provider_key_saveandprovider_key_probe. Fix the key and resume. - A transient provider error is retried 3 times, waiting 30, 120, 300 seconds between attempts; if the step still fails, the article pauses, you get an email with a Resume link, and one automatic resume runs an hour later.
Price before the click
GET /api/account/usage/estimate?feature=<article|topic_generation|target_page_ai|ai_find_sites|website_search|site_metrics|serp_search|prospect_contact_discovery|prospect_email_verify|outreach_polish|prospect_classify|prospect_enhance>&count=N
(MCP: usage_estimate) answers what the meter would hold if you started count units now:
{ feature, count, estimateCents, perUnitCents, onOwnKey } (the two prospect features add ownKeys, per provider). It calls the same functions the start
routes call before they reserve, so the number is the hold. Read only. Pre-screen keeps its own
per-domain estimate (POST .../prescreen-estimate) and AI Mentions its client_llm_check_cost_estimate.
In the app the same line sits under every paid start button ("This will hold about $X; you have $Y prepaid · card on file"); on an account that cannot fund the job it says so and links to add money, a card or your own keys.
Every paid prospecting start asks before it holds: without confirm: true the answer is 400 CONFIRM_REQUIRED
carrying the quote twice — error (the API wording) and confirmMessage (the sentence a person is asked, ending
"Start?") — and nothing is held. The same body with confirm: true starts; dryRun: true answers the quote alone.
What you paid for
GET /api/account/usage/jobs?period=YYYY-MM&limit=N (MCP: usage_jobs) lists the month's
charges one per row, newest first: { id, at, feature, label, keySource: 'yours' | 'linkio' | 'unknown', providerCostCents, feeCents, chargedCents, adjustment, apiKeyId, apiKeyName, ref }. keySource: 'yours' means
your own provider key ran the job: the provider billed you directly and Linkio charged only
feeCents. The rows are the ones GET /api/account/usage sums, so the totals match the report.
The billing page shows the same table under Usage.
Website search price
A topic or filter search (POST /api/internal/websites/search, MCP website_search) costs 0.25c per
site returned after the first 100 sites of the UTC day, rounded up to whole cents per search (a
20-site page costs at most 5c). Domain lookups are free. The answer carries
billing: { sitesReturned, billableSites, freeUsed, chargedCents, shortfallCents, freeRemainingToday, perSiteCents }
(shortfallCents is the part the balance could not cover);
usage_jobs lists each paid search ("Site search: …"); GET /api/account/usage/estimate?feature=website_search&count=20
(MCP usage_estimate) is the price before the click. A search the account cannot fund answers 402 like
any other paid start; the monthly budget and the per-feature cap (prospect_data) apply. The route is
self-serve only: a managed account's key answers 403 with code ACCOUNT_TYPE_DENIED.
AI Find Sites price
A full AI Find Sites run (ai_find_sites_start, MCP usage_estimate feature ai_find_sites) on a self-serve
account pays a run fee of 5c per completed client loop, whatever keys it runs on. Its discovery calls run
on your own OpenAI key when that key is live for pre-screen (then they cost nothing from Linkio; OpenAI bills
you directly), and otherwise on Linkio's OpenAI key at provider cost plus 20%; the pre-screen of each found
site runs on your own keys when both are live (then only the pre-screen fee applies) and on Linkio's keys
otherwise. If your key fails mid-run, the rest of the run continues on Linkio's key when the account has
credits (checked before each further search; the run result's keyNotice says so in the status API and MCP)
and stops otherwise. The estimate before the click carries all of it; the charge is exact and lands in
usage_jobs.
Site metrics price
GET /api/internal/dataforseo/site-metrics?domains=a.com,b.com (MCP dataforseo_site_metrics) returns
domain rank, referring domains, spam score and organic traffic from DataForSEO for up to 200 domains
per call on an account key. It costs 15c per call plus 0.1c per domain, or 1.5c per domain with the
organic lookup (includeOrganic, on by default) — DataForSEO bills the link endpoints per call,
not per domain — rounded up to whole cents, charged after the call under prospect_data; the answer
carries billing: { domains, requestCents, perDomainCents, chargedCents, shortfallCents, onOwnKey }
and usage_jobs lists it ("Site metrics: N domains"). On an account whose own DataForSEO login is on
file for the site-metrics feature the calls run on that login and cost nothing.
GET /api/account/usage/estimate?feature=site_metrics&count=50 is the price before the call.
SERP search price
The campaign itself is free: POST /api/prospectfinder/campaigns with campaignType: 'serp_prospecting' and no
domains (MCP prospect_campaign_create; the brand's clientId, clientName, clientDomain come from
client_search) creates it and charges nothing; with dryRun: true it creates nothing and answers
{ dryRun: true, estimatedCostCents: 0, wouldCreate: { name, campaignType, clientId } }. The campaign needs the
prospect-pipeline:* scopes; everything inside it (the scan below, classification, types, page checks, contacts,
outreach, exports) needs campaign:*.
POST /api/prospectfinder/campaigns/{id}/keyword-search/{searchId}/run (MCP campaign_serp_search_run) scans
Google through DataForSEO for every query of a prepared search (campaign_serp_search_create), up to the
configured per-scan maximum (10,000 by default) queries per search, in the background. It is held before the
scan starts and settled when it ends: Linkio's current fee is 2.5c per completed query (serpSearchCents),
the live value being in GET /api/pricing/self-serve, on your own DataForSEO login and on Linkio's alike; on Linkio's login DataForSEO's price is added at 0.2c per query plus 0.01c per result (1.2c at 100
results) with the 20% margin, so a 100-query search at 100 results holds 394c. On an account whose own
DataForSEO login is on file for the serp feature the hold is the fee alone and the scan runs on that login.
The hold is a reservation (operation_type serp_search) under prospect_data; the settle is one ledger row
(serp_search_fee, "SERP search: N queries" in usage_jobs) for the fee of the completed queries plus the
cost with margin of the queries Linkio's login served. dryRun: true in the body (MCP confirm: false) answers
the quote and starts nothing; GET /api/account/usage/estimate?feature=serp_search&count=N is the same price
before the click; an account caller starts the scan with confirm: true in the body (MCP confirm: true),
else the answer is 400 CONFIRM_REQUIRED with the same quote and nothing is held. When your DataForSEO login
fails during the scan, the remaining queries continue on Linkio's login if the balance and the budget cover them
(the search then carries the notice in error_message), else the scan stops with a message and the completed
queries are kept and charged; a scan whose every query failed ends failed with the first error text.
Contact discovery and email verification price
POST /api/prospectfinder/campaigns with campaignType: 'contact_discovery', a domains list and
settings.workflowName: 'workflow4-hunter-findymail-verification' (the one recipe an account can run) finds and
verifies contacts for every domain with QuickEnrich and MillionVerifier, in the
background (with your own Hunter key on file, Hunter searches run on that key too, fee only — Linkio runs no Hunter
search of its own; a domain QuickEnrich has nothing for ends with no contact and pays no fee); POST /api/prospectfinder/campaigns/{id}/verify-contacts with contactIds verifies contacts a
campaign found. Both are held before the run starts and settled when it ends: Linkio's fee is 0.06c per domain
processed (prospectDomainCents) and 0.2c per contact verified (emailVerifyCents), the live values being
in GET /api/pricing/self-serve, plus the providers' price (QuickEnrich per page,
MillionVerifier per check) with the 20% margin. Own keys: a Hunter or QuickEnrich key
(contact-discovery) or a MillionVerifier key (email-verify) that is on file and passed its check
serves that provider's calls, and on your own key only the fee applies for that provider: the
quote leaves its price out (ownKeys in the quote names the provider; onOwnKey is true only when every
provider of the run is on your keys, and the hold is then the fee alone) and the settle records its cost as
customerKeyCost and does not charge it.
A provider you hold no checked key for runs on Linkio's key. The fee per domain or per contact applies whichever key served
the unit. When your key fails during a run (rejected, no credits left, rate limit), that provider continues on
Linkio's key if the balance and the budget cover every domain of the campaign at the price on Linkio's keys
(discovery) or the contacts not yet checked (verify). If they do not, each remaining domain of the campaign stops
with a message that names the key page, provider_key_save and provider_key_probe, or the verify run stops and keeps what it
checked. What your key already answered inside one step is kept: the QuickEnrich pages it returned are not asked
again on Linkio's key (only the pages after them are), and an Instantly check your key accepted is not sent again.
A catch-all answer stays risky (catch_all): Linkio has no Instantly account, so the address is not re-checked
and not retried on a later pass — unless your own Instantly key is on file and checked, in which case Instantly
re-checks it on your key for the fee alone, and if that key fails the catch-alls are kept, not re-checked on
Linkio's key.
A check the provider did not make (an error answer) leaves the contact
verification_failed, not invalid, and carries no fee. The hold is a reservation (operation_type
prospect_contact_discovery or prospect_email_verify) under prospect_data; the settle is one ledger row
(prospect_contact_discovery_fee, "Contact discovery: N domains", or prospect_email_verify_fee, "Email
verification: N contacts", in usage_jobs) for the fee of the units the run completed plus the cost with margin
of what the providers billed; a domain that carried no provider call pays nothing. dryRun: true in the body
answers the quote and starts nothing; GET /api/account/usage/estimate?feature=prospect_contact_discovery&count=N
(domains) and ?feature=prospect_email_verify&count=N (contacts) are the same price before the click; an account
starts a contact-discovery campaign with confirm: true in the body, else the answer is 400 CONFIRM_REQUIRED
with the same quote and nothing is held. Both routes are open to every account, whatever its billing plan: the
account that starts the run is charged for it; any other discovery recipe answers 403 RECIPE_NOT_AVAILABLE.
Discovery from a campaign type's assigned domains (campaign_contacts_discover, POST /api/prospectfinder/campaigns/{id}/contacts with { action: 'discover', campaignTypeId, mode: 'new' | 'empty' };
new = domains never tried, empty = domains tried before with nothing found) is the same contact-discovery
price on the one account recipe: dryRun: true answers the quote for exactly the domains the mode names, no
confirm: true answers 400 CONFIRM_REQUIRED, 402 when the hold cannot be funded, and a start creates a
child campaign of yours whose spend rolls into the parent's account_spend_cents. Deleting contacts
(campaign_contacts_delete, DELETE …/contacts with contactIds) is free.
Importing a contacts CSV into your own campaign (campaign_contacts_import_csv,
POST /api/prospectfinder/campaigns/{id}/import-apollo-csv) is free: it calls no provider, holds nothing and starts
nothing. Only rows whose company domain is one of the campaign's domains are stored, as unverified contacts; the
other rows are dropped and counted in the answer. One import takes at most 5,000 rows. The paid next steps are the
two above: verifying the imported contacts (campaign_contacts_verify, with the contactIds the import answers)
at the email-verification price, and a contact-discovery campaign for domains the file did not cover.
Outreach polish price
The AI polish reviews the outreach drafts of a prospecting campaign round before launch (approve, edit, or mark
for a person). It runs OpenAI (on your own key when the key is checked for it, else Linkio's) and is held before the polish starts and settled when it ends:
Linkio's fee is 0.5c per draft reviewed (outreachPolishDraftCents), the live value being in
GET /api/pricing/self-serve, plus OpenAI's price for the calls the run made with the 20% margin. A draft
that got a verdict carries the fee; a draft whose call failed carries the cost of that call and no fee.
With your own OpenAI key on file and checked for Outreach polish (the key's card lists it), the polish calls run on your key and only the fee is charged
(onOwnKey: true on the quote and the receipt; OpenAI bills you directly). If your key fails during a run (rejected, out of quota,
or it cannot use the model) and your credits cover the rest at the price on Linkio's key, the rest runs on Linkio's key at that
price — provider cost with margin plus the fee — and the run's notice says so; otherwise the run stops with the budget notice and what
already ran is charged. A run Linkio's team starts is never on your key.
Three starts are metered for an account signed in to the site, whatever its billing plan (these routes take a sign-in, not an account API key):
- composing a round,
POST /api/prospectfinder/campaigns/{id}/outreach/generate-drafts. Composing costs nothing; the quote is for the polish the compose starts when it finishes, on up to contacts x sequence steps drafts. - re-polishing a round,
POST /api/prospectfinder/campaigns/{id}/outreach/rounds/{roundId}/polish. Withforce: trueevery draft not yet sent is polished again and charged again. - re-polishing one draft,
POST /api/prospectfinder/campaigns/{id}/outreach/drafts/{draftId}/re-polish. Every call is a new paid polish of that draft.
Each answers the quote and starts nothing on dryRun: true; answers 400 CONFIRM_REQUIRED with the same quote
until the body carries confirm: true; and answers 402 when the hold is refused, with the round and its drafts
unchanged. A paid start while a polish of the same round is running answers 409 POLISH_ALREADY_RUNNING. The
hold is a reservation (operation_type outreach_polish) under prospect_data; the settle is one ledger row
(outreach_polish_fee, "Outreach polish: N drafts" in usage_jobs) for the fee of the drafts that got a verdict
plus the cost with margin of the OpenAI calls. The hold bounds the run: when the OpenAI cost of a polish reaches
the amount held, it starts no further draft (calls already running finish and are charged). The round is then not
marked polished and cannot launch; the same route without force polishes only the drafts that are left, with its
own quote. One paid run covers at most 1,500 drafts: a larger round or compose answers 400 DRAFT_LIMIT and is
done in parts, as smaller rounds. Cancelling a round while its polish runs stops the polish before its next draft,
and what it used is charged. A run that ends without its settle is settled within about an hour from the calls it
recorded. GET /api/account/usage/estimate?feature=outreach_polish&count=N
(drafts) is the same price before the click.
The paid polish is for every account that starts it: an account in managed mode composes, holds and pays per draft exactly as above. A polish Linkio's team starts for you is not charged.
Domain classification price
The three-layer classification of a prospecting campaign's domains (domain type, page type, relevance to your brand —
campaign_classify_start, POST /api/prospectfinder/campaigns/{id}/classify) runs gpt-4o-mini (on your own OpenAI key when the key is checked for it, else Linkio's) and is
held before the run starts and settled when it ends: Linkio's fee is 0.06c per domain classified
(prospectClassifyCents), the live value being in GET /api/pricing/self-serve, plus OpenAI's price for the calls
the run made with the 20% margin. A domain that reached a relevance verdict carries the fee; a domain the model
omitted after the retry (failed) carries the cost of its calls and no fee.
With your own OpenAI key on file and checked for Domain classification (the key's card lists it), the classification calls run on your key and only the fee is charged
(onOwnKey: true on the quote and the receipt; OpenAI bills you directly). If your key fails during a run (rejected, out of quota,
or it cannot use the model) and your credits cover the rest at the price on Linkio's key, the rest runs on Linkio's key at that
price — provider cost with margin plus the fee — and the run's notice says so; otherwise the run stops with the budget notice and what
already ran is charged. A run Linkio's team starts is never on your key.
The quote counts the campaign's domains with passed metrics (or its distinct SERP domains when none were pulled yet)
at 0.035c of expected OpenAI cost per domain plus the fee, with margin; the domain-type and page-type layers are
cached across campaigns, so the real cost is often lower and the settle is exact. dryRun: true answers the quote
and starts nothing; without confirm: true the answer is 400 CONFIRM_REQUIRED with the same quote and nothing is
held; 402 when the hold cannot be funded, with the campaign untouched; 409 CLASSIFY_ALREADY_RUNNING while a
classification of the campaign is in flight. One paid run covers at most 20,000 domains: a larger campaign answers
400 DOMAIN_LIMIT. The hold is a reservation (operation_type prospect_classify) under prospect_data; the
settle is one ledger row (prospect_classify_fee, "Domain classification: N domains" in usage_jobs). The hold
bounds the run: when the OpenAI cost reaches the amount held, no further batch is started and the run ends partial;
the domains left keep their pending status, and a second start classifies only the domains not yet classified, with
its own quote. A run that ends without its settle is settled within about an hour from the calls it recorded, at
their cost with margin and no fee. Your run does not start the Ahrefs metrics check that a staff-started
classification fires. Resetting a classification (campaign_classify_reset, DELETE on the same route) costs
nothing and is refused (409) while a classification is still running.
GET /api/account/usage/estimate?feature=prospect_classify&count=N (domains) is the same price before the click.
When a paid classification ends, the campaign's domains are assigned to Linkio's predefined campaign types for you
— the types are ranked on the campaign (the paid placement types first) and the assignment runs; nothing of this
is charged. GET …/classify (campaign_classify_get) then carries assignments (every ranked type with its live
count) and autoAssign (what the run recorded); GET /api/prospectfinder/campaigns/{id} (prospect_campaign_get)
carries campaign.type_assignments. campaign_type_list (GET …/campaign-types) shows the predefined types with
their match and assigned counts: a shared type's rules and description are Linkio's and are not in your answer, and
its settings carry want and offer_type only; a type of your own brand comes whole. campaign_type_assign
re-runs one type's assignment (free); the ranking order and the domain filters are Linkio's.
Page checks (enhancements) price
The page checks of a campaign type (campaign_enhancements_run, POST /api/prospectfinder/campaigns/{id}/enhancements
with action: "kickoff") are topical fit — an AI verdict per page, on your own OpenAI key when the key is checked for it, else Linkio's — and, when the type enables
it, content validation, which fetches the page through Scrappey on Linkio's key. A run is
held before it starts and settled when it ends: Linkio's fee is 0.3c per page checked
(prospectEnhanceUrlCents), charged once per page per run for a page that got a topical-fit verdict or was
fetched, the live value being in GET /api/pricing/self-serve, plus the providers' price for the calls the run made
with the 20% margin: OpenAI's price for the topical-fit calls, and 0.1c for each Scrappey fetch that returned a
page (a fetch that returned no page costs nothing).
With your own OpenAI key on file and checked for Page checks (topical fit) (the key's card lists it), the topical-fit calls run on your key and only the fee is charged
(onOwnKey: true on the quote and the receipt; OpenAI bills you directly). If your key fails during a run (rejected, out of quota,
or it cannot use the model) and your credits cover the rest at the price on Linkio's key, the rest runs on Linkio's key at that
price — provider cost with margin plus the fee — and the run's notice says so; otherwise the run stops with the budget notice and what
already ran is charged. A run Linkio's team starts is never on your key. The Scrappey fetches of content validation stay on Linkio's key (no own-key slot), so the quote keeps their price.
The quote counts the pages the run would check at 0.01c of expected OpenAI cost per page when topical fit is on,
plus 0.1c for every page content validation could fetch (the pages it has no result for yet), plus the fee, with
margin. It is an upper bound — topical fit usually removes pages before the fetch — and the settle is exact.
dryRun: true answers the quote and starts nothing; without confirm: true the answer is 400 CONFIRM_REQUIRED
with the same quote and nothing is held; 402 when the hold cannot be funded, with nothing started; 409
ENHANCE_ALREADY_RUNNING while a run of the same campaign type is in flight; 400 NO_PAGES when there is nothing
to check. One paid run covers at most 2,000 pages: a larger campaign type answers 400 URL_LIMIT. An account's run
always runs in the background, whatever mode the request names. The hold is a reservation (operation_type
prospect_enhance) under prospect_data; the settle is one ledger row (prospect_enhance_fee, "Page checks: N
pages" in usage_jobs). The hold bounds the run: when the providers' cost reaches the amount held, the run
stopped at the amount held; start again to check the remaining pages. Such a run shows budget_stopped: true with
that sentence in the run list; it is not a provider failure, and the pages it checked keep their verdicts. A second
start has its own quote: topical fit checks every page of the type again, content validation fetches only the pages
it has no result for. A run that ends without its settle is settled within about an hour from the calls it
recorded, at their cost with margin and no fee. Overriding a verdict (campaign_enhancements_override), the
preflight and the lists cost nothing. A run Linkio's team starts for you is not charged.
GET /api/account/usage/estimate?feature=prospect_enhance&count=N (pages) is the same price before the click.
The positioning text the checks judge against is yours, per campaign: POST /api/prospectfinder/campaigns/{id}/enhancements/config
with { typeId, positioning_intent } (campaign_enhancements_config_update), free. Before a paid classification has
ranked the type it answers 409 rankings_row_not_found ("Run classification first — the type is ranked for you when
it finishes"); a body that also carries enhancements (the check rules) for a shared type answers 422
cannot_configure_shared_type — the rules of Linkio's types are not yours to change, a type of your own brand takes
them. campaign_enhancements_list (GET …/enhancements?list=1, or ?typeId=) reads the positioning text you set
and, for a shared type, no rules.
Exports (free)
Six GET routes under /api/prospectfinder/campaigns/{id}/export/ — serp-results, classification, type-matches,
rejected-domains, outreach-drafts, outreach-template (MCP campaign_export_*, campaign:read) — answer
?format=csv (the default, a file) or ?format=json ({ name, filename, headers, rowCount, truncated, csv }), at
most 10,000 rows each (truncated: true past that). Read only; nothing is held or charged.