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
| Field | Type | Notes |
|---|---|---|
| prefill | required | Must be "connector" to activate the prefilled flow. |
| source | string | Funnel attribution → created_via. Capped at 40 chars. |
| title | string | Listing title. Truncated to 200 chars. |
| shortDescription | string | One-line pitch. Truncated to 160 chars. |
| longDescription | string | Full description. |
| demoUrl | url | Accepted only if http(s)-shaped. |
| repoUrl | url | GitHub repo → sourceRepoUrl. Note: the param is repoUrl, not sourceRepoUrl. |
| techStack | csv | Comma-separated, max 10. |
| askingPrice | number | Digits with up to 2 decimals. |
| type | enum | One of the 9 listing types (see Field values). |
| operatingStatus | enum | One of the 3 operating statuses. |
| domains | csv | Comma-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
| Field | Type | Notes |
|---|---|---|
| title | string · required | 1 to 200 chars. |
| shortDescription | string · required | ≤ 160 chars. |
| longDescription | string · required | ≥ 20 chars. |
| type | enum · required | See Field values. |
| operatingStatus | enum · required | See Field values. |
| domains | string[] | Defaults []. |
| askingPrice | number | Non-negative. |
| demoUrl | url | http/https only. |
| thumbnailUrl | url | http/https only. |
| imageUrls | url[] | Max 5, each http/https. |
| techStack | string[] | Defaults []. |
| testEnvUrl / testEnvInstructions | string | Optional reviewer test env. |
| testEnvCredentials | string | Encrypted at rest; never returned. |
| sourceCodeIncluded | boolean | REST drafts default to false. Set true only when the Builder confirms source is included. |
| sourceRepoUrl | url | github.com/<owner>/<repo>; owner must match your connected GitHub handle. |
| source | string | Accepted 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.
| Field | Type | Notes |
|---|---|---|
| contentType | string | application/zip, application/x-zip-compressed, application/gzip, application/x-gzip, or application/x-tar. |
| sizeBytes | positive integer | Archive size in bytes, at most 104857600 bytes, or 100 MiB. The server also checks the stored file's actual size and archive signature. |
| ownershipAccepted | true | Send only after the Builder explicitly accepts the statement for the selected archive. |
| ownershipVersion | string | 2026-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.
| Tool | Does | Calls | Available through |
|---|---|---|---|
| z2h_search_marketplace | Search live public listings without a token | GET /api/v1/discover | Local and hosted |
| z2h_compare_marketplace_listings | Refresh and compare 2 to 4 public slugs without a token | Public discovery | Hosted |
| z2h_show_marketplace | Show public cards for up to 20 slugs without a token | Public MCP App resource | Hosted |
| z2h_create_draft_listing | Create a draft | POST /api/v1/listings | Local and hosted |
| z2h_list_my_listings | List your listings | GET /api/v1/listings | Local and hosted |
| z2h_get_listing | Read one you own by slug | GET /api/v1/listings/{slug} | Local and hosted |
| z2h_update_draft_listing | Update a draft | PATCH /api/v1/listings/{slug} | Local and hosted |
| z2h_request_source_upload | Get a signed upload URL for an owned draft | POST /api/v1/listings/{slug}/source-bundle | Local and hosted |
| z2h_confirm_source_upload | Verify and attach the uploaded archive | POST /api/v1/listings/{slug}/source-bundle/confirm | Local 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.