Files (search + download)

Files uploaded by your devices through libscadable are queryable here.

Search — GET /v1/files

Returns a newest-first metadata array, scoped to the API key's org.

curl -H "Authorization: Bearer scd_live_..." \
  "https://api.scadable.com/v1/files?from=$(date -v-1d +%s)&limit=50"

Query parameters

ParamTypeDefaultNotes
fromunix secondsnow − 7 daysInclusive.
tounix secondsnow + 1Inclusive.
nsstringFilter to one namespace.
devicestringFilter to one device CN (SC-<device_id>).
sortuploaded_at / size_bytesuploaded_at
orderasc / descdesc
limitint50Max 200.
offsetint0For pagination.

Response

[
  {
    "file_id":      "f_abc123...",
    "org_id":       "org_...",
    "namespace_id": "ns_...",
    "device_cn":    "SC-a3f8b2c4e6d8",
    "filename":     "crashdump.bin",
    "content_type": "application/octet-stream",
    "uploaded_at":  1748678400,
    "size_bytes":   524288,
    "sha256":       "..."
  }
]

Empty array if nothing matches the filters. No metadata about the bytes themselves — request the file itself for the content.

Filter rules

  • ns and device are filters within your org, not access controls. A key with no scope can still only see its own org's files.
  • Range queries are inclusive on both ends.
  • Pagination is offset-based because we sort by score in the underlying zset; cursor pagination arrives if a customer hits the offset ceiling at ~10k.

Download — GET /v1/files/{file_id}

Streams the file's bytes. Standard HTTP — your client downloads it like any other URL.

curl -H "Authorization: Bearer scd_live_..." \
  https://api.scadable.com/v1/files/f_abc123 \
  --output crashdump.bin

Response headers

HeaderValue
Content-Typethe upload's recorded type (e.g. application/octet-stream)
Content-Dispositionattachment; filename="<original>"
Content-Lengthfull size in bytes (omitted on Range responses, replaced with Content-Range)
X-File-SHA256hex digest computed at upload time
Accept-Rangesbytes (so clients know they can resume)

Range / resumable downloads

# Get the file size (HEAD might be added later; for now do a tiny Range)
curl -I -H "Authorization: Bearer scd_live_..." \
  -H "Range: bytes=0-0" \
  https://api.scadable.com/v1/files/f_abc123
# → HTTP 206 Partial Content
#   Content-Range: bytes 0-0/524288

# Resume from byte 1024
curl -H "Authorization: Bearer scd_live_..." \
  -H "Range: bytes=1024-" \
  https://api.scadable.com/v1/files/f_abc123 \
  --output crashdump.bin

Range: bytes=start-end and Range: bytes=start- are both supported. Multi-range requests aren't supported in v1 (rare in practice).

Errors

StatusWhy
401Bad / missing / revoked API key.
404File doesn't exist, OR it belongs to a different org. The two are indistinguishable on purpose so a key can't probe file IDs across orgs.
410File metadata exists but the bytes were already evicted (rare; happens if Valkey is reset without re-uploading).
416Range request out of bounds. The response includes Content-Range: bytes */<size>.

Languages / clients

Anything that does standard HTTP works. A few small examples:

# Python (requests)
import requests
r = requests.get(
    "https://api.scadable.com/v1/files/f_abc123",
    headers={"Authorization": "Bearer scd_live_..."},
    stream=True,
)
with open("out.bin", "wb") as f:
    for chunk in r.iter_content(chunk_size=64*1024):
        f.write(chunk)
// Node (fetch + stream)
const res = await fetch("https://api.scadable.com/v1/files/f_abc123", {
  headers: { Authorization: "Bearer scd_live_..." },
});
const buf = await res.arrayBuffer();
fs.writeFileSync("out.bin", Buffer.from(buf));
// Go
req, _ := http.NewRequest("GET", "https://api.scadable.com/v1/files/f_abc123", nil)
req.Header.Set("Authorization", "Bearer scd_live_...")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(out, resp.Body)

Tips

  • Stable identifiers. file_id is server-assigned, immutable. Use it for archive-keys / dedup, not filenames (filenames can be anything the device chose at upload time).
  • SHA256 cross-check. The library hashes the file on the device side; the API records the digest. If you compute the SHA256 of what you downloaded and it matches X-File-SHA256, you can be confident bytes were not tampered with in transit.
  • Bulk export. No bulk endpoint in v1; iterate with pagination. For large orgs, use ?sort=uploaded_at&order=asc&offset=… to scroll forward stably.