Structured Ingots API v1

Overview

Structured Ingots provide encrypted key/value storage within your Vaults. Unlike standard ingots (files), structured ingots store named fields that can be individually retrieved without downloading the entire ingot.

10K
Keys per Ingot
1MB
Max Value Size
AES-256
Encryption
Atomic
Set/Delete Ops

Use Cases

  • PII Storage: SSN, phone numbers, addresses per customer
  • Credentials: API keys, passwords, secrets with field-level access
  • User Preferences: Encrypted user settings with individual key access
  • Tokenized Data: Store sensitive data, retrieve as ephemeral Sparks
Values Returned as Sparks

For security, values are never returned inline. When you request keys, you receive Spark IDs that can be read once via the Sparks API.

Authentication

All Structured Ingots endpoints require account authentication (a JWT Bearer token or an API key) plus a Vault Access Token (VAT) obtained by unsealing the vault.

Headers

ParameterTypeRequiredDescription
Authorization string Optional JWT Bearer token (Authorization: Bearer <token>). Provide this or X-API-Key.
X-API-Key string Optional Your SparkVault API key. Provide this or Authorization.
X-Vault-Access-Token string Required VAT from unsealing the vault. Missing, unknown or malformed VAT → 401 AUTHENTICATION_ERROR; expired VAT → 403 FORBIDDEN.

API Endpoints

All JSON responses use the {"data": ...} envelope. Successful requests return 201 Created for POST, 200 OK for GET and PATCH, and 204 No Content for DELETE.

Create Structured Ingot

POST /v1/products/structured-ingots/{vault_id}

Create a new structured ingot with initial key/value data.

Request Body

ParameterTypeRequiredDescription
name string Required Name for the ingot (max 255 characters). A name already held in the vault answers 409 CONFLICT and leaves the existing ingot unchanged; change a structured ingot with PATCH. A name held only by an earlier create whose upload never completed is completed by the retry.
data object Required Key/value pairs to store (max 10,000 keys)

Response

FieldTypeDescription
ingot_id string Unique ingot identifier (ing_...)
name string Ingot name
version integer Version number (starts at 1)
key_count integer Number of keys stored
created_at integer Creation timestamp

Example

Request
bash
curl -X POST https://api.sparkvault.com/v1/products/structured-ingots/vlt_abc123 \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: YOUR_VAT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "customer_12345",
    "data": {
      "ssn": "123-45-6789",
      "phone": "+1-555-123-4567",
      "dob": "1990-01-15"
    }
  }'
Response
json
{
  "data": {
    "ingot_id": "ing_xyz789...",
    "name": "customer_12345",
    "version": 1,
    "key_count": 3,
    "created_at": 1702000000
  }
}

List Structured Ingots

GET /v1/products/structured-ingots/{vault_id}

List every structured ingot in a vault, in one response. The list takes no page size and no cursor: it reads the whole vault and returns each structured ingot in it.

Response

FieldTypeDescription
ingots array Array of structured ingot summaries
ingots[].ingot_id string Ingot identifier
ingots[].name string Ingot name
ingots[].version integer|null Always null in list responses. Versions are only decoded on single-ingot GET
ingots[].key_count integer|null Always null in list responses. Key counts are only decoded on single-ingot GET
ingots[].created_at integer Creation timestamp
ingots[].updated_at integer|null Last update timestamp
count integer Number of ingots returned

Get Ingot Metadata

GET /v1/products/structured-ingots/{vault_id}/{ingot_id}

Get metadata and list of keys (without values).

Response

FieldTypeDescription
ingot_id string Ingot identifier
name string Ingot name
version integer Current version
key_count integer Number of keys
keys array List of all key names
created_at integer Creation timestamp
updated_at integer Last update timestamp

Example

Request
bash
curl https://api.sparkvault.com/v1/products/structured-ingots/vlt_abc123/ing_xyz789 \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: YOUR_VAT"
Response
json
{
  "data": {
    "ingot_id": "ing_xyz789...",
    "name": "customer_12345",
    "version": 3,
    "key_count": 5,
    "keys": ["ssn", "phone", "dob", "email", "address"],
    "created_at": 1702000000,
    "updated_at": 1702100000
  }
}

Get Values as Sparks

GET /v1/products/structured-ingots/{vault_id}/{ingot_id}?keys=k1,k2&ttl=900

Retrieve specific values. Values are returned as Spark IDs for secure, auditable access.

Query Parameters

ParameterTypeRequiredDescription
keys string Required Comma-separated list of keys to retrieve (max 100 keys per request)
ttl integer Optional Spark TTL in seconds (min: 60, max: 86400)Default: 900

Response

FieldTypeDescription
ingot_id string Ingot identifier
version integer Version of the ingot these values were read from. Send it back as expected_version on a PATCH built from them
values object Map of key to Spark info
values[key].spark_id string Spark ID containing the value
values[key].expires_at integer When the Spark expires
missing_keys array Keys that were requested but not found

Example

Request
bash
curl "https://api.sparkvault.com/v1/products/structured-ingots/vlt_abc123/ing_xyz789?keys=ssn,phone&ttl=300" \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: YOUR_VAT"
Response
json
{
  "data": {
    "ingot_id": "ing_xyz789...",
    "version": 3,
    "values": {
      "ssn": {
        "spark_id": "spk_abc123...",
        "expires_at": 1702000300
      },
      "phone": {
        "spark_id": "spk_def456...",
        "expires_at": 1702000300
      }
    },
    "missing_keys": []
  }
}
Reading Spark Values

Use the returned Spark IDs with the Sparks API to read the actual values:

GET /v1/sparks/spk_abc123

Update Keys (PATCH)

PATCH /v1/products/structured-ingots/{vault_id}/{ingot_id}

Atomically set or delete keys. Operations are applied server-side.

Request Body

ParameterTypeRequiredDescription
set object Optional Keys to add or update, as an object of key: value pairs
delete array Optional Key names to delete, as an array of strings
expected_version integer Optional The version your last read returned. If the ingot has changed since, the PATCH is refused with 412 PRECONDITION_FAILED and nothing is written. Send it whenever the new values were computed from values you read
At Least One Operation, Optimistic Concurrency

A PATCH with neither set nor delete returns 400 VALIDATION_ERROR ("At least one set or delete operation required"), as does a set that is not an object, a delete that is not an array, or an invalid key or value. These are refused before the ingot is read. Concurrent PATCHes to the same ingot are serialized via optimistic locking: of two PATCHes that overlap, one is applied and the other receives 412 PRECONDITION_FAILED, so no write is silently lost. Re-read the ingot and retry. Pass expected_version to also refuse a PATCH when the ingot changed after your own read.

Response

FieldTypeDescription
ingot_id string Ingot identifier
version integer New version number
key_count integer Updated key count
updated_at integer Update timestamp

Example

Request
bash
curl -X PATCH https://api.sparkvault.com/v1/products/structured-ingots/vlt_abc123/ing_xyz789 \
  -H "X-API-Key: sv_live_xxx" \
  -H "X-Vault-Access-Token: YOUR_VAT" \
  -H "Content-Type: application/json" \
  -d '{
    "set": {
      "email": "updated@example.com",
      "address": "123 New Street"
    },
    "delete": ["old_field"]
  }'
Response
json
{
  "data": {
    "ingot_id": "ing_xyz789...",
    "version": 4,
    "key_count": 6,
    "updated_at": 1702100500
  }
}

Delete Ingot

DELETE /v1/products/structured-ingots/{vault_id}/{ingot_id}

Permanently delete a structured ingot and all its data.

Response

FieldTypeDescription
204 status No Content - ingot deleted successfully

Name Addressing

Structured ingots can be addressed by name using a colon prefix instead of the ingot ID. This allows deterministic addressing based on your own identifiers.

Path Addressing
/structured-ingots/vlt_abc/ing_xyz789 By ingot ID
/structured-ingots/vlt_abc/:customer_12345 By name (colon prefix)
Idempotent Lookups

Name addressing allows you to use your own identifiers (customer IDs, user IDs) without needing to store SparkVault ingot IDs in your database. A create never replaces an ingot that holds its name, so two structured ingots in a vault never share a name and :name addressing is unambiguous for them. A 409 CONFLICT on a :name lookup arises only when a same-named ingot also exists in a different folder (for example a non-structured file ingot); use the ingot ID instead.

Complete Example

javascript Working with Structured Ingots
// Create a structured ingot for a customer
const createResponse = await fetch(
  `https://api.sparkvault.com/v1/products/structured-ingots/${vaultId}`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey,
      'X-Vault-Access-Token': vat,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'customer_12345',
      data: {
        ssn: '123-45-6789',
        phone: '+1-555-123-4567',
        dob: '1990-01-15'
      }
    })
  }
);

const ingot = (await createResponse.json()).data;
console.log('Created:', ingot.ingot_id);

// Later: Retrieve SSN as a Spark (by name)
const readResponse = await fetch(
  `https://api.sparkvault.com/v1/products/structured-ingots/${vaultId}/:customer_12345?keys=ssn`,
  {
    headers: {
      'X-API-Key': apiKey,
      'X-Vault-Access-Token': vat
    }
  }
);

const { values } = (await readResponse.json()).data;
const ssnSparkId = values.ssn.spark_id;

// Read the Spark to get the actual SSN value
const sparkResponse = await fetch(
  `https://api.sparkvault.com/v1/sparks/${ssnSparkId}`,
  {
    headers: { 'X-API-Key': apiKey }
  }
);

const ssn = (await sparkResponse.json()).data.payload;
console.log('SSN:', ssn);

// Update with new fields
await fetch(
  `https://api.sparkvault.com/v1/products/structured-ingots/${vaultId}/:customer_12345`,
  {
    method: 'PATCH',
    headers: {
      'X-API-Key': apiKey,
      'X-Vault-Access-Token': vat,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      set: { email: 'new@example.com' },
      delete: ['old_field']
    })
  }
);

Constraints

Limit Value
Max keys per ingot 10,000
Max key name length 256 characters
Max value size (storage) 1 MB
Max wrapped value size (Spark retrieval) 256,000 bytes
Max keys per retrieval request 100
Max ingot name length 255 characters
Retrieval Size Cap

The 1 MB limit applies to storing a value. Retrieval returns values wrapped in Sparks, whose payloads are capped at 256,000 bytes. A value whose base64/JSON-wrapped size exceeds that cap stores fine but fails retrieval with 400 VALIDATION_ERROR. Keep individual values under ~256 KB wrapped if you need to retrieve them via the API.

Error Reference

Error Responses

StatusCodeDescription
400 VALIDATION_ERROR Invalid request parameters, a malformed or missing set/delete operation on PATCH, or the operation would exceed the 10,000-key limit
401 AUTHENTICATION_ERROR Missing or invalid credentials, or a missing or invalid Vault Access Token
403 FORBIDDEN Expired VAT, VAT issued for a different vault or account, or the ingot is not managed by the Structured Ingots product
404 NOT_FOUND Vault or ingot not found
409 CONFLICT A create names an ingot the vault already holds (change it with PATCH), or an ambiguous :name reference: a same-named ingot also exists in another folder (for example a non-structured file ingot). Use the ingot ID instead
412 PRECONDITION_FAILED Ingot changed while preparing the update, or no longer matches expected_version. Re-read and retry the PATCH
429 RATE_LIMIT_EXCEEDED Rate limit exceeded (5000 operations per minute per account)