{"openapi":"3.1.0","jsonSchemaDialect":"https://spec.openapis.org/oas/3.1/dialect/base","info":{"title":"Proofyo public API","version":"1.0.0","summary":"Read-only JSON for SaaS products tested by an AI agent.","description":"Proofyo shows what each SaaS product is actually worth paying for, with evidence our AI agent gathers by signing up, capturing the screens and calling the APIs. This API serves the same published records as the site: every product leads with its value block (valueHeadline and valueKinds on cards, the full `value` on the product) and carries the hands-on `review` (strengths, things to know, pricing reality, compared products, bottom line). Elapsed times and network latency are not published: they measure our test run, not the product.\n\n- Terms (https://proofyo.com/developers#terms): Free to use with attribution: wherever you show or quote the data, name Proofyo and link the page in the record's `source`. Reading, caching, indexing and answering questions from the API, /llms.txt and /llms-full.txt are fine; mirroring or republishing the dataset, or using it to build a competing directory, is not. Without a key: 60 requests a minute and pages of up to 50 items. For more, ask us for an API key; keys can be revoked.\n- No key needed: 60 requests per 60 s per client and pageSize up to 50. An API key (ask us: https://proofyo.com/developers#keys) raises both, by default to 600 requests per minute and pageSize 200 within each endpoint's maximum; send it as `Authorization: Bearer <key>` or `X-API-Key: <key>`. A malformed, unknown or revoked key is a 401, not anonymous access. Every response carries X-RateLimit-* headers, 429 comes with Retry-After. Without a key, limits are per client (IPv4 address or IPv6 /64) when the deployment's trusted proxy is configured (TRUST_PROXY); without it every anonymous client shares one bucket of 600 requests per 60 s. With a key, the key has its own bucket. X-RateLimit-Limit always shows the limit that applies to you.\n- Every record carries `source` (the canonical page URL) and `verified` (date and mode). When you use the data, cite the source URL and the verified date. Responses with a top-level `source` also carry `attribution` with a ready-made credit line.\n- Records with `isDemo: true` are fictional demo data. Never present them as measurements of a real company.\n- Responses carry a weak ETag; send If-None-Match to get 304. Cache-Control without a key: public, s-maxage=60, stale-while-revalidate=300; with a key: private, max-age=60.\n- CORS is open (Access-Control-Allow-Origin: *). Errors use { \"error\": { \"code\", \"message\", \"details\"? } }.","termsOfService":"https://proofyo.com/developers#terms","contact":{"name":"Proofyo","url":"https://proofyo.com/developers"}},"externalDocs":{"description":"Developer guide","url":"https://proofyo.com/developers"},"servers":[{"url":"https://proofyo.com/api/v1"}],"tags":[{"name":"Products"},{"name":"Categories"},{"name":"Compare"},{"name":"Posts"},{"name":"Search"},{"name":"Meta"}],"security":[{},{"bearerKey":[]},{"headerKey":[]}],"paths":{"/":{"get":{"operationId":"apiIndex","tags":["Meta"],"summary":"API index","description":"Links to the docs, this document and every endpoint.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/openapi.json":{"get":{"operationId":"openapi","tags":["Meta"],"summary":"This OpenAPI document","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/products":{"get":{"operationId":"listProducts","tags":["Products"],"summary":"List and filter products","description":"The product listing as JSON. Takes the same filter keys as the /products page, so any listing URL on the site maps to this endpoint. Values within one key match any; different keys must all match. Facet counts show how many results each filter value would give with the other filters applied.","parameters":[{"name":"q","in":"query","required":false,"description":"Full-text search over name, vendor, tagline, summary and tags. Without `sort`, results come in relevance order. Control characters count as spaces.","schema":{"type":"string","minLength":1,"maxLength":200},"example":"email"},{"name":"category","in":"query","required":false,"description":"Category slug from GET /categories.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"},"example":"auth-identity"},{"name":"free","in":"query","required":false,"description":"Free option: plan = free plan, trial = free trial. Comma-separate (or repeat the key) to match any of them.","schema":{"type":"array","items":{"type":"string","enum":["plan","trial"]}},"style":"form","explode":false,"example":["plan"]},{"name":"access","in":"query","required":false,"description":"Getting access: self-serve, approval wait or sales call required. Comma-separate (or repeat the key) to match any of them.","schema":{"type":"array","items":{"type":"string","enum":["self-serve","approval","sales"]}},"style":"form","explode":false,"example":["self-serve"]},{"name":"card","in":"query","required":false,"description":"Card to start: no = no card needed, yes = card required.","schema":{"type":"string","enum":["no","yes"]},"example":"no"},{"name":"model","in":"query","required":false,"description":"Pricing model. Comma-separate (or repeat the key) to match any of them.","schema":{"type":"array","items":{"type":"string","enum":["flat","per-seat","usage","tiered","hybrid","quote"]}},"style":"form","explode":false,"example":["usage"]},{"name":"public","in":"query","required":false,"description":"Only products with public prices.","schema":{"type":"string","enum":["1","true"]},"example":"1"},{"name":"price","in":"query","required":false,"description":"Starter price band: free, under $20, $50 or $100 per month, or any (the default: no price filter, so facets.price.any equals the total).","schema":{"type":"string","enum":["free","lt20","lt50","lt100","any"]},"example":"lt20"},{"name":"verified","in":"query","required":false,"description":"Who did the hands-on work in the published run: agent alone, or agent with admin-supplied login. Matches verified.mode, so demo records and products without a published hands-on run never match.","schema":{"type":"string","enum":["agent","assisted"]},"example":"agent"},{"name":"sort","in":"query","required":false,"description":"score (default), price (lowest starter price first), newest (most recently checked), name.","schema":{"type":"string","enum":["score","price","newest","name"]},"example":"price"},{"name":"page","in":"query","required":false,"description":"Page number, 1-based. Pages past the end return the last page. 1–10000, default 1.","schema":{"type":"integer","minimum":1,"maximum":10000,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page, default 24. Up to 50 without an API key; with a key, up to that key's page size (200 unless agreed otherwise) and at most 200 on this endpoint. A larger value is a 400, never silently cut.","schema":{"type":"integer","minimum":1,"maximum":200,"default":24}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"400":{"description":"Invalid query parameter. `error.details` names each one.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products/{slug}":{"get":{"operationId":"getProduct","tags":["Products"],"summary":"Get one product with its evidence","description":"Everything on the review page: what you are paying for (value: headline, kinds, summary, proof points, best for, trade-off), the hands-on review (review: setup story, strengths, things to know, pricing reality, exit path, compared products, choose-if / look-elsewhere-if bullets, bottom line), access facts, overall score and breakdown, measured metrics, plans, and the published evidence (run steps, screenshots with absolute URLs, API calls with status codes and secrets masked), plus a ready-made citation. No elapsed times or latency figures anywhere.","parameters":[{"name":"slug","in":"path","required":true,"description":"Product slug.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"},"example":"supabase-auth"}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductResponse"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/categories":{"get":{"operationId":"listCategories","tags":["Categories"],"summary":"List categories","description":"Every category in display order, with its published product count and indexed landing pages.","parameters":[],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryList"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/compare":{"get":{"operationId":"compareProducts","tags":["Compare"],"summary":"Compare 2 to 4 products","description":"Products side by side with rows in the comparison page order: what you are paying for, overall score, the access facts (free option, getting access, card to start, starter price, pricing model, pricing page), then the measured rows (docs quality and category rows) and, when written, each review's bottom line. No time rows. Each row has display values, machine values and the index of the best column where one value wins; a short verdict per use case follows.","parameters":[{"name":"items","in":"query","required":true,"description":"2 to 4 distinct product slugs, comma-separated, in column order.","schema":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"},"minItems":2,"maxItems":4},"style":"form","explode":false,"example":["supabase-auth","firebase-auth"]}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareResponse"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"400":{"description":"Invalid query parameter. `error.details` names each one.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts":{"get":{"operationId":"listPosts","tags":["Posts"],"summary":"List blog posts","description":"Published posts, newest first. Every post is written by our AI agent from recorded evidence.","parameters":[{"name":"kind","in":"query","required":false,"description":"Post kind.","schema":{"type":"string","enum":["review","comparison","guide","news"]},"example":"review"},{"name":"category","in":"query","required":false,"description":"Category slug.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"}},{"name":"product","in":"query","required":false,"description":"Only posts that review or mention this product.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"},"example":"supabase-auth"},{"name":"page","in":"query","required":false,"description":"Page number, 1-based. Pages past the end return the last page. 1–10000, default 1.","schema":{"type":"integer","minimum":1,"maximum":10000,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page, default 12. Up to 50 without an API key; with a key, up to that key's page size (200 unless agreed otherwise) and at most 100 on this endpoint. A larger value is a 400, never silently cut.","schema":{"type":"integer","minimum":1,"maximum":100,"default":12}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostList"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"400":{"description":"Invalid query parameter. `error.details` names each one.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{slug}":{"get":{"operationId":"getPost","tags":["Posts"],"summary":"Get one post","description":"A published post with its Markdown source, sanitized HTML, the products it covers and a citation.","parameters":[{"name":"slug","in":"path","required":true,"description":"Post slug.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,80}$"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResponse"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/search":{"get":{"operationId":"search","tags":["Search"],"summary":"Search products, posts and categories","description":"Full-text search with typo-tolerant fallback. Hits are ranked; snippets mark the matching words.","parameters":[{"name":"q","in":"query","required":true,"description":"Search text. Control characters count as spaces; text that is empty after that is a 400.","schema":{"type":"string","minLength":1,"maxLength":200},"example":"webhooks"},{"name":"limit","in":"query","required":false,"description":"Maximum hits. 1–50, default 20.","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Weak validator of the body. Send it back in If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"304":{"description":"Not modified: the If-None-Match ETag still matches.","headers":{"ETag":{"description":"The matching validator.","schema":{"type":"string"}},"Cache-Control":{"description":"Without a key: public, s-maxage=60, stale-while-revalidate=300. With a key: private, max-age=60.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}}},"400":{"description":"Invalid query parameter. `error.details` names each one.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"invalid_api_key: the request sent an API key (Authorization: Bearer or X-API-Key) that is malformed, unknown or revoked. Send a valid key, or no key for anonymous access.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"api\", error=\"invalid_token\"","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60 requests per 60 s per client without a key, the key's own limit with one; see X-RateLimit-Limit). Also sent, without a lookup, when one client sends too many keys that are not known to be valid. Wait Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error: something failed on our side, or an API key could not be checked right now. Retry shortly.","headers":{"Link":{"description":"Always includes `<…/developers#terms>; rel=\"terms-of-service\"`; list endpoints add RFC 8288 pagination links (first, last, prev, next).","schema":{"type":"string"}},"X-Proofyo-Attribution":{"description":"\"required\": credit Proofyo with a link to the record's source page wherever you use the data.","schema":{"type":"string","enum":["required"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_parameter","not_found","method_not_allowed","rate_limited","internal_error","invalid_api_key"]},"message":{"type":"string"},"details":{"type":"array","items":{"type":"object","properties":{"param":{"type":"string"},"message":{"type":"string"}},"required":["param","message"],"additionalProperties":false}}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"Attribution":{"type":"object","description":"How to credit this response. Showing or quoting the data requires a visible credit with a link to `source`; the terms do not allow mirroring or republishing the dataset.","properties":{"required":{"const":true,"description":"Always true: show this attribution wherever you use the data."},"text":{"type":"string","description":"Ready-to-show line: \"Data from Proofyo (<host>): <source URL>\". Keep the link."},"terms":{"type":"string","description":"The terms of use.","format":"uri"}},"required":["required","text","terms"],"additionalProperties":false},"VerifiedInfo":{"type":"object","description":"When and how the data was verified. Quote the note with the source URL; say \"facts checked\", not \"tested\", unless basis is signup_and_api, signup or api.","properties":{"date":{"type":["string","null"],"description":"YYYY-MM-DD the evidence was gathered; null when not verified yet.","format":"date"},"mode":{"type":["string","null"],"enum":["agent","assisted",null],"description":"Who did the hands-on work in the published run. agent: our AI agent alone. assisted: an admin supplied a login (captcha or phone check), the agent did the rest. null when no hands-on run is published (basis public_pages or none), on demo records (basis demo: their runs are fictional illustrations) and on multi-product posts."},"basis":{"type":"string","enum":["signup_and_api","signup","api","public_pages","none","demo","per_product"],"description":"What the verification rests on. signup_and_api: an account was opened and the vendor API was called. signup: account opened, no API call recorded. api: API called without a completed sign-up. public_pages: no completed sign-up or API call is published (a blocked attempt may be); the facts were read from public sign-up and pricing pages. none: not verified yet. demo: a fictional demo record; nothing was signed up for or called. per_product: a comparison, guide or news post; see products[].verified."},"note":{"type":"string","description":"One sentence you can quote. It never claims more than `basis` supports."}},"required":["date","mode","basis","note"],"additionalProperties":false},"VerifiedRange":{"type":"object","description":"Oldest and newest verification date in this page.","properties":{"earliest":{"type":["string","null"],"format":"date"},"latest":{"type":["string","null"],"format":"date"}},"required":["earliest","latest"],"additionalProperties":false},"Citation":{"type":"object","description":"How to cite this record.","properties":{"source":{"type":"string","description":"Canonical page URL to link.","format":"uri"},"verifiedDate":{"type":["string","null"],"format":"date"},"text":{"type":"string","description":"Ready-to-paste attribution line."}},"required":["source","verifiedDate","text"],"additionalProperties":false},"CategoryRef":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","description":"Category page URL.","format":"uri"}},"required":["slug","name","source"],"additionalProperties":false},"AccessFacts":{"type":"object","description":"The five access questions. A missing key means \"not yet observed\", never a guess.","properties":{"freeOption":{"type":"string","enum":["free-plan","free-trial","paid-only"],"description":"Free option."},"trialDays":{"type":"integer","description":"Length of the free trial in days."},"signup":{"type":"string","enum":["self-serve","approval","sales"],"description":"Getting access: self-serve (usable right after sign-up), approval (waitlist or manual review), sales (must talk to sales first)."},"cardRequired":{"type":"boolean","description":"Card to start."},"starterPrice":{"anyOf":[{"type":"string"},{"const":0}],"description":"Cheapest paid plan as listed, e.g. \"$15\". 0 means the product has no paid plan."},"starterUnit":{"type":"string","description":"Unit of the starter price, e.g. \"/mo\", \"/seat/mo\"."},"starterPlan":{"type":"string","description":"Name of the cheapest paid plan."},"pricingModel":{"type":"string","enum":["flat","per-seat","usage","tiered","hybrid","quote"],"description":"Pricing model."},"publicPricing":{"type":"boolean","description":"Prices are published on the pricing page."},"verified":{"type":"string","description":"YYYY-MM-DD the facts were observed.","format":"date"},"verifiedMode":{"type":"string","enum":["agent","assisted"],"description":"Same as verified.mode; absent when no hands-on run is published."}},"required":[],"additionalProperties":false},"ValueKind":{"type":"string","enum":["compliance","exclusive-data","ease-of-use","cost-efficiency","saves-engineering","built-in-reach"],"description":"What kind of value a product sells. compliance: Compliance included; exclusive-data: Data you can't get elsewhere; ease-of-use: Easy to adopt; cost-efficiency: Worth the price; saves-engineering: Saves engineering time; built-in-reach: Built-in reach."},"ProductValue":{"type":"object","description":"\"What you're paying for\": the real value a product sells, the first block of its review page.","properties":{"headline":{"type":"string","description":"One sentence: what you are really paying for."},"kinds":{"type":"array","items":{"$ref":"#/components/schemas/ValueKind"},"description":"The primary kind first.","minItems":1,"maxItems":3},"summary":{"type":"string","description":"Two to four sentences: the concrete value, who gets it, what it replaces or saves."},"points":{"type":"array","items":{"type":"object","properties":{"kind":{"type":["string","null"],"enum":["compliance","exclusive-data","ease-of-use","cost-efficiency","saves-engineering","built-in-reach",null],"description":"The value kind this point supports, or null."},"title":{"type":"string"},"detail":{"type":"string"},"evidence":{"type":["string","null"],"description":"What the point rests on, e.g. \"plan page, 2026-10-12\"; null when not stated."}},"required":["kind","title","detail","evidence"],"additionalProperties":false},"description":"Proof points, each backed by something we observed (plan page, docs, the run).","minItems":2,"maxItems":4},"bestFor":{"type":"string","description":"Who gets the most out of it."},"tradeoff":{"type":["string","null"],"description":"One neutral, factual trade-off, or null."}},"required":["headline","kinds","summary","points","bestFor","tradeoff"],"additionalProperties":false},"ReviewCompared":{"type":"object","properties":{"slug":{"type":"string","description":"Slug of a published product in the same category."},"name":{"type":"string"},"pickThisWhen":{"type":"string","description":"When to pick the reviewed product over this one."},"pickThatWhen":{"type":"string","description":"When to pick this one instead."},"source":{"type":"string","description":"The compared product's review page.","format":"uri"},"api":{"type":"string","description":"The compared product in this API.","format":"uri"}},"required":["slug","name","pickThisWhen","pickThatWhen","source","api"],"additionalProperties":false},"ProductReview":{"type":"object","description":"The hands-on review: what our agent learned by using the product that the vendor's site does not say. Fair tone; every claim rests on the recorded run, the plan/docs pages or our own data on the compared products.","properties":{"setupStory":{"type":"string","description":"Markdown, 1–3 paragraphs: what we set up, where it was smooth, what tripped us, what surprised us. Steps and conditions, no elapsed times."},"strengths":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"detail":{"type":"string"},"versus":{"type":["string","null"],"description":"A relative statement against a named product we also list, or null."}},"required":["title","detail","versus"],"additionalProperties":false},"description":"What stood out while using it.","minItems":2,"maxItems":5},"weaknesses":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"detail":{"type":"string","description":"A neutral fact with its condition."},"workaround":{"type":["string","null"],"description":"What to do about it, or null."}},"required":["title","detail","workaround"],"additionalProperties":false},"description":"\"Things to know\": limits, errors and support realities, stated neutrally.","minItems":1,"maxItems":4},"pricingReality":{"type":"string","description":"Markdown paragraph: billing unit, what the base plan includes, where it gets expensive, gated features, support and adjacent costs."},"exitPath":{"type":["string","null"],"description":"Lock-in and exit: data export, key/password portability, what a migration would take; null when not observed."},"comparedWith":{"type":"array","items":{"$ref":"#/components/schemas/ReviewCompared"},"description":"Published products in the same category, with \"pick … when\" both ways.","minItems":1,"maxItems":3},"chooseIf":{"type":"array","items":{"type":"string"},"description":"\"Choose it if …\" bullets.","minItems":2,"maxItems":4},"skipIf":{"type":"array","items":{"type":"string"},"description":"\"Look elsewhere if …\" bullets.","minItems":1,"maxItems":3},"bottomLine":{"type":"string","description":"One or two sentences: the conclusion."}},"required":["setupStory","strengths","weaknesses","pricingReality","exitPath","comparedWith","chooseIf","skipIf","bottomLine"],"additionalProperties":false},"ProductCard":{"type":"object","description":"A product as listings, cards and comparison columns show it.","properties":{"slug":{"type":"string"},"name":{"type":"string","description":"Product name exactly as the vendor writes it."},"vendor":{"type":"string"},"tagline":{"type":"string"},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"score":{"type":["number","null"],"description":"Overall score out of 10; null when not scored yet.","minimum":0,"maximum":10},"scoreMax":{"const":10},"facts":{"$ref":"#/components/schemas/AccessFacts"},"starterPriceUsd":{"type":["number","null"],"description":"Cheapest paid plan in USD per month. 0 = free; null = not comparable (custom quote or unknown)."},"tags":{"type":"array","items":{"type":"string"}},"logoUrl":{"type":["string","null"],"description":"Absolute logo URL, or null (use the monogram).","format":"uri"},"monogram":{"type":"string","description":"Two-letter fallback mark."},"valueHeadline":{"type":["string","null"],"description":"\"What you're paying for\" in one sentence: the real value the product sells. null until written."},"valueKinds":{"type":"array","items":{"$ref":"#/components/schemas/ValueKind"},"description":"Value kinds, the primary one first; empty until written."},"isDemo":{"type":"boolean","description":"True for fictional demo records. Never present these as measurements of a real company."},"verified":{"$ref":"#/components/schemas/VerifiedInfo"},"source":{"type":"string","description":"Canonical review page. Cite this.","format":"uri"},"api":{"type":"string","description":"This product in the API.","format":"uri"}},"required":["slug","name","vendor","tagline","category","score","scoreMax","facts","starterPriceUsd","tags","logoUrl","monogram","valueHeadline","valueKinds","isDemo","verified","source","api"],"additionalProperties":false},"ScoreItem":{"type":"object","properties":{"label":{"type":"string"},"score":{"type":"number"},"max":{"type":"number","description":"Default 10."},"note":{"type":"string"}},"required":["label","score"],"additionalProperties":false},"ProductMetrics":{"type":"object","description":"Measured values; units are in the key names. Missing = not measured. No network latency and no elapsed times (sign-up time, time to first call): both depend on where and how fast our test ran, not on the product.","properties":{"apiCallsMeasured":{"type":"integer","description":"Number of vendor API calls recorded in the published runs."},"docsQuality":{"type":"number","description":"0–10."},"extra":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"},"kind":{"type":"string","enum":["data","bool","score"]}},"required":["label","value"],"additionalProperties":false},"description":"Category-specific rows, e.g. { \"label\": \"Free quota\", \"value\": \"3,000 emails/mo\" }."}},"required":[],"additionalProperties":false},"Plan":{"type":"object","properties":{"name":{"type":"string"},"price":{"type":"string","description":"As listed, e.g. \"$15\"."},"priceMonthlyUsd":{"type":["number","null"],"description":"Normalized monthly USD; null for custom quotes."},"period":{"type":"string","description":"e.g. \"/mo\"."},"unitNote":{"type":"string","description":"What the price covers, e.g. \"50,000 emails\"."},"features":{"type":"array","items":{"type":"string"}},"isFree":{"type":"boolean"},"observedAt":{"type":["string","null"],"description":"When we read it on the pricing page.","format":"date-time"}},"required":["name","price","priceMonthlyUsd","period","unitNote","features","isFree","observedAt"],"additionalProperties":false},"RunStep":{"type":"object","description":"One step of a run. Step durations are not published (they measure the agent, not the product).","properties":{"title":{"type":"string","description":"\"STEP 1 · …\" titles match the screenshot steps."},"status":{"type":"string","enum":["pass","warn","fail","running","skipped"]},"detail":{"type":"string"},"actor":{"type":"string","enum":["agent","admin"],"description":"admin = a human helped with this step."},"screenshotId":{"type":"integer"}},"required":["title"],"additionalProperties":false},"Screenshot":{"type":"object","properties":{"step":{"type":["integer","null"]},"caption":{"type":"string"},"alt":{"type":"string","description":"What is on screen."},"pageUrl":{"type":"string","description":"The page that was captured."},"src":{"type":["string","null"],"description":"Absolute image URL, or null when the capture is a placeholder.","format":"uri"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"device":{"type":"string","enum":["desktop","mobile"]},"capturedAt":{"type":"string","format":"date-time"}},"required":["step","caption","alt","pageUrl","src","width","height","device","capturedAt"],"additionalProperties":false},"ApiCall":{"type":"object","properties":{"method":{"type":"string"},"url":{"type":"string"},"status":{"type":"integer","description":"HTTP status; 0 = network error."},"statusText":{"type":"string"},"request":{"type":"string","description":"As sent, secrets masked (e.g. $RELAYMAIL_KEY)."},"response":{"type":"string","description":"As received, secrets masked."},"caption":{"type":"string"},"finding":{"type":"string","description":"One-paragraph finding."},"measuredAt":{"type":"string","format":"date-time"}},"required":["method","url","status","statusText","request","response","caption","finding","measuredAt"],"additionalProperties":false},"Run":{"type":"object","properties":{"kind":{"type":"string","enum":["signup","api","pricing","full"]},"status":{"type":"string","enum":["running","pass","warn","fail"]},"mode":{"type":"string","enum":["agent","assisted"]},"title":{"type":"string"},"meta":{"type":"string","description":"Browser, region and start time of the run."},"summary":{"type":"string"},"agentModel":{"type":"string"},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/RunStep"}},"screenshots":{"type":"array","items":{"$ref":"#/components/schemas/Screenshot"}},"apiCalls":{"type":"array","items":{"$ref":"#/components/schemas/ApiCall"}}},"required":["kind","status","mode","title","meta","summary","agentModel","startedAt","finishedAt","steps","screenshots","apiCalls"],"additionalProperties":false},"ProductDetail":{"type":"object","description":"The full product record with its published evidence.","properties":{"slug":{"type":"string"},"name":{"type":"string","description":"Product name exactly as the vendor writes it."},"vendor":{"type":"string"},"tagline":{"type":"string"},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"score":{"type":["number","null"],"description":"Overall score out of 10; null when not scored yet.","minimum":0,"maximum":10},"scoreMax":{"const":10},"facts":{"$ref":"#/components/schemas/AccessFacts"},"starterPriceUsd":{"type":["number","null"],"description":"Cheapest paid plan in USD per month. 0 = free; null = not comparable (custom quote or unknown)."},"tags":{"type":"array","items":{"type":"string"}},"logoUrl":{"type":["string","null"],"description":"Absolute logo URL, or null (use the monogram).","format":"uri"},"monogram":{"type":"string","description":"Two-letter fallback mark."},"valueHeadline":{"type":["string","null"],"description":"\"What you're paying for\" in one sentence: the real value the product sells. null until written."},"valueKinds":{"type":"array","items":{"$ref":"#/components/schemas/ValueKind"},"description":"Value kinds, the primary one first; empty until written."},"isDemo":{"type":"boolean","description":"True for fictional demo records. Never present these as measurements of a real company."},"verified":{"$ref":"#/components/schemas/VerifiedInfo"},"source":{"type":"string","description":"Canonical review page. Cite this.","format":"uri"},"api":{"type":"string","description":"This product in the API.","format":"uri"},"value":{"anyOf":[{"$ref":"#/components/schemas/ProductValue"},{"type":"null"}]},"review":{"anyOf":[{"$ref":"#/components/schemas/ProductReview"},{"type":"null"}]},"summaryMd":{"type":"string","description":"Overview in Markdown."},"verdict":{"type":"string"},"websiteUrl":{"type":"string"},"pricingUrl":{"type":"string"},"docsUrl":{"type":"string"},"signupUrl":{"type":"string"},"scores":{"type":"array","items":{"$ref":"#/components/schemas/ScoreItem"},"description":"Score breakdown."},"metrics":{"$ref":"#/components/schemas/ProductMetrics"},"plans":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}},"lastVerifiedAt":{"type":["string","null"],"description":"Same as verified.date.","format":"date"},"verifiedMode":{"type":["string","null"],"enum":["agent","assisted",null],"description":"Same as verified.mode."},"evidence":{"type":"object","properties":{"runCount":{"type":"integer","description":"Published test runs in total."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/Run"},"description":"The newest published run of each kind, newest first."}},"required":["runCount","runs"],"additionalProperties":false},"publishedAt":{"type":["string","null"],"format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"notice":{"type":["string","null"],"description":"Set on demo records: says the record is fictional and that no agent signed up or called its API."},"citation":{"$ref":"#/components/schemas/Citation"}},"required":["slug","name","vendor","tagline","category","score","scoreMax","facts","starterPriceUsd","tags","logoUrl","monogram","valueHeadline","valueKinds","isDemo","verified","source","api","value","review","summaryMd","verdict","websiteUrl","pricingUrl","docsUrl","signupUrl","scores","metrics","plans","lastVerifiedAt","verifiedMode","evidence","publishedAt","updatedAt","notice","citation"],"additionalProperties":false},"LandingPageRef":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"source":{"type":"string","format":"uri"}},"required":["slug","title","source"],"additionalProperties":false},"Category":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"introMd":{"type":"string","description":"Markdown."},"icon":{"type":"string","description":"Lucide icon name."},"productCount":{"type":"integer","description":"Published products."},"landingPages":{"type":"array","items":{"$ref":"#/components/schemas/LandingPageRef"},"description":"Indexed filter combinations with a written intro."},"source":{"type":"string","format":"uri"},"products":{"type":"string","description":"This category in GET /products.","format":"uri"}},"required":["slug","name","description","introMd","icon","productCount","landingPages","source","products"],"additionalProperties":false},"ProductLink":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","format":"uri"}},"required":["slug","name","source"],"additionalProperties":false},"PostProduct":{"type":"object","description":"A product a post covers, with how that product was verified.","properties":{"slug":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","format":"uri"},"isDemo":{"type":"boolean","description":"True for a fictional demo product."},"verified":{"$ref":"#/components/schemas/VerifiedInfo"}},"required":["slug","name","source","isDemo","verified"],"additionalProperties":false},"PostCard":{"type":"object","properties":{"slug":{"type":"string"},"kind":{"type":"string","enum":["review","comparison","guide","news"]},"title":{"type":"string"},"dek":{"type":"string"},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"coverUrl":{"type":["string","null"],"format":"uri"},"readMinutes":{"type":"integer"},"publishedAt":{"type":["string","null"],"format":"date-time"},"products":{"type":"array","items":{"$ref":"#/components/schemas/PostProduct"},"description":"Products the post covers, each with its own verified."},"isDemo":{"type":"boolean"},"verified":{"$ref":"#/components/schemas/VerifiedInfo"},"source":{"type":"string","description":"Canonical post URL. Cite this.","format":"uri"},"api":{"type":"string","format":"uri"}},"required":["slug","kind","title","dek","category","coverUrl","readMinutes","publishedAt","products","isDemo","verified","source","api"],"additionalProperties":false},"PostDetail":{"type":"object","properties":{"slug":{"type":"string"},"kind":{"type":"string","enum":["review","comparison","guide","news"]},"title":{"type":"string"},"dek":{"type":"string"},"category":{"anyOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"coverUrl":{"type":["string","null"],"format":"uri"},"readMinutes":{"type":"integer"},"publishedAt":{"type":["string","null"],"format":"date-time"},"products":{"type":"array","items":{"$ref":"#/components/schemas/PostProduct"},"description":"Products the post covers, each with its own verified."},"isDemo":{"type":"boolean"},"verified":{"$ref":"#/components/schemas/VerifiedInfo"},"source":{"type":"string","description":"Canonical post URL. Cite this.","format":"uri"},"api":{"type":"string","format":"uri"},"seoTitle":{"type":"string"},"seoDescription":{"type":"string"},"authorAgent":{"type":"string","description":"The agent that wrote the post."},"bodyMd":{"type":"string","description":"Markdown source."},"html":{"type":"string","description":"Sanitized HTML of bodyMd; links and images are absolute."},"reviewedProduct":{"anyOf":[{"$ref":"#/components/schemas/ProductLink"},{"type":"null"}]},"productCards":{"type":"array","items":{"$ref":"#/components/schemas/ProductCard"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"notice":{"type":["string","null"],"description":"Set on demo posts: says the products are fictional and that no agent signed up or called their APIs."},"citation":{"$ref":"#/components/schemas/Citation"}},"required":["slug","kind","title","dek","category","coverUrl","readMinutes","publishedAt","products","isDemo","verified","source","api","seoTitle","seoDescription","authorAgent","bodyMd","html","reviewedProduct","productCards","createdAt","updatedAt","notice","citation"],"additionalProperties":false},"CompareRow":{"type":"object","properties":{"key":{"type":"string","description":"Stable key: valueHeadline (\"What you're paying for\"), score, freeOption, cardRequired, signup, starterPrice, pricingModel, publicPricing, then measured keys (docsQuality and extra:<label-slug>) and bottomLine (the review's bottom line). No time rows."},"group":{"type":"string","enum":["value","score","access","measured","review"]},"label":{"type":"string"},"hint":{"type":"string"},"kind":{"type":"string","enum":["score","data","bool"],"description":"Cell kind as the comparison table renders it; absent = text."},"values":{"type":"array","items":{"type":["string","number","boolean","null"]},"description":"Display values in product order (null = not measured)."},"raw":{"type":"array","items":{"type":["string","number","boolean","null"]},"description":"Machine values in product order: numbers in `unit`, booleans, vocabulary ids."},"unit":{"type":"string"},"best":{"type":"integer","description":"Index of the winning column; absent on ties."}},"required":["key","group","label","values","raw"],"additionalProperties":false},"CompareVerdict":{"type":"object","properties":{"id":{"type":"string","description":"Stable use-case id."},"question":{"type":"string","description":"The use case, e.g. \"If you need to start today without a sales call\"."},"winners":{"type":"array","items":{"$ref":"#/components/schemas/ProductLink"},"description":"Products that fit; empty when none qualifies."},"reason":{"type":"string","description":"One measured sentence, or why no product qualifies."}},"required":["id","question","winners","reason"],"additionalProperties":false},"ComparePlans":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"pricingUrl":{"type":"string"},"plans":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}}},"required":["slug","name","pricingUrl","plans"],"additionalProperties":false},"SearchHit":{"type":"object","properties":{"kind":{"type":"string","enum":["product","post","category"]},"slug":{"type":"string"},"title":{"type":"string"},"snippet":{"type":"string","description":"HTML: matches in <mark>, everything else escaped."},"snippetText":{"type":"string","description":"Snippet as plain text."},"source":{"type":"string","format":"uri"},"api":{"type":"string","format":"uri"}},"required":["kind","slug","title","snippet","snippetText","source","api"],"additionalProperties":false},"PageLinks":{"type":"object","properties":{"self":{"type":"string","format":"uri"},"first":{"type":"string","format":"uri"},"last":{"type":"string","format":"uri"},"next":{"type":["string","null"],"format":"uri"},"prev":{"type":["string","null"],"format":"uri"}},"required":["self","first","last","next","prev"],"additionalProperties":false},"Facets":{"type":"object","description":"Counts per filter key and value (e.g. facets.free.plan), each applying every other active filter. facets.category is keyed by category slug.","additionalProperties":{"type":"object","additionalProperties":{"type":"integer"}}},"ProductList":{"type":"object","properties":{"source":{"type":"string","description":"The listing page for the same filters and order. With the default pageSize (24, the site's page size) it is the page that shows exactly this set; with another pageSize it is the unpaged listing, since API pages and site pages do not line up.","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ProductCard"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer","description":"Clamped to pageCount."},"pageSize":{"type":"integer"},"pageCount":{"type":"integer"},"sort":{"type":"string","enum":["score","price","newest","name","relevance"]},"verified":{"$ref":"#/components/schemas/VerifiedRange"}},"required":["total","page","pageSize","pageCount","sort","verified"],"additionalProperties":false},"links":{"$ref":"#/components/schemas/PageLinks"},"facets":{"$ref":"#/components/schemas/Facets"}},"required":["source","attribution","data","meta","links","facets"],"additionalProperties":false},"ProductResponse":{"type":"object","properties":{"source":{"type":"string","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"$ref":"#/components/schemas/ProductDetail"}},"required":["source","attribution","data"],"additionalProperties":false},"CategoryList":{"type":"object","properties":{"source":{"type":"string","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}},"meta":{"type":"object","properties":{"total":{"type":"integer"}},"required":["total"],"additionalProperties":false}},"required":["source","attribution","data","meta"],"additionalProperties":false},"CompareResponse":{"type":"object","properties":{"source":{"type":"string","description":"The comparison page: /compare/a-vs-b for two products.","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/ProductCard"},"description":"Columns, in the order requested."},"rows":{"type":"array","items":{"$ref":"#/components/schemas/CompareRow"},"description":"Rows in the comparison page order."},"plans":{"type":"array","items":{"$ref":"#/components/schemas/ComparePlans"}},"footnote":{"type":"string","description":"Test dates and conditions."},"verdicts":{"type":"array","items":{"$ref":"#/components/schemas/CompareVerdict"},"description":"Short verdict per use case, as on the comparison page."}},"required":["products","rows","plans","footnote","verdicts"],"additionalProperties":false},"meta":{"type":"object","properties":{"count":{"type":"integer"},"verified":{"$ref":"#/components/schemas/VerifiedRange"}},"required":["count","verified"],"additionalProperties":false}},"required":["source","attribution","data","meta"],"additionalProperties":false},"PostList":{"type":"object","properties":{"source":{"type":"string","description":"The closest HTML page. Unfiltered (or by kind): /blog with the same kind, and the same page when pageSize is the default. Filtered by product: that product's review page; by category: the category page. Those pages show related posts, not exactly this list.","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PostCard"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"},"pageCount":{"type":"integer"}},"required":["total","page","pageSize","pageCount"],"additionalProperties":false},"links":{"$ref":"#/components/schemas/PageLinks"}},"required":["source","attribution","data","meta","links"],"additionalProperties":false},"PostResponse":{"type":"object","properties":{"source":{"type":"string","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"$ref":"#/components/schemas/PostDetail"}},"required":["source","attribution","data"],"additionalProperties":false},"SearchResponse":{"type":"object","properties":{"source":{"type":"string","format":"uri"},"attribution":{"$ref":"#/components/schemas/Attribution"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchHit"}},"meta":{"type":"object","properties":{"q":{"type":"string"},"total":{"type":"integer"},"limit":{"type":"integer"}},"required":["q","total","limit"],"additionalProperties":false}},"required":["source","attribution","data","meta"],"additionalProperties":false},"ApiIndex":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"docs":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"llms":{"type":"string","format":"uri"},"terms":{"type":"object","description":"Terms of use: attribution required; reading and caching are fine, mirroring or republishing the dataset is not.","properties":{"url":{"type":"string","format":"uri"},"summary":{"type":"string"}},"required":["url","summary"],"additionalProperties":false},"rateLimit":{"type":"object","description":"Anonymous limits, and the defaults for API keys.","properties":{"limit":{"type":"integer","description":"Requests per window without a key."},"windowSec":{"type":"integer"},"maxPageSize":{"type":"integer","description":"Largest pageSize without a key."},"keyed":{"type":"object","description":"Send the key as \"Authorization: Bearer <key>\" or \"X-API-Key: <key>\".","properties":{"limit":{"type":"integer","description":"Default requests per window with an API key; a key can carry its own number."},"maxPageSize":{"type":"integer","description":"Default largest pageSize with an API key, still capped per endpoint."},"request":{"type":"string","description":"How to ask for a key.","format":"uri"}},"required":["limit","maxPageSize","request"],"additionalProperties":false}},"required":["limit","windowSec","maxPageSize","keyed"],"additionalProperties":false},"endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"summary":{"type":"string"},"example":{"type":"string","format":"uri"}},"required":["method","path","summary","example"],"additionalProperties":false}}},"required":["name","version","docs","openapi","llms","terms","rateLimit","endpoints"],"additionalProperties":false}},"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","description":"API key issued by Proofyo (\"psk_…\")."},"headerKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"The same key in a header of its own."}}}}