Linkio MCP tools for account callers
Generated by node scripts/mcp-catalog.cjs from the tool registrations; do not edit by hand.
273 tools are visible to a self-serve account key or an account sign-in (a managed account sees the read-only subset).
Server: https://mcp.linkio.com/mcp, header Authorization: Bearer <your key>. Every write tool answers with usageReceipt; paid tools say what they hold under usage_estimate.
Inputs are read from each tool's live schema: name: type (required) = default; enum(a, b) lists the allowed values; array<T> and object { key?: T } give the shape one level deep (? marks an optional key).
Account and money
account_billing_mode(client-config:write): Reads (no mode) or changes (mode 'managed' | 'self_serve') the account's billing and experience mode, with the self-serve subscription state. Changing it is a real account setting. Needs client-config:read to read, client-config:write to change. Inputs: mode: enum(managed, self_serve).account_billing_summary(billing:read): One financial picture of your account: recent payments, prepaid purchases, open invoices, balance. Inputs: accountId: string (required).account_me(client-config:read): Your account: id, email, company, website and billing mode. The id is the accountId every account-scoped tool takes.account_subscription_current(billing:read): Any Stripe-billed subscription still on your account: status (active, trialing, past due), its plan, the brands it covers, the current billing period and that period's usage. Pay-per-job accounts get an empty list; account_subscription_usage is the month's meter. Pass clientId to look at one brand. Inputs: clientId: string.account_subscription_usage(billing:read): Your usage meter for the month: budget, spent so far, remaining, whether paid work is blocked. (Plans and link limits no longer exist; you pay per job.)billing_history(billing:read): Your payments, invoices and refunds, newest first, paged. Inputs: filter: string; startDate: string; endDate: string; page: integer; pageSize: integer.credits_balance(billing:read): Your prepaid balance in cents, with an optional breakdown by brand pool. Inputs: accountId: string; groupByPool: boolean.credits_by_month(orders:read): Credits by delivery month (budget, delivered, pending), optionally for one client (brand). Inputs: clientId: string; months: integer.credits_convert(billing:write): Turns placement (link-building) credit into general credit, 1:1, one way — general credit funds AI jobs as well as orders. cents is a whole number of cents up to the convertibleCents named in a 402 or in credits_summary. Self-serve accounts only. Needs billing:write. Inputs: cents: integer (required).credits_create_purchase_session(credits:purchase): A one-time Stripe Checkout link to add money to the account, for the owner to open in a browser; card details stay with Stripe and are not part of the API. packageId is an id from credits_packages_list; without clientId the money funds the general pool. Inputs: packageId: string (required); clientId: string.credits_packages_list(billing:read): The prepaid amounts you can buy, cheapest first, with their ids for credits_create_purchase_session.credits_summary(orders:read): Your prepaid balance now, the balance after pending deductions, and what this month spent.credits_transactions(orders:read): Your prepaid ledger: top-ups, charges, refunds, holds, paged. balance_after is the prepaid balance (before holds) right after the row when balance_definition is 'prepaid_v1'; on older rows it is null (their earlier figures were written under other definitions and were moved out of the column). type is earned | spent | expired. Inputs: filter: string; startDate: string; endDate: string; limit: integer; offset: integer.credits_workflow_usage(orders:read): What each article cost, newest first. Inputs: limit: integer; offset: integer.provider_key_probe(client-config:write): Run the capability probe of one provider key: a few small real calls on YOUR key (a few cents, see provider_key_probe_estimate) that confirm the key works for the features Linkio uses. Inputs: keyId: string (required).provider_key_probe_estimate(client-config:read): What the capability probe of one provider key would cost on YOUR key (ids from provider_keys_list). Inputs: keyId: string (required).provider_key_remove(client-config:write): Remove one provider key from your account (ids from provider_keys_list). Inputs: keyId: string (required).provider_key_save(client-config:write): Add or replace a provider key for your account ('openai' takes apiKey; 'dataforseo' takes login + password). The secret is sent once and never returned; the key is unverified until provider_key_probe has run on it. Inputs: provider: enum(openai, dataforseo) (required); apiKey: string; login: string; password: string.provider_keys_list(client-config:read): The OpenAI and DataForSEO keys on file for your account (masked, never the secret) and whether each is live. With both on file the account gets the $10 free start and pays the providers directly.usage_api_key_caps_set(billing:write): Set or clear the daily and monthly spending caps of ONE account API key (ids from usage_limits_get.apiKeys). Inputs: apiKeyId: string (required); dailyCapCents: integer | null; monthlyCapCents: integer | null; askOverCents: integer | null.usage_approvals_decide(billing:write): Approve or deny one job waiting under "ask me before any job over $X", by approval id (from a NEEDS APPROVAL answer or usage_approvals_list). The decision is the account owner's; this tool is marked destructive, so the client asks the person at the keyboard before the call. Result: the approval with its new status (approved or denied) and who decided. 404 unknown or another account's, 409 already decided, 410 expired. An approved job runs once when the refused call is repeated. Inputs: approvalId: string (required); decision: enum(approve, deny) (required); accountId: string.usage_approvals_list(billing:read): Jobs waiting for the owner's yes under "ask me before any job over $X", plus the last decided ones, by approval id (the id a NEEDS APPROVAL answer names). The decision is the owner's: usage_approvals_decide, the billing page or the activity page. Inputs: accountId: string.usage_billing_card_get(billing:read): The card on file (brand, last 4, status), how much usage is not yet charged to it, how much more can run before the next charge, and whether paid work is paused after a failed payment. The owner adds or removes a card on the billing page. Inputs: accountId: string.usage_billing_card_remove(billing:write): Remove the card on file. Refused while usage is still unbilled (usage_billing_pay_now clears it). Afterwards paid work runs on the prepaid balance only.usage_billing_card_setup_link(billing:write): A Stripe-hosted page where the account owner enters a card; nothing is charged there and no card details pass through the API. Valid 24 hours; the result shows in usage_billing_card_get. Inputs: returnUrl: string.usage_billing_charge_sync(billing:write): Fetch one card charge from Stripe again and settle it now (for a charge that shows pending or was confirmed by the bank). Inputs: chargeId: string (required).usage_billing_charges_list(billing:read): The card charges of the account, newest first: amount, reason, status, failure code, next retry. Inputs: accountId: string; limit: integer.usage_billing_ledger(billing:read): The card ledger: usage accrued to the card, charges, the free start, adjustments; optionally one month. The sum is the unbilled balance. Inputs: accountId: string; period: string; limit: integer.usage_billing_pay_now(billing:write): Charge the whole unbilled usage balance to the card now (automatic charges run at $5 the first time, then every $50, and on the 1st of each month). Clears the balance that blocks card removal and resumes paid work paused by a failed payment once the card was replaced.usage_estimate(billing:read): The price before the click: what starting N units of a feature would hold right now — 'article' (one article), 'topic_generation' (one topic session), 'target_page_ai' (one target page), 'ai_find_sites' (count = the site goal), 'website_search' (count = sites the search may return). Same math as the start itself. Nothing is charged. Inputs: accountId: string; feature: enum(article, topic_generation, target_page_ai, ai_find_sites, website_search, site_metrics) (required); count: integer.usage_feature_cap_set(billing:write): Set or remove a monthly spending cap for ONE feature of your account (prescreen, ai_find_sites, workflow_steps, topic_generation, target_page_ai, client_operations, ai_mentions, prospect_data, seo_analysis, requests, other). Inputs: feature: string (required); monthlyCapCents: integer | null (required).usage_jobs(billing:read): What you paid for in one month, one row per charge, newest first: when, which feature, what it was, whether your own provider key or Linkio's ran it, provider cost, Linkio's fee and the amount charged. Amounts in cents; default the current month. Inputs: accountId: string; period: string; limit: integer.usage_limit_increase_request(billing:write): A request to Linkio to raise the monthly budget above the ceiling the account can set on its own. One open request at a time; the decision appears in usage_limits_get. Inputs: requestedCents: integer (required); reason: string.usage_limit_increase_withdraw(billing:write): Withdraw your pending request for a higher spending ceiling (id from usage_limits_get.pendingIncreaseRequest). Inputs: requestId: string (required).usage_limits_get(billing:read): Your spending limits: the monthly budget, spent and held so far this month, per-feature caps, the "ask me before any job over $X" threshold, and whether new paid work is blocked. Inputs: accountId: string; period: string.usage_limits_update(billing:write): Change the spending limits of your account: monthlyBudgetCents (the most Linkio may bill in a UTC month, default 5000 = $50, up to the account's ceiling, $250 by default; above it the call fails with OVER LIMIT CEILING and usage_limit_increase_request is the path to a higher ceiling), budgetMode 'hard' (new paid work stops at the budget) or 'warn' (alerts only; available after 2 successful card payments), alertThresholds (up to 5 percentages, e.g. [50, 80, 100]). Only the fields sent change. Returns the new status. Needs billing:write. Inputs: monthlyBudgetCents: integer; budgetMode: enum(hard, warn); alertThresholds: array<integer>; askOverCents: integer | null; accountId: string.usage_receipt(billing:read): Where the account stands right now: spent and held this month, budget left, prepaid balance, card status, and canStartPaidWork. Free. The same object comes back as usageReceipt on every write result. Inputs: accountId: string.usage_report(billing:read): Your month in totals: spend by day, by feature and by API key, request counts, most-used tools. Cents. Default the current month. Inputs: accountId: string; period: string.
Brands and pages
client_brand_aliases_get(client-config:read): The brand name variants the AI Mentions checks look for in assistant answers: clientName (always included), brandAliases (spacing variants, the domain, accented forms, abbreviations) and autoIncluded. Inputs: clientId: string (required).client_brand_aliases_set(client-config:write): Replace a brand's alias list as a whole with up to 20 aliases (each trimmed, up to 100 characters); the brand's own name is always included and is not an alias. Aliases left out of the list are gone. Inputs: clientId: string (required); aliases: array<string> (required).client_config_readiness(client-config:read): Which of a brand's four settings are set, with a summary of each: topicPreference (the keyword intent the research favours), marketContext (industry, audience, buyer personas, use cases, positioning; 4,000-5,000 characters is the working size), outlinePreferences (competitor exclusions, research instructions) and factCheckInstructions. Takes clientId or orderId. Free. Inputs: clientId: string; orderId: string; scope: enum(all, article-gen, prospecting, pathway); campaignId: string.client_config_target_pages(client-config:read): A brand's target pages — the pages on your site that links should point to — with their prospecting keywords, anchor keywords and descriptions. allProspectingKeywords is the list to feed into website_search. Inputs: clientId: string; orderId: string; status: string.client_config_update(client-config:write): Change one brand setting: topic preference (commercial, informational, mixed...), market context (who buys and why; the text every article and topic session reads), outline preferences, fact-check instructions, or publisher pre-approval. One setting per call. Inputs: clientId: string (required); setting: enum(topicPreference, marketContext, outlinePreferences, factCheckInstructions, engagementStatus, remediationSettings, managedBy) (required); data: object (required).client_create(client-config:write): Add a brand to your account: name, website, optional first target pages. Returns the brand id. Inputs: accountId: string; name: string (required); website: string (required); clientType: enum(prospect, client); description: string; targetPages: array<string>.client_excluded_domains_add(client-config:write): Add domains to a brand's exclusion list by hand (a backlink export, known links). URLs are accepted and reduced to their domain; domains are normalised; duplicates are skipped. Free. Inputs: clientId: string (required); domains: array<string> (required); source: enum(manual, dataforseo).client_excluded_domains_auto_import(client-config:write): Import the sites that already link to this brand (from DataForSEO) into its exclusion list, so searches do not offer them again. Incremental after the first run. Metered. Inputs: clientId: string (required); minDR: number; limit: number; forceFullImport: boolean.client_excluded_domains_list(client-config:read): A brand's exclusion list, the domains site searches leave out: totalCount, sourceCounts (manual, dataforseo) and hasExclusions. With hasExclusions false a site search can return sites that already link to the brand. Inputs: clientId: string (required); includeDomains: boolean.client_fact_check_instructions_get(client-config:read): A brand's fact-check instructions: the claims to verify, the sources to cite and how to word caveats, read by the fact-check pass on every article. Returns the version, whether it is enabled, the text and when it last changed. client_config_update writes them. Inputs: clientId: string (required).client_fact_check_instructions_update(client-config:write): Update the client's fact-check instructions. Inputs: clientId: string (required); enabled: boolean (required); instructions: string (required).client_market_context_get(client-config:read): A brand's market context: who buys, the competitive landscape, how the brand positions itself. Every article and topic session reads it. Returns the version, whether it is enabled, the text and when it last changed. client_config_update writes it. Inputs: clientId: string (required).client_market_context_update(client-config:write): Update the client's market context. Inputs: clientId: string (required); enabled: boolean (required); context: string (required).client_outline_preferences_get(client-config:read): The brand's article outline preferences: whether customisation is on, competitors to leave out, custom research instructions. Inputs: clientId: string (required).client_outline_preferences_update(client-config:write): Change the brand's outline preferences; only the fields you pass change. excludeCompetitors and customInstructions are what the writer reads. Inputs: clientId: string (required); enabled: boolean (required); excludeCompetitors: array<string>; customInstructions: string; outlineInstructions: string.client_search(client-config:read): List or find your brands (name, website, how many target pages and orders each has, what setup is still missing). The id returned is the clientId of the brand-specific tools. Inputs: search: string; accountId: string; accountName: string; hasTargetPages: boolean; hasOrders: boolean; engagementStatus: string; limit: number; offset: number.target_page_fetch_backlinks(client-config:write): Fetch a target page's existing backlinks from DataForSEO and store them, so anchor and link planning can see what the page already has. Metered (on your own DataForSEO key: paid to the provider). Inputs: targetPageId: string (required); maxBacklinks: number.target_pages_add(client-config:write): Add target pages (URLs on your own site) to a brand. Keywords and a description are generated for each new page unless autoGenerate is false; that generation holds the 'target_page_ai' estimate per page, and the response says whether it started or why it was refused. Inputs: clientId: string (required); urls: array<string> (required); autoGenerate: boolean.target_pages_bulk_generate(client-config:write): Generate prospecting keywords, anchor keywords (the seed phrases the anchor text suggestions are built from, not anchor text itself) and a description for the given target pages, in parallel in the background; returns a batchId at once and the pages fill in over 30-60 seconds (client_config_target_pages shows them). Metered, about 15c per page. generateKeywords and generateDescription (both default true) choose what is generated. Inputs: targetPageIds: array<string> (required); generateKeywords: boolean; generateDescription: boolean.target_pages_update(client-config:write): Set a target page's prospecting keywords and anchor keywords by hand, free of charge (keywords the account already has, or corrections to the generated ones). Only the fields sent change. Inputs: targetPageId: string (required); keywords: string; anchorKeywords: string.target_pages_update_status(client-config:write): Set the status of one or more target pages of a brand: 'active' (available for searches and topics), 'inactive' (hidden, kept) or 'completed' (reached its link goal). Returns the brand's full target page list. Inputs: clientId: string (required); pageIds: array<string> (required); status: enum(active, inactive, completed) (required).target_pages_update_url(client-config:write): Change a target page's stored URL (a trailing slash, a typo, a moved page). Keywords and description are kept as they are; order items pointing at the old URL are unchanged (line_items_update changes those). Returns the previous and the new URL. Inputs: targetPageId: string (required); url: string (required).
Finding sites
ai_find_sites_assign_to_items(orders:write): Fill the domainless items of an order from a finished site search's qualified domains (projectId from ai_find_sites_status, clientId, orderId), matching each domain's suggested target page URL to the item's target page URL (protocol, www and trailing slash ignored). Items with no match stay empty; domains with no matching item are skipped. (ai_find_sites_create_order creates new items rather than filling existing ones.) Inputs: projectId: string (required); clientId: string (required); orderId: string (required).ai_find_sites_cancel(orders:write): Stop a running site search and its child tasks. Results found so far are kept (an order is created from them when the run had one to create) and the unused part of the hold is released. Inputs: historyRunId: string (required).ai_find_sites_create_order(orders:write): Create an order from a finished site search's results when none was created automatically (the run shows completed with no orderId). Needs the run's projectId and the brand id. Inputs: projectId: string (required); clientId: string (required).ai_find_sites_history(orders:read): Your past site search runs with their ids, status and outcome: what has already been searched for each brand. Inputs: accountId: string; source: string; limit: number.ai_find_sites_start(orders:write): The automated site search: for the chosen target pages of a brand it searches the inventory with their keywords, qualifies the matches, and repeats up to five rounds until the goal is met. Holds the 'ai_find_sites' estimate for the goal and charges what it uses. With orderDestination empty the results stay on the run and no order is touched; filters: maxPrice in dollars, minDR, minTraffic. Runs 5-30 minutes in the background; ai_find_sites_status reports progress. Inputs: clients: array<object { clientId: string; goal: number }> (required); targetPageIds: array<string> (required); filters: object { maxPrice?: number; minDR?: number; minTraffic?: number; reliabilityFilter?: enum(any, reliable, owner); flexibilityPercent?: number }; skipQualification: boolean; orderDestination: object { type: enum(new, existing, assign_existing); existingOrderId?: string }.ai_find_sites_status(orders:read): Progress and result of a site search run: running / completed / failed, sites found per brand against the goal, the order id when one was created, and the estimated vs actual cost. Inputs: historyRunId: string (required).bulk_analysis_add_domains(bulk-analysis:write): Queue candidate domains for qualification against a brand's target pages before any order is touched: each domain is recorded as pending in the given project (projectId), DataForSEO keyword-overlap analysis runs in the background, and an AI pass grades each one high_quality, good_quality, marginal_quality or disqualified. Takes clientId, projectId, domains[] and either targetPageIds[] or manualKeywords (comma-separated). Qualified domains can later be added to an order with line_items_add_to_order. Inputs: clientId: string (required); projectId: string (required); domains: array<string> (required); targetPageIds: array<string>; manualKeywords: string.bulk_analysis_requalify(bulk-analysis:write): Re-run the qualification pipeline (DataForSEO overlap + AI grading + target matching) on domains already in a bulk analysis project: resets their status to pending, optionally replaces the target page ids they are judged against, and fetches DataForSEO data for domains that have none. Metered where DataForSEO runs. Inputs: domainIds: array<string> (required); targetPageIds: array<string>; skipDataForSeo: boolean = false; forceFullRefresh: boolean = false.dataforseo_site_metrics(websites:read): Domain rank, referring domains, spam score and organic traffic for up to 200 domains, from DataForSEO. 15c per call plus 0.1c per domain, or 1.5c per domain with the traffic lookup (includeOrganic, on by default); free on your own DataForSEO key. usage_estimate feature key: 'site_metrics'. Inputs: domains: string (required); includeOrganic: boolean; rankScale: enum(one_hundred, one_thousand); locationName: string; languageCode: string; includeBalance: boolean.website_pages_query(websites:read): A site's collected pages sorted by organic traffic: pageUrl, organicTrafficValue (estimated monthly visits), organicKeywordCount and the keyword position spread (pos1, pos2_3, pos4_10, pos11_20, pos21_30), with collected and collectionStatus (not-collected, processing, completed, completed-empty, error). website_pages_collect gathers the data for a site that has none. Inputs: domain: string (required); limit: number; minTraffic: number.website_search(websites:read): One search of the Linkio site inventory by topic, domain or quality filters (minimum DR, traffic, maximum price). Each result carries DR, traffic, the guest post price (guestPostCost) with the sellers' spread (priceRange), and the site's record with Linkio (history: attempts, delivered, failed with reason). Topic and filter searches cost 0.25c per site after the first 100 sites of the day; domain lookups are free. Inputs: topicQuery: string; domain: string; maxPrice: number; minDR: number; minTraffic: number; reliabilityFilter: enum(any, reliable, expanded, owner); hasGuestPost: boolean; hasLinkInsert: boolean; clientId: string; excludeDomains: array<string>; limit: number; offset: number; sortBy: enum(relevance, dr, traffic, price_asc, price_desc).
Orders and placements
account_publisher_invoice_mark_paid(orders:write): Records that you paid a publisher invoice: method and reference. Split invoices take paymentType blogger or reseller. The publisher is not emailed. Inputs: invoiceId: string (required); paymentMethod: string; paymentReference: string; paymentType: enum(blogger, reseller).account_publisher_invoices_list(orders:read): Publisher invoices on your orders, pending and paid: publisher, site, amount, payment link and whether the work passed QA. On a self-serve order the account pays the publisher directly. Inputs: orderId: string.line_item_conversation_read(orders:read): The message thread with a site's publisher: everything you, the publisher and Linkio have said, oldest first. Inputs: lineItemId: string (required).line_item_invoice_get(orders:read): The publisher's invoice rows for one line item: status, amount, link, dates. Empty until the publisher invoices. Inputs: lineItemId: string (required).line_item_message_publisher(orders:write): Send a message to a site's publisher (a nudge, a question about timing or their site). Emailed from your company name via Linkio; the reply comes back into the same thread. One message per site per call. Inputs: lineItemId: string (required); body: string (required).line_items_add_to_order(line-items:write): Add sites to an order: each item names the brand id and the domain, optionally the target page URL, anchor text and offering type ('guest_post' or 'link_insertion'). The publisher and the site's listed price are looked up for you; prices are in cents. Inputs: orderId: string (required); items: array<object { clientId: string; assignedDomain: string; targetPageUrl?: string; anchorText?: string; offeringType?: enum(guest_post, link_insertion, content_refresh); publisherInsertionUrl?: string; publisherOfferingId?: string; wholesalePrice?: number; estimatedPrice?: number; deliveryMonth?: string }> (required); reason: string; allowOffPlanTarget: boolean.line_items_assign_domain(line-items:write): Put a site on an order item that has none yet: the publisher is looked up from the domain, the price from the publisher's offering, DR, traffic and qualification data are stored on the item, order totals are recalculated and the item becomes approved. Rejected when the item already has a domain or has moved past the planning statuses. (publisher_assign changes the publisher of an item that has a domain; line_items_add_to_order creates new items.) Inputs: lineItemId: string (required); orderId: string (required); domain: string; domainId: string.line_items_batch_update(line-items:write): Change inclusionStatus and notes on up to 200 items of one order in one request (the same change as line_items_update, applied in bulk); order totals are recalculated once at the end. Target page, delivery month and anchor text are not part of this tool. Inputs: orderId: string (required); updates: array<object { lineItemId: string; inclusionStatus?: enum(included, excluded, saved_for_later); notes?: string }> (required).line_items_conversations_batch(line-items:read): The publisher conversations of up to 50 order items in one call. mode 'summary' (default): the last 5 messages plus signals (publisherHasResponded, followUpSent, escalationCount); mode 'full': the whole thread up to maxMessagesPerItem (default 50). Per item: conversationId (null when none), messageCount, lastMessageAt and the messages (sender, body as text, sent at, channel). Inputs: lineItemIds: array<string> (required); mode: enum(summary, full); maxMessagesPerItem: integer.line_items_enrich(line-items:read): The sites on an order with everything the review screen shows: DR, traffic and price, why each site qualified (the reasoning and evidence against your target page), the publisher's reliability signals (verified owner, reseller status, penalties, score) and any open issue on the item. The decision view; line_items_list gives the plain rows. Inputs: orderId: string (required).line_items_keyword_data(line-items:read): The raw keyword overlap behind the sites on an order: for each domain, every keyword it ranks for that overlaps the brand's target page keywords, with position, search volume, difficulty, competition and ranking URL, plus the target pages it was analysed against and suggestedTargetUrl. Filters: domain, minPosition, minVolume, limit (default 100, max 500; truncated flags more). Only domains that went through bulk analysis have data. Inputs: orderId: string (required); domain: string; minPosition: number; minVolume: number; limit: number.line_items_list(line-items:read): The sites on an order with status, inclusion (included / excluded / saved for later), target page, anchor text, price and workflow id. Pass includeExcluded true for everything. Inputs: orderId: string (required); inclusionStatus: enum(included, excluded, saved_for_later); status: string; includeExcluded: boolean.line_items_operations(line-items:read): Your sites across all orders at once, by where they stand with the publisher: waiting for publication, published and awaiting the link check, pending payment, or quiet for N days (staleDays). view 'summary' gives the counts. Inputs: publisherStatus: string; publisherStatusNot: string; status: string; statusNot: string; accountId: string; orderId: string; clientId: string; assignedTo: string; staleDays: number; hasPublishedUrl: boolean; hasInvoice: boolean; view: enum(summary); sortBy: enum(modified_at, publisher_status_changed_at, added_at); limit: number; offset: number; billingType: string.line_items_qualification(line-items:read): The evidence behind every site on an order: domain metrics (DR, traffic, keyword count), the qualification (status high_quality / good_quality / marginal_quality / disqualified, overlap direct / related / both / none, authority strong / moderate / weak by median position, topic scope, the AI reasoning), per target page the matching keywords with positions and a match quality (excellent / good / fair / poor), the publisher's trust signals (verified owner, reseller trust status, penalties, reliability score, issue history) and prices (estimated, wholesale, approved). Filters: inclusionStatus, clientId, qualifiedOnly, minDR. Items added by hand have no keyword data. Inputs: orderId: string (required); inclusionStatus: enum(included, excluded, saved_for_later); minDR: number; clientId: string; qualifiedOnly: boolean.line_items_update(line-items:write): Change one site on an order: include / exclude / save for later (notes carries the reason), its target page, anchor text or delivery month. Excluding recalculates the order total. Inputs: lineItemId: string (required); orderId: string (required); inclusionStatus: enum(included, excluded, saved_for_later); notes: string; targetPageUrl: string; allowOffPlanTarget: boolean; anchorText: string; status: string; deliveryMonth: string; estimatedPrice: number; clearDomain: boolean; clearResolution: boolean; assignedDomain: string; offeringType: enum(guest_post, link_insertion, content_refresh); publisherId: string; publisherOfferingId: string; wholesalePrice: number.order_article_status(orders:read): Where every article on an order is: waiting for a topic, not started, writing (with the current step and progress), stuck, failed, or complete — with word counts and quality warnings. One call per order. Inputs: orderId: string (required); inclusionStatus: enum(included, excluded, saved_for_later).order_byoc_update(orders:write): Change the title or the article URL of a workflow created by order_byoc_upload (any http(s) link that opens without a login). Not available on AI-written workflows, whose document is produced by Linkio. Inputs: workflowId: string (required); title: string; articleUrl: string.order_byoc_upload(orders:write): Attach an article written outside Linkio to an order item, skipping AI writing: creates a workflow with every content step marked complete and ready for the publisher. articleUrl is any http(s) link that opens without a login (Google Docs, Notion, a hosted page); publishers receive it as is. Returns workflowId and the validated articleUrl. Content checks (workflow_verify_doc) run on Google Docs only. Inputs: orderId: string (required); lineItemId: string (required); title: string (required); articleUrl: string; articleContent: string.order_create(orders:write): Open a new empty order for a brand. Sites reach it through line_items_add_to_order or a site search run whose orderDestination names it. Inputs: accountId: string; billingType: enum(managed, self_serve, managed_lite); deliveryMonth: string; notes: string; subscriptionId: string; clientId: string.order_generate_invoice(billing:write): Creates the invoice for one of your managed orders (action generate_invoice). Self-serve orders have no Linkio invoice: you pay publishers directly. Inputs: orderId: string (required); cancelUnusedItems: boolean.order_get(orders:read): One order in full: state, pricing, and every site on it with its status, target page, anchor text, publisher status and article workflow id. view 'full' adds each article's current step. Inputs: orderId: string (required); view: enum(summary, full).order_list(orders:read): Your orders, newest first, with state, brand names and how many sites are included, in progress and done. Filter by brand id or state; search by order id or brand name. Inputs: state: string; billingType: enum(managed, self_serve, managed_lite); accountId: string; clientId: string; search: string; assignedTo: string; includeTest: boolean; excludeInternalOnly: boolean; sort: enum(newest, oldest, updated); limit: number; offset: number.order_refresh_pricing(orders:write): Refresh the sites on an order with the sellers' current prices and the latest DR and traffic, and recompute the order total. Inputs: orderId: string (required); lineItemIds: array<string>; category: string.order_submit(orders:write): Submit a draft order so work can start on its included sites. The order must have at least one included site; totals are computed from the included sites at this moment. Inputs: orderId: string (required).replacement_status(orders:read): For sites that were credited back (publisher failed), the replacement site and where it stands — delivered, in progress, stuck, or not yet replaced. Inputs: orderId: string; accountId: string.site_approval_batch_add_items(line-items:write): Add order items to a pending site approval batch of a brand (batchId from site_approval_batch_list). Ids already in the batch are ignored; submitted and expired batches cannot change. Inputs: clientId: string (required); batchId: string (required); lineItemIds: array<string> (required).site_approval_batch_list(orders:read): A brand's site approval batches: per batch the id, orderId, status (pending / submitted / expired), delivery month, counts (total, approved, rejected, pending), the sites (domain, target page, anchor text, inclusion status) with the brand's notes, respondedAt and expiresAt. Inputs: clientId: string (required).site_approval_batch_remove_items(line-items:write): Remove order items from a pending site approval batch of a brand: the batch total is recomputed from the remaining included items, its approved and rejected lists are cleaned, and an emptied batch is deleted. The items themselves are untouched. Submitted and expired batches cannot change. Inputs: clientId: string (required); batchId: string (required); removeLineItemIds: array<string> (required).
Topics and articles
anchor_plan_get(article-gen:read): A target page's anchor text plan: the mix of anchor types it aims for, the mix its live links actually have, and the gap per type. Anchor suggestions follow this plan. Inputs: targetPageId: string (required).anchor_plan_update(article-gen:write): Change a target page's anchor text plan, the same write as the anchor page's "Customize %" editor: planId alone assigns that plan (page overrides cleared); customOverrides gives this page its own percentages (keys = anchor types, whole-number percent, total exactly 100, types left out count as 0, unknown types rejected; keeps the current plan unless planId is given); clearOverrides true returns the page to the plan's percentages. Marks the assignment manual (AI Optimize keeps it) and re-ranks the page's active suggestions at once. Returns the updated plan summary and topSuggestions in the new order. Inputs: targetPageId: string (required); planId: string; customOverrides: object<integer>; clearOverrides: boolean.article_gen_anchor_apply(article-gen:write): Set the anchor text of one workflow or line item (lineItemId when no workflow exists yet) from a suggestion (suggestionId, which marks it used so it is not assigned twice) or from customAnchorText. Stored on the line item and synced to the workflow; clears the no_anchor_text blocker in article_gen_readiness. Inputs: workflowId: string; lineItemId: string; suggestionId: string; customAnchorText: string.article_gen_anchor_dismiss(article-gen:write): Remove one or more anchor text suggestions from the active pool, with a reason that is kept; the next-best suggestion takes their place in article_gen_anchor_suggestions. Inputs: suggestionIds: array<string> (required); reason: string.article_gen_anchor_generate(article-gen:write): mode 'generate': produce anchor text suggestions for one target page across the 13 anchor types from its anchor keywords, its current backlink profile (DataForSEO) and the gap per type, waterfall-ordered and stored; runs in the background (metered, about 20c) and returns a runId whose status (pending, running, completed, failed) the same tool reports when called with targetPageId and runId. mode 'refresh': re-rank the existing suggestions against the current anchor distribution, free and immediate. A page with empty anchorKeywords gets its keywords generated as part of 'generate'. Inputs: targetPageId: string (required); mode: enum(generate, refresh) (required); runId: string; skipBacklinkFetch: boolean.article_gen_anchor_suggestions(article-gen:read): The anchor text roadmap for an order (or one workflow): every included site grouped by target page, each with a recommendedSuggestion (suggestedText, anchorType such as exact_keyword / branded / partial_keyword, priority, rationale, category) and allSuggestions. Items without a workflow appear with workflowId null, so anchor text can be set before topic research. Per target page: needsGeneration true means no active suggestions exist yet (article_gen_anchor_generate creates them), isStale true means they predate the latest backlink data; distributionSnapshot gives current vs target percentages per anchor type. Suggestions are pre-ordered by a waterfall that simulates sequential assignment, so each item's recommendation already accounts for the others. Needs anchor keywords on the target pages (client_config_target_pages shows anchorKeywords). Free. Inputs: orderId: string; workflowId: string.article_gen_batch_start(article-gen:write): Write the articles for several sites at once (each holds the 'article' estimate; sites that are not ready are skipped and named in the response). Inputs: workflowIds: array<string> (required); modelTier: enum(standard, value, premium) = "standard".article_gen_readiness(article-gen:read): Whether an order's sites (or one workflow) can start writing, with the exact blocker when not: topic research unfinished, anchor text missing, target page missing, or already writing. Free. Inputs: orderId: string; workflowId: string.article_gen_set_anchor_text(article-gen:write): Write a custom anchor text straight onto a workflow or line item (lineItemId when no workflow exists yet), outside the suggestion system, so nothing is marked used. For text that matches no suggestion. Inputs: workflowId: string; lineItemId: string; anchorText: string (required).article_gen_start(article-gen:write): Write the article for one site: research, draft, audit, fact-check, polish, links and images, then a Google Doc. Holds the 'article' estimate and charges per step as it goes. Takes 15-30 minutes; article_gen_status reports progress. Inputs: workflowId: string (required); modelTier: enum(standard, value, premium) = "standard".article_gen_status(article-gen:read): Writing progress for up to 20 workflows: running / paused / completed / failed, the current step, percent done, and the error when one failed. Inputs: workflowId: string (required).topic_gen_finalize_keyword(topic-gen:write): Approve up to ten keyword candidates for one site; the approved ones get scored (topic_gen_phase23_scores). Not the final pick. Inputs: workflowId: string (required); keywords: array<string> (required).topic_gen_order_status(topic-gen:read): Topic research progress per site: phase, what it is waiting for (keyword choice, title choice), the chosen keyword and title, and which sites are ready to start. inclusionStatus 'included' limits it to the working set. Inputs: orderId: string; workflowId: string; activeOnly: boolean = true; inclusionStatus: string; topicGenReady: boolean.topic_gen_pending_keywords(topic-gen:read): For one site: the topics that site actually ranks for, the keyword candidates with rationale and their search volume, and the ten picked automatically. One site per call. Inputs: orderId: string; workflowId: string.topic_gen_phase23_scores(topic-gen:read): For one site, the scores of the approved keywords: authority, relevance and strategy out of 10 each, plus how hard the current results are to beat. Inputs: orderId: string; workflowId: string.topic_gen_phase4_titles(topic-gen:read): For one site, after the keyword is set: title suggestions with the rationale behind each. Inputs: orderId: string; workflowId: string.topic_gen_reset_session(topic-gen:write): Clear a workflow's topic research session and set its topic step back to pending so research starts over (a wrong keyword, a stuck session). Inputs: workflowId: string (required); reason: string.topic_gen_start(topic-gen:write): Start topic research for included sites on an order: the site's own authority is analysed and about 60 keyword candidates are produced per site. Holds the 'topic_generation' estimate per site and charges what it uses. Runs 2-5 minutes in the background; topic_gen_order_status reports progress. Inputs: orderId: string (required); lineItemIds: array<string> (required); skipPreflightCheck: boolean = false.topic_gen_start_phase4(topic-gen:write): Set the one final keyword for a site after the scores and start title suggestions; they are ready in 2-3 minutes in topic_gen_phase4_titles. Inputs: workflowId: string (required); selectedKeyword: string (required).topic_gen_submit_title(topic-gen:write): Set the article title and keyword for one site and finish topic research for it (the research brief is then prepared in the background). One site per call. articleType 'listicle' marks a list-style topic. Inputs: workflowId: string (required); title: string (required); keyword: string (required); articleType: enum(default, listicle) = "default".topic_gen_target_urls(topic-gen:read): The target page URL each workflow links to, resolved from the order item, the workflow, the brand's defaults or the target pages table in that order. Free. Inputs: orderId: string; workflowId: string.topic_gen_title_approval_status(topic-gen:read): Per workflow on an order: site, title, keyword, volume, target URL and the brand's approval status (none / pending / approved / edited / rejected), with counts readyToGenerate (title but no approval link yet), awaitingResponse, approved, edited and rejected. Inputs: clientId: string; orderId: string.workflow_create(workflows:write): Create the workflow for an order item that has none, from the item's offering type: a guest post gets the full 20-step pipeline, a link insertion the 4-step template, pre-filled with target URL, anchor text and the publisher's insertion URL. Returns workflowId, domain, offeringType and alreadyExists (true when one was there, which is then returned). Inputs: lineItemId: string (required); anchorText: string.workflow_create_for_line_item(workflows:write): Create the guest post or link insertion workflow of one order item (orderId + lineItemId, optional anchorText override); when the item already has one its workflowId is returned, nothing is duplicated. The item must be on the given order (404 otherwise) and the order yours (403 otherwise). Returns workflowId. Inputs: orderId: string (required); lineItemId: string (required); anchorText: string.workflow_export_doc(workflows:write): Create or update a workflow's Google Doc from its finished article (the cleaned article, or the polished one when no cleaned version exists) with its images, and store the URL on the workflow. An image that breaks the export is skipped automatically (up to 3 retries); skipImageIndices (0-based) skips known ones at once; forceNew creates a fresh doc. Returns googleDocUrl, googleDocId, created / updated, imagesTotal, imagesIncluded, imagesSkipped and skippedImageDetails. The automatic export after writing can fail silently, which leaves googleDocUrl null in workflow_read. Inputs: workflowId: string (required); skipImageIndices: array<number>; forceNew: boolean.workflow_generate_brief(workflows:write): For a link insertion: write the short insertion brief for the publisher's existing article (a listicle entry or a contextual sentence, chosen from the page's format). The brief is what the publisher email carries. Inputs: workflowId: string (required); mode: enum(listicle, contextual); originalContent: string; publisherPageUrl: string; targetUrl: string; preferredAnchorText: string; insertionStrategy: enum(ai-choice, minimal-1-sentence, natural-2-3-sentences, light-edit, paragraph-rewrite, section-expansion, aggressive-rewrite); targetPageSummary: string.workflow_generate_contextual(workflows:write): For a link insertion into an ordinary article: find the natural spot and draft the one to three sentences that carry your link with the agreed anchor. Inputs: workflowId: string (required); originalContent: string; publisherPageUrl: string; targetUrl: string; preferredAnchorText: string; insertionStrategy: enum(ai-choice, minimal-1-sentence, natural-2-3-sentences, light-edit, paragraph-rewrite, section-expansion, aggressive-rewrite); targetPageSummary: string.workflow_generate_listicle(workflows:write): For a link insertion into a "Top N" style article: draft a new entry that matches the existing entries' style and carries your link. Inputs: workflowId: string (required); originalContent: string; publisherPageUrl: string; targetUrl: string; preferredAnchorText: string.workflow_generate_listicle_sync(workflows:write): Same as workflow_generate_listicle, waiting for the result and writing it into the brief the publisher email uses. Inputs: workflowId: string (required); force: boolean; timeoutMs: integer; originalContent: string; publisherPageUrl: string; targetUrl: string; preferredAnchorText: string.workflow_read(workflows:read): One site's full workflow: view 'summary' for title, keyword, anchor, target page, publisher status and published URL; 'steps' for every step's status; 'step' with a stepId for that step's content (for example the finished article). Inputs: workflowId: string (required); view: enum(summary, steps, step, metadata, full); stepId: string.workflow_send_to_publisher(workflows:write): Send the finished article to the site's publisher for review and publication. Same as the Send button in the app: the publisher gets an email with the Google Doc and their reply lands in the thread. The article must be complete. Inputs: workflowId: string (required); lineItemId: string; articleTitle: string; googleDocUrl: string; emailSubject: string; emailBody: string.workflow_trigger_resume(workflows:write): Continue a paused or failed article from its current step (after a transient failure, or past a manual pause); skipCurrentStep true jumps over the failing step. Inputs: workflowId: string (required); mode: enum(agentic, manual, hybrid); skipCurrentStep: boolean.workflow_trigger_start(workflows:write): Start a workflow's article pipeline from a chosen step (deep-research after topic research; an earlier step after a reset). mode 'agentic' (default, fully automated), 'manual' or 'hybrid'; modelTier 'standard' (default), 'premium' or 'value'. Holds the 'article' estimate (402 with the shortfall when the account cannot fund it) and clears any previous failure. Returns runId (what workflow_trigger_status reports on), workflowId, mode, modelTier and a realtime token valid 4 hours. Your own workflows only. Inputs: workflowId: string (required); mode: enum(agentic, manual, hybrid); startFromStep: string; stopAtStep: string; skipSteps: array<string>; modelTier: enum(premium, standard, value).workflow_trigger_status(workflows:read): The state of a workflow's pipeline run: runStatus 'running' | 'completed' | 'failed' | 'paused' | 'idle', the current step, every step's status, progress (completed, total, percent) and the error text when it failed. Research and drafting steps can each take 5-10 minutes. Inputs: workflowId: string (required).workflow_verify_doc(workflows:write): Check a workflow's Google Doc against its data. Critical checks (fail = the doc is not ready to send): anchor text present, target URL linked, the anchor linked to the target URL, no placeholder artefacts (TODO, [INSERT], template markers, assistant citations), the target URL linked exactly once, a numbered listicle title ("11 Best ...") matched by numbered item headings 1 to N with none missing, and no repeated sentence of 8+ words in the client's section or naming the client. Warnings: heading structure, intro and conclusion, FAQ when expected, word count drift over 30 percent, one to three links in the article body, repeated sentences elsewhere, images when generated. Info: word count, title match. Returns success (all critical passed), checks[] with name, passed, severity and message, and a score. Free. Google Docs only. Inputs: workflowId: string (required); googleDocId: string.workflow_verify_publication(workflows:write): Tell Linkio where the article went live. The link is checked automatically: it is live, the anchor text is right, the page meets the guidelines. Results appear in workflow_read on the publication-verification step. Inputs: workflowId: string (required); publishedUrl: string (required).
Plans and reports
account_activity_feed(orders:read): What happened on your account lately: publisher status changes, messages, orders created, payments, and what the meter did — charges with their cost (usage_charge), holds placed, settled or released (hold), approvals asked for and decided (approval), workflows paused for the budget (budget_pause), budget alerts sent (budget_alert), limit changes (limits_change). The same feed as the activity page, across every order. Every event has costCents (cents on a charge, hold or approval; null elsewhere). Filter by order, event type or days. Free. Inputs: limit: number; cursor: string; offset: number; types: string; days: number; orderId: string.client_analysis_delete(client-config:write): Delete an analysis entry from a client's report. Inputs: clientId: string (required); entryId: string (required).client_analysis_list(client-config:read): The analysis entries on a brand's report page, newest first, with their text. Inputs: clientId: string (required).client_analysis_publish(client-config:write): Add a dated analysis entry (title, one-line summary, markdown body) to the brand's report page. Inputs: clientId: string (required); date: string (required); title: string (required); summary: string; body: string (required); visibility: enum(public, internal).client_analysis_update(client-config:write): Update an existing analysis entry. Inputs: clientId: string (required); entryId: string (required); date: string; title: string; summary: string; body: string; visibility: enum(public, internal).client_campaign_history(crm:read): Every topic ever researched for a brand across all its orders: usedKeywords and usedTitles (deduplicated), byTargetPage (the angles each page already has), and items with site, target page, anchor text, keyword, title, delivery month, status and published URL. targetPageUrl narrows it to one page. Free. Inputs: clientId: string (required); targetPageUrl: string.client_report_contact_add(client-config:write): Add a report contact for a client. Inputs: clientId: string (required); email: string (required); name: string.client_report_highlights(client-config:read): A brand's results in numbers: which keywords its links are cited for in AI assistants, which Google rankings they reached, deliveries by month, brand mentions, what is new since a date. The source of every reported figure. Inputs: clientId: string (required); since: string; from: string; to: string; excludeAbcGives: boolean; respectPriorSendBoundary: boolean; excludePaymentStatusFlips: boolean.client_report_highlights_batch(client-config:read): client_report_highlights for every brand that matches a filter at once (engagementStatus 'active' default, 'paused', 'churned'; accountId): per brand the citations, Google rankings, delivery stats, brand summary and report contacts. Only brands with a report page are returned. Inputs: engagementStatus: string; accountId: string.client_report_message_create(client-config:write): Write a report message for a brand as a draft, or record one you sent elsewhere. Drafts go out with client_report_message_send. Inputs: clientId: string (required); body: string (required); tag: enum(weekly_update, general); markAsSent: boolean; sentAt: string; channel: enum(linkio_platform, spp_dashboard, external_email); deliveredUrls: array<object { domain: string; url: string; rawDateLabel?: string; anchorText?: string }>; highlightKeywords: array<string>; externalSendRef: string; sendBatchId: string.client_report_message_delete(client-config:write): Delete a draft report message. Sent messages are permanent and cannot be deleted. Returns success or the reason (already sent, not found). Inputs: clientId: string (required); messageId: string (required).client_report_message_send(client-config:write): Send a draft report message to the brand's report contacts that have notifications on: marks it sent and emails each contact (subject "Linkio Report — {brand}", from info@linkio.com, replies thread back into the report), with a "View Full Report" button. Returns emailsSent, emailsFailed and errors. Only a draft can be sent; a sent message cannot be sent again. Inputs: clientId: string (required); messageId: string (required).client_report_messages_list(client-config:read): The report messages written for a brand (drafts and sent), newest first. Inputs: clientId: string (required).link_plan_activate(crm:write): Make a link plan the brand's active one. The brand's other active plans are completed in the same transaction. The plan's months may not overlap a scheduled plan (status 'scheduled'): that returns 400 PLAN_MONTH_OVERLAP with the conflicting plan in overlaps[]. Inputs: clientId: string (required); planId: string (required).link_plan_check_in(crm:read): The active strategy against actual delivery: per page, links delivered versus what its tier expects, an adherence verdict (on_track / missed / over_served) and the gap. Position data is whatever baseline the strategy stores (link_plan_update records a fresh one); nothing is fetched live. Inputs: clientId: string (required).link_plan_create(crm:write): Create a link building plan for a brand. Percentage mode: pageAllocations with a percent per target page. Strategy mode: a strategy object with, per page, a tier ('primary' every month, 'rotation', 'paused', 'opportunistic'), a rationale, a goal, monthlyGuidance ('every' | 'alternate' | 'quarterly') and a baseline (position, traffic, refDomains, date) for later comparison. Created inactive; link_plan_activate turns it on. Inputs: clientId: string (required); name: string (required); pageAllocations: array<object { pageId: string; percent: number; rationale?: string }>; strategy: object { pages: object<object>; startMonth?: string; endMonth?: string; planMonths?: number; rotationBudget?: number; overallGoal?: string; guidance?: string; analysisDate?: string; analysisRef?: string }; status: enum(draft, active, scheduled).link_plan_get(crm:read): A brand's link plans: which target pages get links each month and why; the active plan's page weights. Inputs: clientId: string (required); status: string.link_plan_suggest(crm:read): A suggested spread of next month's links across the brand's target pages, from the active plan and what each page has had so far. Computed on request, not stored; the order setup is free to differ. Inputs: clientId: string (required); slots: integer; month: string.link_plan_update(crm:write): Change a link plan's allocations or strategy (page tiers, new pages, baselines, rationale, percentages); only the fields sent change and a new version of the plan is recorded. Inputs: clientId: string (required); planId: string (required); name: string; pageAllocations: array<object { pageId: string; percent: number; rationale?: string }>; strategy: object { pages?: object<object>; startMonth?: string; endMonth?: string; planMonths?: number; rotationBudget?: number; overallGoal?: string; guidance?: string; analysisDate?: string; analysisRef?: string }; changeNotes: string; status: enum(scheduled, draft, completed, archived).search_queries_batches(search-queries:read): The import batches of a brand's search queries: source, period, query count, notes, when fetched, and each batch's id. Inputs: clientId: string (required).search_queries_delete_batch(search-queries:write): Delete one import batch of search queries and every query in it (ids from search_queries_batches or search_queries_summary.latestBatch). Inputs: batchId: string (required).search_queries_import(search-queries:write): Import search-query data for a brand (Search Console exports, keyword tools, or your own list), tied to target pages by URL. Inputs: clientId: string (required); source: string (required); sourceDetail: string; periodStart: string (required); periodEnd: string (required); notes: string; queries: array<object { targetPageUrl?: string; targetPageId?: string; query: string; wordCount: number; clicks?: number; impressions?: number; position?: string; ctr?: string; searchVolume?: number; cpc?: string; competition?: string }> (required).search_queries_import_csv(search-queries:write): Imports search queries from a CSV that already sits on the Linkio server; over the API the rows go through search_queries_import. Inputs: clientId: string (required); filePath: string (required); source: string (required); sourceDetail: string; periodStart: string (required); periodEnd: string (required); notes: string; csvFormat: string.search_queries_list(search-queries:read): The search queries stored for a brand or one target page, sorted by impressions, with text, metrics, source (gsc, dataforseo, ...) and target page. Filters: targetPageId, source, minWordCount, limit. Inputs: clientId: string (required); targetPageId: string; source: string; minWordCount: number; minImpressions: number; limit: number.search_queries_regenerate_brief(search-queries:write): Rebuild the keyword intelligence brief of one target page from its stored search queries (two model calls, about 90 seconds; metered) and cache it on the page, where topic research reads it. A new query import clears the cached brief by itself; this call forces a rebuild without one. Inputs: targetPageId: string (required); clientId: string (required).search_queries_summary(search-queries:read): Whether a brand has search query data: hasData, totalQueries, targetPagesWithData and the latest import batch. Inputs: clientId: string (required).
AI Mentions
citation_check_curate_keywords(citation:write): Edit one backlink's tracking keywords in one call: deleteKeywordTexts removes keywords, addKeywords adds curated ones, setPrimaryKeywordText names the primary, clearOldChecks true deletes that backlink's citation checks (keywords stay) so the next run re-checks it. Inputs: clientId: string (required); lineItemId: string (required); deleteKeywordIds: array<string>; deleteKeywordTexts: array<string>; setPrimaryKeywordId: string; setPrimaryKeywordText: string; addKeywords: array<object { keyword: string; isPrimary?: boolean }>; clearOldChecks: boolean.citation_check_extract_keywords(citation:write): Generate tracking keywords (a primary keyword plus short, mid and long-tail variations) for a brand's backlinks that have none, from each backlink's URL, page title and anchor text. Metered, about 1c per backlink. Returns counts per source (found, extracted, errors) and totalExtracted. citation_check_readiness shows how many lack keywords. Inputs: clientId: string (required).citation_check_index_status(citation:read): Google index status for every backlink of a brand: url, domain, source (imported / order), isIndexed, resultCount, lastChecked (null when never checked), and a summary (total, indexed, notIndexed, unknown, neverChecked). Citation checks on unindexed backlinks find nothing, which this view shows in advance. Free. Inputs: clientId: string (required).citation_check_readiness(citation:read): For a brand: how many delivered links have never been checked for AI citations, how many lack keywords, and what a check would cost. Free. Inputs: clientId: string (required).citation_check_reset_keywords(citation:write): Two deletions. With lineItemId: remove that backlink's tracking keywords and, with deleteCitationChecks true, its citation checks, so new keywords can be extracted and checked. With orphanKeywords[] (up to 100): remove brand-scoped checks for those keywords that are tied to no backlink and no import (the rows baseline runs leave behind); checks tied to a backlink are never touched. Returns the counts deleted. Inputs: clientId: string (required); lineItemId: string; deleteCitationChecks: boolean; orphanKeywords: array<string>.citation_check_results(citation:read): The three outcomes for a brand's order backlinks in one call: AI citations (was the backlink URL cited as a source), brand mentions (was the brand named in the answer) and Google rankings (does the backlink rank for the keyword). Per backlink plus a summary: totalBacklinks, backlinksCited, backlinksBrandMentioned, backlinksWithGoogleRankings, page1Rankings, topRankings. since (ISO date) and batchId narrow the AI checks; ranking data is always included. Inputs: clientId: string (required); since: string; batchId: string.citation_check_run_index_check(citation:write): Check whether specific URLs are indexed by Google (cached for 7 days). Metered per URL. Inputs: urls: array<string> (required); clientId: string.citation_check_status(citation:read): Status of one citation check batch (batchId from citation_check_trigger): isComplete, progress percent, counts pending / processing / completed / failed / cited, and once complete the citationRate and citedKeywords. Counts AI citations only; Google rankings are in citation_check_results. Inputs: clientId: string (required); batchId: string (required).citation_check_trigger(citation:write): Run AI citation checks on a brand's unchecked links across the assistants. dryRun true returns the cost and runs nothing; metered, and the final cost is usually below the hold. Returns a batchId that citation_check_status reports on. Inputs: clientId: string (required); tier: enum(economy, standard, premium); maxKeywordsPerBacklink: number; providers: array<string>; includeSerpCheck: boolean; dryRun: boolean; gapFill: boolean; forceRecheck: boolean; onlyOrderBacklinks: boolean.citation_pipeline_status(citation:read): The whole citation pipeline for a brand in one view, per backlink: indexStatus (isIndexed, lastChecked, stale after 7 days), omegaStatus (submitted, rechecked, due), citationStatus (hasKeywords, checksCompleted, citationsFound, lastChecked) and one action: ready, needs_index_check, needs_omega_submit, needs_omega_recheck, awaiting_omega, needs_keywords, needs_citation_check or not_indexed. The summary counts each action and lists recommendedActions in order with the URLs each one covers. Free. Inputs: clientId: string (required).citation_prep(citation:write): Prepare a brand's published backlinks for citation checks in one run: lists order backlinks with a published URL, skips those with a valid check (made while known-indexed), runs Google index checks on the rest (metered, about 1c per URL), and reports which are ready. dryRun true previews without spending. Returns readyForCitation, alreadyChecked, needsIndexCheck (dry run), submittedToOmega, awaitingOmega, stillNotIndexed, a summary, the actions executed and the cost in cents. Indexer submissions are not made from an account call (omegaSkippedReason says so); publisher_submit_indexer does that. Inputs: clientId: string (required); dryRun: boolean.client_llm_backlink_delete(llm-tracking:write): Soft-delete imported backlinks (sets deleted_at). Inputs: clientId: string (required); backlinkIds: array<string> (required).client_llm_backlink_extract_keywords(llm-tracking:write): Extract keywords for a specific set of backlinks. Inputs: clientId: string (required); backlinkIds: array<string> (required); source: enum(imported, order) (required); force: boolean; preview: boolean.client_llm_backlink_history(llm-tracking:read): Historical citation results for a single backlink (lineItemId). Inputs: clientId: string (required); lineItemId: string (required).client_llm_backlink_keyword_add(llm-tracking:write): Add a keyword to a single backlink's (line item's) tracked LLM-citation list. Inputs: clientId: string (required); lineItemId: string (required); keyword: string (required); inheritFromClientScopedKeyword: boolean; category: string | null; keywordGroups: array<string>; externalMetrics: object; additionalKeywordGroups: array<string>.client_llm_backlink_keyword_delete(llm-tracking:write): Remove a keyword from a backlink's tracked list. Inputs: clientId: string (required); lineItemId: string (required); keyword: string (required).client_llm_backlink_keywords_list(llm-tracking:read): List keywords tracked for a single backlink. Inputs: clientId: string (required); lineItemId: string (required).client_llm_backlink_tags_set(llm-tracking:write): Add or remove tags on backlinks for filtering/grouping. Inputs: clientId: string (required); backlinkIds: array<string> (required); tagsToAdd: array<string>; tagsToRemove: array<string>.client_llm_backlinks(llm-tracking:read): List published backlinks for a client with citation status. Inputs: clientId: string (required).client_llm_check_async(llm-tracking:write): Run an AI Mentions check: ask ChatGPT, Claude, Gemini, Perplexity and others your brand's prompts and record whether they cite your page (anchored to a placed link with lineItemId, or mode 'baseline' for a brand-level reading before any placement). Metered per provider × prompt; client_llm_check_cost_estimate gives the number. Returns a batchId that client_llm_check_status reports on. Inputs: clientId: string (required); keywords: array<string> (required); providers: array<enum(chatgpt, perplexity, claude, gemini)> (required); lineItemId: string; importedBacklinkId: string; mode: "baseline"; includeSerpCheck: boolean; providerConfigs: array<object { provider: enum(chatgpt, perplexity, claude, gemini); model?: string }>; keywordGroups: array<string>; forceRecheck: boolean.client_llm_check_cost_estimate(llm-tracking:read): What an AI Mentions check would cost before you run it, for a given list of prompts and providers. Inputs: clientId: string (required); providers: array<enum(chatgpt, perplexity, claude, gemini)> (required); keywords: integer (required); includeSerpCheck: boolean.client_llm_check_history(llm-tracking:read): List historical citation-check batches for a client. Inputs: clientId: string (required).client_llm_check_settings_get(llm-tracking:read): Read a client's LLM check settings: per-provider engine config (dataforseo | openai-direct) and the searcher-location mode. Inputs: clientId: string (required).client_llm_check_settings_set(llm-tracking:write): Replace a brand's AI Mentions check settings as a whole (which assistants, and the searcher location mode: agnostic, fixed with defaultLocation, or per_keyword). The full desired state is sent each time; fields left out are reset. Inputs: clientId: string (required); settings: object { engines?: object<object>; locations?: array<object>; locationMode?: enum(agnostic, fixed, per_keyword); defaultLocation?: object } (required).client_llm_check_status(llm-tracking:read): Progress and results of an AI Mentions check: per provider, which prompts cited your page and where it ranks on Google for them. Zero citations with a Google ranking is normal early on — assistants pick pages up weeks after Google does. Inputs: clientId: string (required); batchId: string (required).client_llm_comparison(llm-tracking:read): Historical comparison data for a client's LLM citation performance over time — used to render the historical comparison chart in the UI. Inputs: clientId: string (required); days: number.client_llm_export(llm-tracking:read): Export a client's full LLM citation history as CSV-shaped JSON. Inputs: clientId: string (required); format: enum(csv); lineItemId: string.client_llm_extract_keywords(llm-tracking:write): Generate tracking keywords for all of a brand's backlinks that have none. Metered, about 3c per backlink, held up front. Inputs: clientId: string (required).client_llm_extract_keywords_status(llm-tracking:read): Keyword extraction progress for a brand's backlinks: how many imported and order backlinks still need keywords, how many are in flight, how many are done. Inputs: clientId: string (required).client_llm_import(llm-tracking:write): Import backlinks for a client from a list of URLs. Inputs: clientId: string (required); source: enum(text, csv, dataforseo) (required); urls: string; data: array<object { url: string; title?: string; anchor?: string }>; targetDomain: string; options: object; campaignSource: string; extractKeywords: boolean.client_llm_import_list(llm-tracking:read): List recent backlink import batches for a client (last 20). Inputs: clientId: string (required).client_llm_import_status(llm-tracking:read): Progress of one backlink import batch: imported / failed / total, and the keyword extraction progress behind it. Inputs: clientId: string (required); batchId: string (required).client_llm_keyword_cloud_batch(llm-tracking:write): Generate an AI-powered keyword cloud across selected backlinks for a client. Inputs: clientId: string (required); keywords: array<object { keyword: string; backlinkIds: array<string> }> (required); sourceType: enum(order, imported) (required); providers: array<enum(chatgpt, perplexity, claude, gemini)> (required); providerConfigs: array<object { provider: enum(chatgpt, perplexity, claude, gemini); model?: string }>; includeSerpCheck: boolean.client_llm_keyword_groups_list(llm-tracking:read): The keyword groups (tags) on a brand's tracked prompts, with how many keywords and checks each covers, sorted by reach. Inputs: clientId: string (required).client_llm_keyword_groups_set(llm-tracking:write): Bulk add/remove keyword groups (tags) on existing llm_tracking_keywords rows for a client. Inputs: clientId: string (required); keywordIds: array<string> (required); groupsToAdd: array<string>; groupsToRemove: array<string>.client_llm_keyword_history(llm-tracking:read): Per-keyword historical citation results across providers and time. Inputs: clientId: string (required); keyword: string (required).client_llm_keyword_locations(llm-tracking:write): The searcher location used per prompt in per-prompt mode: list them, set one for a prompt, or pre-derive them for every prompt. Inputs: clientId: string (required); action: enum(list, override, derive) (required); keyword: string; location: object { city?: string; region?: string; country?: string } | null; keywords: array<string>; limit: number; offset: number.client_llm_keyword_suggest_backlinks(llm-tracking:read): Given a keyword, suggest backlinks that should be re-checked for citations under that keyword. Inputs: clientId: string (required).client_llm_keywords(llm-tracking:read): The prompts tracked for a brand, with their check stats and, where set, the page each prompt is meant to surface. Inputs: clientId: string (required); includeProviderDetails: boolean.client_llm_keywords_bulk_add(llm-tracking:write): Add a tracked keyword to one or more backlinks. Inputs: clientId: string (required); keyword: string (required); backlinkIds: array<object { id: string; sourceType: enum(imported, order) }> (required).client_llm_mentions(llm-tracking:read): List brand mentions extracted from LLM citation responses for a client. Inputs: clientId: string (required).client_llm_saved_search_create(llm-tracking:write): Create a saved filter view for a client's LLM tracking dashboard. Inputs: clientId: string (required); name: string (required); query: string (required).client_llm_saved_search_delete(llm-tracking:write): Delete a saved search. Inputs: clientId: string (required); searchId: string (required).client_llm_saved_search_update(llm-tracking:write): Update a saved search's name and/or query. Inputs: clientId: string (required); searchId: string (required); name: string; query: string.client_llm_saved_searches_list(llm-tracking:read): List saved filter views for a client's LLM tracking dashboard. Inputs: clientId: string (required).client_llm_share_create(llm-tracking:write): Create a public share link for a client's LLM tracking dashboard. Inputs: clientId: string (required); name: string (required); expiresInDays: number | null; filterTags: array<string> | null; filterProviders: array<string> | null.client_llm_share_delete(llm-tracking:write): Revoke a public share link. Inputs: clientId: string (required); linkId: string (required).client_llm_share_list(llm-tracking:read): List public share links for a client's LLM tracking dashboard. Inputs: clientId: string (required).client_llm_stats(llm-tracking:read): A brand's AI Mentions dashboard: citation and brand-mention rates per provider over the last N days, average positions, and how many of your links are covered. Inputs: clientId: string (required); days: number; campaignSource: string.llm_tracking_providers(llm-tracking:read): List the supported LLM providers (chatgpt, perplexity, claude, gemini) with their display names and capabilities.topic_bank_create(llm-tracking:write): Add one topic to a brand's topic bank by hand (phrase, target page, kind). No research score is attached; those come from the researched import. Inputs: clientId: string (required); phrase: string (required); targetPageId: string (required); kind: enum(question, commercial); category: string; customWording: string; queueMonth: string.topic_bank_get(llm-tracking:read): A brand's topic bank: per target page, the vetted article topics with their stage, status (used / queued / backlog) and which placements used them. Inputs: clientId: string (required).topic_bank_import(llm-tracking:write): Import researched, finalised topics into a brand's topic bank, each tied to a target page, with its funnel stage and sources. Inputs: clientId: string (required); runTag: string (required); targetPageUrl: string; dryRun: boolean; topics: array<object { topic: string; kind: enum(question, commercial); intent?: string; category?: string; funnelScore?: integer; funnelRubric?: object; receipts?: array<object>; slot?: string }> (required).topic_bank_update(llm-tracking:write): Edit one topic in a brand's topic bank: phrase, target page, kind, category, wording, queue month, or retire it. Renaming a used topic renames its history too. Inputs: clientId: string (required); keywordId: string (required); phrase: string; targetPageId: string; kind: enum(question, commercial); category: string; wording: object { custom?: string; audited?: string; chosen?: enum(original, audited, custom) }; queueMonth: string | null; retired: boolean.
Exchanges and partners
asset_bank_add_site(asset-bank:write): Offer a site to exchange partners from a bare domain: finds or creates the site record, a publisher offering per requested type, the relationship and the asset bank entry in one call, under your account. confirm false previews; confirm true writes and returns websiteId and the assets created (existed true where one already was). Inputs: domain: string (required); offeringTypes: array<enum(guest_post, link_insertion)> (required); customName: string; monthlyQuota: integer; notes: string; pageUrls: array<object { page_url: string; status: string; notes?: string }>; confirm: boolean.asset_bank_create(asset-bank:write): Offer one of your sites to exchange partners from an existing site + offering id (asset_bank_add_site takes a bare domain). confirm false previews; confirm true writes. Inputs: accountId: string (required); websiteId: string (required); publisherOfferingId: string (required); sourceType: enum(client_site, owned_site, third_party, specific_url) (required); clientId: string | null; customName: string; allowedUrls: array<string>; pageUrls: array<object { page_url: string; status: string; notes?: string }>; monthlyQuota: integer; visibility: enum(public, private, account_only); notes: string; confirm: boolean.asset_bank_delete(asset-bank:write): Delete one offered site and its page URL list. Permanent. Your own assets only. confirm false previews; confirm true writes. Inputs: assetId: string (required); confirm: boolean.asset_bank_get(asset-bank:read): One offered site in full: the site (domain, DR, traffic), the offering (type, base price, turnaround), the publisher, allowed URLs, monthly quota and how much of it this month used, source, status, visibility, custom name, notes. Your own assets only (403 otherwise). Inputs: assetId: string (required).asset_bank_list(asset-bank:read): The pages your account offers to exchange partners (your own sites where a partner's link can go), with quota and status. Inputs: accountId: string; websiteId: string; clientId: string; scope: enum(account, client); sourceType: enum(client_owned, publisher_offered, network_partnership); status: string; visibility: enum(public, private, account_only); availableOnly: boolean; limit: integer; offset: integer.asset_bank_page_urls_add(asset-bank:write): Add a page URL to an offered site's list with status 'recommended' | 'available' | 'excluded' and optional notes. Your own assets only. confirm false previews; confirm true writes. Inputs: assetId: string (required); pageUrl: string (required); status: enum(recommended, available, excluded) (required); notes: string; confirm: boolean.asset_bank_page_urls_delete(asset-bank:write): Remove one page URL from an offered site's list. Your own assets only. confirm false previews; confirm true writes. Inputs: assetId: string (required); pageUrlId: string (required); confirm: boolean.asset_bank_page_urls_list(asset-bank:read): The page URLs configured on an offered site (the pages a link insertion may land on, or recommended pages for guest posts), each with status 'recommended' | 'available' | 'excluded' and notes, sorted recommended, available, excluded. Your own assets only. Inputs: assetId: string (required).asset_bank_page_urls_update(asset-bank:write): Change a page URL on an offered site: its status, notes or the URL itself. Your own assets only. confirm false previews; confirm true writes. Inputs: assetId: string (required); pageUrlId: string (required); pageUrl: string; status: enum(recommended, available, excluded); notes: string; confirm: boolean.asset_bank_update(asset-bank:write): Change an offered site's name, allowed URLs, monthly quota, visibility or status. Confirm true writes. Inputs: assetId: string (required); customName: string; allowedUrls: array<string>; monthlyQuota: integer | null; visibility: enum(public, private, account_only); status: string; notes: string; clientId: string | null; confirm: boolean.client_exchange_inventory_summary(asset-bank:read): What a brand can offer exchange partners: link spots on its own pages, mentions in articles being written for other sites, and its marketplace page. Inputs: clientId: string (required).collab_opportunities(collaborative-placements:read): Articles being written for other sites that accept added brands, across orders: per opportunity the site, topic and keyword, the prices for a link and for a mention only (cents), total / filled / available spots, whether it still accepts collaborators, writing progress, tags and a pitch summary. Filters: status 'enabled' | 'disabled' | 'all', tags (comma-separated; tagsMatchAll for AND), hasAvailableSpots. Inputs: status: enum(enabled, disabled, all); tags: string; tagsMatchAll: boolean; hasAvailableSpots: boolean.collab_order_summary(collaborative-placements:read): A compact view of the collaborations on one order: counts of collaborative and standalone articles, whether every collaborator has paid, the related orders (account, state, paid, item count) and per article the site and its collaborator count. Prices, anchors, dates and per-participant detail are in collab_participants. Inputs: orderId: string (required).exchange_cadence_audit(opportunities:read): Pre-flight for the exchange follow-up cadence on one opportunity: what is due, what is blocked and why. Inputs: opportunityId: string (required).exchange_get(opportunities:read): One exchange in full: the parties, what each gives and gets, its status and the conversation. Inputs: exchangeId: string (required).exchange_participant_set_status(opportunities:write): Accept or decline an exchange invitation you received. Confirm true writes. Inputs: exchangeId: string (required); participantId: string (required); action: enum(accept, decline) (required); reason: string; confirm: boolean = false.exchange_reply_batch_send(opportunities:write): Sends the prepared exchange replies for one approval batch. Each send is a write. Inputs: batchId: string (required); decisions: array<object { opportunityId: string; action: enum(send, skip); subjectOverride?: string; bodyOverride?: string; acceptAdvisory?: boolean }>; acceptAdvisory: boolean; confirm: boolean = false.exchange_reply_draft(opportunities:read): The reply exchange_reply_send would send for an opportunity: subject, body, threading headers and the idempotency key. A pure read, nothing is sent or stored. Returns 400 when a hard prerequisite is missing (contact email, sending mailbox, conversation); exchange_reply_prep names the gap. Inputs: opportunityId: string (required).exchange_reply_prep(opportunities:write): Readiness check before replying on an exchange: the thread state, the partner facts and what the reply must cover. Inputs: opportunityId: string (required).exchange_reply_send(opportunities:write): Send a threaded reply on an exchange opportunity by email: a real message to a real contact, irreversible. With confirm false or omitted it returns the draft (same as exchange_reply_draft). Refused with 400 while exchange_reply_prep lists a blocking item, or an advisory item without acceptAdvisory true. Idempotent on (opportunity, body, inReplyTo): the same content again returns the existing message id without sending twice. Inputs: opportunityId: string (required); subjectOverride: string; bodyOverride: string; acceptAdvisory: boolean; confirm: boolean = false.opportunity_accept_collab(opportunities:write): Accept a partner's request for a mention in one of your articles or a link on one of your offered pages. confirm false previews what would be created; confirm true writes. Inputs: opportunityId: string (required); confirm: boolean = false; spotOverride: integer.opportunity_accept_exchange(opportunities:write): Accept a request from your exchange page: creates your side (the link you get) and the partner's side (the link you give) in one step. Confirm true writes. Inputs: opportunityId: string (required); orderId: string; domainTargets: array<object { domain?: string; targetUrl?: string; targetPageId?: string; anchorText?: string; clientId?: string; offeringType?: string; publisherInsertionUrl?: string }>; confirm: boolean = false.opportunity_batch_summary(opportunities:read): One card per batch of your opportunities: counts by status, discovery candidates by status, first-touch messages sent / replied / won, and the last activity. Pass clientId for one brand or batchKey for one batch. Inputs: clientId: string; batchKey: string.opportunity_candidate_add(opportunities:write): Add a contact you know for an opportunity's site. Inputs: opportunityId: string (required); candidateKind: enum(publisher, shadow_publisher, acquisition_identity, research) (required); candidateLabel: string (required); candidateRef: string; candidateEmail: string; discoveryScore: integer; source: string; sourceSignal: object.opportunity_candidate_delete(opportunities:write): Archive a contact on an opportunity (kept for the record, hidden from the list). Inputs: opportunityId: string (required); candidateKey: string (required).opportunity_candidate_list(opportunities:read): Alternative contacts found for an opportunity (who else at the site could answer), with status. Inputs: opportunityId: string (required).opportunity_candidate_scan(opportunities:write): Look for alternative contacts for an opportunity's site; persist true saves them. Inputs: opportunityId: string (required); persist: boolean.opportunity_candidate_update(opportunities:write): Change a contact's status or details on an opportunity. Inputs: opportunityId: string (required); candidateKey: string (required); status: enum(discovered, feeler_sent, replied, declined, won, archived); lastFeelerSentAt: string | null; lastFeelerChannel: enum(slack, email) | null; candidateLabel: string; candidateEmail: string.opportunity_conversation_post(opportunities:write): Reply on an opportunity's conversation; the message is emailed to the other party. confirm true sends; without it the draft is returned. Inputs: opportunityId: string (required); content: string (required); subject: string; internalOnly: boolean; dryRun: boolean; confirm: boolean = false.opportunity_conversation_read(opportunities:read): The conversation of an opportunity (opportunityId) or of one exchange thread (exchangeId + conversationId, as exchange_get lists them), one of the two: the conversation record (status, last message at) and its messages with sender, body, channel, sent at and threading data. Your own only. opportunity_conversation_post and exchange_conversation_post write to them. Inputs: opportunityId: string; exchangeId: string; conversationId: string.opportunity_decline(opportunities:write): Decline a partner request. Inputs: opportunityId: string (required); reason: string; confirm: boolean = false.opportunity_feeler_send(opportunities:write): Sends a first-touch message to a contact found for an opportunity: a real email to a real person, in the account's name. Inputs: opportunityId: string (required); candidateKey: string (required); messageText: string (required); templateTier: enum(T1_no_email, T2_with_email, followup, manual); templateVersion: string; dryRun: boolean.opportunity_get(opportunities:read): One opportunity (a partner or publisher interaction) in full: who, which site, which target page, status, the conversation. view 'replies' lists their replies. Inputs: opportunityId: string (required); view: enum(default, replies); includeAssignmentForm: boolean; accountSearch: string.opportunity_lifecycle(opportunities:read): Everything about one opportunity in one call: workflow, exchange, picks, token, thread, approvals, and a one-line state. Inputs: opportunityId: string (required).opportunity_list(opportunities:read): Your opportunities (partner and publisher interactions) with filters: status, brand, search, batch. Counts by status. Inputs: status: string; clientId: string; accountId: string; publisherId: string; sourceFilter: string; search: string; approval: string; batchKey: string; intent: enum(li_bump_refresh, li_paid, gp_availability, cold_outreach, casework_blast, exchange, other); page: integer; limit: integer.opportunity_osg_enqueue(opportunities:write): The background form of opportunity_osg_run: mode 'enqueue' queues a run (status 'cached', 'no_cache' or 'queued' with a runId; bypassCache and cacheOnly change the cache behaviour), mode 'poll' with runId returns its status and timings. Linkio staff only; an account call returns 403. Inputs: mode: enum(enqueue, poll); runId: string; partnerUrls: array<string>; minDR: number; priceCapCents: number; limit: integer; model: string; exchangeContext: string; includePromotedSingles: boolean; bypassCache: boolean; cacheOnly: boolean; clientContext: object { brandName?: string; targetUrl?: string; anchor?: string }.opportunity_osg_pin(opportunities:write): The offer shortlist pinned to an opportunity's active exchange-offer token, which is what the partner sees on the share page: mode 'read' returns it with its history; mode 'write' with picks (primary, backups, verdictNote) and confirm true replaces it and records the change. Linkio staff only; an account call returns 403. Inputs: mode: enum(read, write); opportunityId: string (required); picks: object; source: string; confirm: boolean.opportunity_osg_run(opportunities:write): Offer suggestions for a partner from 1-20 of their pages, in one synchronous call (about 60 seconds): keyword extraction, marketplace search (minDR default 40, priceCapCents default 6000, limit default 50), scoring and a verdict. Linkio staff only; an account call returns 403. Inputs: partnerUrls: array<string> (required); minDR: number; priceCapCents: number; limit: integer; model: string; exchangeContext: string; includePromotedSingles: boolean; clientContext: object { brandName?: string; targetUrl?: string; anchor?: string }.opportunity_page_get(opportunities:read): One exchange page's full settings: which of your sites it offers, how partners are matched, the marketplace rules, privacy. Inputs: pageId: string (required).opportunity_page_list(opportunities:read): Your exchange pages (the partner-facing pages where others request links from you) with their settings and visit counts.opportunity_page_update(opportunities:write): Change your exchange page: title, privacy, whether collaborations and offered sites are on, notifications. Confirm true writes. Inputs: pageId: string (required); pageTitle: string; privacyMode: boolean; privacyDescription: string; collabEnabled: boolean; assetBankEnabled: boolean; notificationConfig: object { notifyInternal?: boolean; notifyPageOwner?: boolean; notifyPartner?: boolean }; assetBankIds: array<string>; collabSelectionMode: enum(all, by_tags, by_account, by_accounts, manual); collabTagsFilter: array<string>; collabAccountFilter: string; collabClientFilter: string; collabAccountFilters: array<object { account_id: string; client_id: string | null }>; collabManualIds: array<string>; exchangeRequirements: object; internalName: string; expiresInDays: integer; marketplaceEnabled: boolean; marketplacePriceCapCents: integer | null; marketplaceMinDr: integer | null; marketplaceNiches: array<string> | null; marketplaceMaxSelections: integer | null; confirm: boolean.opportunity_picker_history(opportunities:read): The last 20 unexpired picker runs for a brand + publisher site pair, each with its stored full result. Linkio staff only; an account call returns 403. Inputs: clientId: string (required); publisherDomain: string (required).opportunity_picker_record_anchor(opportunities:write): Record the final anchor text of an exchange item or order item after a page pick (one of lineItemId or exchangeItemId, with clientId, anchorText up to 500 characters, source 'dropdown' | 'ai-generated' | 'manual-typed'); confirm true writes, otherwise a dry run. Linkio staff only; an account call returns 403. Inputs: lineItemId: string; exchangeItemId: string; clientId: string (required); anchorText: string (required); source: enum(dropdown, ai-generated, manual-typed); opportunityId: string; confirm: boolean.opportunity_picker_record_pick(opportunities:write): Records the page chosen for an item after a picker run. Inputs: lineItemId: string; exchangeItemId: string; clientId: string (required); publisherDomain: string (required); pick: object { publisherUrl: string; recommendedTargetUrl: string; recommendedTargetPageId: string; rank: integer; confidence: number; reasoning: string; pageFormat: string; runId: string } (required); pickSource: enum(primitive, manual); opportunityId: string; confirm: boolean.opportunity_picker_run(opportunities:write): Ranks a publisher site's pages for a brand's target pages, so a link lands on the page that fits best. Inputs: publisherDomain: string (required); clientId: string (required); maxCandidates: integer; runVerdicts: boolean; cacheOnly: boolean; includeRejected: boolean; dfsRowLimit: enum(100, 500, 1000); seedUrls: array<string>; hints: object { targetPageIds?: array<string>; prospectingKeywords?: array<string> }.opportunity_submissions_list(opportunities:read): The requests that came in through your exchange pages: opportunity_list limited to status 'qualified' and source exchange_page, with the same filters (clientId, search). Your own plus those where you are the publisher. Inputs: clientId: string; accountId: string; publisherId: string; search: string; page: integer; limit: integer.opportunity_token_status(opportunities:read): The latest exchange-offer token of an opportunity (the link a partner opens): id, value, status, expiresAt, isExpired, daysUntilExpiry and the number of open requests on it; { hasToken: false } when none was issued. Your own opportunities only. Inputs: opportunityId: string (required).opportunity_update(opportunities:write): Correct fields on one of your opportunities (contact, notes, status, published URL). Inputs: opportunityId: string (required); accountId: string; clientId: string; publisherId: string; websiteId: string; offeringId: string; targetPageId: unknown | string | null; anchorText: unknown | string | null; contactEmail: string; contactName: string; sourceType: enum(email, chatwoot, manual, smart_import, prospect_finder, exchange_page, client_direct); sourceReference: string; responseClassification: enum(interested, declined, needs_info, counter, auto_reply); outreachChannel: enum(gmail, maildoso, manyreach, resend); outreachMailbox: unknown | string | null; batchKey: unknown | string | null; intent: unknown | enum(li_bump_refresh, li_paid, gp_availability, cold_outreach, casework_blast, exchange, other) | null; outreachSentAt: string; gmailMessageId: string; gmailThreadId: string; chatwootConversationId: string; chatwootInstance: string; notes: string; status: enum(qualified, assigned, in_progress, completed, failed); publishedUrl: string; requestData: object; submitterName: string; submitterCompany: string; currentPublisherPageUrl: string; metadata: object.
Other
account_api_key_revoke(client-config:write): Revoke one of your account API keys (ids from account_api_keys_list). Inputs: keyId: string (required).account_api_keys_list(client-config:read): Your account API keys: name, prefix, scopes, created, last used, revoked (never the secret), plus the scopes your account type may hold. Keys are created on the API keys page by the account owner, not through the API. Needs client-config:read.account_llm_tracking_backlinks(llm-tracking:read): The same links as account_llm_tracking_summary with the counts behind each provider: how many times each link was checked and how many checks came back cited (the "how often" view; the summary is the yes/no view).account_llm_tracking_export(llm-tracking:read): Your AI citation data as rows ready for a spreadsheet. type 'all' (default): every link with its citation summary, one row per link. type 'citations': the outside domains the AI assistants cite for your keywords, deduplicated, which is a prospecting list for outreach. Inputs: type: enum(all, citations).account_llm_tracking_summary(llm-tracking:read): Every published link of your account checked for AI citations, one row per link: whether ChatGPT, Perplexity, Claude and Gemini cited it, whether it came from an order or an import, and when it was last checked. The same data as the AI Mentions page, as JSON. days sets the lookback window (default 30). Your own links only. Inputs: days: number.client_geo_strategy(client-config:write): A brand's topical distribution plan, bound to its active link plan: categories on up to three axes (head-term variants; audience or context modifiers; an optional third), each with name, targetPercentage, matchingTerms, color, sortOrder and axisLabel, plan-wide or per target page. action 'get' (default) reads; 'upsert' saves a set of categories as a diff-merge; 'delete-one' removes one by id; 'delete-scope' empties one axis for one page; 'promote-to-plan' moves older brand-wide categories into the active plan. Writes need an active link plan (otherwise NO_ACTIVE_PLAN); planId is resolved automatically. dryRun true previews a write. Inputs: action: enum(get, upsert, delete-one, delete-scope, promote-to-plan) = "get"; clientId: string (required); axis: enum(primary, secondary, tertiary); targetPageId: string; categories: array<object { id?: string; name: string; targetPercentage?: number | null; matchingTerms?: array<string>; description?: string | null; color?: string | null; sortOrder?: integer }>; axisLabel: string; categoryId: string; dryRun: boolean = false.client_target_page_coverage(client-config:read): Link coverage per target page of a brand: delivered links (site, anchor, month, order), pre-platform history (site, anchor, DR, traffic), the pipeline (site, anchor, publisher status), an optional backlink profile from DataForSEO, enrichment flags and each page's share of all links, plus a summary (pages with links, pages with none, totals). months (default 3) sets how much delivery history is included; includeInactive adds inactive and completed pages. Free. Inputs: clientId: string (required); months: number; includeInactive: boolean.subscription_cancel(orders:write): Cancel a subscription at the end of its current period (action 'cancel'), or undo a pending cancellation (action 'undo'). Inputs: subscriptionId: string (required); action: enum(cancel, undo) = "cancel".subscription_credit_refill(orders:write): Automatic credit top-up on a subscription: action 'get' reads it, 'set' turns it on with amount (whole dollars, 10 to 10000), 'remove' turns it off. Inputs: subscriptionId: string (required); action: enum(get, set, remove) (required); amount: integer.subscription_customer_portal(orders:write): A Stripe customer-portal link where the account owner manages a subscription's payment method and invoices. The link expires. Needs orders:write.