Harness Open Source (Gitness/Drone) — API Surface#
API types#
REST/HTTP, Smart HTTP Git Protocol, Git LFS, OCI Distribution (Docker Registry), Multi-format Package Registry, CLI, Library/SDK, Server-Sent Events (SSE)
REST/HTTP API#
- Router: go-chi/chi v5
- Base path:
/api/v1/(mounted viaAPIRouter; prefix stripped before chi sees it) - Route registration: Explicit chi
r.Route()/r.Get()/r.Post()calls inapp/router/api.go; one large file registers all ~150+ endpoints grouped by domain function - OpenAPI: Full OpenAPI 3 spec generated programmatically via
swaggest/restreflector; available viagitness swagger generateCLI command
Middleware chain (in order)#
| Layer | Middleware | Purpose |
|---|---|---|
| 1 | nocache.NoCache | Prevents caching of API responses |
| 2 | middleware.Recoverer (chi) | Panic recovery → 500 |
| 3 | logging.URLHandler, hlog.MethodHandler, HLogRequestIDHandler, HLogAccessLogHandler | Structured zerolog request logging |
| 4 | address.Handler | Populate request address in context |
| 5 | corsHandler | CORS (configured from config.Cors.*) |
| 6 | audit.Middleware | Audit log injection into context |
| 7 | middlewareauthn.Attempt(authenticator) | Optional JWT authentication — populates auth.Session in context; does NOT 401 on missing auth |
| 8 | middlewareprincipal.RestrictTo(enum.PrincipalTypeUser) | Applied per route group to enforce user-only access (e.g., /user/) |
| 9 | middlewareprincipal.RestrictToAdmin() | Applied to /admin/ routes |
| 10 | middlewareauthz.BlockSessionToken | Applied to git protocol routes — rejects session tokens, forces PAT/SSH |
| 11 | usage.Middleware(usageSender) | Applied to raw/archive/git routes for bandwidth tracking |
Authentication#
Handled by JWTAuthenticator (app/auth/authn/jwt.go), which accepts tokens from:
Authorization: Bearer <jwt>— primary for API calls- Cookie named
config.Token.CookieName— for browser sessions Authorization: RemoteAuth <jwt>— for SSH git-lfs-authenticate tokens- Anonymous principal (JWT with
PrincipalID == -1) — for public repo access
Token types: PAT (Personal Access Token), SAT (Service Account Token), Session (browser), Pipeline JWT (ephemeral 72h, for CI steps). Authorization checks are delegated to authz.Authorizer inside each controller (RBAC with space-level membership hierarchy).
Key endpoint groups#
Account (no auth required)#
POST /v1/login— authenticate, returns JWT session tokenPOST /v1/register— create account + auto-loginPOST /v1/logout— invalidate session cookie (requires auth)
Spaces (/v1/spaces/{space_ref})#
POST /v1/spaces/— create spacePOST /v1/spaces/import— import space from external SCMGET/PATCH/DELETE /v1/spaces/{ref}— find, update, soft-deletePOST /v1/spaces/{ref}/restore//purge— lifecycle managementGET /v1/spaces/{ref}/events— SSE stream of space-level eventsPOST /v1/spaces/{ref}/move— move/rename spaceGET /v1/spaces/{ref}/repos//spaces//pipelines//executions— list sub-resourcesGET /v1/spaces/{ref}/secrets//connectors//templates//gitspaces— list CI resourcesGET/POST/DELETE/PATCH /v1/spaces/{ref}/members/{uid}— membership managementGET/POST/PATCH/DELETE /v1/spaces/{ref}/labels/{key}— label management (with nested /values)GET/POST/PATCH/DELETE /v1/spaces/{ref}/webhooks/{id}— webhook CRUD + execution historyGET/POST/PATCH/DELETE /v1/spaces/{ref}/rules/{id}— branch protection rulesGET/POST/PATCH/DELETE /v1/spaces/{ref}/autolinks/{id}— autolink rules (e.g., ticket refs)GET /v1/spaces/{ref}/settings/general/PATCH— space-level settingsGET/POST /v1/spaces/{ref}/pullreq//pullreq/count— cross-repo PR listing
Repositories (/v1/repos/{repo_ref})#
POST /v1/repos/— create;/import— import;/link— link externalGET/PATCH/DELETE /v1/repos/{ref}— find, update, soft-deletePOST /v1/repos/{ref}/fork//fork-sync— fork managementGET /v1/repos/{ref}/summary— repository summary statsGET /v1/repos/{ref}/content/*— browse file tree at any refGET /v1/repos/{ref}/blame/*— git blame at pathGET /v1/repos/{ref}/raw/*— raw file content download (usage tracked)GET /v1/repos/{ref}/paths— list all file pathsPOST /v1/repos/{ref}/path-details— batch file metadataGET /v1/repos/{ref}/commits/— list commits;POST /— commit filesGET /v1/repos/{ref}/commits/{sha}//diff— single commit detailsPOST /v1/repos/{ref}/commits/calculate-divergence— branch divergenceGET/POST/DELETE /v1/repos/{ref}/branches/*— branch CRUDGET/POST/DELETE /v1/repos/{ref}/tags/*— tag CRUDGET/POST /v1/repos/{ref}/diff/*//diff-stats/*//merge-check/*— diff operationsPOST /v1/repos/{ref}/rebase//squash— git operationsGET /v1/repos/{ref}/archive/{gitRef}.{format}— download archive (tar/zip)GET /v1/repos/{ref}/codeowners/validate— CODEOWNERS validationGET/PATCH /v1/repos/{ref}/settings/security//general— repo settings
Pull Requests (/v1/repos/{ref}/pullreq)#
POST/GET /pullreq— create, listGET/PATCH /pullreq/{num}— find, updatePOST /pullreq/{num}/state— change state (open/closed)GET /pullreq/{num}/activities— activity feedPOST/PATCH/DELETE /pullreq/{num}/comments/{id}— comment CRUDPOST /pullreq/{num}/comments/apply-suggestions— apply code suggestionsGET/PUT/DELETE /pullreq/{num}/reviewers/{id}//usergroups— reviewer managementGET /pullreq/{num}/reviewers/combined— merged reviewer listPOST /pullreq/{num}/reviews— submit review decisionPOST /pullreq/{num}/merge— merge PRPOST /pullreq/{num}/revert— create revert PRPUT/DELETE /pullreq/{num}/automerge— auto-merge managementGET /pullreq/{num}/diff//commits//metadata//codeowners//checksPOST/DELETE /pullreq/{num}/branch— manage PR source branchPUT /pullreq/{num}/target-branch— change target branchGET/PUT/DELETE /pullreq/{num}/file-views— track which files have been reviewedPUT/GET/DELETE /pullreq/{num}/labels— PR label management
Pipelines and CI (/v1/repos/{ref}/pipelines)#
GET/POST/DELETE/PATCH /pipelines/{id}— pipeline CRUDGET /repos/{ref}/pipelines— list;GET /generate— AI-generate pipeline YAMLGET/POST/DELETE/PATCH /pipelines/{id}/executions/{num}— execution CRUD + cancelGET /executions/{num}/logs/{stage}/{step}— fetch stored logGET /executions/{num}/logs/{stage}/{step}/stream— SSE live log tailGET/POST/PATCH/DELETE /pipelines/{id}/triggers/{id}— trigger management
User (/v1/user)#
GET/PATCH /user— self profileGET /user/memberships— space membershipsGET/POST/DELETE /user/tokens/{id}— PAT managementGET/DELETE /user/sessions/{id}— session token managementGET/POST/DELETE/PATCH /user/keys/{id}— SSH public key managementPOST/DELETE /user/favorite/{id}— favorite repos
Admin (/v1/admin, admin principal only)#
GET/POST /admin/users— list, create usersGET/PATCH/DELETE /admin/users/{uid}— user managementPATCH /admin/users/{uid}/admin— grant/revoke admin
CI Infrastructure#
GET/POST/PATCH/DELETE /v1/connectors/{ref}+POST /test— SCM/infra connectorsGET/POST/PATCH/DELETE /v1/secrets/{ref}— secret storeGET/POST/PATCH/DELETE /v1/templates/{type}/{ref}— reusable step/stage templatesGET /v1/plugins— list available CI plugins
Gitspaces (/v1/gitspaces)#
POST /gitspaces/lookup-repo— validate repo URL for gitspace creationPOST/GET /gitspaces— create, list allGET/PATCH/DELETE /gitspaces/{id}— find, update, deletePOST /gitspaces/{id}/actions— start/stop/reset containerGET /gitspaces/{id}/events— SSE lifecycle eventsGET /gitspaces/{id}/logs/stream— SSE provisioning log stream
Infrastructure Providers#
POST/GET/DELETE /v1/infraproviders/{id}— Docker/k8s infra config management
System#
GET /v1/system/health— health check (no auth)GET /v1/system/version— version infoGET /v1/system/config— public configuration (feature flags, SCM providers)
Internal (git hook callbacks)#
POST /v1/internal/git-hooks/pre-receivePOST /v1/internal/git-hooks/updatePOST /v1/internal/git-hooks/post-receive
These are called by the server-side git hook process back to the gitness HTTP server during push operations. Authenticated with ephemeral pipeline JWT tokens.
Migration#
POST /v1/migrate/repos/— create repo via migrationPOST /v1/migrate/repos/{ref}/pullreqs//webhooks//rules— batch import sub-resourcesPOST /v1/migrate/spaces/{ref}/labels— migrate label definitions
Git Protocol (Smart HTTP)#
- Router:
GitRouter(app/router/git_router.go) — handles all traffic that isn’t prefixed with/api/or registry paths - Base path:
/{repo_ref}/(whererepo_refis a slash-encoded space/repo path)
| Method | Path | Description |
|---|---|---|
POST | /{ref}/git-upload-pack | Serve fetch/clone (smart protocol) |
POST | /{ref}/git-receive-pack | Receive push (smart protocol) |
GET | /{ref}/info/refs?service=git-* | Smart protocol capability discovery |
GET | /{ref}/ | Browser redirect to UI repo page |
GET | /{ref}/HEAD, /objects/* | Dumb protocol stubs — intentionally return 502 with message |
Middleware additions for git routes:
goget.Middleware— serves?go-get=1responses forgo getsupportmiddlewareauthz.BlockSessionToken— rejects browser session cookies; forces PAT or basic-authusage.Middleware— tracks bandwidth consumption
Git LFS (under git router)#
| Method | Path | Description |
|---|---|---|
POST | /{ref}/info/lfs/objects/batch | LFS batch API (negotiate transfer) |
PUT | /{ref}/info/lfs/objects/ | Upload LFS object |
GET | /{ref}/info/lfs/objects/ | Download LFS object |
OCI Registry API (/v2/...)#
Implements the OCI Distribution Specification.
- Router:
RegistryRouter— picks up traffic at/v2/prefix - Handler:
registry/app/api/router/oci/route.go— dispatch table keyed by route type + HTTP method
| Operation | Method | Path |
|---|---|---|
| Auth token | GET | /v2/token |
| API check | GET | /v2/ |
| Get manifest | GET/HEAD | /v2/{registry}/manifests/{reference} |
| Push manifest | PUT | /v2/{registry}/manifests/{reference} |
| Delete manifest | DELETE | /v2/{registry}/manifests/{reference} |
| Get blob | GET | /v2/{registry}/blobs/{digest} |
| Head blob | HEAD | /v2/{registry}/blobs/{digest} |
| Delete blob | DELETE | /v2/{registry}/blobs/{digest} |
| Initiate upload | POST | /v2/{registry}/blobs/uploads/ |
| Upload chunk | PATCH | /v2/{registry}/blobs/uploads/{session} |
| Complete upload | PUT | /v2/{registry}/blobs/uploads/{session} |
| Cancel upload | DELETE | /v2/{registry}/blobs/uploads/{session} |
| Get upload status | GET | /v2/{registry}/blobs/uploads/{session} |
| List tags | GET | /v2/{registry}/tags/list |
| Get referrers | GET | /v2/{registry}/referrers/{digest} |
Registry-specific middleware:
middleware.OciCheckAuth— OCI-specific auth challenge (WWW-Authenticate)middleware.BlockNonOciSourceToken— rejects tokens not issued for OCI accessmiddleware.TrackDownloadStat— download count trackingmiddleware.TrackBandwidthStat— bandwidth trackingmiddleware.CheckQuarantineStatusOCI— blocks quarantined artifacts
Package Registry API#
Multi-format artifact registry under /{rootIdentifier}/{registryName}/<type>/.
- Router:
RegistryRouterhandles/registry/,/maven/,/generic/,/pkg/prefixes - Package types supported (each has its own handler package in
registry/app/api/handler/):
| Package Type | Protocol | Use Case |
|---|---|---|
npm | npm registry protocol | Node.js packages |
maven | Maven/Gradle repository protocol | Java artifacts |
python | PyPI/PEP 503 simple index | Python packages |
nuget | NuGet v3 feed | .NET packages |
rpm | YUM/DNF repository | Linux RPM packages |
cargo | Cargo sparse index | Rust crates |
go | Go module proxy protocol | Go modules |
generic | Simple upload/download | Arbitrary binaries |
huggingface | HuggingFace Hub protocol | ML model weights |
Registry Management API (/api/v1/registry/, /api/v1/spaces/.../registries)#
Manages registry lifecycle (create, configure, GC policies, replication, webhooks).
- Handler: Generated from OpenAPI contract at
registry/app/api/openapi/contracts/artifact/viaoapi-codegen; usesartifact.NewStrictHandlerwrappingmetadata.APIController - Key operations: CRUD for registries, cleanup policies, replication rules, upstream proxies, artifact listing, tag management, webhook CRUD for registry events
CLI#
- Framework:
gopkg.in/alecthomas/kingpin.v2 - Binary:
gitness
Command structure#
gitness
├── server # Start HTTP/SSH server (primary command)
│ └── --config env vars # All configuration via environment variables
├── migrate
│ ├── current # Show current DB migration version
│ └── to <version> # Migrate DB to target version
├── user
│ ├── self # Print info about authenticated user
│ └── create-pat # Create Personal Access Token
├── users # Admin user management
│ ├── list
│ ├── create
│ ├── find
│ ├── update
│ └── delete
├── account
│ ├── login # Authenticate and store session
│ ├── register # Create new user account
│ └── logout # Invalidate session
├── hooks # Git hook helpers (for server-side hooks)
└── swagger # OpenAPI spec management
└── generate # Print OpenAPI 3 JSON spec to stdoutFlag patterns#
- Global: no global flags; config is environment-variable only for
servercommand - CLI commands (
user,users,account) use--host/--tokenflags or session file stored at~/.gitness(viacli/session/package) - Commands reading from the session file use
cli/provide/helpers to inject credentials
Library/SDK API#
The client/ package exposes a Go HTTP client for the REST API:
type Client interface {
Login(ctx, *user.LoginInput) (*types.TokenResponse, error)
Register(ctx, *user.RegisterInput) (*types.TokenResponse, error)
Self(ctx) (*types.User, error)
User(ctx, key string) (*types.User, error)
UserList(ctx, types.UserFilter) ([]types.User, error)
UserCreate(ctx, *types.User) (*types.User, error)
UserUpdate(ctx, key string, *types.UserInput) (*types.User, error)
UserDelete(ctx, key string) error
UserCreatePAT(ctx, user.CreateTokenInput) (*types.TokenResponse, error)
}This is a minimal client — only covers user/auth operations. Not a full API SDK. The registry sub-module has a separate generated client from the OpenAPI contract.
Notable API design decisions#
Four-router dispatch avoids conflicting patterns. The outer
RouterchecksIsEligibleTraffic()in priority order (Git → Registry → API → Web). This prevents the SPA catch-all (WebRouter) from swallowing git or registry traffic, and keeps each sub-system’s routing logic independent. The tradeoff: a new traffic type requires a newInterfaceimplementation and a priority insertion decision.Path encoding middleware. The
encode.GitPathBeforeandencode.TerminatedPathBeforewrappers pre-process URLs before chi sees them. Git paths contain slash-encoded repository references (e.g.,myspace%2Fmyrepo); this is decoded into the correct URL segment. Similarly, terminated paths ending in/are normalized. This keeps chi’s routing simple at the cost of a custom URL transformation layer.Optional authentication model.
middlewareauthn.Attempt()does NOT require authentication — it populates the session if credentials exist, but continues without them. Authorization is enforced inside each controller method viaauthz.Authorizer.Check(). This enables public repo access without separate unauthenticated route groups — every route can serve both authenticated and anonymous traffic.Internal git-hook callbacks. Push operations trigger server-side hooks that call back to
POST /api/v1/internal/git-hooks/pre-receiveetc. These are protected with ephemeral pipeline JWT tokens injected into the git hook environment. This design keeps the git hook logic in Go (not shell scripts) and enables hook results to influence the push response, while staying within the same HTTP server.No gRPC. Despite integrating with the Drone CI ecosystem (which uses gRPC in some versions), Gitness is pure HTTP. The pipeline runner executes as an in-process goroutine using the
embeddedclient (adapter pattern overExecutionManager). External Drone runner support is available via thedrone/runner-goclient library, but the server does not expose a dedicated runner RPC endpoint — runners would use the same API surface.SSE for real-time streaming. Live log tailing, gitspace events, and space-level events use Server-Sent Events rather than WebSockets. SSE is simpler (HTTP/1.1 compatible, automatic reconnect in browsers), sufficient for unidirectional server→client streaming, and avoids the connection upgrade overhead of WebSockets.
OpenAPI generated from code, not from YAML. The API spec is created programmatically by the
app/api/openapi/package usingswaggest/restreflector. The registry sub-module uses the opposite approach: an OpenAPI contract YAML (artifact/openapi.yaml) drives code generation viaoapi-codegen, producing typed request/response structs and aStrictHandlerinterface. This contrast shows divergent team philosophies within the same binary.