{
  "version": "riddle-proof.profile-result.v1",
  "profile_name": "riddle-site-v489-preview-tools-guide-url-shape",
  "runner": "riddle",
  "status": "product_regression",
  "baseline_policy": "invariant_only",
  "route": {
    "requested": "https://riddledc.com/guides/preview-tools/",
    "observed": "/guides/preview-tools/",
    "expected_path": "/guides/preview-tools/",
    "matched": true,
    "http_status": 200
  },
  "artifacts": {
    "screenshots": [
      "riddle-site-v489-preview-tools-guide-url-shape-desktop",
      "riddle-site-v489-preview-tools-guide-url-shape-phone",
      "riddle-site-v489-preview-tools-guide-url-shape-ipad-mini",
      "riddle-site-v489-preview-tools-guide-url-shape-ipad"
    ],
    "console": "console.json",
    "proof_json": "proof.json",
    "dom_summary": "dom-summary.json"
  },
  "checks": [
    {
      "type": "route_loaded",
      "label": "route_loaded",
      "status": "passed",
      "evidence": {
        "expected_path": "/guides/preview-tools/",
        "observed_paths": [
          "/guides/preview-tools/",
          "/guides/preview-tools/",
          "/guides/preview-tools/",
          "/guides/preview-tools/"
        ],
        "http_statuses": [
          200,
          200,
          200,
          200
        ]
      }
    },
    {
      "type": "selector_visible",
      "label": "selector_visible",
      "status": "passed",
      "evidence": {
        "selector": ".preview-tools-page",
        "visible_counts": [
          1,
          1,
          1,
          1
        ]
      }
    },
    {
      "type": "text_visible",
      "label": "text_visible",
      "status": "passed",
      "evidence": {
        "text": "Preview Tools",
        "matches": [
          true,
          true,
          true,
          true
        ]
      }
    },
    {
      "type": "text_visible",
      "label": "text_visible",
      "status": "passed",
      "evidence": {
        "text": "riddle_preview — Static Hosting",
        "matches": [
          true,
          true,
          true,
          true
        ]
      }
    },
    {
      "type": "text_visible",
      "label": "text_visible",
      "status": "passed",
      "evidence": {
        "text": "Screenshotting Previews",
        "matches": [
          true,
          true,
          true,
          true
        ]
      }
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "failed",
      "evidence": {
        "selector": ".preview-tools-page",
        "text": "https://preview.riddledc.com/s/",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          }
        ]
      },
      "message": "Selector .preview-tools-page text assertion failed in 4 viewport(s)."
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "failed",
      "evidence": {
        "selector": ".code-block-wrapper",
        "text": "preview_url",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 0,
            "matched": false,
            "samples": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/"
            ]
          }
        ]
      },
      "message": "Selector .code-block-wrapper text assertion failed in 4 viewport(s)."
    },
    {
      "type": "selector_text_absent",
      "label": "selector_text_absent",
      "status": "failed",
      "evidence": {
        "selector": ".preview-tools-page",
        "text": null,
        "pattern": "https://pv-[a-z0-9]+\\.preview\\.riddledc\\.com|pv-a1b2c3d4\\.preview\\.riddledc\\.com",
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ]
          }
        ]
      },
      "message": "Selector .preview-tools-page text assertion failed in 4 viewport(s)."
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "passed",
      "evidence": {
        "selector": ".code-block-wrapper",
        "text": "framework: \"spa\"",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "Copy 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/outpu"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "Copy 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/outpu"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "Copy 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/outpu"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 10,
            "visible_count": 10,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "Copy 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/outpu"
            ]
          }
        ]
      }
    },
    {
      "type": "selector_count_at_least",
      "label": "selector_count_at_least",
      "status": "passed",
      "evidence": {
        "selector": ".code-block-wrapper",
        "min_count": 10,
        "counts": [
          10,
          10,
          10,
          10
        ]
      }
    },
    {
      "type": "link_status",
      "label": "link_status",
      "status": "passed",
      "evidence": {
        "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
        "expected_count": null,
        "min_count": 3,
        "allowed_statuses": [
          "2xx",
          "3xx"
        ],
        "require_nonzero_bytes": true,
        "min_bytes": 1000,
        "allowed_content_types": [
          "text/html",
          "text/markdown"
        ],
        "viewports": [
          {
            "viewport": "desktop",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "total_count": 3,
            "discovered_count": 10,
            "ok_count": 3,
            "failed_count": 0,
            "truncated": false,
            "max_links": 20,
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "status_counts": {
              "206": 3
            },
            "failures": []
          },
          {
            "viewport": "phone",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "total_count": 3,
            "discovered_count": 10,
            "ok_count": 3,
            "failed_count": 0,
            "truncated": false,
            "max_links": 20,
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "status_counts": {
              "206": 3
            },
            "failures": []
          },
          {
            "viewport": "ipad-mini",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "total_count": 3,
            "discovered_count": 10,
            "ok_count": 3,
            "failed_count": 0,
            "truncated": false,
            "max_links": 20,
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "status_counts": {
              "206": 3
            },
            "failures": []
          },
          {
            "viewport": "ipad",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "total_count": 3,
            "discovered_count": 10,
            "ok_count": 3,
            "failed_count": 0,
            "truncated": false,
            "max_links": 20,
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "status_counts": {
              "206": 3
            },
            "failures": []
          }
        ],
        "failures": []
      }
    },
    {
      "type": "no_horizontal_overflow",
      "label": "no_horizontal_overflow",
      "status": "passed",
      "evidence": {
        "max_overflow_px": 1,
        "overflow_px": [
          0,
          0,
          0,
          0
        ],
        "bounds_overflow_px": [
          0,
          0,
          0,
          0
        ],
        "overflow_offender_counts": [
          0,
          0,
          0,
          0
        ],
        "viewports": [
          "desktop",
          "phone",
          "ipad-mini",
          "ipad"
        ]
      }
    },
    {
      "type": "no_fatal_console_errors",
      "label": "no_fatal_console_errors",
      "status": "passed",
      "evidence": {
        "console_fatal_count": 0,
        "page_error_count": 0,
        "total_console_fatal_count": 0,
        "total_page_error_count": 0,
        "allowed_console_fatal_count": 0,
        "explicitly_allowed_console_fatal_count": 0,
        "allowed_expected_network_mock_console_count": 0,
        "allowed_expected_network_mock_console_events": [],
        "allowed_page_error_count": 0,
        "allowed_console_texts": [],
        "allowed_console_patterns": [],
        "allowed_page_error_texts": [],
        "allowed_page_error_patterns": []
      }
    },
    {
      "type": "no_console_warnings",
      "label": "no_console_warnings",
      "status": "passed",
      "evidence": {
        "console_warning_count": 0,
        "total_console_warning_count": 0,
        "allowed_console_warning_count": 0,
        "allowed_console_texts": [],
        "allowed_console_patterns": [],
        "unallowed_console_warning_samples": [],
        "allowed_console_warning_samples": []
      }
    }
  ],
  "summary": "riddle-site-v489-preview-tools-guide-url-shape failed 3 product invariant(s) across 4 viewport(s).",
  "captured_at": "2026-05-17T11:59:40.237Z",
  "evidence": {
    "version": "riddle-proof.profile-evidence.v1",
    "profile_name": "riddle-site-v489-preview-tools-guide-url-shape",
    "target_url": "https://riddledc.com/guides/preview-tools/",
    "baseline_policy": "invariant_only",
    "captured_at": "2026-05-17T11:59:40.237Z",
    "viewports": [
      {
        "name": "desktop",
        "width": 1280,
        "height": 900,
        "url": "https://riddledc.com/guides/preview-tools/",
        "route": {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        "title": "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "body_text_length": 12015,
        "body_text_sample": "Skip to main content Riddle Docs Proof MCP Pricing Blog Playground Sign Up Log In ← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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.p",
        "scroll_width": 1280,
        "client_width": 1280,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10
          }
        },
        "frames": {},
        "text_sequences": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "visible_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ],
            "visible_match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ]
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10,
            "texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "visible_texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ],
            "visible_match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ]
          }
        },
        "text_matches": {
          "text:Preview Tools": true,
          "text:riddle_preview — Static Hosting": true,
          "text:Screenshotting Previews": true
        },
        "http_statuses": {},
        "link_statuses": {
          "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']": {
            "version": "riddle-proof.link-status.v1",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "max_links": 20,
            "same_origin_only": true,
            "dedupe": true,
            "require_nonzero_bytes": true,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "allowed_statuses": [
              "2xx",
              "3xx"
            ],
            "discovered_count": 10,
            "total_count": 3,
            "truncated": false,
            "ok_count": 3,
            "failed_count": 0,
            "status_counts": {
              "206": 3
            },
            "failures": [],
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "results": [
              {
                "url": "https://riddledc.com/docs/",
                "tag": "a",
                "text": "Docs",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/docs/",
                "error": null
              },
              {
                "url": "https://riddledc.com/playground/",
                "tag": "a",
                "text": "Playground",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/playground/",
                "error": null
              },
              {
                "url": "https://riddledc.com/register/",
                "tag": "a",
                "text": "Sign Up",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/register/",
                "error": null
              }
            ]
          }
        },
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v489-preview-tools-guide-url-shape-desktop"
      },
      {
        "name": "phone",
        "width": 390,
        "height": 844,
        "url": "https://riddledc.com/guides/preview-tools/",
        "route": {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        "title": "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "body_text_length": 11961,
        "body_text_sample": "Skip to main content Riddle ← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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.termi",
        "scroll_width": 390,
        "client_width": 390,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10
          }
        },
        "frames": {},
        "text_sequences": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "visible_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ],
            "visible_match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ]
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10,
            "texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "visible_texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ],
            "visible_match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ]
          }
        },
        "text_matches": {
          "text:Preview Tools": true,
          "text:riddle_preview — Static Hosting": true,
          "text:Screenshotting Previews": true
        },
        "http_statuses": {},
        "link_statuses": {
          "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']": {
            "version": "riddle-proof.link-status.v1",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "max_links": 20,
            "same_origin_only": true,
            "dedupe": true,
            "require_nonzero_bytes": true,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "allowed_statuses": [
              "2xx",
              "3xx"
            ],
            "discovered_count": 10,
            "total_count": 3,
            "truncated": false,
            "ok_count": 3,
            "failed_count": 0,
            "status_counts": {
              "206": 3
            },
            "failures": [],
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "results": [
              {
                "url": "https://riddledc.com/docs/",
                "tag": "a",
                "text": "Docs",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/docs/",
                "error": null
              },
              {
                "url": "https://riddledc.com/playground/",
                "tag": "a",
                "text": "Playground",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/playground/",
                "error": null
              },
              {
                "url": "https://riddledc.com/register/",
                "tag": "a",
                "text": "Sign Up",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/register/",
                "error": null
              }
            ]
          }
        },
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v489-preview-tools-guide-url-shape-phone"
      },
      {
        "name": "ipad-mini",
        "width": 768,
        "height": 1024,
        "url": "https://riddledc.com/guides/preview-tools/",
        "route": {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        "title": "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "body_text_length": 11961,
        "body_text_sample": "Skip to main content Riddle ← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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.termi",
        "scroll_width": 768,
        "client_width": 768,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10
          }
        },
        "frames": {},
        "text_sequences": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "visible_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ],
            "visible_match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ]
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10,
            "texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "visible_texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ],
            "visible_match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ]
          }
        },
        "text_matches": {
          "text:Preview Tools": true,
          "text:riddle_preview — Static Hosting": true,
          "text:Screenshotting Previews": true
        },
        "http_statuses": {},
        "link_statuses": {
          "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']": {
            "version": "riddle-proof.link-status.v1",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "max_links": 20,
            "same_origin_only": true,
            "dedupe": true,
            "require_nonzero_bytes": true,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "allowed_statuses": [
              "2xx",
              "3xx"
            ],
            "discovered_count": 10,
            "total_count": 3,
            "truncated": false,
            "ok_count": 3,
            "failed_count": 0,
            "status_counts": {
              "206": 3
            },
            "failures": [],
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "results": [
              {
                "url": "https://riddledc.com/docs/",
                "tag": "a",
                "text": "Docs",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/docs/",
                "error": null
              },
              {
                "url": "https://riddledc.com/playground/",
                "tag": "a",
                "text": "Playground",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/playground/",
                "error": null
              },
              {
                "url": "https://riddledc.com/register/",
                "tag": "a",
                "text": "Sign Up",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/register/",
                "error": null
              }
            ]
          }
        },
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v489-preview-tools-guide-url-shape-ipad-mini"
      },
      {
        "name": "ipad",
        "width": 820,
        "height": 1180,
        "url": "https://riddledc.com/guides/preview-tools/",
        "route": {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        "title": "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "body_text_length": 11961,
        "body_text_sample": "Skip to main content Riddle ← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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.termi",
        "scroll_width": 820,
        "client_width": 820,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10
          }
        },
        "frames": {},
        "text_sequences": {
          ".preview-tools-page": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "visible_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Serve"
            ],
            "match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ],
            "visible_match_texts": [
              "← Back to Docs Preview Tools Deploy any app to an ephemeral URL, screenshot it, and compare changes — all via API. Which Tool Should I Use? What are you deploying? Static files HTML, CSS, JS — already built ↓ riddle_preview ~5 seconds Server app Node, Python, Ruby — stock runtime ↓ riddle_server_preview ~30–60 seconds Custom build Dockerfile, system deps, compiled langs ↓ riddle_build_preview ~1–3 minutes Feature riddle_preview server_preview build_preview Speed ~5s ~30–60s ~1–3min Server process No (static only) Yes Yes Docker image — Stock (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 Tar your build output directory Upload to Riddle Get a live URL in ~5 seconds Screenshot it with any Riddle tool Basic Example Copy 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 Parameter Type Description directory string Absolute path to build output (must contain index.html) framework string \"spa\" (default) — all routes serve index.html. \"static\" — file-based routing. 💡 When to use riddle_preview Your 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: Copy 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 Tar your project directory and upload it Pull a stock Docker image (e.g. node:20-slim) Start your server inside the container Wait for the readiness check to pass Take a Playwright screenshot 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 Copy 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 Copy { 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 Parameter Type Description directory string Path to your project image string Docker image to run (node:20-slim, python:3.12-slim, etc.) command string Command to start your server port number Port your server listens on path string URL path to screenshot (default: /) env object Non-sensitive environment variables sensitive_env object Secrets — stored securely, deleted after use localStorage object Key-value pairs injected before page load script string Playwright script to run after server is ready wait_for_selector string CSS selector to wait for before screenshotting color_scheme string \"dark\" or \"light\" — applied before navigation viewport object { width, height } — default 1920×1080 readiness_path string Health check endpoint to poll timeout number Max 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. How It Works Tar your project (must include a Dockerfile) Build the Docker image on the worker Start the container and wait for readiness Take a Playwright screenshot Cache the built image for fast re-runs Basic Example Copy 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. Copy { 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: Parameter Type Description build_args object Docker --build-arg key-value pairs keep_image_minutes number Cache built image on worker (default: 30, max: 120, 0 = delete immediately) audit boolean Run security audit on code + dependencies exclude string[] Glob patterns to exclude from tarball (default: [\".git\", \"*.log\"]) 💡 Image Caching Set 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 time The 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 s"
            ]
          },
          ".code-block-wrapper": {
            "count": 10,
            "visible_count": 10,
            "texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "visible_texts": [
              "Copy 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/outpu",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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/",
              "Copy { 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\" }, // S",
              "Copy 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/t",
              "Copy { 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:",
              "Copy { \"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, \"pe",
              "Copy // 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.stringif",
              "Copy // 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('onboa",
              "Copy // 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 = "
            ],
            "match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ],
            "visible_match_texts": [
              "Copy 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)",
              "Copy await fetch(\"https://api.riddledc.com/v1/preview/pv_a1b2c3d4\", { method: \"DELETE\", headers: { \"Authorization\": `Bearer ${RIDDLE_API_KEY}` } });",
              "Copy 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 } } }) });",
              "Copy { 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 } }",
              "Copy 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 } }) });",
              "Copy { 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: \"...\" }] // } // }",
              "Copy { \"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\": [] }",
              "Copy // 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\" }) });",
              "Copy // 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 }",
              "Copy // 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 // } // }"
            ]
          }
        },
        "text_matches": {
          "text:Preview Tools": true,
          "text:riddle_preview — Static Hosting": true,
          "text:Screenshotting Previews": true
        },
        "http_statuses": {},
        "link_statuses": {
          "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']": {
            "version": "riddle-proof.link-status.v1",
            "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
            "max_links": 20,
            "same_origin_only": true,
            "dedupe": true,
            "require_nonzero_bytes": true,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/html",
              "text/markdown"
            ],
            "allowed_statuses": [
              "2xx",
              "3xx"
            ],
            "discovered_count": 10,
            "total_count": 3,
            "truncated": false,
            "ok_count": 3,
            "failed_count": 0,
            "status_counts": {
              "206": 3
            },
            "failures": [],
            "result_count": 3,
            "stored_result_count": 3,
            "omitted_result_count": 0,
            "omitted_success_count": 0,
            "results_compacted": false,
            "results": [
              {
                "url": "https://riddledc.com/docs/",
                "tag": "a",
                "text": "Docs",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/docs/",
                "error": null
              },
              {
                "url": "https://riddledc.com/playground/",
                "tag": "a",
                "text": "Playground",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/playground/",
                "error": null
              },
              {
                "url": "https://riddledc.com/register/",
                "tag": "a",
                "text": "Sign Up",
                "status": 206,
                "method": "GET",
                "ok": true,
                "content_type": "text/html; charset=utf-8",
                "content_length": 1000,
                "bytes": 1000,
                "redirected": false,
                "final_url": "https://riddledc.com/register/",
                "error": null
              }
            ]
          }
        },
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v489-preview-tools-guide-url-shape-ipad"
      }
    ],
    "console": {
      "events": [],
      "fatal_count": 0
    },
    "page_errors": [],
    "dialogs": [],
    "network_mocks": [],
    "dom_summary": {
      "expected_viewport_count": 4,
      "viewport_count": 4,
      "partial": false,
      "routes": [
        {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/guides/preview-tools/",
          "observed": "/guides/preview-tools/",
          "expected_path": "/guides/preview-tools/",
          "matched": true,
          "http_status": 200
        }
      ],
      "titles": [
        "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "Preview Tools Guide - Deploy & Screenshot Any App | Riddle",
        "Preview Tools Guide - Deploy & Screenshot Any App | Riddle"
      ],
      "overflow_px": [
        0,
        0,
        0,
        0
      ],
      "bounds_overflow_px": [
        0,
        0,
        0,
        0
      ],
      "overflow_offender_counts": [
        0,
        0,
        0,
        0
      ],
      "frames": [
        {
          "viewport": "desktop",
          "selectors": []
        },
        {
          "viewport": "phone",
          "selectors": []
        },
        {
          "viewport": "ipad-mini",
          "selectors": []
        },
        {
          "viewport": "ipad",
          "selectors": []
        }
      ],
      "http_status": [],
      "link_status": [
        {
          "viewport": "desktop",
          "selectors": [
            {
              "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
              "total_count": 3,
              "ok_count": 3,
              "failed_count": 0,
              "truncated": false
            }
          ]
        },
        {
          "viewport": "phone",
          "selectors": [
            {
              "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
              "total_count": 3,
              "ok_count": 3,
              "failed_count": 0,
              "truncated": false
            }
          ]
        },
        {
          "viewport": "ipad-mini",
          "selectors": [
            {
              "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
              "total_count": 3,
              "ok_count": 3,
              "failed_count": 0,
              "truncated": false
            }
          ]
        },
        {
          "viewport": "ipad",
          "selectors": [
            {
              "selector": "a[href*='/docs/'], a[href*='/register/'], a[href*='/playground/']",
              "total_count": 3,
              "ok_count": 3,
              "failed_count": 0,
              "truncated": false
            }
          ]
        }
      ],
      "route_inventory": [],
      "network_mock_count": 0,
      "network_mock_hit_count": 0,
      "dialog_count": 0,
      "dialog_accept_count": 0,
      "dialog_dismiss_count": 0
    }
  }
}