{"info":{"contact":{"email":"support@linkdr.com","name":"LinkDR support","url":"https://linkdr.com/contact"},"description":"LinkDR is a managed backlink service: real publisher placements priced by Domain Rating (DR), ordered one-time or monthly, fulfilled in 3-4 weeks.\n\nAgents have two doors:\n\n1. Managed backlinks (no auth needed to plan): fetch https://linkdr.com/SKILL.md, plan the DR-tier mix from the budget, then send the user one https://linkdr.com/agent/checkout URL. There is no checkout API by design - the user logs in and pays in the browser.\n2. Research data (admin accounts): the MCP server at https://linkdr.com/mcp exposes six cache-first read tools over Streamable HTTP with OAuth 2.1. Non-admin accounts complete OAuth but receive 403 from every tool.\n\nMachine-readable resources: this spec (https://linkdr.com/openapi.json), agent instructions (https://linkdr.com/llms.txt), the checkout skill (https://linkdr.com/SKILL.md), and docs (https://linkdr.com/docs).","termsOfService":"https://linkdr.com/terms","title":"LinkDR API","version":"1.0.0"},"jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","openapi":"3.1.0","paths":{"/SKILL.md":{"get":{"description":"The LinkDR link-building agent skill (Agent Skills format). Fetch before quoting prices or building checkout URLs. Served as text/markdown by default; send Accept: text/plain for plain text. Complete only if the last line reads END SKILL v<version>.","operationId":"getAgentSkill","responses":{"200":{"content":{"text/markdown":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}}},"description":"Skill file"},"406":{"description":"Accept header matches neither text/markdown nor text/plain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Fetch the managed-backlinks agent skill","tags":["Agent resources"]}},"/api/domain/{domain}":{"get":{"description":"Cached marketplace metrics for one host: Domain Rating, monthly organic traffic, screenshot path, site name, and meta description. Values are null when the host is not in the LinkDR domain database. Rate limit: 20 requests/hour anonymous, 200/hour authenticated.","operationId":"getDomainQuickInfo","parameters":[{"description":"Host name, e.g. example.com. Subdomains and schemes are tolerated and reduced to the registrable domain.","examples":{"example":{"value":"backlinko.com"}},"in":"path","name":"domain","required":true,"schema":{"maxLength":253,"minLength":1,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainQuickInfo"}}},"description":"Metrics (nulls when unknown)"},"400":{"description":"Invalid domain format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Look up DR, traffic, and metadata for one domain","tags":["Public data"]}},"/api/health":{"get":{"description":"Liveness of database, Stripe, Resend, and the domain checker. Returns 200 when healthy, 500 with the failing services listed otherwise. No authentication.","operationId":"getHealth","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}},"description":"Service health"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}},"description":"One or more services unhealthy"}},"summary":"Check service health","tags":["System"]}},"/llms.txt":{"get":{"description":"Site map and guidance for AI agents: when to use LinkDR, both integration doors, developer resources, and an index of pages worth fetching.","operationId":"getLlmsTxt","responses":{"200":{"content":{"text/plain":{"schema":{"type":"string"}}},"description":"llms.txt content"}},"summary":"Fetch the llms.txt agent index","tags":["Agent resources"]}},"/markdown/{path}":{"get":{"description":"Markdown rendering of any public marketing or docs page (for example /markdown/pricing or /markdown/docs/get-started). Also reachable without leaving HTML by sending Accept: text/markdown to the page URL itself. Unknown paths return HTTP 404 with recovery links in the body.","operationId":"getPageMarkdown","parameters":[{"description":"Page path without the leading slash, e.g. pricing or docs/tools/get-domain.","in":"path","name":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"text/markdown":{"schema":{"type":"string"}}},"description":"Markdown representation of the page"},"404":{"content":{"text/markdown":{"schema":{"type":"string"}}},"description":"No markdown for this path; body lists recovery links"}},"summary":"Fetch a page as Markdown","tags":["Agent resources"]}},"/mcp":{"post":{"description":"Model Context Protocol endpoint over Streamable HTTP (JSON-RPC 2.0; POST messages, optional SSE streams).\n\nTools (all cache-first reads): get_domain, search_keywords, get_keyword_serp, search_creators, get_contacts, search_domains. Tool details: https://linkdr.com/docs/tools.\n\nAuth: OAuth 2.1 via Better Auth. Discover https://linkdr.com/.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server first; unauthenticated requests receive 401 with WWW-Authenticate. Dynamic client registration is supported. The signed-in account must have the admin role; other roles receive 403 FORBIDDEN on every tool. Rate limit: 120 requests/hour per user (429 with Retry-After).","operationId":"mcpStreamableHttp","requestBody":{"description":"JSON-RPC 2.0 request","content":{"application/json":{"schema":{"description":"A single MCP JSON-RPC 2.0 message (initialize, tools/list, tools/call, ...)","properties":{"id":{"type":["integer","string"]},"jsonrpc":{"const":"2.0","type":"string"},"method":{"examples":["initialize","tools/list","tools/call"],"type":"string"},"params":{"type":"object"}},"required":["jsonrpc","method"],"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}},"text/event-stream":{"schema":{"type":"string"}}},"description":"JSON-RPC result (or SSE stream for requests that opt in)"},"400":{"description":"Malformed JSON-RPC message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or expired OAuth token; carries WWW-Authenticate pointing at the protected resource metadata"},"403":{"description":"Origin not allowed, or account lacks the admin role","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Research MCP disabled via MCP_DATA_ENABLED=false","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Hourly rate limit exceeded; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"McpOAuth":["openid","email"]}],"summary":"Research MCP server (Streamable HTTP)","tags":["Research MCP"]}},"/openapi.json":{"get":{"description":"This document. Fetch it to discover the API surface programmatically.","operationId":"getOpenApiSpec","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OpenAPI 3.1 specification"}},"summary":"Fetch this OpenAPI specification","tags":["System"]}},"/rpc/domains/quickInfo":{"get":{"description":"Same data as the POST variant for clients that prefer query encoding of the input string.","operationId":"rpcQuickInfoViaGet","parameters":[{"description":"URL-encoded input string (the domain).","in":"query","name":"input","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainQuickInfo"}}},"description":"Metrics (nulls when unknown)"}},"security":[{"SessionCookie":[]}],"summary":"Domain quick info over oRPC GET","tags":["Public data"]},"post":{"description":"oRPC procedure domains.quickInfo. Same payload as GET /api/domain/{domain}; kept for clients already speaking the oRPC protocol used by the dashboard. Body is the raw input string encoded as JSON.","operationId":"rpcQuickInfo","requestBody":{"description":"Domain, e.g. \"example.com\"","content":{"application/json":{"schema":{"type":"string"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainQuickInfo"}}},"description":"Metrics (nulls when unknown)"},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"SessionCookie":[]}],"summary":"Domain quick info over oRPC","tags":["Public data"]}},"/rpc/domainLists/getPublicList":{"post":{"description":"oRPC procedure domainLists.getPublicList. Reads one public curated domain list (marketplace collections such as niche directories). Private lists return an error. Contact fields (emails, prices, notes) are stripped from the response by design.","operationId":"rpcGetPublicList","requestBody":{"description":"Public list id","content":{"application/json":{"schema":{"type":"string"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDomainList"}}},"description":"Public list with items"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"List not found or not public","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"SessionCookie":[]}],"summary":"Read one public domain list over oRPC","tags":["Public data"]}}},"servers":[{"description":"Production","url":"https://linkdr.com"}],"tags":[{"description":"Managed-backlink skill, llms.txt index, and per-page markdown negotiation.","name":"Agent resources"},{"description":"Cache-first research tools behind OAuth 2.1 (admin accounts).","name":"Research MCP"},{"description":"Read-only endpoints that work without an account, subject to rate limits.","name":"Public data"},{"description":"Health and machine-readable metadata.","name":"System"}],"components":{"schemas":{"DomainQuickInfo":{"description":"Marketplace metrics for one host; any field may be null when unknown.","properties":{"description":{"description":"Meta description of the site.","type":["string","null"]},"domainRating":{"description":"Ahrefs-style Domain Rating, 0-100.","type":["integer","null"]},"domainTraffic":{"description":"Monthly organic traffic estimate.","type":["integer","null"]},"name":{"description":"Site display name.","type":["string","null"]},"screenshotPath":{"description":"Path to a cached homepage screenshot, relative to the site origin.","type":["string","null"]}},"type":"object"},"Error":{"description":"Structured error returned by all /api and /rpc failures. Agents should read code and hint.","properties":{"error":{"properties":{"code":{"description":"Stable machine-readable code.","enum":["BAD_REQUEST","NOT_FOUND","METHOD_NOT_ALLOWED","UNAUTHORIZED","FORBIDDEN","RATE_LIMIT_EXCEEDED","INTERNAL_ERROR"],"type":"string"},"hint":{"description":"Resolution hint for the caller.","type":"string"},"message":{"description":"Human-readable summary.","type":"string"},"status":{"description":"HTTP status repeated in the body.","type":"integer"}},"required":["code","message","status"],"type":"object"}},"type":"object"},"HealthStatus":{"properties":{"healthy":{"type":"boolean"},"services":{"properties":{"database":{"type":"boolean"},"env":{"type":"boolean"},"resend":{"type":"boolean"},"stripe":{"type":"boolean"}},"type":"object"},"timestamp":{"type":"string"},"version":{"type":"string"}},"type":"object"},"JsonRpcResponse":{"description":"MCP JSON-RPC 2.0 response envelope.","properties":{"error":{"type":"object"},"id":{"type":["integer","string"]},"jsonrpc":{"const":"2.0","type":"string"},"result":{"type":"object"}},"required":["jsonrpc"],"type":"object"},"PublicDomainList":{"properties":{"creator":{"properties":{"email":{"type":["string","null"]},"id":{"type":"string"},"image":{"type":["string","null"]},"name":{"type":["string","null"]}},"type":"object"},"description":{"type":["string","null"]},"emoji":{"type":["string","null"]},"id":{"type":"string"},"isPublic":{"type":"boolean"},"items":{"items":{"properties":{"backlinks":{"type":["number","null"]},"createdAt":{"type":"string"},"domain":{"type":"string"},"domainListId":{"type":"string"},"domainRating":{"type":["number","null"]},"id":{"type":"string"},"organicKeywords":{"type":["number","null"]},"refDomains":{"type":["number","null"]},"traffic":{"type":["string","null"]},"trafficValue":{"type":["number","null"]},"url":{"type":"string"}},"type":"object"},"type":"array"},"name":{"type":"string"},"page":{"type":"number"},"pageSize":{"type":"number"},"totalCount":{"type":"number"},"totalPages":{"type":"number"}},"type":"object"}},"securitySchemes":{"McpOAuth":{"description":"OAuth 2.1 authorization-code flow with PKCE (required) for the research MCP. Authorization server metadata: https://linkdr.com/.well-known/oauth-authorization-server. Dynamic client registration is accepted. Requesting fewer scopes limits consent surface; research tool calls additionally require the admin role on the LinkDR account.","flows":{"authorizationCode":{"authorizationUrl":"/api/auth/oauth2/authorize","refreshUrl":"/api/auth/oauth2/token","scopes":{"email":"Identify the account by email address during sign-in","offline_access":"Receive a refresh token so long-running agent sessions stay connected until revoked","openid":"Sign in with a LinkDR account","profile":"Read the account display name"},"tokenUrl":"/api/auth/oauth2/token"}},"type":"oauth2"},"SessionCookie":{"description":"Dashboard session cookie (better-auth.session_token) issued by browser sign-in. Endpoints under this scheme serve the logged-in app and its rate limits; there is no header-token alternative yet.","in":"cookie","name":"better-auth.session_token","type":"apiKey"}}}}