API Reference

JSON over HTTPS. Bearer-token auth via API keys you create on your account page. All endpoints live under https://scsipub.com/api/.

Quickstart

Three commands from "I have an API key" to "/dev/sdX mounted on this Linux box". Uses the public-catalog debian-12 image, so no upload step. Requires open-iscsi (apt install open-iscsi on Debian/Ubuntu) and jq.

# 1. Provision a session against a public-catalog image. No upload
#    needed — `debian-12` lives in the global catalog.
RESP=$(curl -fsSL -X POST https://scsipub.com/api/sessions \
  -H "Authorization: Bearer $SCSIPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "debian-12"}')

IQN=$(echo "$RESP"    | jq -r .iqn)
PORTAL=$(echo "$RESP" | jq -r .portal)
USER=$(echo "$RESP"   | jq -r .chap_user)
SECRET=$(echo "$RESP" | jq -r .chap_secret)

# 2. Point the open-iscsi initiator at the new target. CHAP
#    creds are unique to this session — they don't authorize
#    anything else.
sudo iscsiadm -m node -T "$IQN" -p "$PORTAL" -o new
sudo iscsiadm -m node -T "$IQN" -p "$PORTAL" \
  -o update -n node.session.auth.authmethod -v CHAP
sudo iscsiadm -m node -T "$IQN" -p "$PORTAL" \
  -o update -n node.session.auth.username -v "$USER"
sudo iscsiadm -m node -T "$IQN" -p "$PORTAL" \
  -o update -n node.session.auth.password -v "$SECRET"
sudo iscsiadm -m node -T "$IQN" -p "$PORTAL" --login

# 3. The disk shows up as a real /dev/sdX. Find it, mount it,
#    use it. `lsblk` makes the new device obvious.
lsblk
sudo mount /dev/sdX1 /mnt

What you have now: a real block device backed by an ephemeral writable overlay over the catalog image. Disconnect (iscsiadm --logout) and the overlay vanishes. The reference below covers every endpoint, error code, and the upload-your-own-image flow.

Authentication

Pass an API key in the Authorization header:

Authorization: Bearer $SCSIPUB_API_KEY

Anonymous requests are allowed for the few endpoints where it makes sense (listing the global image catalog). Anything that touches user state — creating a session, uploading an image, deleting either — requires a key.

Images

GET /api/images

List the global image catalog. With a key, also includes images you own. Each row's owned field tells you which is which.

curl https://scsipub.com/api/images \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"
{"images":[
  {"id":"…","name":"debian-12","format":"qcow2","size_bytes":2147483648,
   "sha256":"…","owned":false},
  {"id":"…","name":"ci-pi-abc1234","format":"raw","size_bytes":4294967296,
   "sha256":"…","owned":true}
]}

POST /api/images

Tell scsipub to fetch a remote artifact and register it as your private image. Synchronous — returns 201 once the fetch, decompression, checksum, and DB insert have all succeeded.

Body:

{
  "name":   "ci-pi-abc1234",        // [A-Za-z0-9._-]+
  "url":    "https://gitlab.example.com/.../build.img.xz",
  "sha256": "abcdef0123…"           // optional, fail if mismatch
}

Example:

curl -X POST https://scsipub.com/api/images \
  -H "Authorization: Bearer $SCSIPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":   "ci-pi-abc1234",
    "url":    "https://gitlab.example.com/.../build.img.xz",
    "sha256": "deadbeef…"
  }'

Response (201):

{
  "id":          "…",
  "name":        "ci-pi-abc1234",
  "iqn":         "iqn.2025-01.pub.scsipub:image.ci-pi-abc1234",
  "format":      "raw",
  "size_bytes":  4294967296,
  "sha256":      "deadbeef…",
  "source_url":  "https://gitlab.example.com/.../build.img.xz"
}

Errors:

  • 401 unauthorized — no API key sent.
  • 402 payment_required — over your tier's max_storage budget. Free tiers always 402 since their budget is 0.
  • 422 unprocessable_entity — bad name format.
  • 502 bad_gateway — fetch failed. Body's error field has the underlying reason (DNS, HTTP status, checksum mismatch, decompress error, …).

DELETE /api/images/:id

Drop a private image. :id can be the UUID or, if you authenticate, the image name. Owner-only — global catalog images can't be deleted via the API.

curl -X DELETE https://scsipub.com/api/images/ci-pi-abc1234 \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"

Sessions

POST /api/sessions

Provision a new iSCSI session against an image. Returns a unique IQN and CHAP credentials; mount with iscsiadm from there.

Body:

{
  "image":        "ci-pi-abc1234",   // image name or UUID
  "write_limit":  null               // optional, override per-session byte cap
}

Example:

curl -X POST https://scsipub.com/api/sessions \
  -H "Authorization: Bearer $SCSIPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "ci-pi-abc1234"}'

Response (201):

{
  "id":           "…",
  "iqn":          "iqn.2025-01.pub.scsipub:…",
  "chap_user":    "u-12abcd34",
  "chap_secret":  "…",
  "portal":       "scsipub.com:3260",
  "status":       "provisioned"
}

GET /api/sessions/:id

curl https://scsipub.com/api/sessions/<id-or-iqn> \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"

DELETE /api/sessions/:id

Tear down a session immediately, releasing its overlay and write-budget quota. Useful for CI cleanup jobs.

curl -X DELETE https://scsipub.com/api/sessions/<id> \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"

End-to-end: a CI build

The pattern most paid users hit: build something in CI, register it with scsipub, mount it from a runner, run tests, clean up.

# 1. publish — register your build artifact as a private image
curl -fsSL -X POST https://scsipub.com/api/images \
  -H "Authorization: Bearer $SCSIPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":   "ci-build-'"$CI_COMMIT_SHORT_SHA"'",
    "url":    "https://artifacts.example.com/builds/'"$CI_PIPELINE_ID"'/build.img.xz",
    "sha256": "'"$(sha256sum build.img.xz | cut -d' ' -f1)"'"
  }' | tee image.json

# 2. provision a session against it
curl -fsSL -X POST https://scsipub.com/api/sessions \
  -H "Authorization: Bearer $SCSIPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "ci-build-'"$CI_COMMIT_SHORT_SHA"'"}' | tee session.json

# 3. on the runner, mount + test (using the IQN + CHAP returned above)
IQN=$(jq -r .iqn session.json)
PORTAL=$(jq -r .portal session.json)
USER=$(jq -r .chap_user session.json)
SECRET=$(jq -r .chap_secret session.json)

ssh runner "iscsiadm -m node -T $IQN -p $PORTAL -o new
            iscsiadm -m node -T $IQN -p $PORTAL \
              -o update -n node.session.auth.authmethod -v CHAP
            iscsiadm -m node -T $IQN -p $PORTAL \
              -o update -n node.session.auth.username -v $USER
            iscsiadm -m node -T $IQN -p $PORTAL \
              -o update -n node.session.auth.password -v $SECRET
            iscsiadm -m node -T $IQN -p $PORTAL --login
            /opt/runtests.sh"

# 4. cleanup (always, even on test failure)
curl -fsSL -X DELETE \
  "https://scsipub.com/api/sessions/$(jq -r .id session.json)" \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"
curl -fsSL -X DELETE \
  "https://scsipub.com/api/images/ci-build-$CI_COMMIT_SHORT_SHA" \
  -H "Authorization: Bearer $SCSIPUB_API_KEY"

The Pi-fleet post walks through this end-to-end with a real GitLab CI pipeline.

Limits

  • Image uploads: 20/hour per IP, on top of your tier's max_storage byte budget.
  • Session creation: 10/hour per IP, plus your tier's max_sessions concurrent cap.
  • Login (web): 5/30 min per IP — only relevant if you're scripting against the cookie-session web flow rather than the API.
  • Tier limits: see the pricing page for write-limit, session-cap, and storage numbers per tier.

Found something missing or wrong? Email support@defensiblelogic.com.