# Phorge HTTP API

> HTTP API reference for creating, reading, and changing Phorge repositories.

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](https://phorge.net/docs/http#requests)
- [Authentication](https://phorge.net/docs/http#authentication)
- [Repositories](https://phorge.net/docs/http#repositories)
- [Reads](https://phorge.net/docs/http#reads)
- [Writes](https://phorge.net/docs/http#writes)
- [Pagination](https://phorge.net/docs/http#pagination)
- [Errors](https://phorge.net/docs/http#errors)

## Requests

The base URL is scoped to your namespace:

```text
https://{namespace}.api.phorge.net
```

Send JSON request bodies with `Content-Type: application/json` unless an endpoint specifies multipart. Unknown fields and additional JSON values are rejected. Authenticate with a customer-signed bearer token:

```http
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 API JWTs with an active ES256 or RS256 private key for your namespace. `kid` is optional; when present, set it to the key's SHA-256 public-key fingerprint. Without `kid`, Phorge checks the signature against active keys using the selected algorithm. Phorge does not receive customer private keys.

The dashboard can separately issue a 10-minute, read-only Git clone credential for an authenticated namespace member. That credential works only with Git HTTPS for its selected repository; it is not accepted by this HTTP API.

```json
{
  "alg": "ES256",
  "kid": "SHA256:…",
  "typ": "JWT"
}

{
  "iss": "your-org",
  "actor": "agent-7",
  "repo": "workspace",
  "scopes": ["git:read", "repo:write"],
  "iat": 1786579200,
  "exp": 1786580100,
  "refPolicies": ["refs/heads/agent-*" ]
}
```

`iss` identifies the namespace, `repo` is the exact repository name or `*`, and optional `actor` labels the caller in request audit records. `actor` is not a Git author or committer. `iat` and `exp` are NumericDate values. `refPolicies` is optional and restricts writable refs with Git-style patterns.

- `git:read` permits repository metadata and read endpoints.
- `repo:write` permits repository creation and HTTP writes.
- `org:read` with `repo: "*"` 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.

```sh
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
```

Omit `ttl_seconds`, or set it to `0`, for a repository that does not expire. A positive value schedules automatic deletion and must fit within the service limit. Negative values are rejected. `private` defaults to `true`.

```json
{
  "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.

### `DELETE /repos/{repo}`

Permanently delete a repository and its Git history using a `repo:delete` token scoped to that repository. A successful deletion returns HTTP 204.

## 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.
- `cursor` and `limit`: pagination.

```json
{
  "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.

The SDK's `getFileURL` method creates a short-lived URL for viewing one private file as plain text in a browser. Its credential is restricted to the selected repository, file path, and ref and does not grant write access.

```sh
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:

```json
{ "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 using `multipart/form-data`. The first part must be named `metadata` and contain the JSON commit description. Each upsert names a later raw file part through `content_part`. Delete operations have no file part. `mode` may be `100644`, `100755`, or `120000`.

```sh
curl --fail-with-body \
  -H "Authorization: Bearer $TOKEN" \
  -F 'metadata={
    "branch":"main",
    "expectedHeadSha":"012345...",
    "message":"Update worker",
    "author":{"name":"Agent","email":"agent@example.com"},
    "committer":{"name":"Automation","email":"automation@example.com"},
    "operations":[
      {"operation":"upsert","path":"src/worker.ts","content_part":"file-0","mode":"100644"},
      {"operation":"delete","path":"src/old-worker.ts"}
    ]
  };type=application/json' \
  -F 'file-0=@src/worker.ts;type=application/octet-stream' \
  https://your-org.api.phorge.net/repos/workspace/commits
```

File bytes are sent directly and are not Base64 encoded. Part names must be unique and every declared part must be present. The complete multipart request is limited to 128 MiB and still must fit the repository's storage quota.

`author` and `committer` are optional. If only `author` is supplied, it is used for both Git identities. If neither is supplied, Phorge uses `Phorge <noreply@phorge.net>`. The JWT's optional `actor` is audit metadata and is never written into a commit.

`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.

```json
{
  "branch": "main",
  "expectedHeadSha": "012345...",
  "message": "Apply generated patch",
  "diff": "diff --git a/a.txt b/a.txt\n...",
  "author": { "name": "Agent", "email": "agent@example.com" }
}
```

### `POST /repos/{repo}/branches`

Create a branch. `startPoint` defaults to `HEAD`; `expectedHeadSha` must match the resolved start point.

```json
{
  "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.

```json
{
  "source": "agent-7",
  "target": "main",
  "expectedHeadSha": "012345...",
  "mode": "fast-forward-preferred",
  "message": "Merge agent work",
  "author": { "name": "Agent", "email": "agent@example.com" }
}
```

### `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.

```json
{
  "name": "v1.0.0",
  "target": "main",
  "expectedHeadSha": "012345...",
  "message": "Release v1.0.0",
  "tagger": { "name": "Release Bot", "email": "releases@example.com" }
}
```

### `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:

```json
{
  "items": [],
  "next_cursor": "...",
  "has_more": true
}
```

## Errors

Errors use an HTTP status plus a stable machine-readable code:

```json
{
  "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 413 `COMMIT_TOO_LARGE`: a multipart commit exceeds 128 MiB.
- HTTP 415 `MULTIPART_REQUIRED`: a file-operation commit was not sent as multipart.
- 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.

---

[Back to contents](https://phorge.net/docs/http#contents)

---

[Home](https://phorge.net/) · [Platform](https://platform.phorge.net/) · [Docs](https://phorge.net/docs) · [Symbols](https://phorge.net/docs/symbols) · [SDK](https://github.com/sf-tools/phorge) · [Status](https://phorge.net/health)
