Hanzo AI

Git

Git hosting for your org: create repos, clone, push, and see what they cost.

Also for this capability: API · CLI · MCP · SDKs

Git hosting for your org: create repos, clone, push, and see what they cost.

Base URLhttps://api.hanzo.ai
Operations44
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1232 · Git — Repository Hosting — Draft · read the specification →

git is Git hosting for an org: create repos, clone, push, browse, and see what they cost. It mounts the /v1/git surface of the cloud binary — bare repositories on disk, the smart-HTTP and SSH transports git clients speak natively, imports, pulls, mirroring and the browse pages (apps/git/git.go:1-45). It is implemented in hanzoai/cloud at apps/git. This HIP states the target surface: everything under /v1/git, with the host-gated root spellings leaving the binary for the standalone forge that serves that host in production.

Motivation

The capability claimed four top-level prefixes for one product: its own /v1/git, an un-host-gated browse tree at /git, and root routes — /, /explore, /:org/:repo — answered only when the request's Host is the dedicated git host. That host is served in production by the standalone forge, a separate process, so the root claims published addresses this binary never deliverably answers; the manifest walk already refuses a parameter-first prefix.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

The stores

The capability owns per-org stores, not one shared file: each org's repo metadata is its own git.db opened through the org-store seam (apps/git/git.go:222), beside the repositories themselves — bare git repos under {DataDir}/git, with an S3-backed storage seam (apps/git/storage.go) — and one system ssh_keys registry (apps/git/keystore.go:68), global because an SSH connection presents a key before any org is known, so auth is a single fingerprint lookup.

The addresses

Every route is under /v1/git: repos CRUD and usage; the smart-HTTP protocol at /v1/git/:org/:repo/... and, for project-scoped repos, /v1/git/:org/:project/:repo/... — the path is the only channel a git client can carry a project in, since it sends no headers; and the browse pages, folded from /git to /v1/git/explore and /v1/git/:org/:repo{,/tree,/blob,/commits}, where literal segments outrank the :org parameter so nothing collides. The router today still carries the /git tree and the three host-gated root spellings; each pair is a line in cloud's openapi/misfiled.txt. The browse tree closes by fold; the root spellings close by deletion — git.hanzo.ai is served in production by the standalone forge, so removing them changes nothing live and only retires the single-binary dev forge origin, whose clone address remains /v1/git/:org/:repo.

Repos, usage, pulls and control operations are typed. The smart-HTTP operations are declared prose, and cannot be otherwise: their wire is git's own protocol — pkt-line framing, side-band multiplexing, gzip request bodies — not JSON, and the document says so where it matters, telling the consumer this is not an API call to make by hand (apps/git/smart_http.go:38-45). The browse pages answer server-rendered HTML and are declared the same way.

Tenancy

The org is the gateway-minted, IAM-validated claim (HIP-0026), with an optional project sub-scope; every query is scoped by it, so one org can never read, clone, push to or delete another's repos. On the pack routes the path also names an org, and it MUST equal the authenticated one (apps/git/smart_http.go:284). A push is never anonymous. The one read concession is a repository marked public: fetch-side operations answer without an authenticated org, push-side never (apps/git/smart_http.go:292). Over SSH, the presented public key's fingerprint resolves through the global registry to the org whose scope the pack machinery then runs under — the same boundary the HTTP path derives from the header (apps/git/ssh.go:27-33).

Free, in those words

The capability is free: the plugin declares Price: cloud.Free (plugin/git/main.go:21), and no handler gates or meters spend. Usage is measured, not charged: every repo's size is re-measured on create and after each push, exposed per-repo and in total at /v1/git/usage, and each measurement emits a git.usage log line a metering consumer can bill on (apps/git/git.go:696).

Events, observability, stage

The capability publishes no events on the bus, so a customer's webhooks receive nothing from it. A landed receive-pack emits push.landed on the in-process lifecycle stream, and the notification reactor delivers push.landed, deploy.live and deploy.failed to the channels an org has subscribed, durably, deduplicated per event (apps/git/notify.go:48-88). Beyond the request span it emits the git.usage line and structured logs only. Its stage is ga: the manifest row carries no stage field, and absent means ga (HIP-0139 §8).

The push-to-build door is not here. The deploy trigger's one registrant is the platform process, so the forge delivers pushes to platform's hook (HIP-1230); a receiver in this process signed, accepted, answered 204 and built nothing, which is why the address moved.

Upstreams

  • go-git/go-git v5 (Apache-2.0) — repository init and the read path, confined to one file so nothing go-git-shaped escapes it (apps/git/gitbackend.go:3-12).
  • go-git/go-billy v5 (Apache-2.0) — the filesystem abstraction under it.
  • golang.org/x/crypto/ssh (BSD-3-Clause) — the SSH transport.
  • the system git binary (GPL-2.0) — executed as a subprocess for stateless-RPC pack streaming (apps/git/gitexec.go), so multi-GB packs stream through stdin/stdout and never land in this process's memory; invoked, never linked.
  • the smart-HTTP framing patterns are ported from the upstream forge's routers/web/repo/githttp.go (MIT) (apps/git/smart_http.go:30).

Rationale

The alternative to deleting the root spellings is keeping a second clone address alive in a binary that does not serve the host it is gated on — an address the manifest walk refuses and production traffic never reaches. The alternative to declaring the pack operations is typing them, which would publish JSON schemas for a wire that is not JSON and hand every generated SDK twelve methods that corrupt a clone when called.

Security Considerations

The wrong implementation leaks source code. A pack route that trusts the path org instead of comparing it to the authenticated one serves another tenant's repository to anyone who can spell its name; the path-vs-identity guard is the boundary. The SSH key registry is the authentication for the SSH transport, so writing to it is minting access: key registration is org-scoped and the fingerprint lookup is global only because it must run before identity exists. Pack streaming through the CLI is also the memory bound — buffering a push in-process would let one crafted pack exhaust the binary serving every org.

Four surfaces

SurfaceReaches this capability asCoverage
RESTgit at its own prefix44 operations
CLIhanzo git …44 of 44
SDKGitApi in every published client40 of 44 — the clients are generated at their own release
MCPtool git on https://api.hanzo.ai/v1/mcp56 operations, 11 under the document's own id — ask describe for the rest

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/git, operation get_git:

hanzo git list

Endpoints

EndpointWhat it does
POST /v1/git/{org}/{project}/{repo}/git-receive-packAccept a push, and turn it into a build
POST /v1/git/{org}/{project}/{repo}/git-upload-packServe a clone or fetch
GET /v1/git/{org}/{project}/{repo}/info/refsAdvertise a repository's refs to a git client
GET /v1/git/{org}/{repo}/commitsRead a repository's commit log
POST /v1/git/{org}/{repo}/git-receive-packAccept a push, and turn it into a build
POST /v1/git/{org}/{repo}/git-upload-packServe a clone or fetch
GET /v1/git/{org}/{repo}/info/refsAdvertise a repository's refs to a git client
GET /v1/git/{org}/{repo}Open a repository's home page
GET /v1/git/exploreDiscover public repositories across every org
DELETE /v1/git/keys/{id}Removes a registered SSH key, scoped to the caller's org: an org can only delete its own, and a key id it does not own is not found.
GET /v1/git/keysReturns the SSH public keys registered to the caller's org — the keys that authenticate git clone git@<host>:<org>/<repo>.git.
POST /v1/git/keysRegisters an SSH public key so it can authenticate git clone git@<host>:<org>/<repo>.git for the caller's org.
GET /v1/git/repos/{name}/blobReturns one file's bytes at one revision.
GET /v1/git/repos/{name}/commitsWalks a ref's history newest first, or one path's history when a path is given.
GET /v1/git/repos/{name}/filesReturns every file a glob selects at one revision, WITH its bytes and the revision they came from.
POST /v1/git/repos/{name}/gcRepacks a repo into one bitmapped pack and rewrites its commit-graph, so the next clone reuses the bitmap instead of walking the whole object graph.
POST /v1/git/repos/{name}/mirrorImports an external git repository into the caller's repo, provisioning it on first use.
DELETE /v1/git/repos/{name}/mirrors/{id}Removes one outbound mirror target; later pushes stop being forwarded to it.
GET /v1/git/repos/{name}/mirrorsReturns a repo's outbound mirror targets — the downstream remotes the mirror reactor pushes to whenever a push lands here.
POST /v1/git/repos/{name}/mirrorsRegisters a downstream remote the repo's advanced refs are pushed to whenever a push lands here.
POST /v1/git/repos/{name}/pulls/{number}/mergeMerges an open pull request by FAST-FORWARDING base to head, and answers the proposal in its merged state with the revision base now points at.
GET /v1/git/repos/{name}/pulls/{number}Returns one pull request by its per-repo number.
GET /v1/git/repos/{name}/pullsReturns a repo's pull requests, newest number first — what is waiting to be reviewed, and what has already landed.
POST /v1/git/repos/{name}/pullsProposes a branch for merging and returns it with its number.
POST /v1/git/repos/{name}/pushLands a set of files as one commit without a git client — the hanzo.app builder's push.
GET /v1/git/repos/{name}/readmeReturns the README at the tree root as plain text — unrendered, so the caller decides how to present it.
GET /v1/git/repos/{name}/refsLists a repo's branches, tags and default branch — what a branch picker needs in one call.
DELETE /v1/git/repos/{name}/subscriptions/{id}Removes one Slack subscription from a repo; the notifier stops posting that repo's events to that channel.
GET /v1/git/repos/{name}/subscriptionsReturns a repo's Slack subscriptions — which channels the lifecycle notifier posts this repo's push and deploy events to.
POST /v1/git/repos/{name}/subscriptionsBinds a Slack channel to a repo, so the lifecycle notifier posts that repo's push and deploy events there.
GET /v1/git/repos/{name}/treeLists the immediate children of one directory at one revision, directories before files.
GET /v1/git/repos/{name}Returns one repo with its live ref state: every branch name and the resolved HEAD commit.
PATCH /v1/git/repos/{name}Flips a repo's public bit, the one mutable repo setting today.
DELETE /v1/git/repos/{name}Removes a repo's metadata and purges its storage.
GET /v1/git/reposReturns the repos in the caller's scope, most recently updated first.
POST /v1/git/reposProvisions an empty bare repository in the caller's scope and returns it with its clone URLs.
GET /v1/git/usageReturns per-repo and total storage bytes for the caller's org — the queryable, per-tenant number commerce and o11y meter on.
POST /v1/git/webhookRetired — forge pushes build via platform.hanzo.ai
POST /v1/git/zap/createRepoCreate a repository over the ZAP transport
POST /v1/git/zap/deleteRepoDelete a repository over the ZAP transport
POST /v1/git/zap/getRepoRead one repository over the ZAP transport
POST /v1/git/zap/listReposList your repositories over the ZAP transport
POST /v1/git/zap/usageReport your org's git storage footprint over the ZAP transport
GET /v1/gitBrowse your org's repositories

All Hanzo APIs · Interactive reference

How is this guide?