Base URL: http://<host>:<port>/api
All JSON responses use Content-Type: application/json unless noted otherwise.
GET /healthReturns 200 with body ok. This endpoint is outside /api.
Failed requests return JSON:
{
"error": "human-readable message"
}Clients should rely on HTTP status codes, not exact error strings.
| Status | Meaning |
|---|---|
400 |
Invalid request (bad path, invalid commit metadata, malformed body) |
404 |
Repository, branch, or Git object not found |
409 |
Conflict (expected head SHA mismatch, or branch already exists) |
413 |
File or commit exceeds configured size limits |
500 |
Internal server error |
Repository IDs are caller-defined and may contain slashes (for example acme/widgets). Encode slashes in URL paths:
acme/widgets
→ acme%2Fwidgets
The server URL-decodes the {id} path parameter before resolving the repository.
Repository IDs must be relative, non-empty paths without .., .git segments, or null bytes.
Supergit exposes bare repositories for git clone, git fetch, and git push under:
http://<host>:<port>/git/{repository-id}.git
Example:
git clone http://localhost:8080/git/acme/widgets.gitConfigure SUPERGIT_PUBLIC_URL (for example http://localhost:8080/git) so repository metadata includes clone_url.
GET /api/reposResponse 200
{
"repos": [
{
"id": "acme/widgets",
"default_branch": "main",
"clone_url": "http://localhost:8080/git/acme/widgets.git"
}
],
"next_cursor": "",
"has_more": false
}Pagination fields are reserved for future use; listing currently returns all repositories.
POST /api/repos
Content-Type: application/jsonRequest body
{
"id": "acme/widgets",
"default_branch": "main"
}| Field | Required | Description |
|---|---|---|
id |
yes | Repository identifier |
default_branch |
no | Branch created on git init (defaults to server SUPERGIT_DEFAULT_BRANCH) |
If the repository already exists, the call succeeds and returns the existing repository metadata.
Response 201
{
"id": "acme/widgets",
"default_branch": "main",
"clone_url": "http://localhost:8080/git/acme/widgets.git"
}clone_url is omitted when SUPERGIT_PUBLIC_URL is not set.
Creating a repository initializes an empty bare repo. Initial files (for example README.md) are added by the client via the commits endpoint.
GET /api/repos/{id}Response 200
{
"id": "acme/widgets",
"default_branch": "main",
"clone_url": "http://localhost:8080/git/acme/widgets.git"
}DELETE /api/repos/{id}Deletes the bare repository from disk. Returns 204 with no body. Missing repositories are treated as success.
GET /api/repos/{id}/files
GET /api/repos/{id}/files?ref={ref}| Query | Description |
|---|---|
ref |
Branch, tag, or commit SHA (defaults to the repository default branch) |
Response 200
{
"paths": ["README.md", "src/main.go"],
"ref": "main"
}Returns all file paths in the tree at the given ref, recursively.
GET /api/repos/{id}/files?path={path}
GET /api/repos/{id}/files?path={path}&ref={ref}| Query | Required | Description |
|---|---|---|
path |
yes | Repository-relative file path |
ref |
no | Branch, tag, or commit SHA |
Response 200
Content-Type: application/octet-stream- Body: raw file bytes
GET /api/repos/{id}/commits
GET /api/repos/{id}/commits?branch={branch}&limit={limit}| Query | Default | Description |
|---|---|---|
branch |
repo default branch | Branch to walk |
limit |
20 |
Maximum number of commits |
Response 200
{
"commits": [
{
"commit_sha": "9e6a46e7f5affc39101a2bbe03b85ea0c934cdef",
"tree_sha": "abc123...",
"message": "Initialize repository",
"author": {
"name": "SuperPlane",
"email": "bot@superplane.local"
}
}
],
"next_cursor": "",
"has_more": false
}Commits are ordered newest first.
POST /api/repos/{id}/commits
Content-Type: application/x-ndjsonCreates a commit by streaming file operations in NDJSON format.
The body is a newline-delimited stream:
- First line:
metadataobject describing the commit and file operations - Following lines:
blob_chunkobjects with base64-encoded file content
{
"metadata": {
"target_branch": "main",
"base_branch": "",
"expected_head_sha": "",
"commit_message": "Update files",
"author": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"files": [
{
"path": "README.md",
"operation": "upsert",
"content_id": "blob-1",
"mode": "100644"
},
{
"path": "old.txt",
"operation": "delete",
"content_id": "blob-2"
}
]
}
}| Field | Description |
|---|---|
target_branch |
Branch to update |
base_branch |
Optional base branch when creating target_branch |
expected_head_sha |
Optional optimistic concurrency check; commit fails with 409 on mismatch |
commit_message |
Commit message (required) |
author.name / author.email |
Commit author (required) |
files[].operation |
upsert, add, update, or delete |
files[].content_id |
References a blob_chunk stream (required for every file entry) |
files[].mode |
Git file mode for upserts (e.g. 100644); optional |
{"blob_chunk":{"content_id":"blob-1","data":"SGVsbG8=","eof":true}}| Field | Description |
|---|---|
content_id |
Must match a content_id from the metadata files list |
data |
Base64-encoded chunk payload |
eof |
Set to true on the final chunk for this content_id |
Each blob may be sent in multiple chunks; decoded content is concatenated until eof: true.
curl -X POST "http://localhost:8080/api/repos/acme%2Fwidgets/commits" \
-H "Content-Type: application/x-ndjson" \
--data-binary @- <<'EOF'
{"metadata":{"target_branch":"main","commit_message":"Add README","author":{"name":"SuperPlane","email":"bot@superplane.local"},"files":[{"path":"README.md","operation":"upsert","content_id":"blob-1","mode":"100644"}]}}
{"blob_chunk":{"content_id":"blob-1","data":"","eof":true}}
EOFResponse 201
{
"commit": {
"commit_sha": "9e6a46e7f5affc39101a2bbe03b85ea0c934cdef"
},
"result": {
"branch": "main",
"new_sha": "9e6a46e7f5affc39101a2bbe03b85ea0c934cdef",
"old_sha": "",
"success": true
}
}If the commit produces no tree changes, new_sha may equal old_sha.
GET /api/repos/{id}/commit?sha={sha}| Query | Required | Description |
|---|---|---|
sha |
yes | Branch name, tag, or commit SHA |
Response 200
{
"commit_sha": "9e6a46e7f5affc39101a2bbe03b85ea0c934cdef",
"tree_sha": "abc123...",
"message": "Add README",
"author": {
"name": "SuperPlane",
"email": "bot@superplane.local"
}
}Branch names (for example main) are resolved to the branch head commit.
Branch names in URL paths (for delete) are URL-decoded like repository IDs. Encode slashes and other reserved characters as needed.
GET /api/repos/{id}/branches
GET /api/repos/{id}/branches?prefix={prefix}| Query | Description |
|---|---|
prefix |
Optional filter; only branch names with this prefix are returned |
Response 200
{
"branches": ["main", "feature/login"]
}Returns an empty array when the repository has no branches yet.
POST /api/repos/{id}/branches
Content-Type: application/jsonRequest body
{
"branch": "feature/login",
"from_ref": "main"
}| Field | Required | Description |
|---|---|---|
branch |
yes | Name of the branch to create |
from_ref |
no | Branch, tag, or commit SHA to branch from (defaults to the repository default branch) |
Response 204
No body.
Returns 409 if the branch already exists. Returns 404 if from_ref does not resolve to a commit.
DELETE /api/repos/{id}/branches/{branch}Response 204
No body.
Returns 404 if the branch does not exist.
POST /api/repos/{id}/merge
Content-Type: application/jsonMerges source_branch into target_branch and always creates a merge commit (--no-ff), even when a fast-forward is possible.
Request body
{
"source_branch": "feature/login",
"target_branch": "main",
"message": "Merge feature/login",
"author": {
"name": "Jane Doe",
"email": "jane@example.com"
}
}| Field | Required | Description |
|---|---|---|
source_branch |
yes | Branch to merge from |
target_branch |
no | Branch to merge into (defaults to the repository default branch) |
message |
yes | Merge commit message |
author.name |
yes | Merge commit author name |
author.email |
yes | Merge commit author email |
Response 200
{
"commit_sha": "9e6a46e7f5affc39101a2bbe03b85ea0c934cdef"
}commit_sha is the new head of target_branch after the merge.
Returns 404 if either branch does not exist. Merge conflicts and other Git errors return 500.
- File paths must be relative, non-empty, and must not contain
..,.git, or null bytes. - Paths configured in
SUPERGIT_RESERVED_PATHSand anything under them are rejected.
Configured via SUPERGIT_MAX_FILE_BYTES and SUPERGIT_MAX_COMMIT_BYTES. Oversized requests return 413.