Skip to content

Snapshots API

Content versioning and historical access.

Base URL: {base-url}/snapshots Authentication: Required (JWT)


Overview

The snapshot system enables versioned data queries where every entity (domains, trails, concepts, sparks, etc.) is linked to a specific snapshot version. This allows you to:

  • Preview draft content before promoting it to production
  • Query historical versions of your content
  • Rollback to previous snapshots instantly
  • A/B test different content versions
  • Maintain data consistency across all entities in a snapshot

How It Works

  1. Each entity has a snapshot_id column linking it to a specific snapshot version
  2. The current_snapshot table points to the active published snapshot
  3. Discovery queries automatically filter by snapshot ID (current by default, or specific if provided)
  4. All related entities (domains, trails, concepts, sparks, links) share the same snapshot ID for consistency

Endpoints

Endpoint Purpose
GET / List all snapshots with pagination
GET /current Get current active snapshot
GET /{id} Get snapshot by ID with full details

GET /

Returns all content snapshots with pagination support.

Query Parameters

Parameter Type Description
page number Page number (default: 1)
per_page number Items per page (default: 20, max: 100)
status string Filter by status: draft, published, or archived

Response

{
  "data": [
    {
      "id": "uuid",
      "name": "v2.1.0",
      "version_label": "v2.1.0",
      "description": "January 2024 content update",
      "status": "published",
      "entity_counts": {
        "domains": 5,
        "trails": 18,
        "concepts": 65,
        "sparks": 280
      },
      "created_at": "2024-01-15T00:00:00Z",
      "published_at": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "total": 10,
    "page": 1,
    "per_page": 20,
    "total_pages": 1,
    "has_next": false,
    "has_prev": false
  }
}

Example

# List all snapshots
GET /snapshots/

# List only draft snapshots
GET /snapshots/?status=draft

# Paginated list
GET /snapshots/?page=2&per_page=10

GET /current

Returns the currently active snapshot that serves as the default for all queries.

Response

{
  "id": "uuid",
  "name": "v2.1.0",
  "version_label": "v2.1.0",
  "description": "January 2024 content update",
  "status": "published",
  "entity_counts": {
    "domains": 5,
    "trails": 18,
    "concepts": 65,
    "sparks": 280,
    "beacons": 45,
    "concept_sparks": 520,
    "spark_links": 180,
    "concept_links": 95
  },
  "created_at": "2024-01-15T00:00:00Z",
  "published_at": "2024-01-15T10:30:00Z",
  "parent_snapshot_id": "previous-uuid"
}

Error Response

If no snapshot has been published yet:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "No snapshot has been published yet"
  }
}

GET /{id}

Get full details of a specific snapshot by ID.

Path Parameters

Parameter Type Description
id UUID Snapshot ID

Response

{
  "id": "uuid",
  "name": "v2.1.0",
  "version_label": "v2.1.0",
  "description": "January 2024 content update",
  "status": "draft",
  "entity_counts": {
    "domains": 5,
    "trails": 18,
    "concepts": 65,
    "sparks": 280
  },
  "created_at": "2024-01-15T00:00:00Z",
  "published_at": null,
  "parent_snapshot_id": "previous-uuid"
}

Using Snapshots in Discovery Queries

All Discovery endpoints accept an optional snapshot_id query parameter to query specific snapshot versions.

How Snapshot Selection Works

  1. If snapshot_id is provided → Uses that specific snapshot
  2. If snapshot_id is NOT provided → Automatically uses the current active snapshot
  3. If no current snapshot exists → Returns empty results (no snapshot filtering)

Example Queries

# Use current snapshot (default behavior)
GET /discovery/domains

# Query specific snapshot
GET /discovery/domains?snapshot_id=abc-123-def-456

# Preview draft snapshot before promoting
GET /discovery/domains?snapshot_id=<draft-snapshot-id>

# Query historical version
GET /discovery/trails?snapshot_id=<old-snapshot-id>

# Combine with other filters
GET /discovery/concepts?trail=neural-networks&snapshot_id=<specific-id>

Supported Discovery Endpoints

All discovery endpoints support the snapshot_id parameter:

  • GET /discovery/domains?snapshot_id={uuid}
  • GET /discovery/trails?snapshot_id={uuid}
  • GET /discovery/concepts?snapshot_id={uuid}
  • GET /discovery/sparks?snapshot_id={uuid}
  • GET /discovery/browse?snapshot_id={uuid}
  • GET /discovery/facets?snapshot_id={uuid}

Complete Workflow Example

1. Import Creates Draft Snapshot

# Import bundle creates a draft snapshot
deno task import bundle.json
# → Creates snapshot with status="draft"
# → Returns: snapshot_id, import_id

2. Verify Draft Snapshot

# Verify what's in the draft snapshot
deno task verify-snapshot:local
# → Shows entity counts, sample entities, relationship verification

3. Preview Draft in API

# Preview draft content via API
GET /discovery/domains?snapshot_id=<draft-id>
GET /discovery/browse?snapshot_id=<draft-id>
# → See how draft content looks before promoting

4. Promote Draft to Current

# Option A: Via import script
deno task import bundle.json --promote

# Option B: Via SQL (if import already completed)
SELECT promote_import('<import_id>', '<snapshot_id>');

After promotion: - Draft snapshot becomes status="published" - Previous current snapshot becomes status="archived" - New snapshot becomes the current active snapshot

5. Default Queries Use New Snapshot

# All queries now automatically use new current snapshot
GET /discovery/domains
GET /discovery/trails
# → No snapshot_id needed, uses current automatically

6. Query Old Snapshot for Comparison

# View historical content
GET /discovery/domains?snapshot_id=<old-snapshot-id>
# → Compare versions or view past content

Snapshot Lifecycle

┌─────────┐
│  DRAFT  │ ← Import creates draft snapshot
└────┬────┘
     │ promote_import()
┌─────────┐
│ PUBLISHED│ ← Becomes current active snapshot
└────┬────┘
     │ (new snapshot promoted)
┌──────────┐
│ ARCHIVED │ ← Previous snapshot archived
└──────────┘

Snapshot States

  • draft: Import in progress or awaiting promotion, not visible to default queries
  • published: Successfully imported and ready, can be current or archived
  • archived: Historical snapshot, replaced by newer published snapshot

Benefits

  1. Atomic Updates: All entities in a snapshot are consistent - either all succeed or all fail
  2. Zero-Downtime Deployments: Preview drafts before promoting to production
  3. Rollback Capability: Instantly switch back to previous snapshots
  4. Historical Queries: Compare versions or view content as it existed at any point
  5. A/B Testing: Test different content versions side-by-side
  6. Data Integrity: All related entities (domains, trails, concepts, sparks, links) share the same snapshot ID

Implementation Details

Database Schema

  • All content tables have a snapshot_id column (UUID, nullable for backward compatibility)
  • Junction tables (concept_sparks, spark_links, concept_links) also have snapshot_id
  • The current_snapshot table is a singleton pointing to the active snapshot
  • Views like domain_stats, trail_stats, concept_stats respect snapshot filtering

Query Filtering

Every discovery query follows this pattern:

// Get effective snapshot ID
const snapshotId = await getEffectiveSnapshotId(
  supabase,
  c.req.query("snapshot_id")
);

// Apply snapshot filter
let query = supabase.from("domains").select("*");
if (snapshotId) {
  query = query.eq("snapshot_id", snapshotId);
}

This ensures: - Consistent data across all related entities - Historical queries return complete, consistent snapshots - Default queries always use the current published content