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.

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:

{
  "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..."
  }
}

Back to contents