Documentation

API reference

For scripts and agents that call Linkio with an account API key. The same text lives in the repository and is checked against the code on every build. The full route table and the tool catalog are their own pages.

Linkio API for account callers

The contract for a script or an agent that calls Linkio with an account API key. Every list, code, field and number on this page is checked against the code on every build. The routes a key can call: http-routes-for-accounts.md. The MCP tools: mcp-tools-for-accounts.md. Prices and the receipt in detail: self-serve-billing-api.md. A five-minute walk: first-job-in-5-minutes.md.

1. Authenticate

A key starts with acc_. Send it in one of two headers; both are read the same way:

X-API-Key: acc_...
Authorization: Bearer acc_...

Create a key on the API keys page, or with POST /api/account/api-keys from an account sign-in; a key cannot mint keys (an acc_ key gets 403 owner_only). Body: name (required), scopes (required, from the list below), clientId (optional: the key sees one brand only), description, expiresAt. The key is shown once. At most 10 active keys per account; DELETE /api/account/api-keys revokes one (a key holding client-config:write may revoke its account's keys). No account yet? Section 9 creates one from the API.

Base URL: https://www.linkio.com. Answers are JSON. A missing or revoked key answers 401.

2. Scopes

A scope is namespace:action; the actions are read, write and purchase. A route names the scope it needs; a key without it answers 403 with error insufficient_scope, code SCOPE_DENIED and required_scope. An account sign-in holds every scope; a key holds only the ones it was created with.

Two key types exist. A self-serve account's key may hold any of these 34 scopes: orders:read, orders:write, line-items:read, line-items:write, websites:read, workflows:read, workflows:write, topic-gen:read, topic-gen:write, article-gen:read, article-gen:write, billing:read, billing:write, search-queries:read, search-queries:write, bulk-analysis:write, client-config:read, client-config:write, collaborative-placements:read, citation:read, citation:write, change-requests:read, change-requests:write, opportunities:read, opportunities:write, account-campaign:read, account-campaign:write, llm-tracking:read, llm-tracking:write, asset-bank:read, asset-bank:write, crm:read, crm:write, credits:purchase.

A managed account's key may hold these 11 read scopes: orders:read, line-items:read, workflows:read, billing:read, citation:read, collaborative-placements:read, client-config:read, search-queries:read, crm:read, opportunities:read, asset-bank:read. A managed key on a self-serve-only route (site search, for one) answers 403 ACCOUNT_TYPE_DENIED.

billing:write changes the account's own spending limits and card. The keys page's Full preset sends every scope on the list, billing:write included; for an agent, create a key with the Read-only preset or pick the scopes by hand.

NamespaceWhat the scope covers
ordersView and manage guest post orders
line-itemsView and manage individual line items within orders
websitesBrowse available publisher websites
workflowsView and manage article workflows
topic-genGenerate topics and keywords for articles
article-genGenerate and manage AI-written articles
billingView credit balance and billing history; write changes your own spending limits and card
search-queriesImport and manage search query research
bulk-analysisRun bulk domain analysis
client-configView and update client settings, target pages, provider keys and API keys
collaborative-placementsView collaborative placement opportunities
citationView AI citation and ranking data
change-requestsReserved, no tool yet
opportunitiesView and manage link exchange opportunities and partner pages
account-campaignYour campaigns over HTTP (/api/account/campaigns); no MCP tool yet
llm-trackingTrack where AI assistants mention and cite your brand (AI Mentions)
asset-bankManage the pages and sites you offer in link exchanges
crmView CRM and communication data
creditsBuy prepaid credit (purchase only)

3. Refusals

StatuscodeMeaningWhat to do
401No key, or a revoked or expired keyCreate a key
403SCOPE_DENIEDThe key lacks required_scopeCreate a key with that scope
403ACCOUNT_TYPE_DENIEDA managed key on a self-serve-only routeNothing: the route is not for managed accounts
429DAILY_READ_CAPThe key passed 5000 reads per key per UTC dayWait for resetsAt; Retry-After is set
429RATE_LIMIT_EXCEEDEDMCP only: 60 calls per minute with a burst of 20Wait Retry-After seconds
402one of six, belowThe account cannot fund this startRead the body: it names the way out

4. The 402 bodies

A route that spends checks money before it works. A refusal is a 402 with one of three JSON shapes. The code field tells them apart; error is a plain sentence for a person; estimateCents is what the start would have held.

Budget: budget_exceeded or budget_paused

{ error, code, scope, feature, apiKeyId, period, limitCents, spentCents, heldCents, estimateCents,
  remainingCents, affordableCount?, resetsAt, approvalId: null,
  raise: { canSelfRaise, maxSelfServeCents, settingsUrl, api, requestIncreaseApi } }

scope says which limit stopped the start (the account's month, a feature, this key's day or month). raise.api is PUT /api/account/usage-limits (needs billing:write); when canSelfRaise is false the budget is at maxSelfServeCents and requestIncreaseApi asks Linkio for more. affordableCount is how many units the remaining budget would fund, when the start was for several.

Approval: needs_approval

{ error, code, scope, feature, apiKeyId, estimateCents, askOverCents, approvalId, expiresAt, refused?,
  approve: { settingsUrl, waitingUrl, api, header, listApi, tool } }

The job costs more than the account's "ask me before any job over" amount (askOverCents). The decision is the account owner's. A key that holds billing:write may record it: approve.api is POST /api/account/usage-limits/approvals/<approvalId> with body {"decision":"approve"} or {"decision":"deny"}; the MCP tool is usage_approvals_decide (approve.tool), marked destructive so a client asks the person at the keyboard first. Answers: 404 for an unknown or another account's id, 409 when already decided, 410 when expired. A key without billing:write hands the owner approve.waitingUrl (the Waiting view of the activity page) or approve.settingsUrl; listApi (GET /api/account/usage-limits/approvals) reads the state. Once approved, repeat the same call; optionally name the approval in the header X-Linkio-Approval (approve.header), with the approvalId as its value. The approval is matched to the retry by account, feature, key and jobRef (the batch, workflow or order the start names), not by the exact estimate, so a retry that costs a little more still runs. refused is set when a previous decision was a denial.

Card: card_required, payment_failed or credit_line

{ error, code, reason, feature, estimateCents, prepaidCents, cardStatus, unbilledCents, creditLineCents,
  pausedAt, card: { settingsUrl, api, statusApi, setupLinkApi, payNowApi } }

Prepaid balance does not cover the start and no card can. card_required: no card (or the card was removed); payment_failed: the last charge failed and paid work is paused until the card changes; credit_line: unbilled usage is at creditLineCents and is charged shortly. Adding a card is the owner's click: card.setupLinkApi (billing:write) returns a Stripe-hosted page to hand to the owner; card.statusApi reads the card; card.payNowApi (billing:write) charges the unbilled balance now.

The convert hint

When the account holds placement credit (link_building) that the meter cannot spend, the card body (or a route's older Insufficient credits body) adds convertibleCents and a convert object with route /api/account/credits/convert, tool credits_convert, rate 1:1, from link_building and to general. POST /api/account/credits/convert (billing:write) moves that amount into spendable credit at 1:1.

5. The receipt

GET /api/account/usage/receipt (needs billing:read) answers where the account stands: accountId, period, asOf, spentCents, heldCents, budgetCents, budgetRemainingCents, resetsAt, prepaidCents, unbilledCents, cardStatus, cardHeadroomCents, canStartPaidWork, feesCents. canStartPaidWork is the one bit to read before a paid call.

The same object (without accountId and asOf) comes back without a second call:

  • API: on every 2xx answer of a route marked "receipt" in http-routes-for-accounts.md, in the header X-Linkio-Usage-Receipt (JSON), with X-Linkio-Usage-Receipt-Url naming the route above. Only for a key that holds billing:read.
  • MCP: as usageReceipt on every write tool's result.

6. Prices and the estimate

Prices are not on this page. GET /api/pricing/self-serve (public) returns the per-job fees, the markup on provider cost when a job runs on Linkio's keys, and the publisher price range; the per-job rules are in self-serve-billing-api.md. Before a paid start, GET /api/account/usage/estimate?feature=<article|topic_generation|target_page_ai|ai_find_sites|website_search|site_metrics>&count=N (billing:read; MCP usage_estimate) answers the exact hold the start would take. Every API or MCP write call carries a per-call fee; reads are free and capped as in section 3.

7. Idempotency and retries

There is no idempotency key: no route an account can call reads an Idempotency-Key header. A call that starts work (an article, a topic session, a site search, a batch) answers with a run, batch or job id; the caller watches that id through its status route or tool and never starts the work again. A step re-run after a reset is a new job and is charged again. The approval retry (section 4) is the one place a repeated call is expected, and it is matched by jobRef, not by a header you invent.

The HTTP API has no rate limit of its own beyond the daily read cap; the MCP server rate-limits per key (section 3). A 429 carries Retry-After in seconds.

8. Connect an agent

MCP endpoint: https://mcp.linkio.com/mcp, header Authorization: Bearer <your key>. The server lists only the tools the key's scopes allow (mcp-tools-for-accounts.md), answers usageReceipt on every write, and serves the agent rules as its instructions.

Connectors that sign in instead of pasting a key (Claude.ai, ChatGPT) use OAuth 2.1: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource publish the endpoints; POST /api/oauth/register registers the client (RFC 7591, no credentials needed); the authorize step is PKCE (S256); /api/oauth/token supports authorization_code and refresh_token. There is no client_credentials grant: OAuth signs an existing account in, it cannot create an account. The access token is a JWT and travels only as Authorization: Bearer.

9. Sign up as an agent

An agent can create the account without a browser and without a key. Two public routes; the person who owns the mailbox proves it once by reading one mail. The browser signup form keeps its captcha; these routes stand behind the limits in the table below instead. Accounts that Linkio staff create follow their own path; the gate covers these two public routes.

  1. Register with POST /api/signup:
curl -s -X POST https://www.linkio.com/api/signup -H 'content-type: application/json' \
  -d '{"email":"owner@example.com","password":"<8+ chars>","name":"Dana","website":"https://example.com"}'

202 {"status":"verification_sent"} is the one success answer, the same for a new, a pending or an already registered email (the body never says which). password needs at least 8 characters; name is required; website and company are optional strings. Refusals: 400 INVALID_REQUEST (not JSON, or email, password or name missing), 400 PASSWORD_TOO_SHORT, 400 EMAIL_REJECTED (the address does not parse, its domain has no MX record, or it is a disposable domain), 429 RATE_LIMITED with a Retry-After header (a limit in the table), 409 IDENTITY_PASSWORD_LOCKED (the email is a login of another kind with another password). A pending address registered again gets the same code mailed once more (once a minute, 5 a day). No mail within 10 minutes means the email is already registered and verified: sign in on the web.

  1. The mail "Verify Your Email Address - Linkio" carries the link for a person and, on its own line, the sentence "Did an AI agent create this account for you? Give it this verification code:" followed by the code. The person gives the code to the agent.

  2. Verify and receive the first key with POST /api/signup/verify:

curl -s -X POST https://www.linkio.com/api/signup/verify -H 'content-type: application/json' -d '{"token":"<code>"}'

201 { accountId, apiKey, keyPrefix, scopes, expiresAt: null, next: { receipt, providerKeys, addMoney, pricing } }. apiKey is the acc_ key, shown once. Refusals: 410 TOKEN_USED (the code was already used through this route; the account is verified, use the key it returned; an account blocked while pending answers this too), 400 TOKEN_EXPIRED (older than 24 hours: register the same email again and a fresh mail with the code is sent), 404 TOKEN_UNKNOWN (no pending account created through this route carries the code; if the person clicked the link first, the code answers 404: the account is verified, sign in on the web and create a key on /account/api-keys), 400 INVALID_REQUEST (no token).

  1. What the first key can do: it holds 33 scopes, the self-serve list without billing:write, so it reads and works but cannot change the account's spending limits or card, cannot decide approvals and cannot mint keys (403 owner_only; the owner adds keys on /account/api-keys, and a key for an account whose email is not verified is refused with 403 email_not_verified). Nothing is spendable until the account has its own provider keys (both live, which also grants the free start described in self-serve-billing-api.md) or a card: GET /api/account/usage/receipt answers canStartPaidWork false until then. next names the four calls that follow.
LimitValueAnswer
Per IP2 per hour429 RATE_LIMITED
Per new email1 per day429 RATE_LIMITED
Per email domain5 per day429 RATE_LIMITED; shared mail providers such as gmail.com are exempt
Verification mail resentonce a minute, 5 per day429 RATE_LIMITED
New emails, all callers together60 per hour429 RATE_LIMITED
Code lifetime24 hours400 TOKEN_EXPIRED

When the hourly cap is reached a new email answers 429 while an already registered one still answers 202; the cap is an outbound-mail bound, not a secrecy promise. The limits count per server process.

Self-serve billing, limits and receipts: API and MCP

For agents and scripts that use Linkio with an account API key (acc_…). Managed accounts are not billed this way. Every number on this page is checked against the code on every build; the auth headers, scopes and 402 bodies are in api-for-accounts.md, the routes a key can call in http-routes-for-accounts.md.

GET /api/pricing/self-serve (public, no key needed) returns the numbers every page states: Linkio's per-job fees (fees, the meter's own prices), platformMarkupPercent on provider cost when a job runs on Linkio keys, and publisher (median / quartile listed guest-post prices, refreshed hourly). An agent that quotes a price to its user should read it from here, never from page copy.

Creating a key (POST /api/account/api-keys, or the API keys page) needs only an active account; there are no plans or subscriptions to hold first. Money is checked per job: a paid call is refused with a 402 until the account has prepaid balance, the free start (both own keys healthy) or a card on file.

How you are billed

  • Every job pays provider cost on Linkio's keys + 20% margin + a compute fee for its job type (whole cents). Prices live in a settings row and are read with no deploy.
  • Work on your own provider keys (OpenAI, DataForSEO) pays the compute fee only, for every feature the key passed the capability probe for (features on the key; the list is under Own keys).
  • Every API or MCP write call (create, update, run) costs 1c per API or MCP write call, counted per UTC day and charged once a day as one ledger line under the feature requests. Reads are free. An MCP tool call that writes is counted once, not again for the HTTP call it makes.
  • A failed job pays no fee. Work started by Linkio staff on your account is never charged.
  • Prepaid credits are used first, then the card on file (credit line $10 → $100 → $1,000 as charges succeed; charged at thresholds and at month end). A new account with its own keys gets a $10 free start for 90 days.
  • A step re-run after a reset is a new job and is charged again; a retry inside the same run is charged once.

Limits

  • Monthly budget (hard or warn), per-feature caps, per-key daily and monthly caps, approvals for one-off overruns, alerts at 50/80/100% of the budget.
  • A refused start answers 402 with a machine-readable body. One code field names the reason: budget (budget_exceeded, budget_paused), approval (needs_approval: the owner, or a key with billing:write through usage_approvals_decide, decides; then repeat the call, optionally with the header X-Linkio-Approval), card (card_required, payment_failed, credit_line). Each body carries estimateCents and the route or page that lifts the block; the three shapes are quoted in api-for-accounts.md.
  • Routes: GET/PUT /api/account/usage-limits, POST /api/account/usage-limits/increase-requests, /approvals, /features. MCP: usage_limits_get, usage_limits_update, usage_limit_increase_request, usage_limit_increase_withdraw, usage_approvals_list, usage_approvals_decide, usage_feature_cap_set, usage_api_key_caps_set.
  • Reads are free but capped: 5000 reads per key per UTC day over both channels, since an MCP tool call reaches the API with your key. Over the cap a read answers 429 with code DAILY_READ_CAP, cap, used, resetsAt and a Retry-After header. Writes are priced instead and not counted here.
  • The MCP server rate-limits each key: 60 calls per minute with a burst of 20 (token bucket). Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-RatePerMin; over the limit it answers 429 with code RATE_LIMIT_EXCEEDED and a Retry-After header. The HTTP API has no rate limit of its own beyond the read cap.

Receipt

After any paid call you can read where you stand:

  • GET /api/account/usage/receipt (needs billing:read): accountId, period, asOf, spentCents, heldCents, budgetCents, budgetRemainingCents, resetsAt, prepaidCents, unbilledCents, cardStatus, cardHeadroomCents, canStartPaidWork, feesCents.
  • MCP: every write tool result carries usageReceipt with the same fields.
  • API: every 2xx answer of a route marked "receipt" in http-routes-for-accounts.md carries the same receipt (without accountId and asOf) in the header X-Linkio-Usage-Receipt (JSON) plus X-Linkio-Usage-Receipt-Url. The header appears when the key holds billing:read; it is read at most once every 15 seconds per account. A 4xx or 5xx answer carries no receipt.
  • GET /api/account/usage gives the full report by day, feature and key.

Own keys

Features a saved key can serve (each lit on the key panel once a check grants it), by provider: OpenAI: Pre-screen (pre-screen), Article generation (article), Topic generation (topic-generation), Market context (market-context), Anchor optimization (anchor-optimization), AI Mentions (ai-mentions), Target page AI (target-page-ai), Exchange page picker (exchange-osg); DataForSEO: Pre-screen (pre-screen), Topic generation (topic-generation), AI Mentions (ai-mentions), Site metrics (site-metrics), Backlink data (backlink-data). The exchange page picker is opportunity_osg_run; site metrics is dataforseo_site_metrics; backlink data is target_page_fetch_backlinks and client_excluded_domains_auto_import. Keyword extraction (client_llm_extract_keywords and friends) and the search-query brief run on the AI Mentions and target page AI grants. When the work ran on your key the answer says onOwnKey: true and the provider cost is not charged; the sync citation routes (citation_check_trigger, the one-off check) stay on Linkio keys because their per-check reserve is priced for them — use client_llm_check_async to run checks on your own keys.

  • Add keys in Settings → AI & Data Keys, or POST /api/account/provider-keys; probe with POST /api/account/provider-keys/{id}/probe. The probe reports features (the ids above) and, for OpenAI, builds your private copy of the Semantic SEO store.
  • When your key fails mid-run (invalid, quota, rate limit): a step falls back to Linkio's key and credits when the balance allows, else it stops with a plain message naming the key page. Fix the key and resume.
  • A transient provider error is retried 3 times, waiting 30, 120, 300 seconds between attempts; if the step still fails, the article pauses, you get an email with a Resume link, and one automatic resume runs an hour later.

Price before the click

GET /api/account/usage/estimate?feature=<article|topic_generation|target_page_ai|ai_find_sites|website_search|site_metrics>&count=N (MCP: usage_estimate) answers what the meter would hold if you started count units now: { feature, count, estimateCents, perUnitCents, onOwnKey }. It calls the same functions the start routes call before they reserve, so the number is the hold. Read only. Pre-screen keeps its own per-domain estimate (POST .../prescreen-estimate) and AI Mentions its client_llm_check_cost_estimate.

In the app the same line sits under every paid start button ("This will hold about $X; you have $Y prepaid · card on file"); on an account that cannot fund the job it says so and links to add money, a card or your own keys.

What you paid for

GET /api/account/usage/jobs?period=YYYY-MM&limit=N (MCP: usage_jobs) lists the month's charges one per row, newest first: { id, at, feature, label, keySource: 'yours' | 'linkio' | 'unknown', providerCostCents, feeCents, chargedCents, adjustment, apiKeyId, apiKeyName, ref }. keySource: 'yours' means your own provider key ran the job: the provider billed you directly and Linkio charged only feeCents. The rows are the ones GET /api/account/usage sums, so the totals match the report. The billing page shows the same table under Usage.

Website search price

A topic or filter search (POST /api/internal/websites/search, MCP website_search) costs 0.25c per site returned after the first 100 sites of the UTC day, rounded up to whole cents per search (a 20-site page costs at most 5c). Domain lookups are free. The answer carries billing: { sitesReturned, billableSites, freeUsed, chargedCents, shortfallCents, freeRemainingToday, perSiteCents } (shortfallCents is the part the balance could not cover); usage_jobs lists each paid search ("Site search: …"); GET /api/account/usage/estimate?feature=website_search&count=20 (MCP usage_estimate) is the price before the click. A search the account cannot fund answers 402 like any other paid start; the monthly budget and the per-feature cap (prospect_data) apply. The route is self-serve only: a managed account's key answers 403 with code ACCOUNT_TYPE_DENIED.

AI Find Sites price

A full AI Find Sites run (ai_find_sites_start, MCP usage_estimate feature ai_find_sites) on a self-serve account pays a run fee of 5c per completed client loop, whatever keys it runs on, plus its discovery calls at provider cost plus 20% on Linkio's OpenAI key; the pre-screen of each found site runs on your own keys when both are live (then only the pre-screen fee applies) and on Linkio's keys otherwise. The estimate before the click carries all of it; the charge is exact and lands in usage_jobs.

Site metrics price

GET /api/internal/dataforseo/site-metrics?domains=a.com,b.com (MCP dataforseo_site_metrics) returns domain rank, referring domains, spam score and organic traffic from DataForSEO for up to 200 domains per call on an account key. It costs 15c per call plus 0.1c per domain, or 1.5c per domain with the organic lookup (includeOrganic, on by default) — DataForSEO bills the link endpoints per call, not per domain — rounded up to whole cents, charged after the call under prospect_data; the answer carries billing: { domains, requestCents, perDomainCents, chargedCents, shortfallCents, onOwnKey } and usage_jobs lists it ("Site metrics: N domains"). On an account whose own DataForSEO login is on file for the site-metrics feature the calls run on that login and cost nothing. GET /api/account/usage/estimate?feature=site_metrics&count=50 is the price before the call.