X402 Git

API routes

Everything the platform can do, and the exact shape of every request and response, is generated from the zod schemas the handlers validate against. This page is the same registry in reading order. Every error body on every route is { code, message, docs_url, details? } from the catalogue.

The base URL is https://x402git.com.

Public

Free and unauthenticated, except where a wallet signature buys a more specific answer. These are the routes a buying agent uses.

13 routes
MethodPathAuthWhat it doesErrors
GET/{owner}/{slug}publicThe manifest page

Server-rendered HTML (§14.1). The text is in the server response, not fetched by script.

not_found
GET/api/label/{owner}/{slug}publicThe public manifest

Free, cacheable, unauthenticated. Computed fields and self-reported fields are separately namespaced (FR-52).

not_found rate_limited
GET/api/v/{owner}/{slug}wallet signatureLatest version and prices

With a valid X-Wallet-Signature this also returns owned, entitled and price_kind. Without one it is version data only: ?address= alone never reveals entitlement state (FR-20). scan is the latest release's trust report in three numbers ("PRD Trust Report.md" §5) — absent, never zeroed, when there is no report to read.

not_found invalid_signature rate_limited
GET/api/v/{owner}/{slug}/releasespublicThe public release log

FR-15. Every release, with changelog, diff summary, scan verdict and the prices in force. Creators cannot delete entries, failing releases are published too, and a retired listing keeps its log (FR-43). Pages newest-first by cursor.

not_found rate_limited
GET/api/r/{owner}/{slug}wallet signatureBuy or fetch the latest release

402 then 200. Send the payment authorization in PAYMENT-SIGNATURE; access is granted only after the facilitator's settle returns a transaction hash (FR-22). A free listing returns 200 immediately (FR-24), as does a holder whose purchase was stamped all_versions (FR-16).

not_found retired invalid_signature insufficient_balance expired_authorization replay settlement_pending settlement_timeout authorization_mismatch price_changed sanctioned_counterparty blocked_jurisdiction rate_limited
GET/api/r/{owner}/{slug}/v/{version}wallet signatureRe-fetch a version you own

FR-19 / FR-46. Owned versions stay fetchable forever, including after the listing retires.

not_found not_entitled invalid_signature rate_limited
GET/api/entitlementswallet signatureWhat this wallet owns

FR-32. Requires X-Wallet-Signature: entitlement state is never disclosed without a signature (FR-40).

invalid_signature rate_limited
GET/api/termspublicThe creator agreement and the buyer terms

FR-6 and §19. accepted_terms_version on POST /api/creators must equal the top-level version. buyer_terms carries the same three fields for the terms a buyer is bound by.

GET/api/openapi.jsonpublicThis document

FR-33. Generated from the zod schemas.

GET/api/healthpublicIs the platform answering

One cheap round trip to the database, the dependency every paid path shares. status is ok or degraded; the facilitator, GitHub and Storage are deliberately not probed. Never cached. This is the status target in /.well-known/api-catalog.

GET/.well-known/api-catalogpublicWhere to start, if nobody told you

RFC 9727. A linkset (application/linkset+json) naming this document, /docs and /api/terms, anchored on the API and on the MCP server. The home page and every manifest page carry RFC 8288 Link headers pointing here, so an agent needs one HEAD request and no prior knowledge of §9.

GET/api/snippet/{owner}/{slug}publicThe README snippet

FR-30. The paste-ready preamble that tells a downstream agent to check GET /api/v/… before each run, as text/markdown. The wording differs for update_price_micro = 0 (updates included) and > 0.

not_found rate_limited
POST/api/mcppublicBuy-side MCP server (Streamable HTTP)

FR-31. JSON-RPC 2.0 over Streamable HTTP, stateless. Five tools: how_to_buy (free, no wallet: whether the listing is for sale, the price, the listing page, the skill and the endpoint — the tool a client that cannot sign answers with), get_listing (wraps GET /api/label/:owner/:slug), check_version (wraps GET /api/v/:owner/:slug), purchase (wraps GET /api/r/:owner/:slug; takes a pre-signed PAYMENT-SIGNATURE and returns the 402 body as structured content when unpaid) and fetch_release (wraps GET /api/r/:owner/:slug/v/:version). The last four call the same core module (§11); purchases are recorded with source: mcp. GET opens the notification stream and DELETE ends a session. Connect with npx @modelcontextprotocol/inspector --transport http --server-url ${APP_URL}/api/mcp.

rate_limited

Creator

Authenticated with Authorization: Bearer x4c_live_…, your agent key. This website calls exactly these routes with a key of its own, minted for your browser session; there is no private admin API.

15 routes
MethodPathAuthWhat it doesErrors
POST/api/creatorspublicRegister (headless)

FR-2. Send Idempotency-Key: <uuid> and the identical 201 replays for 24 hours, so a dropped response does not strand you without your API key. Rate limit: 5 per hour per IP.

invalid_request payout_address_unverified terms_version_stale invite_required sanctioned_counterparty blocked_jurisdiction rate_limited
GET/api/creators/meAPI keyThe authenticated creatorinvalid_key rate_limited
PATCH/api/creators/meAPI keyChange payout address or abuse contact

FR-5. Both need step_up — a signature from the *currently* bound payout address. A payout change must be signed over a challenge minted with new_payout_address=<the new address> on GET /api/auth/challenge, so the signature names where the money will go. A payout change takes effect after 48 hours and sweeps continue to the old address until then; an abuse-contact change also takes 48 hours and is announced to the old contact.

invalid_request invalid_key step_up_required payout_address_unverified sanctioned_counterparty rate_limited
POST/api/listingsAPI keyCreate a listing

Rail B (source: hosted) provisions a private repo and adds ssh_public_key as a write deploy key for that repo only (FR-7, §14.6); the 201 carries the remote, the default branch and github.com's SSH host key fingerprints so the next call is git push. Rail A (source: github) needs installation_id plus either github_repo_id or repo: "owner/name", and the repository must be private — a public one is refused with 422 public_repo (FR-10). Caps: 3 unpublished listings per creator (D2c).

invalid_request invalid_key insufficient_scope invite_required not_found public_repo too_many_unpublished rate_limited
GET/api/listingsAPI keyYour listings

The authenticated creator's own listings, newest first. Every entry carries the same pricing block as GET /api/listings/{id} (FR-13).

invalid_request invalid_key rate_limited
GET/api/listings/{id}API keyListing status, licence, latest release, pricing

latest_release is the newest release with scan = pass (FR-9), and newest_release is the newest of any verdict when the two differ — the release that failed is the one whose report a creator needs. Both carry scan_report, the full trust report including the path and line of a secret ("PRD Trust Report.md" §4.4); it is served here and nowhere else, and never on the list route. Another creator's id returns 404, never 403.

invalid_request invalid_key not_found rate_limited
PATCH/api/listings/{id}API keyChange price, update price, description or trigger hint

FR-16: price changes apply to future purchases only. Explicit API values are never rounded (FR-12).

invalid_request invalid_key insufficient_scope not_found retired rate_limited
POST/api/listings/{id}/syncAPI keyCut a version now

FR-8. Also the recovery path for a dropped webhook, and the only path for a tag pushed before the listing existed. With no ref and no version, cuts the repository's newest vX.Y.Z tag that has not been cut yet; version is required when a ref is given and that ref is untagged.

invalid_request invalid_key insufficient_scope not_found retired version_exists repo_too_large rate_limited
POST/api/listings/{id}/licenceAPI keyCommit an allow-listed LICENSE (Rail B)

FR-11. MIT, Apache-2.0, BSD-3-Clause or MPL-2.0, committed to main. The attestation is made by the registering operator (D4).

invalid_request invalid_key insufficient_scope not_found retired rate_limited
PUT/api/listings/{id}/ssh-keyAPI keyRotate the deploy key

FR-7 / FR-5. Step-up required. One public key, one repo (§14.6): the new key is added before the old one is removed.

invalid_request invalid_key insufficient_scope step_up_required not_found retired rate_limited
POST/api/listings/{id}/publishAPI keyPublish: draft → live

FR-42. Needs a licence pass, at least one passing release, a description, and — on Rail A — a repository that is still private (FR-10). A 409 listing_not_ready carries details.blocking[] with every unmet requirement at once. Publishing a live listing is an idempotent 200.

invalid_key insufficient_scope not_found retired listing_not_ready rate_limited
DELETE/api/listings/{id}API keyRetire (stop selling)

FR-43. Terminal. Holders keep every version they own; the manifest and release log stay up, marked no longer sold. A hosted repo is archived, not deleted.

invalid_key insufficient_scope not_found retired rate_limited
DELETE/api/listings/{id}/repoAPI keyDelete the hosted repo

FR-44. Step-up required. Releases with at least one purchase are retained — they are the product (FR-46).

invalid_request invalid_key insufficient_scope step_up_required not_found rate_limited
GET/api/earningsAPI keyThe ledger view

FR-29. Sale rows carry version, kind, amount and time — never the buyer address, never the transaction hash (FR-40).

invalid_key insufficient_scope rate_limited
POST/api/payouts/sweepAPI keyForce a sweep of the released balance

FR-26. Payouts go only to the bound payout address; there is no to parameter. Normally the daily cron does this and you never call it.

invalid_key sanctioned_counterparty rate_limited

Identity

Identity. A challenge is single-use and expires in five minutes.

2 routes
MethodPathAuthWhat it doesErrors
GET/api/auth/challengepublicGet a challenge to sign

FR-20. Single use, 5-minute expiry. A challenge is bound to one purposeaddress_proof on registration, step_up on a payout change, rotate_key, or wallet_ownership for the X-Wallet-Signature header — and a signature is refused on any other path (W2.2). purpose is required; there is no default. A payout rebind additionally requires new_payout_address: the signed message names the incoming address, and the signature authorises that one change only.

invalid_request rate_limited
POST/api/auth/rotate-keypublicRotate the API key with a wallet signature

FR-4. No Authorization header: the signature from the bound payout address is the credential. The old key is revoked immediately.

invalid_request invalid_signature not_found rate_limited

Internal

Called by GitHub and by the scheduler, never by you.

3 routes
MethodPathAuthWhat it doesErrors
POST/api/webhooks/githubpublicGitHub webhook

FR-8. push (version tags), release (published), installation and installation_repositories. The signature is verified over the raw body; deliveries older than 5 minutes are refused; X-GitHub-Delivery is deduplicated; the listing is resolved by installation and repo id, never by name. Every outcome after a valid signature is a 2xx, so a failure never teaches GitHub to disable the hook.

invalid_signature
GET/api/cron/sweeppublicDaily sweep

FR-26 / FR-28. Vercel Cron.

GET/api/cron/reconcilepublicDaily reconciliation

FR-28. Reconciles the receiving address's on-chain balance and every recorded tx hash against chain. Variance halts sweeps.

MCP

The same routes are an MCP server, so an agent gets them as tools in one line.

claude mcp add --transport http x402git https://x402git.com/api/mcp

Any client that takes a remote MCP server can add it by URL — in claude.ai and ChatGPT that is a custom connector at https://x402git.com/api/mcp; there is no login step, because every tool that moves money is authenticated by the payment itself.

6 tools, in the order an agent uses them
ToolWhat it doesWraps
search_listingsFind live repos and agent skills by what they do. Free, no wallet.nothing
how_to_buyWhether a listing is for sale, its price, the page to buy on, the skill and the endpoint. Free, no wallet.nothing
get_listingThe free manifest: file tree, licence, dependencies, security scan.GET /api/label/:owner/:slug
check_versionThe latest version, and what the next fetch costs this wallet.GET /api/v/:owner/:slug
purchaseBuy the latest release over x402 with a payment you signed locally.GET /api/r/:owner/:slug
fetch_releaseRe-fetch a version this wallet already owns. Free, forever.GET /api/r/:owner/:slug/v/:version

search_listings is where an agent starts when it does not know a listing's name. It takes the job in plain words — { "query": "turn git history into a changelog" } — and searches every live listing's name, description, computed tags and README. Each result carries the owner and slug the other five tools take, the price, and which of your words it matched. Results are ordered by how well they match, then by name, and never by sales or popularity. max_price_micro caps the price and limit caps the count.

how_to_buy is for the client that cannot sign. A chat model has no wallet and this server never holds one for it, so it cannot buy; what it can do is read the listing, quote the price, and hand the person the page to buy on or the skill to give an agent that has a wallet. That tool returns all of it in one call.

The four tools that wrap a route are the same code path as that route — an agent without MCP loses nothing by calling the routes directly. purchase follows the x402 MCP transport: an unpaid call returns the 402 as a structured result, and a client that signs locally retries with the payment in _meta["x402/payment"].

Rate limits

Fixed window, counted server-side
RouteLimitPer
POST /api/creators5 / hourIP
GET /api/auth/challenge30 / minaddress
GET /api/r/60 / minaddress
GET /api/v/600 / minIP
GET /api/label/600 / minIP
Pushes20 / hourrepository

Over the limit is 429 rate_limited with retry_after_seconds in the body and a Retry-After header. Unpublished listings are capped at three per creator.