Zero to Hundred

Connector platform

Integration docs

Everything you need 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

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=lovable
  &title=My%20Cool%20App
  &shortDescription=Drop-in%20analytics%20for%20indie%20apps
  &demoUrl=https%3A%2F%2Fmycoolapp.lovable.app
  &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 /api/v1/*. 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 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.

Scopes: listings:write, listings:read, and listings:publish(reserved — publish 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) — 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.

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 · required3–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.
sourceCodeIncludedbooleanDefaults true.
sourceRepoUrlurlgithub.com/<owner>/<repo>; owner must match your connected GitHub handle.
sourcestringFunnel attribution → created_via (1–40 chars).

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.

GET/api/v1/listings

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"
}

nextCursoris 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.

PATCH/api/v1/listings/{slug}

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 (e.g. sourceRepoUrl: null clears the repo).

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

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

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).

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).

MCP server

The official MCP server, @zerotohundred/mcp-server, reaches every MCP-capable AI coding tool from one package. It reads a Personal Access Token from Z2H_API_KEY and auto-detects the calling client (Claude Code, Cursor, Windsurf, Continue, Cline) for source attribution. If Z2H_API_KEY is unset, every tool returns a one-time setup link instead of calling the API.

Claude Code

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

Cursor / Windsurf / Continue (mcp.json)

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

Tools

ToolDoesCalls
z2h_create_draft_listingCreate a draftPOST /api/v1/listings
z2h_list_my_listingsList your listingsGET /api/v1/listings
z2h_get_listingRead one by slugGET /api/v1/listings/{slug}
z2h_update_draft_listingUpdate a draftPATCH /api/v1/listings/{slug}

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 — 9 values

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

operatingStatus — 3 values

ready_to_launch · live · live_with_revenue

domains — 29 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.

Ready to build?

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.