{"openapi":"3.1.0","info":{"title":"CitedIndex — machine API","version":"1.0.0","description":"The neutral index of AI-visibility and GEO tools: which ones track whether ChatGPT, Perplexity, Gemini and AI Overviews cite your brand. Researched and compared. Free, keyless read endpoints over the tools corpus, plus verified-session submission intake. CitedIndex Data Terms (https://citedindex.com/terms): Search engines and AI assistants may read CitedIndex and quote it in their answers, and AI models may be trained on it. Credit CitedIndex and link to https://citedindex.com when you quote it.","license":{"name":"CitedIndex Data Terms","url":"https://citedindex.com/terms"}},"servers":[{"url":"https://citedindex.com"}],"paths":{"/api/search":{"get":{"operationId":"searchListings","summary":"Full-text + faceted search over the Published tools","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"},"description":"What to search for."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":25}}],"responses":{"200":{"description":"Matching cards.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}}}}},"/listings.json":{"get":{"operationId":"listListings","summary":"Every Published tool as a structured export record","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque cursor from a previous page (`nextCursor`). Omit for the first page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. Omit BOTH cursor and limit for the full unpaginated export."}],"responses":{"200":{"description":"The export page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListingsExport"}}}}}}},"/{slug}.md":{"get":{"operationId":"getListingMarkdown","summary":"One tool as its answer-first markdown twin","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The markdown twin. `X-Markdown-Tokens` estimates its token cost.","content":{"text/markdown":{"schema":{"type":"string"}}},"headers":{"X-Markdown-Tokens":{"schema":{"type":"integer"},"description":"ceil(chars/4) token estimate of this body."},"X-License":{"schema":{"type":"string"},"description":"License URL for reuse of the data."}}},"404":{"description":"No Published tool with that slug."}}}},"/{slug}/alternatives.md":{"get":{"operationId":"getAlternativesMarkdown","summary":"A ranked \"alternatives to this tool\" twin: the competing tools + a facts table","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The alternatives twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No alternatives page for that tool."}}}},"/category/{slug}.md":{"get":{"operationId":"getCategoryMarkdown","summary":"A category answer sheet: capsule + facts table of every member","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The category twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Unknown category."}}}},"/category/{slug}.json":{"get":{"operationId":"getCategoryExport","summary":"A category as a structured JSON export","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The category export.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Unknown category."}}}},"/guides/{slug}.md":{"get":{"operationId":"getGuideMarkdown","summary":"A guide as its answer-first markdown twin: the prose, what it covers and its FAQ","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The guide twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Unknown guide."}}}},"/collections/{slug}.md":{"get":{"operationId":"getCollectionMarkdown","summary":"A collection answer sheet: the curated tools of one intersection + a facts table","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The collection twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Unknown collection."}}}},"/blog.md":{"get":{"operationId":"getBlogIndexMarkdown","summary":"The editorial index as one twin: every post with its date and capsule","responses":{"200":{"description":"The editorial index twin.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/blog/{slug}.md":{"get":{"operationId":"getBlogPostMarkdown","summary":"One editorial post as its answer-first markdown twin","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The post twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Unknown post."}}}},"/methodology.md":{"get":{"operationId":"getMethodologyMarkdown","summary":"How we rank","responses":{"200":{"description":"The methodology twin.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/llms.txt":{"get":{"operationId":"getLlmsTxt","summary":"The agent manifest — read this first: every machine surface, the terms, the twin map","responses":{"200":{"description":"The manifest.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms-full.txt":{"get":{"operationId":"getLlmsFullTxt","summary":"The WHOLE Published tools corpus as one answer-first markdown document","responses":{"200":{"description":"Every Published record inlined — one fetch instead of N.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/index.json":{"get":{"operationId":"getMachineManifest","summary":"The machine manifest: every machine surface, live counts, license, attribution","responses":{"200":{"description":"The manifest.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/submit":{"post":{"operationId":"submitListing","summary":"Submit a tool (verified account session; Draft pending review)","description":"Requires an existing signed-in account with verified email. Sign in or create an account explicitly at https://citedindex.com/login?next=%2Fsubmit, verify its email, then send its session cookie. Identity and email come only from that session; submission never creates an account or sends verification email. Missing session returns 401 sign_in_required with an actionable loginUrl; unverified email returns 403 email_unverified with email and remediation. Neither refusal stores a listing or sends mail. Submissions come from the company itself: the account's address, or an address it proved with a code through /api/domain-proof/start and /api/domain-proof/verify, must be on the website's domain; otherwise the answer is 403 domain_unverified with the domain and remediation. A code to an address at the domain is the only proof. A research pass + quality gate decide publication; the Draft cannot inject page copy or self-publish. The fit check starts at once and usually answers within a minute: poll GET https://citedindex.com/api/submissions/{id} and wait the Retry-After seconds it sends while fit is \"checking\". fit then reads \"accepted\" (it goes live, and the account is mailed), \"rejected\" (mailed with the reason) or \"undecided\" (a person looks within 2 days). Authentication and verification precede idempotency: your own (url, session email) replay within 24h returns the existing draft (200). An Idempotency-Key header is accepted; the effective dedupe key is (url, session email).","security":[{"sessionCookie":[]},{"agentKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Accepted for convention; own replay dedupe is by (url, session email) within 24h regardless."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitInput"}}}},"responses":{"200":{"description":"Deduplicated — your existing draft for this (url, session email).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionStatus"}}}},"201":{"description":"Draft created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionStatus"}}}},"400":{"description":"Validation failed (bad url/name).","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Sign in or create an account explicitly at loginUrl, then verify email and retry. No account, mail or listing created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInRequired"}}}},"403":{"description":"email_unverified: verify the session account email and retry. domain_unverified: prove an address at the returned domain with a code through /api/domain-proof/start and /verify, then retry. No listing or mail created.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EmailUnverified"},{"$ref":"#/components/schemas/DomainUnverified"}]}}}},"409":{"description":"Already listed / duplicate, or free_limit: the account has its free listings (one a day, two in all).","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/domain-proof/start":{"post":{"operationId":"startDomainProof","summary":"Mail a 6-digit code to an address at the domain of the website you want to submit","description":"The first step past a 403 domain_unverified from /api/submit. Requires the same verified session. The address must be on the website's domain (a personal mailbox never is). Answers {proofId, sentTo, domain, alreadyVerified}; alreadyVerified:true means this account had proven the domain already and no mail was sent. Codes expire after 30 minutes; sends are capped per address and per account per day.","security":[{"sessionCookie":[]},{"agentKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","email"],"properties":{"url":{"type":"string","description":"The website being submitted."},"email":{"type":"string","format":"email","description":"An address at that website's domain."}}}}}},"responses":{"200":{"description":"Code sent (or the domain was already proven).","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid url or email, or the address is not on the website's domain.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Sign in first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInRequired"}}}},"403":{"description":"Unverified account email, or a blocked identity.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Too many codes requested.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/domain-proof/verify":{"post":{"operationId":"verifyDomainProof","summary":"Redeem the code; the account may then submit websites on that domain","security":[{"sessionCookie":[]},{"agentKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["proofId","code"],"properties":{"proofId":{"type":"string"},"code":{"type":"string","pattern":"^[0-9]{6}$"}}}}}},"responses":{"200":{"description":"{verified: true, domain} — submit again.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Wrong code.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Unknown proof for this account.","content":{"application/json":{"schema":{"type":"object"}}}},"409":{"description":"The code is no longer open; request a new one.","content":{"application/json":{"schema":{"type":"object"}}}},"410":{"description":"The code expired; request a new one.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Too many tries; request a new code.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/submissions/{id}":{"get":{"operationId":"getSubmissionStatus","summary":"The status of a submission by its (unguessable) id","description":"The fit check starts at once and usually answers within a minute: poll GET https://citedindex.com/api/submissions/{id} and wait the Retry-After seconds it sends while fit is \"checking\". fit then reads \"accepted\" (it goes live, and the account is mailed), \"rejected\" (mailed with the reason) or \"undecided\" (a person looks within 2 days).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionStatus"}}},"headers":{"Retry-After":{"description":"Sent only while fit is \"checking\": the seconds to wait before asking again.","schema":{"type":"integer"}}}},"404":{"description":"Unknown id."}}}},"/api/lead":{"post":{"operationId":"submitLead","summary":"Send a qualified buyer-intent enquiry (lead) to a listing or category","description":"A buyer-intent enquiry routed to the listing owner (or a category's providers). Keyless and CSRF-free when sent as application/json. The quality gate requires consent=true, a real name, a permanent (non-disposable) email, a message of at least 20 chars, and a target (listingSlug OR categorySlug). The same (email + target) within 7 days is idempotent (200, duplicate:true). Nothing here changes a listing's rank, tier or copy.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadInput"}}}},"responses":{"200":{"description":"Accepted (routed or deduplicated): { ok:true, delivered }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Rejected by the quality gate: { ok:false, error } (e.g. no_consent, thin_message, bad_email, no_target).","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/newsletter/subscribe":{"post":{"operationId":"subscribeNewsletter","summary":"Single opt-in newsletter subscribe (form-encoded)","description":"Single opt-in email capture for the directory digest. Content-Type MUST be application/x-www-form-urlencoded (or multipart/form-data); because it is a form POST, Astro's cross-origin protection requires a same-origin Origin header. Field: email (required); optional source, interest. Responds with a self-contained text/html confirmation page (200). The intake is rate-limited per IP per hour.","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"source":{"type":"string","description":"Where the signup happened (optional)."},"interest":{"type":"string","description":"Optional page-context interest tag."}}}}}},"responses":{"200":{"description":"A text/html confirmation page.","content":{"text/html":{"schema":{"type":"string"}}}},"400":{"description":"Invalid email — a text/html page explaining the fix."},"404":{"description":"Newsletter disabled for this tenant."}}}},"/api/owner/listings":{"get":{"operationId":"getOwnerListings","summary":"The owner dashboard's data for the listings the account owns","description":"Each listing's status (in review with the expected date, accepted and going live on a day, live, or not accepted with the reason), the listing as it shows, 30 days of numbers with the 30 before, where AI answers named it or cited its page, and what it can buy here. Only for the account itself: its session, or an agent key with the read scope. Read-only. Same answer as the MCP tool my_listings.","parameters":[{"name":"slug","in":"query","required":false,"schema":{"type":"string"},"description":"One listing, by its slug."}],"security":[{"sessionCookie":[]},{"agentKey":[]}],"responses":{"200":{"description":"The account's listings, or the one named.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No session, or no agent key with the read scope."},"404":{"description":"The account owns no listing by that slug."},"429":{"description":"Too many reads this hour; the answer says when to come back."}}}},"/mcp":{"post":{"operationId":"mcp","summary":"The MCP door — stateless JSON-RPC 2.0 POST (initialize / tools/list / tools/call)","description":"Read tools need no authentication. submit_listing requires a verified account session cookie, or an agent key (Authorization: Bearer) the account made at https://citedindex.com/dashboard/agents, on this request; sign in or explicitly create an account at https://citedindex.com/login?next=%2Fsubmit. Its JSON-RPC text content reports the submission httpStatus (401 with loginUrl, 403 with email/remediation, or 201/200 on success).","security":[{},{"sessionCookie":[]},{"agentKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"JSON-RPC response.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/compare/{pair}.md":{"get":{"operationId":"getCompareMarkdown","summary":"A head-to-head comparison twin ({a}-vs-{b}): facts table + verdict","parameters":[{"name":"pair","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The compare twin.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Unknown pair."}}}},"/best/{category}/{tag}.md":{"get":{"operationId":"getBestOfMarkdown","summary":"A ranked best-of intersection twin (the tools in {category} that are {tag})","parameters":[{"name":"category","in":"path","required":true,"schema":{"type":"string"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The best-of twin: facts table + verdict.","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No page for that intersection."}}}}},"components":{"securitySchemes":{"agentKey":{"type":"http","scheme":"bearer","description":"An agent key the account made at https://citedindex.com/dashboard/agents (it starts with mak_). It acts as that account on submit, the claim and domain-proof steps, quotes, orders and reading its own listings, each by the scope the key was given, with the same refusals as its session; it cannot sign in, change the account or pay."},"sessionCookie":{"type":"apiKey","in":"cookie","name":"__Secure-better-auth.session_token","description":"Existing Better Auth session cookie issued through https://citedindex.com/login?next=%2Fsubmit. The account email must be verified for submission. This is a session cookie, not an API key to provision."}},"schemas":{"SearchResult":{"type":"object","properties":{"cards":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"},"textMatched":{"type":"boolean"}}},"ListingExportRecord":{"type":"object","required":["slug","name","url","tier"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"tagline":{"type":["string","null"]},"url":{"type":"string","description":"Canonical listing page on this directory."},"websiteUrl":{"type":["string","null"]},"tier":{"type":"string"},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"apiAvailable":{"type":["boolean","null"],"description":"Derived: true when apiAccess is yes OR partial, because the question a boolean answers is whether an API exists at all. Read apiAccess for the qualification."},"apiAccess":{"type":["string","null"],"enum":["yes","partial","no",null],"description":"The sourced answer to \"Can you pull the data into your own stack?\". \"partial\" means an API exists but is gated to a higher plan."},"pricing":{"type":["string","null"],"description":"The entry price as a prose fragment, from the typed price columns: \"from $99/month\", \"free, then $189/month\", \"on quote\" for a vendor whose price is researched and published nowhere, and null when no price has been collected — never a coarse bucket. ONE FIELD, and it carries the billing commitment when the figure requires one: \"from $2,417/month billed annually\" is a yearly contract divided by twelve, not a month-to-month rate. Where the row holds a real month-to-month price that is what the figure is, and no term is stated. Do not parse the amount out of this string expecting a monthly rate."},"verdict":{"type":["string","null"]},"lastVerified":{"type":["string","null"],"description":"ISO date of last fact verification."}}},"ListingsExport":{"type":"object","properties":{"directory":{"type":"string"},"url":{"type":"string"},"generatedAt":{"type":"string"},"count":{"type":"integer","description":"Total Published count (not the page size)."},"nextCursor":{"type":["string","null"],"description":"Pass as ?cursor= for the next page; null when exhausted."},"listings":{"type":"array","items":{"$ref":"#/components/schemas/ListingExportRecord"}}}},"SubmitInput":{"type":"object","required":["url"],"description":"The website alone: we write its name and one-line description from its own pages.","properties":{"url":{"type":"string","description":"The website being submitted (https)."}}},"SignInRequired":{"type":"object","required":["error","message","loginUrl"],"properties":{"error":{"type":"string","const":"sign_in_required"},"message":{"type":"string"},"loginUrl":{"type":"string","format":"uri","examples":["https://citedindex.com/login?next=%2Fsubmit"]}}},"EmailUnverified":{"type":"object","required":["error","message","email","remediation"],"properties":{"error":{"type":"string","const":"email_unverified"},"message":{"type":"string"},"email":{"type":"string","format":"email"},"remediation":{"type":"string"}}},"DomainUnverified":{"type":"object","required":["error","message","domain","remediation"],"properties":{"error":{"type":"string","const":"domain_unverified"},"message":{"type":"string"},"domain":{"type":"string","description":"The host the account has not yet proven."},"remediation":{"type":"string"}}},"LeadInput":{"type":"object","required":["name","email","message","consent"],"properties":{"name":{"type":"string","maxLength":120},"email":{"type":"string","description":"A permanent (non-disposable) email."},"message":{"type":"string","minLength":20,"maxLength":4000,"description":"The enquiry — real demand, at least 20 chars."},"consent":{"description":"Must be truthy — the sender agreed their details go to the provider(s).","oneOf":[{"type":"boolean"},{"type":"string"}]},"listingSlug":{"type":"string","description":"Target listing slug (this OR categorySlug is required)."},"categorySlug":{"type":"string","description":"Target category slug (this OR listingSlug is required)."},"company":{"type":"string"},"source":{"type":"string"}}},"SubmissionStatus":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["Draft","Processing","Review","Published","Rejected"]},"submittedAt":{"type":"string","format":"date-time"},"fit":{"type":"string","enum":["checking","accepted","rejected","undecided"],"description":"Where the fit check stands. It runs right after submission and usually answers within a minute; undecided means a person decides it within 2 days."}}}}}}