The Git LFS protocol is small — four routes, plus a health check:
| Method | Path | Purpose |
|---|---|---|
POST |
/{org}/{repo}/objects/batch |
negotiation: the client announces its objects, the server answers per object with an upload or download link |
PUT |
/{org}/{repo}/objects/{oid} |
store an object |
GET |
/{org}/{repo}/objects/{oid} |
retrieve an object, whole or by Range |
POST |
/{org}/{repo}/objects/verify |
post-upload verification |
GET |
/{org}/{repo} |
a page showing what the repository holds |
GET |
/{org}/{repo}/objects/stats |
the same numbers as JSON |
POST |
/{org}/{repo}/objects/retain |
reclaim space, see Reclaiming space |
POST |
/{org}/{repo}/objects/dedupe |
fold objects stored before the shared store into it |
POST |
/{org}/{repo}/objects/compress |
fold objects stored before compression into it |
POST |
/{org}/{repo}/objects/audit |
read every object back and check it against its own digest |
POST |
/{org}/{repo}/locks |
take a lock on a path |
GET |
/{org}/{repo}/locks |
list locks, filterable by path or id |
POST |
/{org}/{repo}/locks/verify |
the client's own locks, and everyone else's |
POST |
/{org}/{repo}/locks/{id}/unlock |
release a lock |
GET |
/metrics |
Prometheus exposition |
GET |
/health |
liveness: the process is up |
GET |
/ready |
readiness: the storage root is writable |
Objects already present are returned by batch with no actions, so the client skips re-uploading
them. Missing objects on a download are reported per object with a 404 error rather than failing
the whole batch.
Downloads honour Range, so a transfer that drops at 90% of a three-gigabyte asset resumes from
where it stopped instead of starting over — which on a home upstream is the difference between an
annoyance and an afternoon. A range that cannot be satisfied is refused with 416 carrying the
object's real size; a range we cannot parse is ignored and the whole object is served, since
refusing a transfer over a malformed header would be worse than the header.