Skip to content

Downloads

Downloading is two steps: authorize a set of oids, then GET the presigned URLs you get back.

Terminal window
export API=https://cavsnode.com/api/v1
export REPO=<repository-uuid>
export H="Authorization: Bearer $CAVS_TOKEN"

POST /repositories/{repoID}/downloads/authorize

Section titled “POST /repositories/{repoID}/downloads/authorize”

Permission: repository.pull (repo:read scope).

Terminal window
curl -sS -X POST "$API/repositories/$REPO/downloads/authorize" \
-H "$H" -H "Content-Type: application/json" \
-d '{"oids": ["9f2c8b0e…", "aa11bb22…", "deadbeef…"]}'
Field Type Notes
oids string[] Lowercase hex SHA-256 values. Batch as many as fit in a 1 MiB body.
200 OK — one entry per requested oid, in order
{
"objects": [
{ "oid": "9f2c8b0e…", "size": 104857600,
"download_url": "https://<object-storage>/…?X-Amz-Signature=…",
"expires_in": 900 },
{ "oid": "aa11bb22…", "size": 52428800,
"download_url": "https://…", "expires_in": 900 },
{ "oid": "deadbeef…", "error": "not found" }
]
}
Terminal window
curl -sS --fail-with-body -o model.safetensors.part "$URL"
echo "$OID model.safetensors.part" | sha256sum -c -
mv model.safetensors.part model.safetensors

Presigned URLs support Range, so you can resume:

Terminal window
curl -sS -C - -o model.safetensors.part "$URL" # resume from where it stopped

They carry no Authorization header — the signature is the credential — and expire in 15 minutes.

Authorizing a download:

  • Increments each served object’s download counter (visible in GET …/objects and the artifact detail view).
  • Records egress against the organization’s usage — for managed storage only; BYOS egress is your provider’s bill.
  • Appends to the usage ledger with the acting user, object count and byte total.

One call for many oids is much better than many calls:

Terminal window
# Authorize every object in a release, in one request
jq -n --argjson oids "$(cat oids.json)" '{oids: $oids}' \
| curl -sS -X POST "$API/repositories/$REPO/downloads/authorize" \
-H "$H" -H "Content-Type: application/json" -d @- \
| jq -r '.objects[] | select(.download_url) | "\(.oid)\t\(.download_url)"' \
| while IFS=$'\t' read -r oid url; do
curl -sS --fail-with-body -o "$oid.part" "$url"
echo "$oid $oid.part" | sha256sum -c - && mv "$oid.part" "$oid"
done

The request body cap is 1 MiB, so a batch of a few thousand oids is fine. Respect the 30 req/s API limit — but note the transfers themselves run against object storage and don’t consume your API budget.

Authorization is cheap and tells you what’s there. This is how cav verify works:

Terminal window
curl -sS -X POST "$API/repositories/$REPO/downloads/authorize" \
-H "$H" -H "Content-Type: application/json" \
-d "{\"oids\":[\"$OID\"]}" \
| jq -e '.objects[0].download_url != null' >/dev/null \
&& echo present || echo missing

Or cav verify ./file — it hashes locally and asks the same question.

Path Use
This endpoint SDKs, CLI, CI, custom clients
POST …/lfs/objects/batch Standard git-lfs
GET …/lfs/* The CAVS transfer agent’s chunk-level fetch, with Range

The chunk-tree path is the only one that transfers less than the whole object — it fetches only the chunks the local cache lacks. See CAVS LFS agent.

  • Check the per-entry error field before touching download_url.
  • Stream to a temp path; never write directly to the destination.
  • Hash while streaming; compare against the oid; delete on mismatch.
  • Rename atomically only after verification passes.
  • Re-request an expired URL rather than caching it.
  • Retry 5xx and network errors with backoff; don’t retry 403 from storage (the URL expired — re-authorize instead).