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.
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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
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 header → 400 VALIDATION_ERROR; 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
/v1/products/structured-ingots/{vault_id}
Create a new structured ingot with initial key/value data.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Required | Name for the ingot (max 255 characters). A create with a name that already exists in the vault overwrites the existing structured ingot in place: it resets version to 1 and replaces every stored key, so the prior contents and keys are destroyed. Two structured ingots in one vault cannot share a name; use a fresh name (or PATCH the existing ingot) unless you intend to replace it. |
data |
object | Required | Key/value pairs to store (max 10,000 keys) |
Response
| Field | Type | Description |
|---|---|---|
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
Requestcurl -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"
}
}'
{
"data": {
"ingot_id": "ing_xyz789...",
"name": "customer_12345",
"version": 1,
"key_count": 3,
"created_at": 1702000000
}
}
List Structured Ingots
/v1/products/structured-ingots/{vault_id}
List structured ingots in a vault as a single, non-paginated page.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | Optional | Maximum number of ingots to fetch before filtering (1-500). This bounds a single fetch across all ingot types in the vault; only structured ingots are then returned, so count can be smaller than the true number of structured ingots (even zero) when the vault also holds many file ingots. There is no pagination cursor, so raise the limit if you need more coverage.Default: 100 |
Response
| Field | Type | Description |
|---|---|---|
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
/v1/products/structured-ingots/{vault_id}/{ingot_id}
Get metadata and list of keys (without values).
Response
| Field | Type | Description |
|---|---|---|
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
Requestcurl 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"
{
"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
/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
| Parameter | Type | Required | Description |
|---|---|---|---|
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
| Field | Type | Description |
|---|---|---|
ingot_id |
string | Ingot identifier |
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
Requestcurl "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"
{
"data": {
"ingot_id": "ing_xyz789...",
"values": {
"ssn": {
"spark_id": "spk_abc123...",
"expires_at": 1702000300
},
"phone": {
"spark_id": "spk_def456...",
"expires_at": 1702000300
}
},
"missing_keys": []
}
}
Use the returned Spark IDs with the Sparks API to read the actual values:
GET /v1/sparks/spk_abc123
Update Keys (PATCH)
/v1/products/structured-ingots/{vault_id}/{ingot_id}
Atomically set or delete keys. Operations are applied server-side.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
set |
object | Optional | Keys to add or update (key: value pairs) |
delete |
array | Optional | Key names to delete |
A PATCH with neither set nor delete returns
400 VALIDATION_ERROR ("At least one set or delete operation required").
Concurrent PATCHes to the same ingot are serialized via optimistic locking: the losing
request receives 412 PRECONDITION_FAILED. Re-read the ingot and retry.
Response
| Field | Type | Description |
|---|---|---|
ingot_id |
string | Ingot identifier |
version |
integer | New version number |
key_count |
integer | Updated key count |
updated_at |
integer | Update timestamp |
Example
Requestcurl -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"]
}'
{
"data": {
"ingot_id": "ing_xyz789...",
"version": 4,
"key_count": 6,
"updated_at": 1702100500
}
}
Delete Ingot
/v1/products/structured-ingots/{vault_id}/{ingot_id}
Permanently delete a structured ingot and all its data.
Response
| Field | Type | Description |
|---|---|---|
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) |
Name addressing allows you to use your own identifiers (customer IDs, user IDs) without
needing to store SparkVault ingot IDs in your database. Because a create reuses an existing
same-named ingot, two structured ingots in a vault never share a name, so :name
addressing is unambiguous for them. A 409 CONFLICT arises only when a same-named
ingot also exists in a different folder (for example a non-structured file ingot), in which
case use the ingot ID instead.
Complete Example
// 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 |
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
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Invalid request parameters, missing X-Vault-Access-Token header, no set/delete operation on PATCH, or the operation would exceed the 10,000-key limit |
| 401 | AUTHENTICATION_ERROR |
Missing or invalid credentials, or the Vault Access Token is invalid |
| 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 |
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. Re-read and retry the PATCH |
| 429 | RATE_LIMIT_EXCEEDED |
Rate limit exceeded (300 operations per minute per account) |