Zero to Hundred

Public software search and comparison. Scoped account access for owned drafts; review and publish on the Zero to Hundred website.

Hosted MCP Server

npx add-mcp 'https://zerotohundred.ai/api/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

There are 4 ways to send a draft listing to Zero to Hundred. A prefill URL for web platforms, a stable REST API for custom backends, OAuth 2.1 + PKCE for partner apps, and an official MCP server for AI coding tools. Connector callers create drafts; the Builder reviews and publishes in the wizard.

Overview

The connector platform is three layers. Pick the one that fits how your platform works. They all land in the same place: a status="draft" listing the Builder finishes in the Zero to Hundred wizard.

Prefill URL

Web platforms (no backend)

Build one link, drop it in your Share menu. No SDK, no keys. The Builder signs into Zero to Hundred mid-flow and the wizard mounts pre-filled.

REST API

Custom backends

POST a draft with a bearer token. Personal Access Tokens for individual Builders, OAuth 2.1 + PKCE for partner apps acting on a Builder's behalf.

MCP server

AI coding tools

The published local server can create a software draft from a compatible MCP client. The Builder reviews and publishes it on the website.

Public discovery

Search live public software without signing in or sending a token. Use the REST endpoint directly or call z2h_search_marketplace through either MCP server.

curl --get https://zerotohundred.ai/api/v1/discover \
  --data-urlencode "search=invoicing" \
  --data-urlencode "type=tool" \
  --data-urlencode "limit=5"

The JSON response contains items, nextCursor and hasMore. Results contain public listing metadata, not source archives. Search matches words in listing text. An empty result does not prove that no suitable software exists.

REST accepts limit from 1 to 50 and defaults to 20. Repeat filter keys to select multiple values. With a search, the default sort is relevance. If hasMore is true and nextCursor is null, narrow the search. Other sorts support the returned cursor. Requests are rate-limited per IP; on 429, wait for the Retry-After interval.

Prefill URL

The lightest integration: a single link. No auth, no SDK. Add ?prefill=connector to the wizard URL and pass the fields you know as query params. The Builder lands there, signs in (Clerk preserves the query string), and the wizard opens pre-filled. Attribute the funnel with source=<your-platform>. Every draft created this way lands with created_via set to your name.

https://zerotohundred.ai/listings/new?prefill=connector
  &source=your-platform
  &title=My%20Cool%20App
  &shortDescription=Drop-in%20analytics%20for%20indie%20apps
  &demoUrl=https%3A%2F%2Fmycoolapp.example.com
  &repoUrl=https%3A%2F%2Fgithub.com%2Falice%2Fmycoolapp
  &techStack=nextjs,supabase,tailwind
  &type=tool
  &operatingStatus=ready_to_launch
  &domains=analytics,productivity
FieldTypeNotes
prefillrequiredMust be "connector" to activate the prefilled flow.
sourcestringFunnel attribution → created_via. Capped at 40 chars.
titlestringListing title. Truncated to 200 chars.
shortDescriptionstringOne-line pitch. Truncated to 160 chars.
longDescriptionstringFull description.
demoUrlurlAccepted only if http(s)-shaped.
repoUrlurlGitHub repo → sourceRepoUrl. Note: the param is repoUrl, not sourceRepoUrl.
techStackcsvComma-separated, max 10.
askingPricenumberDigits with up to 2 decimals.
typeenumOne of the 9 listing types (see Field values).
operatingStatusenumOne of the 3 operating statuses.
domainscsvComma-separated, max 5.

Everything is re-validated server-side. The prefill only seeds the form; the Builder confirms before anything is written. A ready-to-drop React button for a Share menu lives in the partner kit.

Authentication

The REST API takes a bearer token in the Authorization header. Cookies are never accepted on authenticated listing routes. Public discovery needs no account or token. There are two token types:

Authorization: Bearer z2h_pat_<prefix>_<secret>   # Personal Access Token
Authorization: Bearer z2h_oat_<secret>            # OAuth access token (1h TTL)
  • Personal Access Token. A Builder creates an account or signs in on the website, then mints one at /account/developers. Best for individual Builders and CLI/agent use.
  • OAuth access token. A partner app obtains one via the OAuth 2.1 + PKCE flow to act on a Builder's behalf.

Request only the scopes your integration uses. Owned reads need listings:read; draft creation, edits and source uploads need listings:write. Request both when you need both workflows. Account creation and sign-in happen on the website. MCP tools do not collect passwords or create accounts.

Scopes: listings:write, listings:read, and listings:publish. The publish scope is reserved. It needs the async source scan and isn't grantable at v1.

Failure semantics: a missing or unresolvable token returns a uniform 401 { "error": "Unauthorized" } (with WWW-Authenticate: Bearer). It is the same response for missing/invalid/expired/revoked, so a token can't be probed. A valid token missing the required scope returns 403 { "error": "Forbidden", "reason": "missing_scope:..." }.

REST API

Base path https://zerotohundred.ai/api/v1. Every response carries CORS headers (Access-Control-Allow-Origin: *, methods GET, POST, PATCH, OPTIONS, and Expose-Headers: Retry-After so cross-origin clients can read the rate-limit backoff). OPTIONS preflight returns 204. Connector callers only ever create draft rows. Public reads never see them.

POST/api/v1/listings

Create a draft listing. Scope listings:write. Rate limit 10 requests / 60s, keyed per token.

Compatibility notice: source code default

Draft creation now defaults sourceCodeIncluded to false when you omit it. Previously, omission defaulted to true. Update your client to send sourceCodeIncluded: true only after the Builder confirms that the listing includes source code. A repository URL alone does not make that choice. This applies to new REST drafts authenticated with a Personal Access Token or OAuth access token. Existing listings retain their saved choice.

curl -X POST https://zerotohundred.ai/api/v1/listings \
  -H "Authorization: Bearer z2h_pat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My SaaS",
    "shortDescription": "One-line pitch",
    "longDescription": "What it does, in ~100 words.",
    "type": "micro_saas",
    "operatingStatus": "ready_to_launch",
    "domains": ["analytics", "productivity"],
    "askingPrice": 199,
    "demoUrl": "https://example.com",
    "sourceRepoUrl": "https://github.com/me/myapp",
    "techStack": ["nextjs", "tailwind"],
    "source": "api"
  }'

Body

FieldTypeNotes
titlestring · required1 to 200 chars.
shortDescriptionstring · required≤ 160 chars.
longDescriptionstring · required≥ 20 chars.
typeenum · requiredSee Field values.
operatingStatusenum · requiredSee Field values.
domainsstring[]Defaults [].
askingPricenumberNon-negative.
demoUrlurlhttp/https only.
thumbnailUrlurlhttp/https only.
imageUrlsurl[]Max 5, each http/https.
techStackstring[]Defaults [].
testEnvUrl / testEnvInstructionsstringOptional reviewer test env.
testEnvCredentialsstringEncrypted at rest; never returned.
sourceCodeIncludedbooleanREST drafts default to false. Set true only when the Builder confirms source is included.
sourceRepoUrlurlgithub.com/<owner>/<repo>; owner must match your connected GitHub handle.
sourcestringAccepted for compatibility but ignored. The server records PAT creates as api and OAuth creates under the registered client ID.

201 Created

{
  "id": "...", "slug": "...", "status": "draft",
  "createdAt": "<ISO8601>",
  "editUrl": "https://zerotohundred.ai/listings/<slug>/edit",
  "publishUrl": "https://zerotohundred.ai/listings/<slug>/edit?step=review"
}

Errors: 400 (invalid JSON, Zod validation with issues[], bad URL, or GitHub-ownership mismatch), 401 / 403, 429, 500.

List your own listings, newest first. Scope listings:read. No rate limit. Keyset pagination: pass limit (default 25, max 100) and the opaque cursor from the previous page.

GET https://zerotohundred.ai/api/v1/listings?limit=25&cursor=<opaque>

200 OK
{
  "items": [
    { "id","slug","title","status","type","operatingStatus",
      "askingPrice","demoUrl","thumbnailUrl","createdVia",
      "createdAt","updatedAt","editUrl","publishUrl" }
  ],
  "nextCursor": "<opaque>|null"
}

nextCursor is non-null only when more rows exist past this page. Stop when it's null.

GET/api/v1/listings/{slug}

Read one listing you own. Scope listings:read. A slug you don't own returns 404 (anti-probe, never 403). Returns the full object; testEnvCredentials is never included.

Update a draft. Scope listings:write. Rate limit 10 / 60s per token. Drafts only. A non-draft listing returns 409. All body fields are optional; unknown keys are rejected. Send a field as null to clear it only for askingPrice, demoUrl, videoUploadUrl, thumbnailUrl or sourceRepoUrl. The legacy testEnvUrl, testEnvCredentials and testEnvInstructions fields also accept null. Omitted fields stay unchanged. Send an empty array to clear an array field.

PATCH https://zerotohundred.ai/api/v1/listings/<slug>
{ "askingPrice": 149, "techStack": ["nextjs","trpc"] }

200 OK
{ "id","slug","status":"draft","editUrl","publishUrl" }

Source archive uploads

Attach source to a draft you own with listings:write. The REST endpoints below accept a PAT or a REST OAuth grant. Hosted MCP exposes the same sequence through z2h_request_source_upload and z2h_confirm_source_upload.

Select the archive first. Show the Builder this exact statement and require their explicit acceptance for that archive before requesting or confirming an upload:

I own or have the rights to sell the codebase I am about to upload.

The current ownershipVersion is 2026-09-27. Connecting an account or creating a draft does not establish acceptance. If the archive changes, ask the Builder to accept the statement for the replacement archive.

FieldTypeNotes
contentTypestringapplication/zip, application/x-zip-compressed, application/gzip, application/x-gzip, or application/x-tar.
sizeBytespositive integerArchive size in bytes, at most 104857600 bytes, or 100 MiB. The server also checks the stored file's actual size and archive signature.
ownershipAcceptedtrueSend only after the Builder explicitly accepts the statement for the selected archive.
ownershipVersionstring2026-09-27

Request a signed upload URL. Replace the slug with your draft's slug and 1048576 with your archive's size in bytes. JSON request bodies are limited to 8 KiB.

curl --fail-with-body --request POST "https://zerotohundred.ai/api/v1/listings/<slug>/source-bundle" \
  --header "Authorization: Bearer z2h_pat_..." \
  --header "Content-Type: application/json" \
  --data '{
    "contentType": "application/zip",
    "sizeBytes": 1048576,
    "ownershipAccepted": true,
    "ownershipVersion": "2026-09-27"
  }'

200 OK
{
  "signedUrl": "<private signed upload URL>",
  "objectKey": "<returned object key>",
  "contentType": "application/zip"
}

Upload the archive bytes

Keep signedUrl and objectKey private. Set SIGNED_URL locally to the exact returned URL and PUT the selected archive before the URL expires. Use the returned contentType. The ZIP example sends only the archive bytes and its content type to storage. Do not send your account bearer token to the signed URL.

curl --fail-with-body --request PUT "$SIGNED_URL" \
  --header "Content-Type: application/zip" \
  --data-binary @./source.zip

After the PUT succeeds, confirm using the returned object key and the same archive metadata and accepted statement version.

curl --fail-with-body --request POST "https://zerotohundred.ai/api/v1/listings/<slug>/source-bundle/confirm" \
  --header "Authorization: Bearer z2h_pat_..." \
  --header "Content-Type: application/json" \
  --data '{
    "objectKey": "<returned object key>",
    "contentType": "application/zip",
    "sizeBytes": 1048576,
    "ownershipAccepted": true,
    "ownershipVersion": "2026-09-27"
  }'

200 OK
{ "ok": true }

Confirmation verifies the stored archive, attaches it to the draft and records the accepted wording. It sets sourceCodeIncluded to true and replaces any repository source attachment. Confirming the same attached object again returns ok: true without another change.

Errors include 400 for invalid JSON, metadata or archive bytes, 401 for invalid authentication, 403 for a missing scope, 404 for a missing or unowned listing, and 409 when the draft or source eligibility changes. On 429, wait before retrying. A successful PUT alone does not attach source; confirmation must succeed.

These API operations accept drafts only. If your MCP host cannot read the selected file and PUT its bytes, open the listing's editUrl and upload in the web wizard. The Builder finishes source review and publication there after the required gates pass.

OAuth 2.1 + PKCE

For partner apps acting on a Builder's behalf. Authorization-code + PKCE only. No implicit flow, no password grant. Register an app to get a client_id; choose public (PKCE-only, no secret, for browser/CLI/native clients) or confidential (a client_secret you keep server-side).

The following flow creates a REST API grant and omits resource. Hosted MCP requires a separate resource-bound grant, described below. Register your client and its exact redirect URI before starting either flow. The authorization server does not advertise automatic client registration.

1 · Authorize

Send the Builder to the consent screen. This is a page route at /oauth/authorize (not under /api/v1). PKCE is required. A request without code_challenge is rejected.

GET https://zerotohundred.ai/oauth/authorize
  ?client_id=<your-client-id>
  &redirect_uri=<exact-registered-uri>
  &response_type=code
  &scope=listings:write%20listings:read
  &state=<csrf-token>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

→ redirects to: <redirect_uri>?code=<code>&state=<state>
   (or ?error=access_denied&state=<state> on deny)

redirect_uri must match a registered URI exactly (no wildcards). The auth code is single-use and expires in 10 minutes.

2 · Exchange the code for tokens

POST /api/v1/oauth/token (accepts form or JSON; rate-limited 10 / 60s per client).

POST https://zerotohundred.ai/api/v1/oauth/token
grant_type=authorization_code
&client_id=<your-client-id>
&code=<code>
&redirect_uri=<same-as-authorize>
&code_verifier=<pkce-verifier>
# confidential clients ALSO send &client_secret=...
# public clients MUST omit client_secret

200 OK
{ "access_token": "z2h_oat_...", "refresh_token": "...",
  "token_type": "Bearer", "expires_in": 3600,
  "scope": "listings:read listings:write" }

A public client that sends a client_secret is rejected (no silent downgrade); a confidential client that omits it is rejected. Errors use RFC 6749 shape: invalid_request, invalid_client (401), invalid_grant, unsupported_grant_type.

3 · Refresh & revoke

Refresh with grant_type=refresh_token. Refresh tokens rotate: the old one is revoked and a new pair issued. Replaying a revoked refresh token revokes every live grant for that Builder + app (chain-compromise detection). Revoke any token (access or refresh) via POST /api/v1/oauth/revoke with token=.... It returns 200 { "ok": true } unconditionally (RFC 7009).

Hosted MCP authorization

The hosted endpoint is https://zerotohundred.ai/api/mcp. Clients can discover its protected resource and authorization server at these public URLs:

GET https://zerotohundred.ai/.well-known/oauth-protected-resource/api/mcp
GET https://zerotohundred.ai/.well-known/oauth-authorization-server

For an MCP OAuth grant, send the canonical resource=https://zerotohundred.ai/api/mcp at authorization, code exchange and every refresh. Keep the exact same value at each step. For example, a registered public client uses:

GET https://zerotohundred.ai/oauth/authorize
  ?client_id=<your-client-id>
  &redirect_uri=<exact-registered-uri>
  &response_type=code
  &scope=listings:read%20listings:write
  &state=<csrf-token>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fzerotohundred.ai%2Fapi%2Fmcp

POST https://zerotohundred.ai/api/v1/oauth/token
grant_type=authorization_code
&client_id=<your-client-id>
&code=<code>
&redirect_uri=<same-as-authorize>
&code_verifier=<pkce-verifier>
&resource=https%3A%2F%2Fzerotohundred.ai%2Fapi%2Fmcp

POST https://zerotohundred.ai/api/v1/oauth/token
grant_type=refresh_token
&client_id=<your-client-id>
&refresh_token=<current-refresh-token>
&resource=https%3A%2F%2Fzerotohundred.ai%2Fapi%2Fmcp

Send token requests as form data or JSON. A confidential client also sends its client_secret at code exchange and refresh. Use the resulting access token to authenticate calls to /api/mcp. MCP-bound OAuth tokens cannot authenticate direct REST listing calls, and REST OAuth grants cannot authenticate hosted MCP Builder tools. Scoped PATs support both interfaces.

MCP server

Add https://zerotohundred.ai/api/mcp as a hosted Streamable HTTP connection in a host that supports it. Search, comparison and the marketplace panel need no token. Builder tools need a scoped PAT or the MCP OAuth connection described above. A private or custom host connection is separate from a public directory listing or provider approval.

The MCP package, @zerotohundred/mcp-server, connects local coding tools to Zero to Hundred. Its Builder tools read a Personal Access Token from Z2H_API_KEY. Without that token, those tools return a setup link instead of calling the API. The server records PAT-created listings as api; client-supplied source labels do not establish integration attribution.

The local server offers public marketplace search and Builder tools for owned drafts. It can request a signed source archive upload and confirm the archive after your MCP client uploads it. The hosted /api/mcp endpoint offers the same listing tools with an OAuth grant or scoped Personal Access Token. No MCP tool publishes a listing; the Builder reviews and publishes it in the web wizard.

Claude Code, public search

claude mcp add z2h -- npx -y @zerotohundred/mcp-server

Claude Code, Builder access

To use Builder tools, configure the PAT in the server's environment and restart it. For a new local setup:

claude mcp add z2h \
  --env Z2H_API_KEY=YOUR_TOKEN \
  -- npx -y @zerotohundred/mcp-server

Local stdio configuration

Use your host's MCP configuration format and location. This server runs with command, args and an optional env containing the PAT. Omit env for public search only.

{
  "mcpServers": {
    "z2h": {
      "command": "npx",
      "args": ["-y", "@zerotohundred/mcp-server"],
      "env": { "Z2H_API_KEY": "YOUR_TOKEN" }
    }
  }
}

Tools

The local package exposes 7 tools. Hosted MCP exposes all 9 tools below. The hosted panel depends on MCP Apps support in the host; clients without it can use the text search and comparison results.

ToolDoesCallsAvailable through
z2h_search_marketplaceSearch live public listings without a tokenGET /api/v1/discoverLocal and hosted
z2h_compare_marketplace_listingsRefresh and compare 2 to 4 public slugs without a tokenPublic discoveryHosted
z2h_show_marketplaceShow public cards for up to 20 slugs without a tokenPublic MCP App resourceHosted
z2h_create_draft_listingCreate a draftPOST /api/v1/listingsLocal and hosted
z2h_list_my_listingsList your listingsGET /api/v1/listingsLocal and hosted
z2h_get_listingRead one you own by slugGET /api/v1/listings/{slug}Local and hosted
z2h_update_draft_listingUpdate a draftPATCH /api/v1/listings/{slug}Local and hosted
z2h_request_source_uploadGet a signed upload URL for an owned draftPOST /api/v1/listings/{slug}/source-bundleLocal and hosted
z2h_confirm_source_uploadVerify and attach the uploaded archivePOST /api/v1/listings/{slug}/source-bundle/confirmLocal and hosted

There is no publish tool. Publishing runs an async source scan and stays in the wizard, where the Builder reviews the draft first.

Field values

type (10 values)

full_product · micro_saas · tool · template · ai_agent
component · bot · extension · automation · design_system

operatingStatus (3 values)

ready_to_launch · live · live_with_revenue

domains (34 values, kebab-case)

ai · analytics · art-design · conference · developer-tools
e-commerce-marketplace · education · entertainment · environmental
fashion · finance · food · gaming · graphic-design · health
interior-design · marketing · membership · non-profit · political
productivity · real-estate · religious · services · social
technology · travel · wedding · other

mode is always license (pay once, own forever), and connector-created listings always start as draft.

Build an integration

Generate a Personal Access Token to call the REST API or wire up the MCP server, or drop a prefill-URL button in your Share menu. No keys needed.