# Vendor facets — Zuplo. An OpenAPI-native edge gateway with a Zudoku developer portal, Stripe-backed # monetization, and an MCP server handler that re-invokes gateway routes as tools. The strongest # gateway on the agent-auth layer: it serves /.well-known/oauth-protected-resource on the customer's # own gateway host. What it cannot reach: rate-limit headers in the provider's OpenAPI (the policy # emits Retry-After, which the score accepts only once the provider declares it), SDKs, idempotency, or any discovery document for the authorization server. vendor: zuplo name: Zuplo website: https://zuplo.com areas: - developer-portal - api-gateway registry_keys: - zuplo rubric_schema_version: 0.22.0 generated: '2026-09-25' features_refreshed: '2026-09-25' basis: capability summary: >- Zuplo's lift lands on access clarity and agent readiness: a public pricing page with multiple plans, self-serve sign-up and keys, a try-it console, an MCP server generated from the provider's own routes on the provider's own gateway host (graded `templated`, 0.6), and a served OAuth protected-resource document that also earns the composite .well-known check. The portal only counts once the provider declares it in apis.yml, the rate-limit policy emits Retry-After — a header the `verified` grade accepts, but only once the provider declares it on its 429 responses in its own OpenAPI — and the RFC 7807 error envelope scores only when the provider writes that schema into its own contract. features: - id: rate-limit-policy name: Rate limiting inbound policy (429 + Retry-After) description: >- Per-key/per-IP rate limiting at the edge that returns 429 with a Retry-After header by default (headerMode retry-after | none). source: https://zuplo.com/docs/policies/rate-limit-inbound tier: all - id: mcp-server-handler name: MCP Server handler description: >- Turns configured gateway routes into MCP tools (plus prompts and resources), re-invoked inside the gateway and served on the gateway's own route, e.g. https:///mcp. source: https://zuplo.com/docs/handlers/mcp-server tier: all - id: oauth-protected-resource name: OAuthProtectedResourcePlugin description: >- Registers /.well-known/oauth-protected-resource on the gateway host, returning resource, authorization_servers, resource_name and scopes_supported, and adds resource_metadata to 401 WWW-Authenticate. source: https://zuplo.com/docs/programmable-api/oauth-protected-resource-plugin tier: all - id: dev-portal name: Developer portal (Zudoku) generated from OpenAPI description: >- Branded portal whose reference is generated from the same OpenAPI that drives the gateway, with MDX pages, custom domain via CNAME, IdP sign-in and a Try-it panel that calls the live API. source: https://zuplo.com/features/developer-portal tier: all - id: self-serve-keys name: Self-serve sign-up and API key management description: >- Customers sign up, create and roll API keys, and see usage from the portal without contacting the provider. source: https://zuplo.com/features/developer-portal tier: all - id: try-it name: Try-it panel on every operation description: An interactive console on each reference operation that calls the live API. source: https://zuplo.com/features/developer-portal tier: all - id: monetization-pricing-page name: Monetization plugin — public pricing page, subscriptions, usage dashboard description: >- Adds a Pricing page listing every published plan with feature comparison and prices (visible to unauthenticated visitors), plus subscription management, per-plan keys and a quota usage dashboard, billed through Stripe. source: https://zuplo.com/docs/articles/monetization/developer-portal tier: paid - id: problem-details name: RFC 7807 Problem Details on every gateway error description: >- Built-in policies, handlers and system errors return application/problem+json with type, title, status, instance and a trace block; HttpProblems helper for custom handlers. source: https://zuplo.com/docs/concepts/api-errors tier: all - id: llms-export name: LLM-friendly docs export (llms.txt, llms-full.txt, per-page .md) description: >- Zudoku builds per-page markdown by default and, opt-in, llms.txt and llms-full.txt from the portal's own pages. source: https://zuplo.com/docs/dev-portal/zudoku/configuration/llms tier: all - id: usage-dashboard name: Consumer usage dashboard description: Real-time quota consumption per metered feature for the signed-in customer. source: https://zuplo.com/docs/articles/monetization/developer-portal tier: paid maps: - feature: dev-portal check: portal_present layer: composite provider_must: Declare the portal URL as a DeveloperPortal entry in apis.yml common[]. catalog_pass_rate: 0.228 facet: developer_ergonomics points: 4 baseline_pass_rate: 0.633 - feature: dev-portal check: api_reference_present layer: composite provider_must: Declare the reference pages as an APIReference entry in apis.yml common[]. catalog_pass_rate: 0.222 facet: developer_ergonomics points: 3 baseline_pass_rate: 0.942 saturated: true saturated_note: >- 94% of providers with a contract, docs and a reference already earn this; the vendor cannot move it for most of its buyers. - feature: try-it check: console_or_sandbox layer: composite provider_must: Declare the try-it reference as a Console (or Playground) entry in apis.yml common[]. catalog_pass_rate: 0.089 facet: developer_ergonomics points: 3 baseline_pass_rate: 0.332 - feature: self-serve-keys check: sign_up_present layer: composite provider_must: Declare the portal sign-up/login URL as a SignUp or Login entry in apis.yml common[]. catalog_pass_rate: 0.19 facet: access_clarity points: 5 baseline_pass_rate: 0.463 - feature: monetization-pricing-page check: pricing_link layer: composite provider_must: >- Enable the monetization plugin and declare the portal Pricing page as a Pricing entry in apis.yml common[]. catalog_pass_rate: 0.224 facet: access_clarity points: 4 baseline_pass_rate: 0.47 - feature: monetization-pricing-page check: plans_present layer: composite provider_must: >- Publish at least one plan on the public Pricing page; the catalog's plans artifact is harvested from it. catalog_pass_rate: 0.172 facet: access_clarity points: 8 baseline_pass_rate: 0.44 - feature: monetization-pricing-page check: plans_multiple layer: composite conditional: true condition: >- Only if the provider publishes three or more plans — Zuplo renders whatever plans exist, it does not create tiers. catalog_pass_rate: 0.11 facet: access_clarity points: 4 baseline_pass_rate: 0.325 - feature: monetization-pricing-page check: rate_limits_documented layer: composite provider_must: >- Show per-plan quotas/limits on the public Pricing page (or a docs page) so the rate_limits artifact can be harvested. catalog_pass_rate: 0.145 facet: operational_transparency points: 8 baseline_pass_rate: 0.438 - feature: rate-limit-policy check: rate_limit_signal layer: agent_readiness grade: documented partial: true partial_note: >- `verified` reads response headers declared in the provider's OpenAPI; score.rb's RATELIMIT_HEADER_RE (/\A(x-)?ratelimit|\Aretry-after\z/i) accepts Retry-After, which the policy emits by default. But nothing fetched shows Zuplo writing that header into the spec, so the vendor alone reaches only the `documented` fallback; a provider that declares Retry-After on its 429 responses earns `verified` itself. points: 7 baseline_pass_rate: 0.381 - feature: mcp-server-handler check: mcp_server layer: agent_readiness grade: templated note: >- Tools are generated from the provider's own gateway routes and served on the provider's own gateway host (custom domain possible), which is `templated` (0.6), not `platform`. It reaches `verified` only if the catalog's probe of the provider's mcp/ manifest passes. points: 12 baseline_pass_rate: 0.22 - feature: oauth-protected-resource check: protected_resource_metadata layer: agent_readiness grade: verified provider_must: >- Register the plugin with the authorization server's issuer and serve it on the host the catalog probes; the served document carries both `resource` and `authorization_servers`. points: 5 baseline_pass_rate: 0.133 - feature: oauth-protected-resource check: well_known_published layer: composite provider_must: >- Serve the protected-resource document on the provider's own API host so the well-known probe finds it; the check accepts a protected-resource doc as one of its four qualifying files. catalog_pass_rate: 0.005 facet: discoverability points: 6 baseline_pass_rate: 0.018 - feature: problem-details check: error_semantics layer: agent_readiness grade: verified conditional: true condition: >- Only if the provider's own OpenAPI $refs a shared problem+json schema across 4xx/5xx responses — the gateway returns the envelope at runtime, and Zuplo's docs leave documenting it in the OpenAPI to the provider. points: 8 baseline_pass_rate: 0.524 - feature: problem-details check: error_responses_documented layer: composite conditional: true condition: Only if the provider's OpenAPI declares 4xx response schemas on at least half its operations. catalog_pass_rate: 0.367 facet: contract_quality points: 4 baseline_pass_rate: 0.621 - feature: llms-export check: llms_txt_published layer: composite credit: 1.0 note: >- Built from the provider's own portal pages and served on the provider's portal domain, so it is authored from first-party content (rule 2). It is opt-in (`llmsTxt`), so it only counts once turned on. provider_must: >- Enable llmsTxt in the Zudoku config; declare an LLMsTxt pointer if the portal is not at the probed root. catalog_pass_rate: 0.351 facet: discoverability points: 4 baseline_pass_rate: 0.65 earns_nothing: - feature: usage-dashboard why: Consumer usage analytics are real value to a customer and no check reads them. - feature: oauth-protected-resource check: dynamic_client_registration why: >- The plugin advertises the resource; DCR needs a registration_endpoint in the authorization server's discovery document, which lives at the external IdP, not at Zuplo. - feature: oauth-protected-resource check: auth_clarity why: >- The `served` grade reads an openid-configuration/oauth-authorization-server document with an issuer on the provider's host; Zuplo serves the resource metadata only, and the discovery document is the IdP's. - feature: rate-limit-policy check: rate_limits_rich why: Enforcing three limits is not documenting them; the check counts limits the provider publishes. out_of_reach: checks: - sdk_count_1 - sdk_count_3 - cli_present - idempotency - dry_run_mode - reversibility_documented - delegated_identity - agent_card - asyncapi_present - webhooks_or_callbacks - change_log_present - status_page_present note: >- A gateway and portal cannot ship the provider's SDKs, CLI, event contract or changelog, and idempotency, dry-run and reversal are properties of the API behind the gateway. unscored_practice: - feature: problem-details why: >- A uniform RFC 7807 envelope on every gateway-originated error (auth, rate limit, validation) is exactly the behaviour error_semantics rewards, yet the score only sees it if the provider writes the schema into its own OpenAPI. - feature: rate-limit-policy why: >- Retry-After on 429 is the one header an agent needs to back off; no check reads a runtime Retry-After, only header declarations in the contract. surface: discoverability: reachable: 10.0 total: 54 contract_quality: reachable: 4.0 total: 211 operational_transparency: reachable: 8.0 total: 38 developer_ergonomics: reachable: 10.0 total: 42 access_clarity: reachable: 21.0 total: 38 agent_readiness: reachable: 23.7 total: 139 hard_rule: >- A model, not a score. Adopting this vendor changes a provider's Kin Score only when the provider publishes the resulting artifacts on its own surface; nothing here writes a score, and no sponsorship or partnership can. method: searched source: - https://zuplo.com/docs/articles/monetization/developer-portal - https://zuplo.com/docs/concepts/api-errors - https://zuplo.com/docs/dev-portal/zudoku/configuration/llms - https://zuplo.com/docs/handlers/mcp-server - https://zuplo.com/docs/policies/rate-limit-inbound - https://zuplo.com/docs/programmable-api/oauth-protected-resource-plugin - https://zuplo.com/features/developer-portal measured: cohort: method: vendors-catalog.json detections (CNAME / header / URL shape / markup), never a name match detected: 0 in_baseline: 0 control: basis: providers earning contract_present + documentation_present + api_reference_present, minus the cohort n: 5216 metric: >- cohort_pct / control_pct = mean share of the check's points earned (derived and platform credit weighted), x100 measured_on: '2026-09-25' status: 'not measurable: 0 detected customers clear the baseline (need 20)' simulation: simulated_on: '2026-09-25' rubric: 0.23.0 population: providers publishing a contract (contract_present earned), replayable exactly providers: 8977 providers_unreplayable: 987 providers_moved: 8972 conditional_rows: excluded (they depend on what the API already does) composite_lift: median: 10.9 p75: 13.6 p90: 15.7 max: 18.4 mean_among_movers: 10.7 agent_readiness_lift: median: 8.8 p75: 10.2 p90: 11.3 max: 13.1 mean_among_movers: 8.7 facet_lift_median_among_movers: discoverability: 11.1 operational_transparency: 21.0 developer_ergonomics: 16.6 access_clarity: 27.6 composite_band_moves: thin -> developing: 2951 developing -> strong: 2595 strong -> exemplar: 581 emerging -> thin: 256 emerging -> developing: 166 thin -> strong: 41 developing -> exemplar: 21 minimal -> emerging: 2 minimal -> thin: 1 agent_readiness_band_moves: agent-aware -> agent-ready: 4453 agent-ready -> agent-native: 290 agent-aware -> agent-native: 7 method: >- each provider's own kin/checks file, the vendor's maps at their stated credit, the scorer's composite formula; from -> to, nothing written