# Authentication: SparkVault API Reference

> Learn how to authenticate your API requests using API keys or JWT tokens. SparkVault supports multiple authentication methods to fit different integration patterns.

Canonical: https://sparkvault.com/api/docs/authentication/ · OpenAPI: https://sparkvault.com/openapi.yaml

## Overview

SparkVault supports two authentication methods:

| Method | Header | Best For |
| --- | --- | --- |
| **API Key** | `X-API-Key` | Server-to-server integrations, automation, CI/CD pipelines |
| **JWT Token** | `Authorization: Bearer` | User-facing applications, browser sessions |

> **Which should I use?**
>
> For most integrations, **API keys** are the simplest choice. Use JWT tokens only if you're building a user-facing application that needs to manage individual user sessions.

## API Key Authentication

API keys provide simple, persistent authentication for server-side integrations. Create an API key from the [API Keys page](https://app.sparkvault.com/api/keys) and include it in the `X-API-Key` header.

```bash
curl https://api.sparkvault.com/v1/sparks \
  -H "X-API-Key: sv_live_abc123xyz789..."
```

#### API Key Format

API keys follow a consistent format:

-   `sv_live_...`: Production keys (live data)

> **Keep API keys secret**
>
> API keys grant full access to your account. Never expose them in client-side code, public repositories, or logs.
>
> If a key is compromised, revoke it immediately from the [API Keys page](https://app.sparkvault.com/api/keys).

#### Example: Creating a Spark with an API key

```bash
curl -X POST https://api.sparkvault.com/v1/sparks \
  -H "X-API-Key: sv_live_abc123xyz789..." \
  -H "Content-Type: application/json" \
  -d '{
    "payload": "super_secret_password_123"
  }'
```

## Managing API Keys Programmatically

API keys can be managed in the app at [app.sparkvault.com/api/keys](https://app.sparkvault.com/api/keys), or programmatically with the endpoints below. All three endpoints require authentication (JWT or API key). Creating and revoking keys additionally require an **admin or owner** role. Non-admin users receive `403 FORBIDDEN`.

#### `POST /v1/api-keys`

Create a new API key. Requires an admin or owner role. The full key string is returned once and is never retrievable again.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Required | Display name identifying the key's purpose (max 100 characters). |
| `expires_in_days` | integer | Optional | Days until the key expires (1-3650). Omit for a non-expiring key. |

#### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `api_key_id` | string | Unique key identifier (`key_...`). Use this id to revoke the key. |
| `api_key` | string | The full API key (`sv_live_...`). Shown only in this response. Store it securely. |
| `key_preview` | string | Masked preview for display (e.g. `sv_live_ab...wxyz`). |
| `name` | string | The key's display name. |
| `created_at` | integer | Creation time (Unix epoch seconds). |
| `expires_at` | integer | Expiration time (Unix epoch seconds), or `null` if the key does not expire. |
| `warning` | string | Reminder that the key will not be shown again. |

#### Example

Request

```bash
curl -X POST https://api.sparkvault.com/v1/api-keys \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI/CD pipeline",
    "expires_in_days": 90
  }'
```

Response

```json
{
  "data": {
    "api_key_id": "key_abc123...",
    "api_key": "sv_live_abc123xyz789...",
    "key_preview": "sv_live_ab...z789",
    "name": "CI/CD pipeline",
    "created_at": 1783036800,
    "expires_at": 1790812800,
    "warning": "Store this API key securely. It will not be shown again."
  }
}
```

#### `GET /v1/api-keys`

List the account's API keys with cursor-based pagination. Key strings are never returned, only masked previews.

#### Query Parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | Optional | Maximum keys per page (1-100).Default: `50` |
| `cursor` | string | Optional | Pagination cursor from a previous response's `next_cursor`. |

#### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `api_keys` | array | Key objects: `api_key_id`, `name`, `key_preview`, `status` (`active` | `expired` | `revoked`), `created_at`, `last_used_at`, `expires_at`, `revoked_at`. |
| `count` | integer | Number of keys in this page. |
| `has_more` | boolean | Whether more pages are available. |
| `next_cursor` | string | Cursor for the next page. Present only when `has_more` is `true`. |
| `active` | integer | Number of active keys in this page. |

#### `DELETE /v1/api-keys/:id`

Revoke an API key by its api_key_id. Requires an admin or owner role. Idempotent: revoking an already-revoked key succeeds and returns its original revocation time.

#### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `api_key_id` | string | The revoked key's identifier. |
| `name` | string | The key's display name. Returned only on first revocation. When the key was already revoked, the response omits `name` and includes a `message` field instead. |
| `status` | string | Always `revoked`. |
| `revoked_at` | integer | Revocation time (Unix epoch seconds). |

## JWT Token Authentication

JWT tokens are used for user session management in browser-based applications. SparkVault uses the [Identity Product](/api/docs/products/identity/) as an OIDC provider for user authentication. After successful authentication via Identity, you'll receive SparkVault session tokens.

```bash
curl https://api.sparkvault.com/v1/sparks \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
```

#### Token Types

| Type | Lifetime | Purpose |
| --- | --- | --- |
| **Access Token** | 1 hour | Short-lived token for API requests |
| **Refresh Token** | 30 days | Single-use token to obtain new access tokens (rotated on every refresh) |

## User Authentication Flow

SparkVault uses an OIDC (OpenID Connect) flow with the Identity Product for user authentication. This provides passwordless login via passkeys, magic links, social login, and more.

#### Flow Overview

1.  **Initiate Login**: Redirect user to Identity Product with PKCE challenge
2.  **User Authenticates**: Via passkey, magic link, or social login
3.  **Callback**: Identity Product redirects back with authorization code
4.  **Token Exchange**: Exchange code for SparkVault session tokens
5.  **Registration (New Users)**: Complete profile setup if needed

> **Identity Product**
>
> For detailed OIDC integration instructions, see the [Identity Product documentation](/api/docs/products/identity/). The SparkVault web app uses Identity Product for all user authentication.

## Session Endpoints

#### `POST /v1/auth/identity/token`

Exchange an OIDC authorization code from Identity Product for SparkVault session tokens. This is called after the user completes authentication via Identity Product.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | Required | Authorization code from Identity Product callback (max 512 characters). |
| `code_verifier` | string | Required | PKCE code verifier that must match the code\_challenge sent to Identity Product. 43-128 characters, per RFC 7636. |

#### Response (Existing User)

| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | JWT access token (1 hour lifetime) |
| `refresh_token` | string | JWT refresh token (30 day lifetime) |
| `user` | object | User profile information |
| `account` | object | Account information |

#### Response (New User)

| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | Identity-only JWT (limited access) |
| `refresh_token` | string | Identity-only refresh token |
| `user` | null | null indicates registration required |
| `account` | null | null indicates registration required |

#### Example

Request

```bash
curl -X POST https://api.sparkvault.com/v1/auth/identity/token \
  -H "Content-Type: application/json" \
  -d '{
    "code": "auth_code_from_identity_product",
    "code_verifier": "your_pkce_code_verifier"
  }'
```

Response

```json
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIs...",
    "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
    "user": {
      "user_id": "usr_abc123...",
      "email": "user@example.com",
      "name": "John Doe",
      "role": "admin"
    },
    "account": {
      "account_id": "acc_xyz789...",
      "organization_name": "Acme Corp",
      "status": "active"
    }
  }
}
```

#### `POST /v1/auth/identity/verify`

Directly verify an Identity Product JWT token without using the OIDC redirect flow. Useful for XHR-based authentication where redirect flows are not practical.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | Required | JWT token received from Identity Product verification |

#### Response

| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | JWT access token (1 hour lifetime) |
| `refresh_token` | string | JWT refresh token (30 day lifetime) |
| `user` | object | User profile (null if registration required) |
| `account` | object | Account information (null if registration required) |

#### Example

Request

```bash
curl -X POST https://api.sparkvault.com/v1/auth/identity/verify \
  -H "Content-Type: application/json" \
  -d '{"token": "eyJhbGciOiJFZDI1NTE5..."}'
```

Response

```json
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIs...",
    "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
    "user": {
      "user_id": "usr_abc123...",
      "email": "user@example.com",
      "name": "John Doe",
      "role": "admin"
    },
    "account": {
      "account_id": "acc_xyz789...",
      "organization_name": "Acme Corp",
      "status": "active"
    }
  }
}
```

> **When to use this endpoint**
>
> Use this endpoint when you're using the Identity Product SDK in XHR mode (without redirects). The SDK will return a JWT token directly after verification, which you can exchange for SparkVault session tokens using this endpoint.

#### `POST /v1/auth/complete-signup`

Complete registration for new users. Requires an identity-only JWT (from token exchange with user: null). Creates the account and upgrades to full session tokens.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `organization_name` | string | Required | Name of the organization/company (1-255 characters) |
| `full_name` | string | Optional | User's display name (up to 255 characters) |

#### Response

| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | Full JWT access token |
| `refresh_token` | string | Full JWT refresh token |
| `user` | object | Created user profile |
| `account` | object | Created account object |

> **Authentication Required**
>
> This endpoint requires an identity-only JWT in the Authorization header. The JWT proves email ownership via the Identity Product verification.

> **Claimed Domains**
>
> Signup returns `403 Forbidden` if the email's domain is already claimed by another organization. The user must be invited by that organization's admin instead.

#### `POST /v1/auth/refresh`

Exchange a refresh token for a new access token and a new refresh token. Use this when your access token expires.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `refresh_token` | string | Required | Valid refresh token |

#### Response

| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | New JWT access token |
| `refresh_token` | string | New rotated refresh token. Replace your stored token |
| `token_type` | string | Always "Bearer" |
| `expires_in` | integer | Token lifetime in seconds |

#### Example

Request

```bash
curl -X POST https://api.sparkvault.com/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "eyJhbGciOiJSUzI1NiIs..."}'
```

Response

```json
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIs...",
    "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
    "token_type": "Bearer",
    "expires_in": 3600
  }
}
```

> **Refresh Tokens Rotate on Every Use**
>
> Refresh tokens are **one-time-use**. Every call to `/v1/auth/refresh` revokes the presented token and returns a new `refresh_token`. Always replace your stored token with the one in the response. Reusing an already-rotated token fails with `401 AUTHENTICATION_ERROR`; if two requests race with the same token, the loser fails with `401 TOKEN_REPLAY`. In either case, discard stored tokens and sign the user in again.

#### `POST /v1/auth/logout`

Revoke a refresh token. Requires authentication. Returns 204 No Content on success.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `refresh_token` | string | Required | Refresh token to revoke |

#### Example

Request

```bash
curl -X POST https://api.sparkvault.com/v1/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "eyJhbGciOiJSUzI1NiIs..."}'
```

Response

```text
(204 No Content)
```

#### `POST /v1/auth/viewer-token`

Generate a short-lived viewer token for cross-domain direct-access viewing on x.sv. Requires authentication. Takes no request body.

#### Response

| Field | Type | Description |
| --- | --- | --- |
| `viewer_token` | string | Short-lived JWT for the x.sv viewer (5 minute lifetime) |
| `expires_in` | integer | Token lifetime in seconds, always 300 |

## Vault Access Tokens (VAT)

Vault Access Tokens are special, short-lived tokens required for reading and writing data in encrypted vaults. They are obtained by "unsealing" a vault with its Vault Master Key (VMK).

| Property | Value |
| --- | --- |
| **Header** | `X-Vault-Access-Token` |
| **Lifetime** | 1 hour (default), up to 24 hours |
| **Scope** | Single vault only |
| **Revocation** | Automatic on expiry, or manual via seal operation |

#### `POST /v1/vaults/:id/unseal`

Unseal a vault with its Vault Master Key to obtain a Vault Access Token.

#### Request Body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `vmk` | string | Required | The Vault Master Key. Also accepts a `dvak_` token (Delegated Vault Access Key) in place of the raw VMK. |
| `ttl_seconds` | integer | Optional | VAT lifetime in seconds (1-86400).Default: `3600` |

#### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `vat` | string | The Vault Access Token (`vat_...`). Send it in the `X-Vault-Access-Token` header. |
| `vault_id` | string | The unsealed vault's id. |
| `issued_at` | integer | Issue time (Unix epoch seconds). |
| `expires_at` | integer | Expiration time (Unix epoch seconds). |
| `ttl_seconds` | integer | Effective VAT lifetime in seconds. |
| `warning` | string | Reminder to store the VAT securely. |

```bash
# First, unseal the vault to get a VAT
curl -X POST https://api.sparkvault.com/v1/vaults/vlt_abc123/unseal \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"vmk": "YOUR_VAULT_MASTER_KEY"}'

# Then use the VAT for ingot operations
curl https://api.sparkvault.com/v1/vaults/vlt_abc123/ingots \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "X-Vault-Access-Token: vat_xyz789..."
```

> **VAT Best Practices**
>
> -   Obtain a VAT only when you need to access vault contents
> -   Store VATs securely in memory, never persist to disk
> -   VATs are vault-specific. Each vault requires its own VAT.
> -   Consider sealing vaults when done to immediately invalidate VATs

## Authentication Errors

#### Error Responses

| Status | Code | Description |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Missing or invalid request parameters |
| 401 | `AUTHENTICATION_ERROR` | Invalid credentials, expired access token, invalid or revoked refresh token, or suspended account |
| 401 | `TOKEN_REPLAY` | Refresh token was consumed by a concurrent rotation. Sign in again |
| 403 | `FORBIDDEN` | Insufficient permissions: e.g. identity-only token on a protected endpoint (complete registration first), or admin role required |
| 429 | `RATE_LIMIT_EXCEEDED` | Too many requests |

## Security Best Practices

#### API Keys

-   Generate separate API keys for each integration or service
-   Use descriptive names to identify key purpose
-   Rotate keys periodically (every 90 days recommended)
-   Revoke keys immediately if compromised
-   Never commit keys to version control. Use environment variables.

#### JWT Tokens

-   Store access tokens in memory only (not localStorage)
-   Implement automatic token refresh before expiry
-   Persist the rotated refresh token from every refresh response. The old one is immediately revoked
-   Clear tokens on logout and tab close
-   Handle 401 errors by redirecting to login

#### PKCE Security

-   Always use S256 code challenge method (never plain)
-   Generate cryptographically random code verifiers (32+ bytes)
-   Store PKCE parameters in sessionStorage (not localStorage)
-   Validate the state parameter to prevent CSRF attacks

#### General

-   Always use HTTPS. Never send credentials over plain HTTP.
-   Implement proper error handling for auth failures
-   Log authentication events for security monitoring
-   Use passkeys where possible for phishing resistance
