Overview
SparkVault uses one grant system for sparks and ingots with configurable visibility and expiration. Spark and one-time ingot grants use x.sv and are consumed after one access. Durable ingot grants use files.sv and remain available until the owner disables sharing. An ingot grant serves its file only while the owning account has an active plan and bandwidth left in its pool (see Usage).
Key Features
- Unified Sharing: One system for sparks and ingots
- Visibility Controls: Public, authenticated, or invite-only access
- Purpose-Built Lifecycles: Single-use links for sparks and one-time ingot delivery; durable links for ongoing ingot sharing
- Expiration: Automatic expiration for single-use grants
- Audit Trail: Track who accessed what and when
Link Types
| Type | Purpose | Created Via |
|---|---|---|
spark |
Share ephemeral secrets, including a link-spark (a text/uri-list redirect / magic link), burn-on-read |
PATCH /v1/sparks/{spark_id}/share, or POST /v1/sparks with content_type: 'text/uri-list' for a link-spark |
ingot |
Share a vault file through a durable link or an independent one-time link | PUT /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing (durable), or POST /v1/vaults/{vault_id}/ingots/{ingot_id}/sparklinks (one-time) |
Visibility Modes
| Mode | Who Can Access | Identity Verification |
|---|---|---|
public |
Anyone with the link | No |
authenticated |
Anyone who verifies their identity | Yes (via Identity Product) |
invite_only |
Specific invited identities only | Yes (must match invite) |
URL Format
Sparks and one-time ingot links use x.sv/{link_code}. Durable ingot shares use files.sv/{link_code}.
List SparkLinks
/v1/sparklinks
List all SparkLinks created by your account. Supports filtering by type and pagination.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
X-Vault-Access-Token |
string | Optional | A VAT for one of your vaults. Ingot rows of that vault carry their link_code and link_url; every other ingot row carries null for both. A VAT that is expired, revoked, or another account's fails the request (401 or 403)Default: none |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
type |
string | Optional | Filter by link type: spark or ingotDefault: all |
limit |
integer | Optional | Results per page, at most 100; a larger value reads as 100 (Pagination)Default: 50 |
cursor |
string | Optional | The next_cursor a previous page returned, with the same type. A cursor that was altered, or minted for another account, resource or type, returns 400 VALIDATION_ERRORDefault: null |
Response
| Field | Type | Description |
|---|---|---|
sparklinks |
array | Array of SparkLink objects |
sparklinks[].link_code |
string? | Unique link code. null for an ingot link unless the request presents a VAT for its vault |
sparklinks[].link_url |
string? | Full URL: https://x.sv/{link_code} for sparks and one-time ingot links; https://files.sv/{link_code} for durable ingot shares. null whenever link_code is |
sparklinks[].link_type |
string | Type: spark or ingot |
sparklinks[].asset_id |
string? | Associated asset ID (spk_xxx or ing_xxx) |
sparklinks[].vault_id |
string? | Vault ID for ingot links |
sparklinks[].visibility |
string | Visibility mode |
sparklinks[].verification_level |
string | Verification a recipient must present to open the link |
sparklinks[].interaction |
string | What the recipient is asked to do: view, acknowledge, sign, approve, or reply |
sparklinks[].reveal_freshness |
integer? | Seconds within which the recipient must have verified to open the link (the policy's reveal_freshness_minutes × 60); null when the link has no freshness requirement |
sparklinks[].single_use |
boolean | Whether the link is spent by one use: a view link by its reveal (a file by its download), an interactive link by its answer while its reveal stays repeatable. false for a durable files.sv share |
sparklinks[].invites |
array | Invited identities (email/phone strings) |
sparklinks[].status |
string | Lifecycle status: active, or consumed (a single-use link once spent) |
sparklinks[].bound_ip |
string? | IP address the link is bound to |
sparklinks[].session_length |
integer? | Access session length in seconds |
sparklinks[].expires_at |
integer? | Expiration timestamp |
sparklinks[].created_at |
integer | Creation timestamp |
sparklinks[].verified_by |
string? | Last verified identity (for authenticated/invite_only) |
sparklinks[].verified_at |
integer? | Last verification timestamp |
count |
integer | Number of items in this page |
has_more |
boolean | Whether another page follows. A page is filled to limit when that many links remain, the type filter included |
next_cursor |
string? | Opaque, sealed cursor for the next page, bound to your account and type; it reveals nothing about the rows it pages past. Present only when has_more is true |
List All SparkLinks
Requestcurl "https://api.sparkvault.com/v1/sparklinks?type=spark&limit=10" \
-H "X-API-Key: sv_live_xxx"
{
"data": {
"sparklinks": [
{
"link_code": "ABCDefgh1234567890_-xy",
"link_url": "https://x.sv/ABCDefgh1234567890_-xy",
"link_type": "spark",
"asset_id": "spk_abc123",
"visibility": "public",
"verification_level": "none",
"interaction": "view",
"reveal_freshness": null,
"single_use": true,
"invites": [],
"status": "active",
"expires_at": 1702086400,
"created_at": 1702000000
}
],
"count": 1,
"has_more": false
}
}
A spark's link answers to your account. An ingot's link publishes a file of a vault, so its code answers only to a holder of that vault's key: present a Vault Access Token for the link's vault in X-Vault-Access-Token. The list withholds the code and URL of every ingot link the token does not unlock, reading one ingot link requires a token for its vault, and deleting one also requires a Full License seat and a read-write token.
Create a Redirect / Magic Link (Link-Spark)
A single-use redirect or magic link is a link-spark: a Spark whose encrypted payload is the destination URL, marked by content_type: 'text/uri-list'. The destination is sealed at rest (never stored in plaintext) and burns on first open. Create it on the Sparks API: the server validates the URL, seals it, and auto-mints a PUBLIC spark SparkLink grant, returning link_url and link_code. There is no POST /v1/sparklinks create path.
/v1/sparks
Create a link-spark by sealing a destination URL. Pass content_type 'text/uri-list' and the URL as the payload.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
payload |
string | Required | The http/https destination URL to seal |
content_type |
string | Required | Must be 'text/uri-list' to mint a link-spark |
ttl_minutes |
integer | Optional | Time-to-live in minutesDefault: 1440 (24 hours) |
Response
| Field | Type | Description |
|---|---|---|
spark_id |
string | The sealed link-spark (spk_xxx) |
size_bytes |
integer | Size of the sealed payload in bytes |
status |
string | The spark's status: active |
created_at |
integer | Creation timestamp |
expires_at |
integer | Expiration timestamp |
ttl_minutes |
integer | Time-to-live in minutes |
link_url |
string | Full URL: https://x.sv/{link_code} |
link_code |
string | Unique link code |
filename |
string? | Echoed back when provided in the request |
kindling |
string? | External kindling ID, when provided in the request |
Create Magic Link (Link-Spark)
Requestcurl -X POST https://api.sparkvault.com/v1/sparks \
-H "X-API-Key: sv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"payload": "https://myapp.com/auth/callback?token=xyz",
"content_type": "text/uri-list",
"ttl_minutes": 15
}'
{
"data": {
"spark_id": "spk_abc123",
"size_bytes": 41,
"status": "active",
"created_at": 1702000000,
"expires_at": 1702000900,
"ttl_minutes": 15,
"link_url": "https://x.sv/ABCDefgh1234567890_-xy",
"link_code": "ABCDefgh1234567890_-xy"
}
}
Opening x.sv/{link_code} serves a "Decrypting the secure SparkLink destination…" page without burning. Page JS then posts to burn: the server decrypts the URL and returns it, and the page redirects to the destination host. A non-JS scanner that issues only a bare GET never consumes the single-use link.
Get SparkLink
/v1/sparklinks/{link_code}
Get details about a specific SparkLink. An ingot link requires a Vault Access Token for its vault.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
X-Vault-Access-Token |
string | Optional | Required for an ingot link: a VAT for the link's vault (a read-only VAT suffices). Without one the request returns 401; with another vault's, 403Default: none |
Response
| Field | Type | Description |
|---|---|---|
link_code |
string | Unique link code |
link_url |
string | Full URL: https://x.sv/{link_code} for sparks and one-time ingot links; https://files.sv/{link_code} for durable ingot shares |
link_type |
string | Type: spark or ingot |
asset_id |
string? | Associated asset ID (spk_xxx or ing_xxx) |
vault_id |
string? | Vault ID for ingot links |
visibility |
string | Visibility mode |
verification_level |
string | Verification a recipient must present to open the link |
interaction |
string | What the recipient is asked to do: view, acknowledge, sign, approve, or reply |
reveal_freshness |
integer? | Seconds within which the recipient must have verified to open the link (the policy's reveal_freshness_minutes × 60); null when the link has no freshness requirement |
single_use |
boolean | Whether the link is spent by one use: a view link by its reveal (a file by its download), an interactive link by its answer while its reveal stays repeatable. false for a durable files.sv share |
invites |
array | Invited identities (email/phone strings), as in the list |
status |
string | Lifecycle status: active, or consumed (a single-use link once spent) |
bound_ip |
string? | IP address the link is bound to |
session_length |
integer? | Access session length in seconds |
expires_at |
integer? | Expiration timestamp |
created_at |
integer | Creation timestamp |
verified_by |
string? | Last verified identity (for authenticated/invite_only) |
verified_at |
integer? | Last verification timestamp |
Get SparkLink Details
Requestcurl "https://api.sparkvault.com/v1/sparklinks/ABCDefgh1234567890_-xy" \
-H "X-API-Key: sv_live_xxx"
{
"data": {
"link_code": "ABCDefgh1234567890_-xy",
"link_url": "https://x.sv/ABCDefgh1234567890_-xy",
"link_type": "spark",
"asset_id": "spk_abc123",
"visibility": "public",
"verification_level": "none",
"interaction": "view",
"reveal_freshness": null,
"single_use": true,
"invites": [],
"status": "active",
"expires_at": 1702086400,
"created_at": 1702000000
}
}
An unknown link code, or one owned by another account, returns 404 NOT_FOUND on both the read and the delete, so a response never says a code exists in another account.
Delete SparkLink
/v1/sparklinks/{link_code}
Delete a SparkLink. The associated asset (spark/ingot) will no longer be shared, but the asset itself is not deleted. Deleting an ingot link requires a Full License seat and a read-write Vault Access Token for its vault.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
X-Vault-Access-Token |
string | Optional | Required for an ingot link: a read-write VAT for the link's vault. Without one the request returns 401; with another vault's, 403; with a read-only one, 403 (VAT_READ_ONLY). A View Only or unlicensed member is refused first (403 LICENSE_REQUIRED)Default: none |
Response
| Field | Type | Description |
|---|---|---|
deleted |
boolean | Always true on success |
link_code |
string | The deleted link code |
Delete SparkLink
Requestcurl -X DELETE "https://api.sparkvault.com/v1/sparklinks/ABCDefgh1234567890_-xy" \
-H "X-API-Key: sv_live_xxx"
{
"data": {
"deleted": true,
"link_code": "ABCDefgh1234567890_-xy"
}
}
An unknown link code, or one owned by another account, returns 404 NOT_FOUND with message SparkLink not found, and nothing is deleted.
Each spark has at most one SparkLink. An ingot can have one durable files.sv share plus independent, expiring one-time SparkLinks. To change a shared spark's settings, PATCH /v1/sparks/{spark_id}/share again, which updates the existing link in place.
Sharing Sparks
Share an existing spark by creating a SparkLink for it. The spark remains burn-on-read but can be accessed via the short URL.
/v1/sparks/{spark_id}/share
Create a SparkLink for an existing spark. If the spark is already shared, its existing link is updated in place.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
visibility |
string | Optional | Visibility mode: public, authenticated, or invite_onlyDefault: public |
invites |
array | Optional | Identities (email/phone) to invite for invite_only visibility (max 1, invitation sent automatically)Default: [] |
expires_in_seconds |
integer | Optional | Link expiration TTL (min 60, capped at spark expiration). Applies only on the first share; it is ignored when the spark already has a SparkLink.Default: Spark expiration |
Response
| Field | Type | Description |
|---|---|---|
shared |
boolean | Whether the spark is shared |
spark_id |
string | Spark ID |
share_url |
string | Full share URL: https://x.sv/{link_code} |
link_code |
string | Unique link code |
visibility |
string | Visibility mode |
invites |
array | Invited identities (invite_only visibility) |
status |
string | Single-use lifecycle: active → consumed (opened once) |
expires_at |
integer | Link expiration timestamp |
created_at |
integer | Creation timestamp |
already_shared |
boolean? | true when the spark already had a SparkLink. The existing link is updated in place and returned, including invites and expires_at. |
Share Spark (Public)
Requestcurl -X PATCH "https://api.sparkvault.com/v1/sparks/spk_abc123/share" \
-H "X-API-Key: sv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"visibility": "public"
}'
{
"data": {
"shared": true,
"spark_id": "spk_abc123",
"share_url": "https://x.sv/ABCDefgh1234567890_-xy",
"link_code": "ABCDefgh1234567890_-xy",
"visibility": "public",
"invites": [],
"status": "active",
"expires_at": 1702086400,
"created_at": 1702000000
}
}
Share Spark (Invite Only)
Requestcurl -X PATCH "https://api.sparkvault.com/v1/sparks/spk_abc123/share" \
-H "X-API-Key: sv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"visibility": "invite_only",
"invites": ["recipient@example.com"]
}'
{
"data": {
"shared": true,
"spark_id": "spk_abc123",
"share_url": "https://x.sv/ABCDefgh1234567890_-xy",
"link_code": "ABCDefgh1234567890_-xy",
"visibility": "invite_only",
"invites": ["recipient@example.com"],
"status": "active",
"expires_at": 1702086400,
"created_at": 1702000000
}
}
Get Sharing Status
/v1/sparks/{spark_id}/share
Get the current sharing status for a spark.
Response
| Field | Type | Description |
|---|---|---|
shared |
boolean | Whether the spark is shared. When false, only shared and spark_id are returned |
spark_id |
string | Spark ID |
share_url |
string? | Full share URL (when shared) |
link_code |
string? | Unique link code (when shared) |
visibility |
string? | Visibility mode (when shared) |
invites |
array? | Invited identities (when shared) |
status |
string? | Single-use lifecycle: active → consumed (opened once) |
expires_at |
integer? | Link expiration timestamp (when shared) |
created_at |
integer? | Creation timestamp (when shared) |
Unshare a Spark
/v1/sparks/{spark_id}/share
Unshare a spark by deleting its SparkLink. The spark itself is not deleted.
Response
| Field | Type | Description |
|---|---|---|
shared |
boolean | Always false after unsharing |
message |
string | Result message (also returned when the spark was not shared) |
When sharing with invite_only visibility, an invitation email/SMS is automatically sent to each invited identity with the pre-populated share URL.
Sharing Ingots
Share vault files (ingots) with external users through one permanent files.sv URL. Public, authenticated, and invite-only policies update that durable URL in place. One-time SparkLinks are independent, expiring x.sv grants that are consumed when their decrypt stream is issued. Both draw the owner's bandwidth pool, and both are refused while the owning account has no active plan or an exhausted pool (see Usage).
/v1/vaults/{vault_id}/ingots/{ingot_id}/sharing
Enable, update, or disable sharing for an ingot. Requires a Vault Access Token (X-Vault-Access-Token header) and Public File Sharing enabled on the vault.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
shared |
boolean | Optional | true to enable sharing, false to disableDefault: Current state |
visibility |
string | Optional | Visibility mode: public, authenticated, or invite_only (required when enabling sharing)Default: null |
invites |
array | Optional | Identities (email/phone) to invite for invite_only visibilityDefault: [] |
Response
| Field | Type | Description |
|---|---|---|
ingot_id |
string | Ingot ID |
shared |
boolean | Whether the ingot is shared |
visibility |
string? | Visibility mode (null when not shared) |
public_url |
string? | Permanent share URL: https://files.sv/{link_code} |
link_code |
string? | Unique link code |
updated_at |
integer | Update timestamp |
Share Ingot
Requestcurl -X PUT "https://api.sparkvault.com/v1/vaults/vlt_abc123/ingots/ing_xyz789/sharing" \
-H "X-API-Key: sv_live_xxx" \
-H "X-Vault-Access-Token: YOUR_VAT" \
-H "Content-Type: application/json" \
-d '{
"shared": true,
"visibility": "authenticated"
}'
{
"data": {
"ingot_id": "ing_xyz789",
"shared": true,
"visibility": "authenticated",
"public_url": "https://files.sv/ABCDefgh1234567890_-xy",
"link_code": "ABCDefgh1234567890_-xy",
"updated_at": 1702000000
}
}
GET /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing: Get the current sharing configurationPOST /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite: Invite an identity to the durable share (body:identity)DELETE /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite/{invite_id}: Revoke an invitePOST /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite/{invite_id}/resend: Resend an invitationPOST /v1/vaults/{vault_id}/ingots/{ingot_id}/sparklinks: Mint an independent one-time x.sv URL (body:expires_in_seconds, 300-86400)POST /v1/vaults/{vault_id}/ingots/{ingot_id}/sparklinks/{code}/deliver: Send an existing one-time URL to an email or E.164 phone (body:identity)
Custom Domains
Prove your organization owns a domain with a DNS TXT record. Verified domains authorize cross-domain SSO, Identity SDK browser origins, and signup reservation under that domain for the claiming organization. Shares remain on the platform-owned x.sv and files.sv domains, so there is no link-hosting step here.
A domain is only ever stored verified: request the TXT record from the challenge endpoint, publish it at your DNS host, then claim the domain. The claim runs the DNS check and stores the domain only when it passes. There is no pending or unverified state. Listing domains works with any authenticated caller (JWT or API key); the challenge, claim, and delete endpoints are admin-only (JWT admin session).
/v1/domains
List the account's verified domains.
Response
| Field | Type | Description |
|---|---|---|
domains |
array | Array of domain objects |
domains[].domain |
string | The hostname |
domains[].verification_method |
string | Always dns_txt |
domains[].txt_name |
string | TXT record name (_sparkvault.{domain}). Keep it published so ownership stays verifiable |
domains[].txt_value |
string | TXT record value (sparkvault-domain-verification=svdv_...) |
domains[].created_at |
integer | Claim timestamp |
domains[].verified_at |
integer | Ownership verification timestamp (equal to created_at, since a domain is stored only once verified) |
/v1/domains/challenge
Get the TXT record to publish for a domain (admin only). Stores nothing: the record is derived, so you can request it now and claim the domain whenever DNS has propagated. Rejections DNS cannot fix (a domain claimed by another organization, an invalid hostname, the 10-domain account limit) are raised here, before you touch your DNS host.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
domain |
string | Required | Hostname to claim (e.g. app.example.com). SparkVault-owned domains are rejected. |
Response
| Field | Type | Description |
|---|---|---|
domain |
string | The normalized hostname |
txt_name |
string | TXT record name to create (_sparkvault.{domain}) |
txt_value |
string | TXT record value (sparkvault-domain-verification=svdv_...) |
Get the TXT Record
Requestcurl -X POST https://api.sparkvault.com/v1/domains/challenge \
-H "Authorization: Bearer ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{ "domain": "app.acme.com" }'
{
"data": {
"domain": "app.acme.com",
"txt_name": "_sparkvault.app.acme.com",
"txt_value": "sparkvault-domain-verification=svdv_..."
}
}
/v1/domains
Claim a domain (admin only). Runs the DNS TXT check and stores the domain only if it passes. A failed check stores nothing, and the error names the exact record to fix. Maximum 10 domains per account.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
domain |
string | Required | Hostname whose TXT record (from the challenge endpoint) is already published. |
Response
| Field | Type | Description |
|---|---|---|
domain |
object | The stored, verified domain object (see GET /v1/domains fields) |
message |
string | Confirmation |
Verify & Claim a Domain
Requestcurl -X POST https://api.sparkvault.com/v1/domains \
-H "Authorization: Bearer ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{ "domain": "app.acme.com" }'
{
"data": {
"domain": {
"domain": "app.acme.com",
"verification_method": "dns_txt",
"txt_name": "_sparkvault.app.acme.com",
"txt_value": "sparkvault-domain-verification=svdv_...",
"created_at": 1702000600,
"verified_at": 1702000600
},
"message": "app.acme.com is verified and ready to use."
}
}
/v1/domains/{domain}
Remove a domain (admin only) and release its global claim.
Response
| Field | Type | Description |
|---|---|---|
deleted |
boolean | Always true on success |
domain |
string | The removed hostname |
A successful claim reserves the domain globally: a domain verified by one organization cannot be claimed by another (the attempt fails with 400 VALIDATION_ERROR). Keep the TXT record published. It is the standing proof of ownership behind cross-domain SSO and Identity SDK browser origins.
Access History
Track who accessed your SparkLinks using the Audit Log API. An access event carries the IP address, the user agent and, for a link that requires verification, the verified identity. It never carries the full link code: metadata.link_code is the code's first six characters followed by *** (a display form that can repeat across links), and metadata.link_code_hash (32 hex chars, 128 bits of SHA-256 over the full code) is the key to match an event to its link. Responses are paginated: entries are returned under entries with count, has_more, and (when more pages exist) next_cursor, and each response includes an event_types array listing every valid event type for the scope. Filter with the comma-separated event_types query parameter.
Query SparkLink Access Events
Requestcurl "https://api.sparkvault.com/v1/audit-logs?event_types=sparklink_accessed" \
-H "X-API-Key: sv_live_xxx"
{
"data": {
"entries": [
{
"timestamp": 1702001000,
"event_type": "sparklink_accessed",
"actor_id": "user@example.com",
"ip_address": "203.0.113.7",
"user_agent": "Mozilla/5.0...",
"metadata": {
"link_code": "ABCDef***",
"link_code_hash": "9f2c1a7be04d38c56a1e0b7d2f93c4e8",
"link_type": "ingot",
"visibility": "authenticated",
"verification_level": "identifier",
"interaction": "view",
"asset_id": "ing_xyz789",
"vault_id": "vlt_abc123",
"identity": "user@example.com"
}
}
],
"count": 1,
"has_more": false,
"event_types": ["sparklink_created", "sparklink_accessed", "..."]
}
}
sparklink_created: SparkLink was createdsparklink_updated: SparkLink settings were updated in placesparklink_accessed: the link delivered its content (a spark reveal or a file download). An open that failed or was refused records none, and repeat reveals of an interactive link inside one verification record onesparklink_deleted: SparkLink was deletedsparklink_signed,sparklink_approved,sparklink_denied,sparklink_replied: interaction receipts, signed, JWKS-verifiable proof of the recipient's action
Common Use Cases
Passwordless Authentication (Magic Links)
// Create a magic auth link as a link-spark: the destination URL is
// sealed (encrypted at rest) and burns on the first open.
const response = await fetch('https://api.sparkvault.com/v1/sparks', {
method: 'POST',
headers: {
'X-API-Key': process.env.SPARKVAULT_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
payload: `https://myapp.com/auth/verify?token=${authToken}`,
content_type: 'text/uri-list',
ttl_minutes: 15 // single-use, burns on first hop
})
});
const { link_url } = (await response.json()).data;
// Send link_url to user via email
await sendEmail(user.email, `Login here: ${link_url}`);
Secure File Sharing with Identity Verification
// Share a confidential document requiring identity verification
const response = await fetch(
'https://api.sparkvault.com/v1/vaults/vlt_abc/ingots/ing_xyz/sharing',
{
method: 'PUT',
headers: {
'X-API-Key': process.env.SPARKVAULT_API_KEY,
'X-Vault-Access-Token': vaultAccessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
shared: true,
visibility: 'invite_only',
invites: ['client@example.com']
})
}
);
// Recipient automatically receives invitation email
// They must verify their email before accessing the file
Time-Limited Share Links
import requests
import os
# Share a spark with public visibility that expires in 1 hour
response = requests.patch(
f'https://api.sparkvault.com/v1/sparks/{spark_id}/share',
headers={
'X-API-Key': os.environ['SPARKVAULT_API_KEY'],
'Content-Type': 'application/json'
},
json={
'visibility': 'public',
'expires_in_seconds': 3600
}
)
share_url = response.json()['data']['share_url']
print(f"Share this link: {share_url}") # Valid for 1 hour (capped at spark expiration)
Usage
A SparkLink backed by a Spark is free, like the Spark itself. A SparkLink backed by an ingot draws the bandwidth pool included with your plan when the file is downloaded. There are no per-operation charges either way. For subscription tiers and capacity blocks, see the pricing page.
| Operation | Usage |
|---|---|
| Create SparkLink | Free on every account |
| Access SparkLink | Counts against pooled bandwidth (included) |
| List SparkLinks | Free on every account |
| Delete SparkLink | Free on every account |
A shared-file download, through a durable files.sv share or a one-time x.sv ingot SparkLink, is refused with 402 while the owning account has no active plan (PLAN_REQUIRED) or has exhausted its bandwidth pool (QUOTA_EXCEEDED, with details.resource bandwidth). The link is kept, a one-time link is not consumed, and recipients see "Link temporarily unavailable" until the owner restores a plan or adds a capacity block. Spark-backed SparkLinks are unaffected.
Error Reference
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Invalid request parameters. Includes malformed or non-http(s) link-spark destination URLs and a list cursor that was altered or minted for another account, resource or type |
| 401 | AUTHENTICATION_ERROR |
Invalid or missing API key, or no Vault Access Token on a request that reads or deletes an ingot link |
| 403 | FORBIDDEN |
Sharing a non-active or expired spark. On the SparkLinks endpoints, a Vault Access Token that is expired (VAT_EXPIRED), for another vault or account, or read-only on a delete (VAT_READ_ONLY) |
| 403 | LICENSE_REQUIRED |
Deleting an ingot link as a View Only or unlicensed member |
| 403 | VERIFICATION_REQUIRED |
Revealing an x.sv link, answering its request, or downloading a shared file without the verification the link asks for, or with one already spent: verify again and retry. The link's metadata read answers such a request 200 with requires_verification: true and the verify_url that runs the verification |
| 403 | STRONGER_VERIFICATION_REQUIRED |
The same, with a valid verification weaker than the link asks for (an emailed code against a passkey level). Only the sender can resend the link at a level the recipient can clear |
| 403 | NOT_INVITED |
Opening an invite-scoped x.sv link or files.sv share with a verified identity that is not on its invite list |
| 403 | ALREADY_ANSWERED |
Answering an x.sv request with a verification an earlier answer already spent, while the link is still open. An answer that fails before it is recorded spends nothing, so it can be retried with the same verification. Once an answer has spent the link, a repeat answers 404 SPARKLINK_UNAVAILABLE |
| 402 | PLAN_REQUIRED |
Shared-file download refused: the owning account has no active plan |
| 402 | QUOTA_EXCEEDED |
Shared-file download refused: the owning account's bandwidth pool is exhausted (details.resource bandwidth) |
| 404 | NOT_FOUND |
Spark or ingot not found on the share endpoints; an unknown or another account's link code on GET and DELETE /v1/sparklinks/{link_code} |
| 404 | SPARKLINK_UNAVAILABLE |
Opening an x.sv link or a files.sv share whose code names no link (a malformed code, and a one-time link's code on files.sv, included), or whose link is already consumed, expired or bound to another IP. Every case answers the same status, code and message, This link is no longer available, so a response never discloses which |
| 409 | CONFLICT |
Sharing a spark or an ingot while another change lands: an update or unshare of the same spark, another share of the same ingot, or a change to the vault's sharing settings. Nothing was changed; read the sharing again and retry |
| 409 | REVEAL_IN_PROGRESS |
Revealing an x.sv link while another open of it is reading it. Nothing was spent; retry in a moment |
| 412 | PRECONDITION_FAILED |
Public File Sharing is not enabled on the vault when enabling ingot sharing (enable it on the vault first) |
| 429 | RATE_LIMIT_EXCEEDED |
Too many requests. From one address, each hour: 120 opens of one link code (an x.sv page, its metadata or its reveal, or a durable files.sv share's metadata), 120 shared-file downloads, 120 answers to x.sv requests, and 120 verifications started by opening links that ask for one (the downloads, answers and verifications are counted across every code). An open, a download and an answer are counted before the code is looked up, so a code that names no link counts like one that does. Retry-After gives the seconds until that hour's window reopens |
Sharing a spark that already has a SparkLink does not conflict. The existing link is updated in place and returned with already_shared: true, and its link_code is never rotated. Only a concurrent change to the same spark's sharing answers 409 CONFLICT. Changing visibility or invites applies to the same link, so switching an invite_only link to public downgrades access on the existing URL.