Git LFS API

June 30, 2026 ยท View on GitHub

Git LFS stores large blobs outside the Git object graph. Clients negotiate upload/download through batch requests and use separate object endpoints for binary transfer.

Official references:

Interactive docs: start mono and open Swagger UI at /swagger-ui (LFS routes under the LFS tag). See architecture.md.

URL layout

Mega exposes the same handlers on two prefixes:

AudienceBase pathExample
Git LFS clients<repo>.git/info/lfs/project/foo.git/info/lfs/objects/batch
OpenAPI / tools/api/v1/lfs/api/v1/lfs/objects/batch

Repo paths follow monorepo layout (/project/..., /third-party/...). The HTTP server rewrites .../info/lfs/... request URIs so handlers see a normalized path (mono/src/server/http_server.rs).

Content types

UseContent-Type
JSON request/responseapplication/vnd.git-lfs+json
Object download bodyapplication/octet-stream

Endpoints

Relative to either base path above:

MethodPathPurpose
POST/objects/batchBatch upload/download negotiation
GET/objects/{oid}Download object (binary stream)
PUT/objects/{oid}Upload object (binary body)
GET/locksList locks (path, cursor, limit, refspec query params)
POST/locksCreate lock
POST/locks/verifyVerify locks before push
POST/locks/{id}/unlockDelete lock

Chunk download endpoints (/objects/{oid}/chunks/...) are not exposed in the current router.

Examples

Batch (download)

curl -X POST \
  -H "Content-Type: application/vnd.git-lfs+json" \
  -d '{
    "operation": "download",
    "transfers": ["basic"],
    "objects": [{"oid": "abc123...", "size": 1024}],
    "hash_algo": "sha256"
  }' \
  http://localhost:8000/project/demo.git/info/lfs/objects/batch

Upload object

curl -X PUT \
  --data-binary @file.bin \
  http://localhost:8000/project/demo.git/info/lfs/objects/abc123...

Download object

curl -L \
  -H "Accept: application/octet-stream" \
  http://localhost:8000/project/demo.git/info/lfs/objects/abc123... -o file.bin

Lock management

# List locks
curl "http://localhost:8000/project/demo.git/info/lfs/locks?path=foo.bin&limit=50"

# Create lock
curl -X POST \
  -H "Content-Type: application/vnd.git-lfs+json" \
  -d '{"path":"foo.bin","ref":{"name":"main"}}' \
  http://localhost:8000/project/demo.git/info/lfs/locks

# Delete lock
curl -X POST \
  -H "Content-Type: application/vnd.git-lfs+json" \
  -d '{"force":false,"ref":{"name":"main"}}' \
  http://localhost:8000/project/demo.git/info/lfs/locks/{id}/unlock

Implementation notes

  • Error mapping: Router maps handler messages to HTTP status โ€” 404 for not found, 400 for invalid input, 500 otherwise (map_lfs_error in mono/src/api/router/lfs_router.rs).
  • Batch download: Missing objects should appear as per-object error fields with overall HTTP 200, not a top-level failure.
  • Download Content-Type: Object downloads return application/octet-stream, not LFS JSON.

Source files

LayerPath
Routesmono/src/api/router/lfs_router.rs
Business logicceres/src/lfs/handler.rs
Typesceres/src/lfs/lfs_structs.rs