{"openapi":"3.1.0","info":{"title":"BotFriendly API","description":"A directory of services AI agents can actually use. Query, filter, and discover bot-friendly APIs, tools, and merchants by category, interface (MCP, OpenAPI, llms.txt) and agent payment/commerce protocol.","version":"1.0.0","contact":{"url":"https://botfriendly.xyz"}},"servers":[{"url":"https://botfriendly.xyz/api/v1"}],"paths":{"/services":{"get":{"operationId":"listServices","summary":"List and filter services","description":"Returns a paginated list of bot-friendly services. Supports filtering by category, protocol support, auth method, and search.","parameters":[{"name":"category","in":"query","schema":{"type":"string","enum":["think","see","hear-speak","create","remember","search","browse","communicate","execute","pay","shop","markets","platforms","identify","monitor","other","locate","book","hire","sign","trade","bet","connect","automate"]},"description":"Filter by category id (think, see, hear-speak, create, remember, search, browse, communicate, execute, pay, shop, markets, platforms, identify, monitor, other). A merged category's old slugs (trade, bet, connect, automate, locate, book, sign, hire) are still accepted and select just that slug's listings; the id selects the whole group."},{"name":"has_mcp","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter to services with MCP servers"},{"name":"no_human","in":"query","schema":{"type":"string","enum":["true"]},"description":"Only services an agent can start using with no human setting up a key: probe-backed — its own (non-documentation) MCP server lists its tools to an unauthenticated client and the service needs no key (no auth, or a wallet), or one of its interfaces answers HTTP 402 to be paid per request"},{"name":"accepts_mpp","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter to services accepting MPP payments"},{"name":"accepts_x402","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter to services accepting x402 payments"},{"name":"protocol","in":"query","schema":{"type":"string","enum":["x402","mpp","l402","ap2","tap","agent-pay","ace","acp","ucp","actp","icc"]},"description":"Filter to services supporting this agent payment/commerce protocol. Repeat or comma-separate for 'supports all of these'."},{"name":"has_skill_file","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter to services with skill files"},{"name":"has_llms_txt","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter to services with llms.txt"},{"name":"auth_method","in":"query","schema":{"type":"string","enum":["api-key","m2m-oauth","bot-token","wallet","x402-none","none"]}},{"name":"search","in":"query","schema":{"type":"string"},"description":"Full-text search across name, description, and tags"},{"name":"min_score","in":"query","schema":{"type":"integer","minimum":1,"maximum":5},"description":"Minimum bot-friendly score"},{"name":"promoted","in":"query","schema":{"type":"string","enum":["true","false"]},"description":"Filter by promoted status: true for promoted only, false for organic only"},{"name":"sort","in":"query","schema":{"type":"string","enum":["score","votes","name","newest"],"default":"score"},"description":"Sort order: score (default), votes, name, newest"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"List of services","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"organic_count":{"type":"integer","description":"Number of organic (non-ad) results"},"ad_count":{"type":"integer","description":"Number of ad/promoted results"},"ad_positions":{"type":"array","items":{"type":"integer"},"description":"1-indexed positions of ads in the results array"}}}}}}}}}}},"/services/{slug}":{"get":{"operationId":"getService","summary":"Get a single service by slug","description":"Full details for one listed service: auth method, MCP/OpenAPI/skill-file URLs, supported payment protocols, bot score, and verification status. Slugs come from listServices or searchServices results.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Service slug, e.g. browserbase"}],"responses":{"200":{"description":"Service detail","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Service"}}}}}},"404":{"description":"Service not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/categories":{"get":{"operationId":"listCategories","summary":"List all categories with service counts","description":"Every directory category (think = Models, see = Vision, hear-speak = Speech, create = Media, remember = Memory, search = Search, browse = Browsers, communicate = Messaging, execute = Compute, pay = Payments, shop = Merchants, markets = Markets, platforms = Platforms, identify = Identity, monitor = Observability, other = Other) with its label, description, aliases, and current service count. Category ids are the values accepted by the ?category= filter; aliases are older slugs that are still accepted.","responses":{"200":{"description":"List of categories","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Value for the ?category= filter"},"label":{"type":"string","description":"Human label (Models, Vision, Markets, …)"},"name":{"type":"string","description":"Same as label; kept for older clients"},"icon":{"type":"string"},"description":{"type":"string"},"aliases":{"type":"array","items":{"type":"string"},"description":"Older or merged slugs ?category= also accepts; each selects only its own listings"},"slugs":{"type":"array","items":{"type":"string"},"description":"The category values listings in this group carry"},"service_count":{"type":"integer"}}}}}}}}}}}},"/featured":{"get":{"operationId":"getFeatured","summary":"Get site-wide promoted/featured services (max 3)","description":"Returns site-wide promoted services. These are paid placements clearly marked as promoted.","responses":{"200":{"description":"Featured services","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}},"meta":{"type":"object","properties":{"slots_total":{"type":"integer"},"slots_filled":{"type":"integer"}}}}}}}}}}},"/protocols":{"get":{"operationId":"listProtocols","summary":"List trackable protocols with adoption counts","description":"Returns every protocol on /protocols — how agents call a service (MCP, OpenAPI, A2A), identity and authorization (Cross App Access, AP2, TAP, Agent Pay, ACE), and payment and commerce (x402, MPP, L402, ACP, UCP, ACTP, ICC) — with its name, sponsor, spec URL and how many listed services support it. count is null for A2A and Cross App Access, which aren't listing attributes.","responses":{"200":{"description":"Protocol stats","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Protocol slug; for payment and commerce protocols, also the ?protocol= filter value"},"name":{"type":"string"},"description":{"type":"string"},"layer":{"type":"string","enum":["interface","identity","settlement","authorization","commerce","bridge"]},"spec_url":{"type":"string","format":"uri"},"sponsor":{"type":"string"},"count":{"type":["integer","null"],"description":"Listed services that support it; null when it isn't a listing attribute"}}}}}}}}}}}},"/stats":{"get":{"operationId":"getStats","summary":"Directory statistics","description":"Aggregate stats for the whole directory: total services, per-category counts, and how many services support each protocol. Useful for a quick lay of the land before filtering.","responses":{"200":{"description":"Directory stats including total services and protocol coverage","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"total_services":{"type":"integer"},"categories":{"type":"object","additionalProperties":{"type":"integer"},"description":"Service count per category slug"},"protocols":{"type":"object","additionalProperties":{"type":"integer"},"description":"Service count per supported protocol"}}}}}}}}}}},"/verify":{"post":{"operationId":"verifyService","summary":"Check whether a service really exposes the interfaces it claims","description":"Fetches each claimed endpoint and reports what it found. Nothing is written and no auth is required. This is the same check applied to owner-submitted updates and to automated discovery, exposed so an external workflow doesn't have to reimplement it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"has_mcp_server":{"type":"boolean"},"mcp_server_url":{"type":"string"},"has_openapi_spec":{"type":"boolean"},"openapi_spec_url":{"type":"string"},"has_api":{"type":"boolean"},"api_url":{"type":"string","description":"A live API base URL (api.example.com, or a path under /api or /v1) that answers in JSON"},"has_skill_file":{"type":"boolean"},"skill_file_url":{"type":"string"},"has_llms_txt":{"type":"boolean"},"llms_txt_url":{"type":"string"},"has_structured_feed":{"type":"boolean"},"structured_feed_url":{"type":"string"}}}}}},"responses":{"200":{"description":"Probe verdicts per claimed interface, plus `listable` (true when an MCP server, OpenAPI spec or API completed a handshake and the URL is a product rather than an article; `not_listable_reason` says why when false). Per-domain limits are only applied at submission."},"400":{"description":"Validation error"},"429":{"description":"Rate limit exceeded"}}}},"/submissions":{"post":{"operationId":"submitService","summary":"Submit a new service","description":"Submit a service to the directory. No auth required, and no review queue: each claimed interface URL must sit on the service's own domain or be linked from the service's own site (the listed page or its llms.txt), and we fetch it during the request. If the MCP server, OpenAPI spec or API completes a handshake (MCP initialize succeeds or answers 401 with the MCP OAuth challenge; the spec parses; an API host — api.example.com, or a path under /api or /v1 — answers in JSON), the listing is published immediately and the response carries its slug; otherwise the submission is rejected with 422 and the probe results explaining why. Other claimed badges whose endpoint did not answer are dropped from the published listing rather than blocking it. Listing URLs that look like articles (/blog/, /news/, dated or article-title paths) are rejected, and a domain can add at most 2 listings per 24 hours and 3 in total.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"has_mcp_server, has_openapi_spec or has_api must be true — an interface an agent can call. has_skill_file, has_llms_txt and has_structured_feed add badges but don't qualify a listing on their own.","required":["name","url","category","description"],"properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri"},"docs_url":{"type":"string","format":"uri"},"category":{"type":"string","enum":["think","see","hear-speak","create","remember","search","browse","communicate","execute","pay","shop","markets","platforms","identify","monitor","other","locate","book","hire","sign","trade","bet","connect","automate"],"description":"Category id from /categories (a merged category's old slugs are accepted too)"},"description":{"type":"string","maxLength":120},"icon_url":{"type":"string","format":"uri","description":"Link to the service's icon. Fetched, downscaled to fit 512px, converted to PNG and re-hosted. Max 1MB. If neither icon_url nor icon_base64 is given, the service's own site is checked for a declared icon or a favicon; a listing with no icon at all is rejected."},"icon_base64":{"type":"string","description":"The icon inline instead of by URL — a data: URI or a bare base64 payload, max 1MB decoded. Takes precedence over icon_url."},"auth_method":{"type":"string","enum":["api-key","m2m-oauth","bot-token","wallet","x402-none","none"]},"has_mcp_server":{"type":"boolean"},"mcp_server_url":{"type":"string"},"has_openapi_spec":{"type":"boolean"},"openapi_spec_url":{"type":"string"},"has_api":{"type":"boolean"},"api_url":{"type":"string","description":"A live API base URL (api.example.com, or a path under /api or /v1) that answers in JSON"},"accepts_mpp":{"type":"boolean"},"accepts_x402":{"type":"boolean"},"payment_protocols":{"type":"array","items":{"type":"string","enum":["x402","mpp","l402","ap2","tap","agent-pay","ace","acp","ucp","actp","icc"]}},"has_skill_file":{"type":"boolean"},"skill_file_url":{"type":"string"},"has_llms_txt":{"type":"boolean"},"llms_txt_url":{"type":"string"},"has_structured_feed":{"type":"boolean"},"structured_feed_url":{"type":"string"},"billing_model":{"type":"string","enum":["pay-per-request","pay-per-token","pay-per-second","subscription","per-transaction","free"]},"has_free_tier":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"contact_email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Accepted. status is 'approved' when the listing published (slug and url are set) or 'pending' in the rare case it was held for a look.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["approved","pending"]},"created_at":{"type":"string","format":"date-time"},"slug":{"type":["string","null"],"description":"Set when the listing published."},"url":{"type":["string","null"],"description":"Canonical listing URL when published."},"probes":{"type":"array","description":"What we fetched for each claimed interface, and what came back.","items":{"$ref":"#/components/schemas/Probe"}},"dropped_flags":{"type":"array","description":"Badges that were claimed but whose endpoint did not answer, so they are not on the listing.","items":{"type":"string"}},"icon":{"type":"object","description":"The stored icon and where it came from.","properties":{"source":{"type":"string","enum":["icon_url","icon_base64","discovered"]},"found_at":{"type":["string","null"],"description":"Set when the icon was found on the service's own site."},"width":{"type":"integer"},"height":{"type":"integer"}}}}},"meta":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"400":{"description":"Validation error — missing fields, bad category, neither has_mcp_server nor has_openapi_spec set (code no_callable_interface), a ticked badge with no URL, an interface URL on a different domain from the service, or no usable icon (icon_url unreachable, icon_base64 undecodable, and nothing found on the site)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Not published. `error.code` names the rule: content_url (the URL is an article, not a product), domain_daily_limit (2 listings from this domain in the last 24h), domain_listing_limit (the domain already has 3 listings), no_callable_interface (no MCP server, OpenAPI spec or API completed a handshake), nothing_answered, interface_url_invalid, or admission_unavailable (try again shortly). The message says what was tried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Submission rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/search":{"get":{"operationId":"searchServices","summary":"Full-text search across services","description":"Search services by keyword. Returns results ranked by bot_score.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"},"description":"Search query (full-text, websearch syntax)"},{"name":"category","in":"query","schema":{"type":"string","enum":["think","see","hear-speak","create","remember","search","browse","communicate","execute","pay","shop","markets","platforms","identify","monitor","other","locate","book","hire","sign","trade","bet","connect","automate"]},"description":"Filter by category id (same rules as /services)"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}},"meta":{"type":"object","properties":{"query":{"type":"string"},"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"}}}}}}}},"400":{"description":"Missing required query parameter 'q'"}}}},"/health":{"get":{"operationId":"healthCheck","summary":"Health check and endpoint discovery","description":"Liveness check that doubles as an index of every machine-readable surface the site offers — REST endpoints, MCP server, llms.txt, and discovery manifests. Call this first when exploring the API.","responses":{"200":{"description":"Service health and available endpoints","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"version":{"type":"string"},"services_count":{"type":"integer"},"last_updated":{"type":"string","format":"date-time"},"endpoints":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of endpoint name to path"}}}}}}}}},"/check":{"get":{"operationId":"checkSite","summary":"Score how bot-friendly a site is","description":"Fetches the target site the way an AI agent would (no JavaScript) and scores access, no-JS content, and machine discovery out of 100, with per-check advice. Rate limited to 6 checks/min per IP.","parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string"},"description":"Site to check (scheme optional, https assumed)"},{"name":"format","in":"query","schema":{"type":"string","enum":["json","prompt"],"default":"json"},"description":"prompt returns a ready-to-paste coding-agent prompt (text/markdown) covering every failed or partial finding, instead of the JSON report"}],"responses":{"200":{"description":"Check report","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input_url":{"type":"string"},"final_url":{"type":"string"},"checked_at":{"type":"string","format":"date-time"},"duration_ms":{"type":"integer"},"score":{"type":"integer","minimum":0,"maximum":100},"max_score":{"type":"integer"},"verdict":{"type":"string"},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["access","content","discovery"]},"label":{"type":"string"},"earned":{"type":"number"},"possible":{"type":"number"},"passed":{"type":"integer"},"total":{"type":"integer"}}}},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"category":{"type":"string","enum":["access","content","discovery"]},"status":{"type":"string","enum":["pass","warn","fail","skip"]},"weight":{"type":"number"},"bonus":{"type":"number"},"detail":{"type":"string"},"advice":{"type":"string"}}}}}}}}}}},"400":{"description":"Missing, invalid, or blocked URL"},"429":{"description":"Rate limit exceeded (6 checks/min per IP)"}}}}},"components":{"schemas":{"Probe":{"type":"object","description":"The result of fetching one claimed interface URL.","properties":{"flag":{"type":"string","description":"Which badge was checked."},"url":{"type":["string","null"],"description":"What we fetched."},"verdict":{"type":"string","enum":["pass","responded","fail"],"description":"pass: the artifact was fetched and is what it claims to be. responded: a server answered but would not show it (401). fail: nothing there, or provably not the artifact."},"detail":{"type":"string"},"status":{"type":"integer"},"checkedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","description":"Standard error envelope returned by every non-2xx response","properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"hint":{"type":"string"}},"required":["message"]}}},"Service":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","description":"Category slug the listing carries; map it to a label via /categories (slugs array)"},"url":{"type":"string","format":"uri"},"docs_url":{"type":["string","null"],"format":"uri"},"icon_url":{"type":["string","null"],"format":"uri","description":"Service icon URL"},"auth_method":{"type":["string","null"],"enum":["api-key","m2m-oauth","bot-token","wallet","x402-none","none"]},"has_mcp_server":{"type":"boolean"},"mcp_server_url":{"type":["string","null"]},"has_openapi_spec":{"type":"boolean"},"openapi_spec_url":{"type":["string","null"]},"has_api":{"type":"boolean","description":"A live API host answered in JSON when probed"},"api_url":{"type":["string","null"]},"no_human_needed":{"type":"boolean","description":"Probe-backed: the service's own (non-documentation) MCP server listed its tools to an unauthenticated client and its auth needs no key (none or wallet), or a callable interface answered HTTP 402. An agent can start with nobody setting up a key"},"payment_protocols":{"type":"array","items":{"type":"string","enum":["x402","mpp","l402","ap2","tap","agent-pay","ace","acp","ucp","actp","icc"]},"description":"Agent payment / commerce protocols this service supports"},"accepts_mpp":{"type":"boolean","description":"Derived from payment_protocols"},"accepts_x402":{"type":"boolean","description":"Derived from payment_protocols"},"supports_acp":{"type":"boolean","description":"Derived from payment_protocols"},"supports_ucp":{"type":"boolean","description":"Derived from payment_protocols"},"has_skill_file":{"type":"boolean"},"skill_file_url":{"type":["string","null"]},"has_llms_txt":{"type":"boolean"},"llms_txt_url":{"type":["string","null"]},"has_structured_feed":{"type":"boolean"},"structured_feed_url":{"type":["string","null"]},"billing_model":{"type":["string","null"]},"has_free_tier":{"type":"boolean"},"has_webhooks":{"type":"boolean"},"programmatic_onboarding":{"type":["string","null"],"enum":["full","partial","none"]},"bot_friendly_tos":{"type":["string","null"],"enum":["explicit-welcome","neutral","restricts-bots","unknown"]},"bot_score":{"type":"integer","minimum":1,"maximum":5},"vote_count":{"type":"integer","description":"Community upvote count"},"tags":{"type":"array","items":{"type":"string"}},"is_pro":{"type":"boolean"},"is_verified":{"type":"boolean"},"verified_interfaces":{"type":"array","items":{"type":"string"},"description":"The machine-readable interface flags we have actually fetched and confirmed. A flag that is true on the service but absent here is claimed rather than verified — the listing says it exists and nothing has reached it yet. Prefer a verified interface when choosing how to call a service."},"promoted":{"type":"boolean","description":"Whether this is a paid promoted placement (ad)"},"ad":{"type":"boolean","description":"Whether this is an ad (redundant with promoted for compatibility)"},"promotion_type":{"type":["string","null"],"enum":["site","category"],"description":"Type of promotion if promoted"},"created_at":{"type":"string","format":"date-time"},"last_verified_at":{"type":["string","null"],"format":"date-time"}}}}}}