SparkLinks API v1

Overview

SparkLinks are the unified sharing mechanism for SparkVault. They provide a single, consistent way to share sparks and ingots with configurable visibility, single-use access, and expiration. Every SparkLink works exactly once, then expires.

Key Features

  • Unified Sharing: One system for sparks and ingots
  • Visibility Controls: Public, authenticated, or invite-only access
  • Single-Use: Every link opens exactly once, then is consumed
  • Expiration: Automatic link expiration
  • Audit Trail: Track who accessed what and when
  • Link Safety: A link-spark destination URL is checked for safety before redirect

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 vault files PUT /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing

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

SparkLinks always use the short domain format x.sv/{link_code}.

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.

PATCH /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

ParameterTypeRequiredDescription
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

FieldTypeDescription
shared boolean Whether the spark is shared
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)
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)

Request
bash
curl -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"
  }'
Response
json
{
  "data": {
    "shared": true,
    "share_url": "https://x.sv/ABCDefgh1234567890_-xy",
    "link_code": "ABCDefgh1234567890_-xy",
    "visibility": "public",
    "invites": [],
    "expires_at": 1702086400,
    "created_at": 1702000000
  }
}

Share Spark (Invite Only)

Request
bash
curl -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"]
  }'
Response
json
{
  "data": {
    "shared": true,
    "share_url": "https://x.sv/ABCDefgh1234567890_-xy",
    "link_code": "ABCDefgh1234567890_-xy",
    "visibility": "invite_only",
    "invites": ["recipient@example.com"],
    "expires_at": 1702086400,
    "created_at": 1702000000
  }
}

Get Sharing Status

GET /v1/sparks/{spark_id}/share

Get the current sharing status for a spark.

Response

FieldTypeDescription
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) | revoked
expires_at integer? Link expiration timestamp (when shared)
created_at integer? Creation timestamp (when shared)

Unshare a Spark

DELETE /v1/sparks/{spark_id}/share

Unshare a spark by deleting its SparkLink. The spark itself is not deleted.

Response

FieldTypeDescription
shared boolean Always false after unsharing
message string Result message (also returned when the spark was not shared)
Automatic Invitation

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 via SparkLinks. Every SparkLink grant is single-use regardless of type: it is minted active, atomically flipped to consumed on the one open, and then rejects all further access. Re-access requires minting a new link.

PUT /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

ParameterTypeRequiredDescription
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

FieldTypeDescription
ingot_id string Ingot ID
shared boolean Whether the ingot is shared
visibility string? Visibility mode (null when not shared)
public_url string? Full share URL: https://x.sv/{link_code}
link_code string? Unique link code
updated_at integer Update timestamp

Share Ingot

Request
bash
curl -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"
  }'
Response
json
{
  "data": {
    "ingot_id": "ing_xyz789",
    "shared": true,
    "visibility": "authenticated",
    "public_url": "https://x.sv/ABCDefgh1234567890_-xy",
    "link_code": "ABCDefgh1234567890_-xy",
    "updated_at": 1702000000
  }
}
Related Endpoints
  • GET /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing: Get the current sharing configuration
  • POST /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite: Invite an identity (body: identity, optional expires_in_seconds)
  • DELETE /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite/{invite_id}: Revoke an invite
  • POST /v1/vaults/{vault_id}/ingots/{ingot_id}/sharing/invite/{invite_id}/resend: Resend an invitation

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. SparkLinks are always served from x.sv, so there is no link-hosting step here.

Listing domains works with any authenticated caller (JWT or API key). Adding, deleting, and verifying domains are admin-only (JWT admin session).

GET /v1/domains

List the account's custom domains and their ownership-verification status.

Response

FieldTypeDescription
domains array Array of domain objects
domains[].domain string The hostname
domains[].status string Ownership verification: pending | verified | failed
domains[].verification_method string Always dns_txt
domains[].txt_name string TXT record name to create (_sparkvault.{domain})
domains[].txt_value string TXT record value (sparkvault-domain-verification=svdv_...)
domains[].created_at integer Creation timestamp
domains[].verified_at integer? Ownership verification timestamp
domains[].last_checked_at integer? Last TXT check timestamp
domains[].last_check_result string? Last TXT check outcome
POST /v1/domains

Add a custom domain (admin only). The domain starts pending. Prove ownership via the verify endpoint. Maximum 10 domains per account.

Request Body

ParameterTypeRequiredDescription
domain string Required Hostname to add (e.g. app.example.com). SparkVault-owned domains are rejected.

Response

FieldTypeDescription
domain object The created domain object (see GET /v1/domains fields)
message string Setup instructions

Add a Custom Domain

Request
bash
curl -X POST https://api.sparkvault.com/v1/domains \
  -H "Authorization: Bearer ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "app.acme.com" }'
Response
json
{
  "data": {
    "domain": {
      "domain": "app.acme.com",
      "status": "pending",
      "verification_method": "dns_txt",
      "txt_name": "_sparkvault.app.acme.com",
      "txt_value": "sparkvault-domain-verification=svdv_...",
      "created_at": 1702000000,
      "verified_at": null,
      "last_checked_at": null,
      "last_check_result": null
    },
    "message": "Domain added. Create the TXT record to verify ownership."
  }
}
DELETE /v1/domains/{domain}

Remove a custom domain (admin only) and release its global claim.

Response

FieldTypeDescription
deleted boolean Always true on success
domain string The removed hostname
POST /v1/domains/{domain}/verify

Verify DNS ownership via a TXT record (admin only). Rate limited: one attempt per domain every 10 minutes (429 RATE_LIMITED with retry_after).

Response

FieldTypeDescription
domain object The updated domain object
verified boolean Whether ownership verification passed
result string TXT ownership check outcome (e.g. success, txt_not_found, txt_wrong_value)
message string Human-readable result with the next step
actual_value string? TXT value found, when it differs from the expected value

Verify a Custom Domain

Request
bash
curl -X POST https://api.sparkvault.com/v1/domains/app.acme.com/verify \
  -H "Authorization: Bearer ADMIN_JWT"
Response
json
{
  "data": {
    "domain": {
      "domain": "app.acme.com",
      "status": "verified",
      "verified_at": 1702000600
    },
    "verified": true,
    "result": "success",
    "message": "Domain verified successfully"
  }
}
Global Domain Claims

Successful verification claims the domain globally: a domain verified by one organization cannot be verified by another (the attempt fails with 400 VALIDATION_ERROR). A verified domain authorizes cross-domain SSO and Identity SDK browser origins for the claiming organization.

Access History

Track who accessed your SparkLinks using the Audit Log API. SparkLink access events include IP address, user agent, and verified identity (for authenticated/invite_only links). 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

Request
bash
curl "https://api.sparkvault.com/v1/audit-logs?event_types=sparklink_accessed" \
  -H "X-API-Key: sv_live_xxx"
Response
json
{
  "data": {
    "entries": [
      {
        "event_id": "evt_abc123",
        "event_type": "sparklink_accessed",
        "timestamp": 1702001000,
        "details": {
          "link_code": "ABCDefgh1234567890_-xy",
          "link_type": "ingot",
          "visibility": "authenticated",
          "verified_by": "user@example.com",
          "ip": "192.168.1.1",
          "user_agent": "Mozilla/5.0..."
        }
      }
    ],
    "count": 1,
    "has_more": false
  }
}
Event Types
  • sparklink_created: SparkLink was created
  • sparklink_updated: SparkLink settings were updated in place
  • sparklink_accessed: SparkLink was accessed (spark / link-spark / ingot)
  • sparklink_deleted: SparkLink was deleted
  • sparklink_revoked: SparkLink was revoked
  • sparklink_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)

javascript
// 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

javascript
// 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

python
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

SparkLink operations are covered by your seat subscription. Bandwidth consumed by link access is tracked against the pool included with your plan. There are no per-operation charges. For subscription tiers and capacity blocks, see the pricing page.

Operation Usage
Create SparkLink Included with subscription
Access SparkLink Counts against pooled bandwidth (included)
List SparkLinks Included with subscription
Delete SparkLink Included with subscription

Error Reference

Error Responses

StatusCodeDescription
400 VALIDATION_ERROR Invalid request parameters. Includes malformed or non-http(s) link-spark destination URLs, and unknown or unowned link codes on GET /v1/sparklinks/{link_code} (message: SparkLink not found)
401 AUTHENTICATION_ERROR Invalid or missing API key
402 PLAN_REQUIRED An active subscription is required
402 QUOTA_EXCEEDED Pooled capacity exhausted (details include resource: bandwidth). Add a capacity block
403 FORBIDDEN Denied at link open: visibility requirements not met, link already consumed, revoked, expired, or IP-binding mismatch. Also returned when sharing a non-active or expired spark
404 NOT_FOUND Spark or ingot not found on the share endpoints; unknown or expired link code at public access (SparkLink not found or expired)
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
429 RATE_LIMITED Domain verification attempted again within 10 minutes (details include retry_after)
Re-Sharing Is Not an Error

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. Changing visibility or invites applies to the same link, so switching an invite_only link to public downgrades access on the existing URL.