Coregit
API Reference

LFS API

Git LFS Batch API, verify callback, and file locking endpoints.

Overview

These endpoints implement the Git LFS Batch API specification. They are called automatically by the git lfs client — you typically don't need to call them directly.

All LFS endpoints use application/vnd.git-lfs+json content type and Basic authentication (same credentials as git push).

For a usage guide with git lfs commands, see Git LFS Guide.

Batch API

POST /:org/:repo.git/info/lfs/objects/batch
POST /:org/:namespace/:repo.git/info/lfs/objects/batch

Auth: Basic auth. Upload requires write access, download requires read access. Public repos allow unauthenticated download.

Request

{
  "operation": "upload",
  "transfers": ["basic"],
  "objects": [
    { "oid": "78579d983fa87c4f9c3f4f86a32b02ff312db25dd0ec1f5cb7853c28c19ed060", "size": 524288 }
  ],
  "ref": { "name": "refs/heads/main" }
}
FieldRequiredDescription
operationYes"upload" or "download"
transfersNoTransfer adapters (only "basic" supported)
objectsYesArray of objects with oid (64-char hex SHA-256) and size (bytes)
refNoBranch reference for authorization context

Upload Response 200

{
  "transfer": "basic",
  "objects": [
    {
      "oid": "78579d...",
      "size": 524288,
      "actions": {
        "upload": {
          "href": "https://{account}.r2.cloudflarestorage.com/coregit-lfs/{orgId}/{repoId}/lfs/{oid}?X-Amz-Signature=...",
          "header": { "Content-Type": "application/octet-stream" },
          "expires_in": 900
        },
        "verify": {
          "href": "https://api.coregit.dev/{org}/{repo}.git/info/lfs/verify",
          "header": { "Authorization": "Basic ..." },
          "expires_in": 900
        }
      }
    }
  ],
  "hash_algo": "sha256"
}

The client PUTs the file directly to the upload.href URL (presigned R2), then POSTs to verify.href to confirm.

If the object already exists, actions is omitted — the client skips the upload:

{ "oid": "78579d...", "size": 524288 }

Download Response 200

{
  "transfer": "basic",
  "objects": [
    {
      "oid": "78579d...",
      "size": 524288,
      "actions": {
        "download": {
          "href": "https://{account}.r2.cloudflarestorage.com/coregit-lfs/...?X-Amz-Signature=...",
          "expires_in": 3600
        }
      }
    }
  ],
  "hash_algo": "sha256"
}

Per-Object Errors

Objects that fail validation are returned with an error instead of actions:

{
  "oid": "78579d...",
  "size": 524288,
  "error": { "code": 422, "message": "File too large. Max 100 MB for free tier." }
}
CodeMeaning
404Object not found (download only)
422Invalid oid, invalid size, or file too large
507Storage quota exceeded

Request-Level Errors

CodeMeaning
400Invalid JSON or missing required fields
401Missing or invalid credentials
403Insufficient permissions (e.g. read-only token on upload)
404Repository not found
422Too many objects in batch (max 20 free, 100 usage)
500LFS storage not configured

Verify Callback

POST /:org/:repo.git/info/lfs/verify
POST /:org/:namespace/:repo.git/info/lfs/verify

Auth: Basic auth (write access required).

Called by the client after a successful upload. Confirms the object exists in R2 and the size matches.

{ "oid": "78579d983fa87c4f9c3f4f86a32b02ff312db25dd0ec1f5cb7853c28c19ed060", "size": 524288 }

Response 200 (empty body) on success.

ErrorMeaning
404Object not found in R2 (upload didn't complete)
422Size mismatch between claimed and actual

On success, the object is recorded in the database and storage/transfer usage is tracked.

Create Lock

POST /:org/:repo.git/info/lfs/locks
POST /:org/:namespace/:repo.git/info/lfs/locks

Auth: Basic auth (write access required).

{
  "path": "assets/logo.psd",
  "ref": { "name": "refs/heads/main" }
}

Response 201:

{
  "lock": {
    "id": "umoiA4_WhzEbgWoUKLaS4",
    "path": "assets/logo.psd",
    "locked_at": "2026-04-06T12:00:00.000Z",
    "owner": { "name": "VKi4S4wvJQmZP23FxV6s6" }
  }
}

The owner.name is the API key ID that created the lock.

409 if already locked:

{
  "lock": { "id": "...", "path": "assets/logo.psd", "locked_at": "...", "owner": { "name": "..." } },
  "message": "already created lock"
}

List Locks

GET /:org/:repo.git/info/lfs/locks
GET /:org/:namespace/:repo.git/info/lfs/locks

Auth: Basic auth (read access required).

Query parameters:

ParamDescription
pathFilter by file path
idFilter by lock ID
limitMax results (1-100, default 100)
cursorPagination cursor

Response 200:

{
  "locks": [
    {
      "id": "umoiA4_WhzEbgWoUKLaS4",
      "path": "assets/logo.psd",
      "locked_at": "2026-04-06T12:00:00.000Z",
      "owner": { "name": "VKi4S4wvJQmZP23FxV6s6" }
    }
  ],
  "next_cursor": ""
}

Verify Locks

POST /:org/:repo.git/info/lfs/locks/verify
POST /:org/:namespace/:repo.git/info/lfs/locks/verify

Auth: Basic auth (write access required).

{
  "ref": { "name": "refs/heads/main" },
  "limit": 100
}

Response 200:

{
  "ours": [
    { "id": "...", "path": "assets/logo.psd", "locked_at": "...", "owner": { "name": "..." } }
  ],
  "theirs": [
    { "id": "...", "path": "data/model.bin", "locked_at": "...", "owner": { "name": "..." } }
  ],
  "next_cursor": ""
}

ours = locks owned by the current API key. theirs = locks owned by others. Git LFS blocks push if modified files appear in theirs.

Unlock

POST /:org/:repo.git/info/lfs/locks/:id/unlock
POST /:org/:namespace/:repo.git/info/lfs/locks/:id/unlock

Auth: Basic auth (write access required).

{ "force": false }
FieldDescription
forceSet true to delete another user's lock. Requires master API key.

Response 200:

{
  "lock": { "id": "...", "path": "assets/logo.psd", "locked_at": "...", "owner": { "name": "..." } }
}

403 if trying to unlock another user's lock without force: true or without master key.

Limits

TierMax File SizeObjects per Batch
Free100 MB20
Usage2 GB100
EnterpriseCustomCustom

LFS storage and transfer count toward existing org quotas. No separate LFS billing.

Presigned URL Details

  • Upload URLs expire in 15 minutes (900 seconds)
  • Download URLs expire in 1 hour (3600 seconds)
  • URLs point directly to Cloudflare R2 — the Coregit Worker is not in the data path
  • Each URL is signed for a specific object key and cannot be reused for other objects

On this page