← Back to Docs

Preview Tools

Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API.

Plain text for agents: /docs/preview/markdown.md

Which Tool Should I Use?

What are you deploying?
Static filesHTML, CSS, JS — already built
riddle_preview~5 seconds
Server appNode, Python, Ruby — stock runtime
riddle_server_preview~30–60 seconds
Custom buildDockerfile, system deps, compiled langs
riddle_build_preview~1–3 minutes
Featureriddle_previewserver_previewbuild_preview
Speed~5s~30–60s~1–3min
Server processNo (static only)YesYes
Docker imageStock (pull)Custom (build)
System packages
Image caching✓ (30 min default)
Security audit
Playwright scripts
Env vars
Preview URL✓ (24hr)

riddle_preview — Static Hosting

Deploy built static files (HTML, CSS, JS) to an ephemeral URL at preview.riddledc.com. The preview expires after 24 hours. Best for SPAs, Vite builds, Next.js static exports, or any pre-built frontend.

How It Works

  1. Tar your build output directory
  2. Upload to Riddle
  3. Get a live URL in ~5 seconds
  4. Screenshot it with any Riddle tool

Basic Example

const response = await fetch("https://api.riddledc.com/v1/preview", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${RIDDLE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    directory: "/path/to/build/output",
    framework: "spa"   // "spa" (default) or "static"
  })
});

const { url, id } = await response.json();
// url: "https://pv-a1b2c3d4.preview.riddledc.com"
// id:  "pv_a1b2c3d4" (use to delete early)

Parameters

ParameterTypeDescription
directorystringAbsolute path to build output (must contain index.html)
frameworkstring"spa" (default) — all routes serve index.html. "static" — file-based routing.
💡 When to use riddle_previewYour app is already built and just needs hosting. You have a dist/ or out/ folder with static files. You want a shareable URL that works for 24 hours.

Delete Early

Previews auto-expire after 24 hours, but you can clean up immediately:

await fetch("https://api.riddledc.com/v1/preview/pv_a1b2c3d4", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${RIDDLE_API_KEY}` }
});

riddle_server_preview — Server Apps

Run a server-side application inside an isolated Docker container and screenshot it with Playwright. Uses stock images from Docker Hub — no Dockerfile needed. Perfect for Next.js, Express, Django, FastAPI, Rails, and any app that needs a running server process.

How It Works

  1. Tar your project directory and upload it
  2. Pull a stock Docker image (e.g. node:20-slim)
  3. Start your server inside the container
  4. Wait for the readiness check to pass
  5. Take a Playwright screenshot
  6. Return the screenshot and logs

Polling responses include a current phase, phase_updated_at, and sometimesphase_details. They also include progress with step,total, percent, label, and terminal fields for simple status displays. This makes stuck jobs easier to read: you can tell whether Riddle is pulling an image, extracting the upload, starting the server, waiting for readiness, running browser proof, collecting artifacts, or finalizing outputs.

Basic Example

const response = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${RIDDLE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    server_preview: {
      directory: "/path/to/my-express-app",
      image: "node:20-slim",
      command: "npm start",
      port: 3000,
      path: "/dashboard",
      env: { NODE_ENV: "production" },
      viewport: { width: 1920, height: 1080 }
    }
  })
});

With Authentication & Custom Scripts

{
  server_preview: {
    directory: "/path/to/app",
    image: "node:20-slim",
    command: "npm start",
    port: 3000,

    // Inject localStorage tokens before page loads
    localStorage: {
      "auth_token": "eyJhbGciOiJIUzI1NiJ9...",
      "user_id": "12345"
    },

    // Sensitive env vars — stored securely, deleted after use
    sensitive_env: {
      DATABASE_URL: "postgres://...",
      API_SECRET: "sk-..."
    },

    // Wait for React hydration before screenshotting
    wait_for_selector: "#app[data-hydrated]",

    // Run a Playwright script after server is ready
    script: `
      await page.click('button.load-data');
      await page.waitForSelector('.data-table');
      await saveScreenshot('with-data');
    `,

    // Emulate dark mode
    color_scheme: "dark",

    // Readiness check config
    readiness_path: "/health",
    readiness_timeout: 30,
    timeout: 120
  }
}

Key Parameters

ParameterTypeDescription
directorystringPath to your project
imagestringDocker image to run (node:20-slim, python:3.12-slim, etc.)
commandstringCommand to start your server
portnumberPort your server listens on
pathstringURL path to screenshot (default: /)
envobjectNon-sensitive environment variables
sensitive_envobjectSecrets — stored securely, deleted after use
localStorageobjectKey-value pairs injected before page load
scriptstringPlaywright script to run after server is ready
wait_for_selectorstringCSS selector to wait for before screenshotting
color_schemestring"dark" or "light" — applied before navigation
viewportobject{ width, height } — default 1920×1080
readiness_pathstringHealth check endpoint to poll
readiness_timeoutnumberSeconds to wait for the server to answer before browser proof starts
navigation_timeoutnumberSeconds Playwright should wait for the target page after readiness passes
timeoutnumberMax execution time in seconds (default: 120)

Common Docker Images

Node.js

node:20-slim

Express, Fastify, Next.js, Nuxt

Python

python:3.12-slim

Django, FastAPI, Flask

Ruby

ruby:3.3-slim

Rails, Sinatra

riddle_build_preview — Custom Builds

Build a Docker image from your own Dockerfile, run the server, and screenshot it. Use this when you need custom system packages, compiled languages, multi-stage builds, or anything beyond a stock runtime image.

The word "build" means Docker image build. It does not mean "run my frontend build command".

How It Works

  1. Tar your project (must include a Dockerfile)
  2. Build the Docker image on the worker
  3. Start the container and wait for readiness
  4. Take a Playwright screenshot
  5. Cache the built image for fast re-runs

Basic Example

const response = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${RIDDLE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    build_preview: {
      directory: "/path/to/project",   // Must contain Dockerfile
      command: "npm start",
      port: 3000,
      build_args: { NODE_ENV: "production" },
      keep_image_minutes: 30,          // Cache the built image
      timeout: 180                      // Includes build time
    }
  })
});

With Security Audit

Enable audit: true to run a security scan on your code and dependencies. Returns a detailed report of findings, risk flags, and dependency inventory.

{
  build_preview: {
    directory: "/path/to/project",
    command: "python server.py",
    port: 8000,
    audit: true,   // Security scan
    timeout: 180
  }
}

// Response includes:
// {
//   audit: {
//     security_findings: [...],
//     deps_extracted: { runtime: { pip: [...], npm: [...] } },
//     code_summary: { files: [...], total_lines: 1234 },
//     risk_flags: [{ level: "info", reason: "..." }]
//   }
// }

Additional Parameters

Supports all riddle_server_preview parameters plus:

ParameterTypeDescription
build_argsobjectDocker --build-arg key-value pairs
keep_image_minutesnumberCache built image on worker (default: 30, max: 120, 0 = delete immediately)
auditbooleanRun security audit on code + dependencies
excludestring[]Glob patterns to exclude from tarball (default: [".git", "*.log"])
💡 Image CachingSet keep_image_minutes: 30 to avoid rebuilding on every run. The built image stays on the worker, so subsequent runs skip the Docker build step entirely. Set to 0 if you always want a fresh build.
⚠️ Timeout includes build timeThe default timeout is 180 seconds. Complex multi-stage builds may need more — set timeout: 600 (max). Cached images skip the build step, so subsequent runs are much faster.

Status Phases And Timeout Triage

Server preview and build preview report progress as phases. Use the last phase to decide whether to tune the preview command, the readiness timeout, the browser wait, or the proof script.

Poll responses also include a derived progress object. Display progress.labeland progress.percent for user-facing updates, and use progress.terminal to distinguish running states from final complete or failed responses.

{
  "status": "running",
  "phase": "waiting_for_readiness",
  "phase_updated_at": "2026-05-02T22:10:00.000Z",
  "phase_details": {
    "port": 3000,
    "readiness_path": "/health",
    "readiness_timeout": 30
  },
  "progress": {
    "step": 9,
    "total": 13,
    "percent": 69,
    "label": "Waiting for readiness",
    "terminal": false
  },
  "outputs": []
}
PhaseMeaningCommon Fix
awaiting_upload / queued / leasedThe job is accepted but worker execution has not reached the app yet.Wait or check worker capacity.
pulling_image / building_imageDocker work is still running.Increase timeout or use cached/static output when possible.
starting_server / waiting_for_readinessThe app command started, but the readiness URL is not answering yet.Increase readiness_timeout, fix the command, or use a route-specific readiness path.
running_browser_proofPlaywright is navigating, waiting for selectors, screenshotting, or running a script.Use wait_for_selector, tune navigation_timeout, or simplify the proof script.
collecting_artifactsRiddle is saving screenshots, logs, HAR, console output, and JSON artifacts.Usually wait; large artifacts may need a larger total timeout.

Readiness failures include the server log tail in phase_details.error. Failedriddle_server_preview responses can also include server_log, which is the app command output captured from inside the preview container. Check server_log first when the phase is starting_server or waiting_for_readiness. If logs still look like dependency install or app build work, use more readiness time, prebuild a static preview, or move to build preview if the Dockerfile is the production contract.

Screenshotting Previews

After deploying with riddle_preview, you get a live URL. Screenshot it with any Riddle tool:

Simple Screenshot

// After riddle_preview returns a URL
const screenshot = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${RIDDLE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    url: "https://pv-a1b2c3d4.preview.riddledc.com"
  })
});

Interactive Testing

// Run a Playwright script against the preview
{
  script: `
    await page.goto("https://pv-a1b2c3d4.preview.riddledc.com");
    await page.click('button#get-started');
    await page.waitForSelector('.onboarding-flow');
    await saveScreenshot('onboarding');
  `,
  timeout_sec: 60
}

Before/After Visual Diffs

The real power of preview tools: deploy two versions and compare them pixel-by-pixel. Perfect for verifying CSS changes, layout tweaks, or redesigns.

The Workflow

Deploy "before"Make changesDeploy "after"Visual diff
// 1. Deploy the "before" version
const before = await riddlePreview({ directory: "./build-before" });

// 2. Deploy the "after" version  
const after = await riddlePreview({ directory: "./build-after" });

// 3. Compare them
const diff = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${RIDDLE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    visual_diff: {
      url_before: before.url,
      url_after: after.url,
      full_page: true,
      threshold: 0.1        // Pixel match sensitivity (0–1)
    }
  })
});

// Returns:
// {
//   change_percentage: 2.3,
//   changed_pixels: 4521,
//   images: {
//     before: "https://...",
//     after:  "https://...",
//     diff:   "https://..."   ← highlighted changes
//   }
// }
💡 Pro tipUse selector to diff just a specific component instead of the full page. Set delay_ms to wait for animations to settle before capturing.

Start Previewing

Get an API key and deploy your first preview in under a minute.