Ingots API v1

Overview

Ingots are encrypted data objects stored within Vaults. All ingot operations require two credentials: an API key (or session JWT) to authenticate the caller, plus a valid Vault Access Token (VAT) obtained by unsealing the vault.

Looking for key/value storage?

Named-field key/value data with atomic updates is a separate product, Structured Ingots, with its own endpoints and semantics. This page covers file ingots only.

Features

Feature Description
Size 1 byte to 5 TB per ingot
Encryption AES-256-GCM under the per-vault Vault Access Key (VAK), HKDF-derived from SVMK + AMK + VMK (Triple Zero-Trust). Forge encrypts each 50 MB chunk with the VAK using per-chunk derived nonces
Streaming Server-side encryption/decryption via Forge TUS uploads
VAT Required

All ingot operations require two credentials: an API key (X-API-Key) or session JWT (Authorization: Bearer) to authenticate the caller, plus the X-Vault-Access-Token header with a valid VAT. See the Vaults API for how to obtain a VAT.

Standard Ingots

Standard ingots are file-based encrypted storage. Upload any file up to 5 TB through the same Forge TUS flow.

Name-Based Addressing

{ingot_id} in the endpoints below accepts either an ingot ID (ing_...) or a name reference: a colon followed by the name, e.g. /ingots/:report.pdf. Name lookups are case-insensitive and scoped to your account. Names are unique per folder, not per vault: when the same name exists in multiple folders, a name reference returns 409 CONFLICT with the candidate ingot_id/folder_id pairs. Use the ingot ID instead.

Create Standard Ingot

POST /v1/vaults/{vault_id}/ingots

Create a new standard ingot and get a Forge URL for uploading. Two-step process: create the ingot record, then upload to Forge with TUS.

Headers

ParameterTypeRequiredDescription
X-Vault-Access-Token string Required Valid VAT from unseal operation

Request Body

ParameterTypeRequiredDescription
name string Required File name or identifier
size_bytes integer Required Exact file size in bytes (1 byte to 5 TB = 5497558138880 bytes)
content_type string Optional MIME typeDefault: application/octet-stream
original_filename string Optional Original file name, used for the Content-Disposition header on downloads
folder_id string Optional ID of an existing folder to place the ingot in. Mutually exclusive with folder_path
folder_path string Optional Slash-delimited folder path (e.g. reports/2026), found or created from the vault root. Mutually exclusive with folder_id
Upsert by Name

Creating an ingot with a name that already exists in the same folder upserts: the existing ingot's ID is reused and its content is replaced when the upload completes. An active ingot stays readable until Forge finalizes the new upload.

Response

FieldTypeDescription
ingot_id string Unique ingot identifier (ing_...)
name string Ingot name
size_bytes integer Declared size in bytes
content_type string MIME type
status string Initial status: uploading
forge_url string Forge URL containing the upload ISTK
expires_at integer Upload URL expiration timestamp
javascript Complete Upload Example (JavaScript)
import * as tus from 'tus-js-client';

// Step 1: Create ingot record
const file = document.querySelector('input[type="file"]').files[0];

const createResponse = await fetch(
  `https://api.sparkvault.com/v1/vaults/${vaultId}/ingots`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey,
      'X-Vault-Access-Token': vat,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: file.name,
      size_bytes: file.size,
      content_type: file.type
    })
  }
);

const { ingot_id, forge_url } = (await createResponse.json()).data;

// Step 2: Upload file to Forge with TUS
const forgeUrl = new URL(forge_url);
const istk = forgeUrl.searchParams.get('istk');
const upload = new tus.Upload(file, {
  endpoint: `${forgeUrl.origin}/encrypt`,
  chunkSize: 50 * 1024 * 1024,
  headers: { 'X-ISTK': istk },
  retryDelays: [0, 1000, 3000, 5000, 10000]
});

upload.start();

// Status will change: uploading → active

List Ingots

GET /v1/vaults/{vault_id}/ingots

List all ingots in an unsealed vault.

Query Parameters

ParameterTypeRequiredDescription
limit integer Optional Maximum ingots to return (max: 200)Default: 100
sort_by string Optional Sort field: name, size, content_type, created_at, access_count, or bandwidth
sort_order string Optional asc or descDefault: asc

Response

FieldTypeDescription
ingots array Array of ingot objects
count integer Number of ingots returned
ingots[].ingot_id string Ingot identifier
ingots[].name string Ingot name
ingots[].file_type string Category derived from the file extension (e.g. image, video, document)
ingots[].size_bytes integer Size in bytes
ingots[].content_type string MIME type
ingots[].status string Status: uploading, encrypting, active, or failed
ingots[].folder_id string Containing folder ID; null for the vault root
ingots[].managed_by string Product that manages this ingot (e.g. structured-ingots, product-messaging); null for standard ingots. Managed ingots reject direct read/download/update/delete with 403; use the product's endpoints
ingots[].upload_source string Origin of the upload (uploader's email, or API: {key name} for API-key uploads); null when unknown
ingots[].created_at integer Creation timestamp
ingots[].updated_at integer Last-modified timestamp (bumps on every write)
ingots[].access_count integer Number of downloads
ingots[].total_bandwidth_bytes integer Total bytes served for this ingot

Search Ingots

GET /v1/vaults/{vault_id}/ingots/search

Search and filter ingots within a vault using indexed queries. Filters can be combined.

Query Parameters

ParameterTypeRequiredDescription
q string Optional Name prefix search (case-insensitive). Prefix-only: no substring or fuzzy matching
file_type string Optional Filter by category: image, video, audio, document, spreadsheet, presentation, archive, code, design, cad, ebook, font, or other
min_size integer Optional Minimum file size in bytes
max_size integer Optional Maximum file size in bytes
limit integer Optional Maximum results per page (max: 200)Default: 50
cursor string Optional Signed pagination cursor from the previous response's next_cursor
sort_order string Optional asc or descDefault: desc

Response

FieldTypeDescription
ingots array Matching ingot objects (same shape as List Ingots)
count integer Number of ingots in this page
has_more boolean Whether more results are available
next_cursor string? Signed cursor for the next page (present when has_more is true)
filters object Echo of the applied filters: q, file_type, min_size, max_size
bash Example Searches
# Files starting with "Q3"
curl "https://api.sparkvault.com/v1/vaults/$VAULT_ID/ingots/search?q=Q3" \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: $VAT"

# Large videos (over 1 GB), newest first
curl "https://api.sparkvault.com/v1/vaults/$VAULT_ID/ingots/search?file_type=video&min_size=1073741824" \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: $VAT"

Get Ingot Metadata

GET /v1/vaults/{vault_id}/ingots/{ingot_id}

Retrieve an ingot's metadata. Use the download endpoint to retrieve decrypted contents.

Response

FieldTypeDescription
ingot_id string Ingot identifier
name string Ingot name
size_bytes integer Size in bytes
content_type string MIME type
status string Status: uploading, encrypting, active, or failed
managed_by string Managing product ID; null for standard ingots
created_at integer Creation timestamp
updated_at integer Last-modified timestamp. Pass it as expected_updated_at to optimistically lock a subsequent PUT
access_count integer Number of downloads
total_bandwidth_bytes integer Total bytes served for this ingot

Download Ingot

POST /v1/vaults/{vault_id}/ingots/{ingot_id}/download

Get a time-limited download URL for decrypting an ingot via Forge.

Request Body

ParameterTypeRequiredDescription
stream boolean Optional Mint a reusable, seek-able streaming decrypt URL (many Range requests can be made against one link, e.g. media playback) instead of the single-use defaultDefault: false

Response

FieldTypeDescription
status string Status (always ready)
download_url string Signed Forge URL for streaming decryption (includes ISTK)
ingot_id string Ingot identifier
name string Original file name
size_bytes integer File size in bytes
content_type string MIME type
original_filename string Original filename for Content-Disposition
expires_at integer Download URL expiration timestamp
stream boolean Whether the URL is a reusable streaming URL
javascript Download Example
// Step 1: Get download URL
const downloadResponse = await fetch(
  `https://api.sparkvault.com/v1/vaults/${vaultId}/ingots/${ingotId}/download`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey,
      'X-Vault-Access-Token': vat
    }
  }
);

const { download_url, name } = (await downloadResponse.json()).data;

// Step 2: Fetch decrypted content from Forge
const fileResponse = await fetch(download_url);
const blob = await fileResponse.blob();

// Trigger browser download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();

Replace Ingot Content

PUT /v1/vaults/{vault_id}/ingots/{ingot_id}

Replace an ingot's content. Same two-step flow as create: the response includes a fresh Forge URL for the TUS upload. An active ingot stays readable until Forge finalizes the new upload.

Request Body

ParameterTypeRequiredDescription
size_bytes integer Required Exact size of the replacement content in bytes (1 byte to 5 TB)
name string Optional Rename the ingot as part of the replace. Must not collide with another name in the same folder
content_type string Optional New MIME type; defaults to the existing content type
expected_updated_at integer Optional Optimistic lock: the ingot's updated_at from your last read. If the ingot changed since, the request fails with 412 PRECONDITION_FAILED

Response

FieldTypeDescription
ingot_id string Ingot identifier (unchanged)
name string Ingot name (after any rename)
size_bytes integer Declared replacement size in bytes
content_type string MIME type
status string Status: uploading
forge_url string Forge URL containing the upload ISTK
expires_at integer Upload URL expiration timestamp

Rename or Move Ingot

PATCH /v1/vaults/{vault_id}/ingots/{ingot_id}

Partially update an ingot: rename it and/or move it to a folder. Content is not modified.

Request Body

ParameterTypeRequiredDescription
name string Optional New name (max 255 characters). Must not collide with another name in the target folder
folder_id string|null Optional Target folder ID; null moves the ingot to the vault root

At least one of name or folder_id is required; otherwise the request fails with 400 VALIDATION_ERROR. Returns the updated ingot object (same shape as List Ingots items).

Delete Ingot

DELETE /v1/vaults/{vault_id}/ingots/{ingot_id}

Permanently delete an ingot. This immediately frees pooled storage and stops it counting against your storage pool. Returns 204 No Content and cascade-deletes any SparkLink for the ingot.

Irreversible

Deleted ingots cannot be recovered. The encrypted data is permanently destroyed.

In-Flight Upload Guard

An ingot whose status is uploading and which is less than 1 hour old cannot be deleted: the request returns 400 VALIDATION_ERROR (prevents a race with Forge). A stale uploading ingot (older than 1 hour) can be deleted to clear a stuck upload.

Forge: Streaming Encryption

Forge is SparkVault's resumable encryption proxy. All cryptographic operations happen server-side using AES-256-GCM, eliminating the need for client-side crypto libraries.

Why Use Forge
  • Resumable uploads: Recover failed transfers by resuming from the last accepted offset
  • Bounded chunks: Upload bytes in a predictable memory envelope
  • No client-side crypto: Encryption happens server-side over TLS

How It Works

Forge uses Ingot Secure Transfer Keys (ISTKs) for authentication. ISTKs are automatically embedded in the URLs returned by the Ingots API:

  • Upload: The forge_url from Create Ingot includes the ISTK as a query parameter
  • Download: The download_url from Download Ingot includes the ISTK as a query parameter

You don't need to manually extract or manage ISTKs. Simply use the URLs returned by the API.

javascript Upload via Forge (using forge_url from Create Ingot)
import * as tus from 'tus-js-client';

const forgeUrl = new URL(createResponse.data.forge_url);
const istk = forgeUrl.searchParams.get('istk');

const upload = new tus.Upload(file, {
  endpoint: `${forgeUrl.origin}/encrypt`,
  chunkSize: 50 * 1024 * 1024,
  headers: { 'X-ISTK': istk },
  metadata: {
    filename: file.name,
    filetype: file.type || 'application/octet-stream'
  }
});

upload.start();
bash Download via Forge (using download_url from Download Ingot)
# The download_url already contains the ISTK
curl "$DOWNLOAD_URL" --output decrypted-file.tar.gz

See the Forge documentation for complete details on resumable encryption, including TUS protocol details and error handling.

Audit Logs

Per-ingot audit logs track uploads, downloads, sharing changes, and shared-link access. Audit logs are immutable and retained according to the vault's retention setting. For account- and vault-level audit logs, see the Audit Logs API.

GET /v1/vaults/{vault_id}/ingots/{ingot_id}/audit-logs

List audit events for an ingot.

Query Parameters

ParameterTypeRequiredDescription
limit integer Optional Entries per page (1-100)Default: 25
cursor string Optional Signed pagination cursor from the previous response's next_cursor
event_types string Optional Comma-separated event types to filter by (e.g. ingot_downloaded,ingot_deleted)

Response

FieldTypeDescription
entries array Audit event entries, newest first
entries[].event_type string Event type (e.g. ingot_created, ingot_downloaded, sparklink_accessed)
entries[].timestamp integer Event timestamp
entries[].actor_id string User or API key that performed the action
entries[].ip_address string Client IP address
entries[].user_agent string Client user agent
entries[].metadata object Event-specific details. VAT downloads record name, size_bytes, content_type; SparkLink downloads record link_code_prefix, visibility, identity, access_mode
count number Number of entries in this page
has_more boolean Whether more entries are available
next_cursor string? Signed cursor for the next page (present when has_more is true)
vault_id string Vault identifier
ingot_id string Ingot identifier
event_types array All valid ingot event types, usable as event_types filter values

Example

Request
bash
curl https://api.sparkvault.com/v1/vaults/vlt_abc123/ingots/ing_xyz789/audit-logs \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: YOUR_VAT"
Response
json
{
  "data": {
    "entries": [
      {
        "event_type": "ingot_downloaded",
        "timestamp": 1704157200,
        "actor_id": "usr_abc123",
        "ip_address": "203.0.113.7",
        "user_agent": "curl/8.5.0",
        "metadata": {
          "name": "report.pdf",
          "size_bytes": 1048576,
          "content_type": "application/pdf"
        }
      }
    ],
    "count": 1,
    "has_more": false,
    "vault_id": "vlt_abc123",
    "ingot_id": "ing_xyz789",
    "event_types": [
      "ingot_created", "ingot_renamed", "ingot_deleted", "ingot_moved",
      "ingot_shared", "ingot_unshared", "invite_created", "invite_revoked",
      "ingot_accessed", "ingot_downloaded", "ingot_uploaded",
      "sparklink_accessed", "upload_error", "download_error"
    ]
  }
}

Usage

Ingot operations consume the pooled storage and bandwidth included with your seat subscription. There is no per-GB fee; usage is tracked and counted against your pool. For subscription tiers and capacity blocks, see the pricing page.

Resource Usage

Operation Usage
Upload/Download Counts against pooled bandwidth (included)
Storage Counts against pooled storage (included)
Delete Frees pooled storage
Extending Capacity

When pooled storage or bandwidth is exhausted, extend it with a capacity block. See the pricing page for available blocks.

Error Reference

Error Responses

StatusCodeDescription
400 VALIDATION_ERROR Invalid request parameters, including size_bytes above the 5 TB maximum (size_bytes must be at most 5497558138880) and deleting an ingot whose upload is still in flight
401 AUTHENTICATION_ERROR Missing, malformed, or unknown Vault Access Token (X-Vault-Access-Token header), or a missing/invalid API key (X-API-Key) or session JWT (Authorization: Bearer)
402 PLAN_REQUIRED An active subscription is required for this operation
402 QUOTA_EXCEEDED Pooled storage or bandwidth exhausted (resource: storage|bandwidth); add a capacity block
403 FORBIDDEN VAT expired or bound to a different vault/account, read-only (Viewer) VAT on a write operation, or direct access to a managed ingot
404 NOT_FOUND Ingot not found (or name lookup failed)
409 CONFLICT Ambiguous name lookup (multiple ingots share the name) or target folder is being deleted
412 PRECONDITION_FAILED expected_updated_at did not match on PUT: the ingot changed; fetch the latest metadata and retry