Phorge HTTP API
Use repositories directly over HTTP without creating a working tree. The API reads files and history, creates commits, and manages refs in focused requests.
Contents
Requests
The base URL is scoped to your namespace:
https://{namespace}.api.phorge.net
Send JSON request bodies with Content-Type: application/json. Unknown fields and additional JSON values are rejected. Authenticate with a customer-signed bearer token:
Authorization: Bearer <jwt>
A repository name occupies one URL segment. Percent-encode grouped names: team/storefront becomes team%2Fstorefront beneath /repos/. File paths and ref names in path segments must also be percent-encoded.
Authentication
Sign JWTs with the ES256 or RS256 private key corresponding to the public key registered for your namespace. Phorge does not mint customer credentials or receive the private key.
{
"alg": "ES256",
"typ": "JWT"
}
{
"iss": "your-org",
"sub": "agent-7",
"repo": "workspace",
"scopes": ["git:read", "repo:write"],
"iat": 1786579200,
"exp": 1786580100,
"refPolicies": ["refs/heads/agent-*" ]
}
iss identifies the namespace, sub identifies the actor, and repo is the exact repository name or *. iat and exp are NumericDate values. refPolicies is optional and restricts writable refs with Git-style patterns.
git:readpermits repository metadata and read endpoints.repo:writepermits repository creation and HTTP writes.org:readwithrepo: "*"permits repository listing.
Reads from a public repository may omit the token. Creation, listing, private reads, and every write require one.
Repositories
POST /repos
Create a repository with a repo:write token scoped to its name. Repeating the same request for a repository you own returns the existing repository with HTTP 200; initial creation returns HTTP 201.
curl --fail-with-body \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{
"name": "workspace",
"ttl_seconds": 86400,
"private": true
}' \
https://your-org.api.phorge.net/repos
ttl_seconds is optional and must resolve to a finite lifetime within the service limit. private defaults to true.
{
"id": "01J...",
"name": "workspace",
"namespace": "your-org",
"private": true,
"expires_at": "2026-08-14T00:00:00Z",
"size_bytes": 0,
"content_version": 0,
"git_url": "https://your-org.phorge.net/workspace.git",
"api_url": "https://your-org.api.phorge.net/repos/workspace"
}
GET /repos
List repositories using an org:read token with repo: "*". Accepts cursor and limit.
GET /repos/{repo}
Return repository metadata. Requires git:read unless the repository is public.
Reads
Unless noted otherwise, read endpoints require git:read, accept ref, and default to HEAD.
GET /repos/{repo}/files
List tree entries. Query parameters:
ref: branch, tag, or commit SHA.path: optional subtree.recursive=false: disable recursive traversal; traversal is recursive by default.metadata=true: include the latest commit for each path.cursorandlimit: pagination.
{
"items": [{
"path": "src/main.ts",
"mode": "100644",
"type": "blob",
"sha": "012345...",
"size": 418
}],
"next_cursor": "",
"has_more": false
}
GET|HEAD /repos/{repo}/files/{path}
Stream a file as application/octet-stream. Accepts ref, a single HTTP Range, and If-None-Match. Responses include ETag and Accept-Ranges: bytes; ranges return HTTP 206.
curl --fail-with-body \
-H "Authorization: Bearer $TOKEN" \
-H "Range: bytes=0-1023" \
"https://your-org.api.phorge.net/repos/workspace/files/src%2Fmain.ts?ref=main"
GET /repos/{repo}/archive
Stream a tar.gz archive. Accepts ref and an optional path.
GET /repos/{repo}/grep
Search repository content. q is required. Also accepts ref, context from 0 through 20, cursor, and limit. Items contain path, line, and text.
GET /repos/{repo}/blame
Return Git line-porcelain blame output as { "porcelain": "..." }. path is required; ref is optional.
GET /repos/{repo}/branches
List branches with name, SHA, date, and commit message. Accepts cursor and limit.
GET /repos/{repo}/commits
List commits. Accepts ref, cursor, and limit. Items contain sha, parents, author, date, and message.
GET /repos/{repo}/commits/{sha}
Return one commit in the same shape used by the commit list.
GET /repos/{repo}/commits/{sha}/diff
Return the commit patch as text/x-diff.
GET /repos/{repo}/diff?base={base}&head={head}
Return a three-dot diff between two revisions as text/x-diff.
Writes
Writes require repo:write. Successful mutations return HTTP 201:
{ "sha": "012345...", "ref": "refs/heads/main" }
Use expectedHeadSha to make concurrent work safe. If the ref changed, Phorge returns HTTP 412 and leaves it unchanged.
POST /repos/{repo}/commits
Create a commit from file operations. operation is upsert or delete. Upserts carry standard Base64 in content_base64; mode may be 100644, 100755, or 120000.
{
"branch": "main",
"expectedHeadSha": "012345...",
"message": "Update worker",
"author": { "name": "Agent", "email": "agent@example.com" },
"operations": [
{
"operation": "upsert",
"path": "src/worker.ts",
"content_base64": "Y29uc29sZS5sb2coXCJoaVwiKTtcCg==",
"mode": "100644"
},
{ "operation": "delete", "path": "src/old-worker.ts" }
]
}
expectedHeadSha is optional for this endpoint. When omitted, Phorge snapshots the current head and still uses an atomic ref update; supplying it is recommended for coordination between agents.
POST /repos/{repo}/commits/apply-diff
Apply a unified diff and commit it in one request. expectedHeadSha is required.
{
"branch": "main",
"expectedHeadSha": "012345...",
"message": "Apply generated patch",
"diff": "diff --git a/a.txt b/a.txt\n..."
}
POST /repos/{repo}/branches
Create a branch. startPoint defaults to HEAD; expectedHeadSha must match the resolved start point.
{
"name": "agent-7",
"startPoint": "main",
"expectedHeadSha": "012345..."
}
DELETE /repos/{repo}/branches/{branch}?expectedHeadSha={sha}
Delete a branch only if its current SHA matches expectedHeadSha.
POST /repos/{repo}/merges/preview
Check a merge without moving the target ref. Uses the same body as merge and returns fast_forward, mergeable, result_sha, target_sha, and source_sha.
POST /repos/{repo}/merges
Merge source into target. Mode is fast-forward-only or fast-forward-preferred; the latter is the default. message is optional.
{
"source": "agent-7",
"target": "main",
"expectedHeadSha": "012345...",
"mode": "fast-forward-preferred",
"message": "Merge agent work"
}
POST /repos/{repo}/tags
Create a lightweight tag, or an annotated tag when message is present. target defaults to HEAD; expectedHeadSha must match the resolved target.
{
"name": "v1.0.0",
"target": "main",
"expectedHeadSha": "012345...",
"message": "Release v1.0.0"
}
DELETE /repos/{repo}/tags/{tag}?expectedHeadSha={sha}
Delete a tag only if its current ref SHA matches expectedHeadSha.
Pagination
List endpoints accept limit and cursor. The default limit is 20 and the maximum is 100. Treat cursors as opaque and pass next_cursor unchanged:
{
"items": [],
"next_cursor": "...",
"has_more": true
}
Errors
Errors use an HTTP status plus a stable machine-readable code:
{
"code": "HEAD_MOVED",
"message": "ref has moved",
"details": {
"ref": "refs/heads/main",
"expected": "012345...",
"actual": "abcdef..."
}
}
- HTTP 400: malformed JSON, cursor, path, query input, or revision.
- HTTP 401: missing or invalid token.
- HTTP 403: insufficient scope, ref policy, or quota.
- HTTP 404: repository, file, ref, or route not found.
- HTTP 409: merge conflict or non-fast-forward merge.
- HTTP 412
HEAD_MOVED: the expected ref no longer matches. - HTTP 416
INVALID_RANGE: the requested byte range is invalid. - HTTP 422: a repository name, ref, commit operation, diff, or merge input is invalid.
- HTTP 428
EXPECTED_HEAD_REQUIRED: a required expected SHA was omitted. - HTTP 429
RATE_LIMITED: request rate exceeded.