{
  "version": "riddle-proof.profile-result.v1",
  "profile_name": "riddle-site-v421-agent-guide-proof-loop-contract",
  "runner": "riddle",
  "status": "product_regression",
  "baseline_policy": "invariant_only",
  "route": {
    "requested": "https://riddledc.com/ai-agents/guide/",
    "observed": "/ai-agents/guide/",
    "expected_path": "/ai-agents/guide/",
    "matched": true,
    "http_status": 200
  },
  "artifacts": {
    "screenshots": [
      "riddle-site-v421-agent-guide-proof-loop-contract-desktop",
      "riddle-site-v421-agent-guide-proof-loop-contract-phone",
      "riddle-site-v421-agent-guide-proof-loop-contract-ipad-mini",
      "riddle-site-v421-agent-guide-proof-loop-contract-ipad"
    ],
    "console": "console.json",
    "proof_json": "proof.json",
    "dom_summary": "dom-summary.json",
    "riddle_artifacts": [
      {
        "name": "proof.json",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/proof.json.json",
        "source": "artifacts"
      },
      {
        "name": "console.json",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/console.json.json",
        "source": "artifacts"
      },
      {
        "name": "dom-summary.json",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/dom-summary.json.json",
        "source": "artifacts"
      },
      {
        "name": "riddle-site-v421-agent-guide-proof-loop-contract-desktop.png",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/riddle-site-v421-agent-guide-proof-loop-contract-desktop.png",
        "source": "artifacts"
      },
      {
        "name": "riddle-site-v421-agent-guide-proof-loop-contract-phone.png",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/riddle-site-v421-agent-guide-proof-loop-contract-phone.png",
        "source": "artifacts"
      },
      {
        "name": "riddle-site-v421-agent-guide-proof-loop-contract-ipad-mini.png",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/riddle-site-v421-agent-guide-proof-loop-contract-ipad-mini.png",
        "source": "artifacts"
      },
      {
        "name": "riddle-site-v421-agent-guide-proof-loop-contract-ipad.png",
        "url": "https://cdn.riddledc.com/scripts/job_77aceb4b/riddle-site-v421-agent-guide-proof-loop-contract-ipad.png",
        "source": "artifacts"
      }
    ]
  },
  "checks": [
    {
      "type": "route_loaded",
      "label": "route_loaded",
      "status": "passed",
      "evidence": {
        "expected_path": "/ai-agents/guide/",
        "observed_paths": [
          "/ai-agents/guide/",
          "/ai-agents/guide/",
          "/ai-agents/guide/",
          "/ai-agents/guide/"
        ],
        "http_statuses": [
          200,
          200,
          200,
          200
        ]
      }
    },
    {
      "type": "selector_visible",
      "label": "selector_visible",
      "status": "passed",
      "evidence": {
        "selector": ".guide-content",
        "visible_counts": [
          1,
          1,
          1,
          1
        ]
      }
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "passed",
      "evidence": {
        "selector": ".guide-content",
        "text": "The Vision Loop",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          }
        ]
      }
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "passed",
      "evidence": {
        "selector": ".guide-content",
        "text": "Four Input Modes",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 1,
            "matched": true,
            "samples": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ]
          }
        ]
      }
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "failed",
      "evidence": {
        "selector": ".guide-content",
        "text": "Riddle Proof",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          }
        ]
      },
      "message": "Selector .guide-content text assertion failed in 4 viewport(s)."
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "failed",
      "evidence": {
        "selector": ".guide-content",
        "text": "proof receipts",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          }
        ]
      },
      "message": "Selector .guide-content text assertion failed in 4 viewport(s)."
    },
    {
      "type": "selector_text_visible",
      "label": "selector_text_visible",
      "status": "failed",
      "evidence": {
        "selector": ".guide-content",
        "text": "Bring your agent; Riddle brings the proof.",
        "pattern": null,
        "viewports": [
          {
            "viewport": "desktop",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "phone",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad-mini",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          },
          {
            "viewport": "ipad",
            "selector_count": 1,
            "visible_count": 1,
            "matched_count": 0,
            "matched": false,
            "samples": []
          }
        ]
      },
      "message": "Selector .guide-content text assertion failed in 4 viewport(s)."
    },
    {
      "type": "http_status",
      "label": "agent guide markdown includes proof-loop path",
      "status": "failed",
      "evidence": {
        "url": "https://riddledc.com/ai-agents/guide/markdown.md",
        "method": "GET",
        "allowed_statuses": [
          200
        ],
        "require_nonzero_bytes": false,
        "min_bytes": 1000,
        "allowed_content_types": [
          "text/markdown",
          "text/plain"
        ],
        "viewports": [
          {
            "viewport": "desktop",
            "key": "GET https://riddledc.com/ai-agents/guide/markdown.md",
            "url": "https://riddledc.com/ai-agents/guide/markdown.md",
            "method": "GET",
            "status": 200,
            "status_text": "OK",
            "ok": false,
            "error": null,
            "content_type": "text/markdown",
            "content_length": 7392,
            "bytes": 7392,
            "body_contains": {
              "## The Vision Loop": true,
              "## Four Input Modes": true,
              "Riddle Proof": false,
              "proof receipts": false,
              "Bring your agent; Riddle brings the proof.": false
            },
            "body_contains_missing": [
              "Riddle Proof",
              "proof receipts",
              "Bring your agent; Riddle brings the proof."
            ],
            "body_not_contains": null,
            "body_not_contains_found": [],
            "body_not_patterns": null,
            "body_not_patterns_found": [],
            "body_sample": "## The Vision Loop\n\nYour workflow for web tasks:\n\n1. Screenshot the page\n->\n2. Send to vision LLM\n->\n3. Decide what to do\n->\n4. Execute action\n->\n5. Screenshot to verify\n->\n6. Repeat\n\n## Four Input Modes\n\nThe unified `/v1/run` endpoint supports four input modes:\n\n### URL Mode - Simple screenshot\n\n```\nPOST /v1/run\n{\"url\": \"https://example.com\"}\n```\n\nReturns job_id, poll for completion. Good for single page captures.\n\n### URLs Mode - Batch screenshots\n\n```\nPOST /v1/run\n{\"urls\": [\"https://example1.com\", \"https://example2.com\"]}\n```\n\nUp to 10 URLs per batch. Cost-effective: one 30s minimum covers all.\n\n### Steps Mode - JSON workflow (recommended for agents)\n\n```\nPOST /v1/run\n{\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]}\n```\n\nStructured JSON, no code strings. Easy for LLMs to generate.\n\n### Script Mode - Full Playwright\n\n```\nPOST /v1/run\n{\"script\": \"await page.goto('https://example.com');\\\\nawait page.click('.button');\\\\nawait saveScreensh",
            "failures": [
              {
                "code": "http_status_failed",
                "url": "https://riddledc.com/ai-agents/guide/markdown.md",
                "status": 200,
                "method": "GET",
                "error": null,
                "content_type": "text/markdown",
                "bytes": 7392,
                "allowed_statuses": [
                  200
                ],
                "min_bytes": 1000,
                "allowed_content_types": [
                  "text/markdown",
                  "text/plain"
                ],
                "body_contains": [
                  "## The Vision Loop",
                  "## Four Input Modes",
                  "Riddle Proof",
                  "proof receipts",
                  "Bring your agent; Riddle brings the proof."
                ],
                "body_contains_missing": [
                  "Riddle Proof",
                  "proof receipts",
                  "Bring your agent; Riddle brings the proof."
                ],
                "body_not_contains": null,
                "body_not_contains_found": [],
                "body_not_patterns": null,
                "body_not_patterns_found": [],
                "body_sample": "## The Vision Loop\n\nYour workflow for web tasks:\n\n1. Screenshot the page\n->\n2. Send to vision LLM\n->\n3. Decide what to do\n->\n4. Execute action\n->\n5. Screenshot to verify\n->\n6. Repeat\n\n## Four Input Modes\n\nThe unified `/v1/run` endpoint supports four input modes:\n\n### URL Mode - Simple screenshot\n\n```\nPOST /v1/run\n{\"url\": \"https://example.com\"}\n```\n\nReturns job_id, poll for completion. Good for single page captures.\n\n### URLs Mode - Batch screenshots\n\n```\nPOST /v1/run\n{\"urls\": [\"https://example1.com\", \"https://example2.com\"]}\n```\n\nUp to 10 URLs per batch. Cost-effective: one 30s minimum covers all.\n\n### Steps Mode - JSON workflow (recommended for agents)\n\n```\nPOST /v1/run\n{\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]}\n```\n\nStructured JSON, no code strings. Easy for LLMs to generate.\n\n### Script Mode - Full Playwright\n\n```\nPOST /v1/run\n{\"script\": \"await page.goto('https://example.com');\\\\nawait page.click('.button');\\\\nawait saveScreensh"
              }
            ]
          }
        ],
        "failures": [
          {
            "viewport": "desktop",
            "failure": {
              "code": "http_status_failed",
              "url": "https://riddledc.com/ai-agents/guide/markdown.md",
              "status": 200,
              "method": "GET",
              "error": null,
              "content_type": "text/markdown",
              "bytes": 7392,
              "allowed_statuses": [
                200
              ],
              "min_bytes": 1000,
              "allowed_content_types": [
                "text/markdown",
                "text/plain"
              ],
              "body_contains": [
                "## The Vision Loop",
                "## Four Input Modes",
                "Riddle Proof",
                "proof receipts",
                "Bring your agent; Riddle brings the proof."
              ],
              "body_contains_missing": [
                "Riddle Proof",
                "proof receipts",
                "Bring your agent; Riddle brings the proof."
              ],
              "body_not_contains": null,
              "body_not_contains_found": [],
              "body_not_patterns": null,
              "body_not_patterns_found": [],
              "body_sample": "## The Vision Loop\n\nYour workflow for web tasks:\n\n1. Screenshot the page\n->\n2. Send to vision LLM\n->\n3. Decide what to do\n->\n4. Execute action\n->\n5. Screenshot to verify\n->\n6. Repeat\n\n## Four Input Modes\n\nThe unified `/v1/run` endpoint supports four input modes:\n\n### URL Mode - Simple screenshot\n\n```\nPOST /v1/run\n{\"url\": \"https://example.com\"}\n```\n\nReturns job_id, poll for completion. Good for single page captures.\n\n### URLs Mode - Batch screenshots\n\n```\nPOST /v1/run\n{\"urls\": [\"https://example1.com\", \"https://example2.com\"]}\n```\n\nUp to 10 URLs per batch. Cost-effective: one 30s minimum covers all.\n\n### Steps Mode - JSON workflow (recommended for agents)\n\n```\nPOST /v1/run\n{\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]}\n```\n\nStructured JSON, no code strings. Easy for LLMs to generate.\n\n### Script Mode - Full Playwright\n\n```\nPOST /v1/run\n{\"script\": \"await page.goto('https://example.com');\\\\nawait page.click('.button');\\\\nawait saveScreensh"
            }
          }
        ]
      },
      "message": "HTTP status failed in 1 viewport(s)."
    },
    {
      "type": "http_status",
      "label": "riddle proof markdown remains reachable",
      "status": "passed",
      "evidence": {
        "url": "https://riddledc.com/docs/riddle-proof/markdown.md",
        "method": "GET",
        "allowed_statuses": [
          200
        ],
        "require_nonzero_bytes": false,
        "min_bytes": 1000,
        "allowed_content_types": [
          "text/markdown",
          "text/plain"
        ],
        "viewports": [
          {
            "viewport": "desktop",
            "key": "GET https://riddledc.com/docs/riddle-proof/markdown.md",
            "url": "https://riddledc.com/docs/riddle-proof/markdown.md",
            "method": "GET",
            "status": 200,
            "status_text": "OK",
            "ok": true,
            "error": null,
            "content_type": "text/markdown",
            "content_length": 16996,
            "bytes": 16996,
            "body_contains": {
              "# Riddle Proof": true,
              "The contract is agent-agnostic": true,
              "Short version: bring your agent; Riddle brings the proof.": true
            },
            "body_contains_missing": [],
            "body_not_contains": null,
            "body_not_contains_found": [],
            "body_not_patterns": null,
            "body_not_patterns_found": [],
            "body_sample": "# Riddle Proof\n\nEvidence-backed workflows for agent-authored browser changes.\n\nPlain text for agents: https://riddledc.com/docs/riddle-proof/markdown.md\n\n## What It Is\n\nRiddle Proof is a workflow for turning an agent's code change into inspectable evidence. It runs the actual app in a browser, exercises the behavior that matters, captures artifacts, and records whether the change met the stated proof criteria.\n\nBuild output and unit tests are still important, but they do not prove that a user-facing browser flow works. Riddle Proof fills that gap with real previews, screenshots, JSON artifacts, console output, diagnostics, and ship gates.\n\nThe contract is agent-agnostic: use it from Codex, Claude Code, OpenClaw, CI, or any workflow that can run a browser proof script and preserve the resulting evidence.\n\nShort version: bring your agent; Riddle brings the proof.\n\n## When To Use It\n\nUse Riddle Proof when correctness depends on the browser, a running server, generated assets, timing, layo",
            "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": []
      }
    }
  ],
  "summary": "riddle-site-v421-agent-guide-proof-loop-contract failed 4 product invariant(s) across 4 viewport(s).",
  "captured_at": "2026-05-16T19:45:32.975Z",
  "evidence": {
    "version": "riddle-proof.profile-evidence.v1",
    "profile_name": "riddle-site-v421-agent-guide-proof-loop-contract",
    "target_url": "https://riddledc.com/ai-agents/guide/",
    "baseline_policy": "invariant_only",
    "captured_at": "2026-05-16T19:45:32.975Z",
    "viewports": [
      {
        "name": "desktop",
        "width": 1280,
        "height": 900,
        "url": "https://riddledc.com/ai-agents/guide/",
        "route": {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        "title": "Agent Guide - RiddleDC",
        "body_text_length": 7427,
        "body_text_sample": "Skip to main content Riddle Docs Proof MCP Pricing Blog Playground Sign Up Log In ← Back to AI Agents Agent Guide Copy as Markdown Technical reference for AI agents using the RiddleDC API. Copy the markdown to include in your agent's context. Show Raw Markdown The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely. Get Started Full API Docs Try Playground Riddle Distributed Computing Product Riddle Proof Good Catch Diary Pricing Maximize Value Documentation MCP Recipes Playground Use Cases AI Agents Agent Guide Serverless Authenticated Pages vs Self-Hosted Blog Evidence Over Summaries Frontend Dev Without a Browser E2E Testing Tips Chatty Agents (Part I) Batching Heuristics (Part II) Vision Agents Teaching AI to Ski Support FAQ Status Contact Legal Terms Privacy © 2026 Riddle. All rights reserved.",
        "scroll_width": 1280,
        "client_width": 1280,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1
          }
        },
        "frames": {},
        "text_sequences": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "visible_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ],
            "visible_match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ]
          }
        },
        "text_matches": {},
        "http_statuses": {
          "GET https://riddledc.com/ai-agents/guide/markdown.md": {
            "version": "riddle-proof.http-status.v1",
            "url": "https://riddledc.com/ai-agents/guide/markdown.md",
            "method": "GET",
            "status": 200,
            "ok": false,
            "error": null,
            "request_body_bytes": 0,
            "allowed_statuses": [
              200
            ],
            "require_nonzero_bytes": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/markdown",
              "text/plain"
            ],
            "redirected": false,
            "final_url": "https://riddledc.com/ai-agents/guide/markdown.md",
            "content_type": "text/markdown",
            "content_length": 7392,
            "status_text": "OK",
            "bytes": 7392,
            "body_sample": "## The Vision Loop\n\nYour workflow for web tasks:\n\n1. Screenshot the page\n->\n2. Send to vision LLM\n->\n3. Decide what to do\n->\n4. Execute action\n->\n5. Screenshot to verify\n->\n6. Repeat\n\n## Four Input Modes\n\nThe unified `/v1/run` endpoint supports four input modes:\n\n### URL Mode - Simple screenshot\n\n```\nPOST /v1/run\n{\"url\": \"https://example.com\"}\n```\n\nReturns job_id, poll for completion. Good for single page captures.\n\n### URLs Mode - Batch screenshots\n\n```\nPOST /v1/run\n{\"urls\": [\"https://example1.com\", \"https://example2.com\"]}\n```\n\nUp to 10 URLs per batch. Cost-effective: one 30s minimum covers all.\n\n### Steps Mode - JSON workflow (recommended for agents)\n\n```\nPOST /v1/run\n{\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]}\n```\n\nStructured JSON, no code strings. Easy for LLMs to generate.\n\n### Script Mode - Full Playwright\n\n```\nPOST /v1/run\n{\"script\": \"await page.goto('https://example.com');\\\\nawait page.click('.button');\\\\nawait saveScreensh",
            "body_contains": {
              "## The Vision Loop": true,
              "## Four Input Modes": true,
              "Riddle Proof": false,
              "proof receipts": false,
              "Bring your agent; Riddle brings the proof.": false
            }
          },
          "GET https://riddledc.com/docs/riddle-proof/markdown.md": {
            "version": "riddle-proof.http-status.v1",
            "url": "https://riddledc.com/docs/riddle-proof/markdown.md",
            "method": "GET",
            "status": 200,
            "ok": true,
            "error": null,
            "request_body_bytes": 0,
            "allowed_statuses": [
              200
            ],
            "require_nonzero_bytes": false,
            "min_bytes": 1000,
            "allowed_content_types": [
              "text/markdown",
              "text/plain"
            ],
            "redirected": false,
            "final_url": "https://riddledc.com/docs/riddle-proof/markdown.md",
            "content_type": "text/markdown",
            "content_length": 16996,
            "status_text": "OK",
            "bytes": 16996,
            "body_sample": "# Riddle Proof\n\nEvidence-backed workflows for agent-authored browser changes.\n\nPlain text for agents: https://riddledc.com/docs/riddle-proof/markdown.md\n\n## What It Is\n\nRiddle Proof is a workflow for turning an agent's code change into inspectable evidence. It runs the actual app in a browser, exercises the behavior that matters, captures artifacts, and records whether the change met the stated proof criteria.\n\nBuild output and unit tests are still important, but they do not prove that a user-facing browser flow works. Riddle Proof fills that gap with real previews, screenshots, JSON artifacts, console output, diagnostics, and ship gates.\n\nThe contract is agent-agnostic: use it from Codex, Claude Code, OpenClaw, CI, or any workflow that can run a browser proof script and preserve the resulting evidence.\n\nShort version: bring your agent; Riddle brings the proof.\n\n## When To Use It\n\nUse Riddle Proof when correctness depends on the browser, a running server, generated assets, timing, layo",
            "body_contains": {
              "# Riddle Proof": true,
              "The contract is agent-agnostic": true,
              "Short version: bring your agent; Riddle brings the proof.": true
            }
          }
        },
        "link_statuses": {},
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v421-agent-guide-proof-loop-contract-desktop"
      },
      {
        "name": "phone",
        "width": 390,
        "height": 844,
        "url": "https://riddledc.com/ai-agents/guide/",
        "route": {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        "title": "Agent Guide - RiddleDC",
        "body_text_length": 7373,
        "body_text_sample": "Skip to main content Riddle ← Back to AI Agents Agent Guide Copy as Markdown Technical reference for AI agents using the RiddleDC API. Copy the markdown to include in your agent's context. Show Raw Markdown The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely. Get Started Full API Docs Try Playground Riddle Distributed Computing Product Riddle Proof Good Catch Diary Pricing Maximize Value Documentation MCP Recipes Playground Use Cases AI Agents Agent Guide Serverless Authenticated Pages vs Self-Hosted Blog Evidence Over Summaries Frontend Dev Without a Browser E2E Testing Tips Chatty Agents (Part I) Batching Heuristics (Part II) Vision Agents Teaching AI to Ski Support FAQ Status Contact Legal Terms Privacy © 2026 Riddle. All rights reserved.",
        "scroll_width": 390,
        "client_width": 390,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1
          }
        },
        "frames": {},
        "text_sequences": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "visible_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ],
            "visible_match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ]
          }
        },
        "text_matches": {},
        "http_statuses": {},
        "link_statuses": {},
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v421-agent-guide-proof-loop-contract-phone"
      },
      {
        "name": "ipad-mini",
        "width": 768,
        "height": 1024,
        "url": "https://riddledc.com/ai-agents/guide/",
        "route": {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        "title": "Agent Guide - RiddleDC",
        "body_text_length": 7373,
        "body_text_sample": "Skip to main content Riddle ← Back to AI Agents Agent Guide Copy as Markdown Technical reference for AI agents using the RiddleDC API. Copy the markdown to include in your agent's context. Show Raw Markdown The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely. Get Started Full API Docs Try Playground Riddle Distributed Computing Product Riddle Proof Good Catch Diary Pricing Maximize Value Documentation MCP Recipes Playground Use Cases AI Agents Agent Guide Serverless Authenticated Pages vs Self-Hosted Blog Evidence Over Summaries Frontend Dev Without a Browser E2E Testing Tips Chatty Agents (Part I) Batching Heuristics (Part II) Vision Agents Teaching AI to Ski Support FAQ Status Contact Legal Terms Privacy © 2026 Riddle. All rights reserved.",
        "scroll_width": 768,
        "client_width": 768,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1
          }
        },
        "frames": {},
        "text_sequences": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "visible_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ],
            "visible_match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ]
          }
        },
        "text_matches": {},
        "http_statuses": {},
        "link_statuses": {},
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v421-agent-guide-proof-loop-contract-ipad-mini"
      },
      {
        "name": "ipad",
        "width": 820,
        "height": 1180,
        "url": "https://riddledc.com/ai-agents/guide/",
        "route": {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        "title": "Agent Guide - RiddleDC",
        "body_text_length": 7373,
        "body_text_sample": "Skip to main content Riddle ← Back to AI Agents Agent Guide Copy as Markdown Technical reference for AI agents using the RiddleDC API. Copy the markdown to include in your agent's context. Show Raw Markdown The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely. Get Started Full API Docs Try Playground Riddle Distributed Computing Product Riddle Proof Good Catch Diary Pricing Maximize Value Documentation MCP Recipes Playground Use Cases AI Agents Agent Guide Serverless Authenticated Pages vs Self-Hosted Blog Evidence Over Summaries Frontend Dev Without a Browser E2E Testing Tips Chatty Agents (Part I) Batching Heuristics (Part II) Vision Agents Teaching AI to Ski Support FAQ Status Contact Legal Terms Privacy © 2026 Riddle. All rights reserved.",
        "scroll_width": 820,
        "client_width": 820,
        "overflow_px": 0,
        "bounds_overflow_px": 0,
        "overflow_offenders": [],
        "selectors": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1
          }
        },
        "frames": {},
        "text_sequences": {
          ".guide-content": {
            "count": 1,
            "visible_count": 1,
            "texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "visible_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input m"
            ],
            "match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ],
            "visible_match_texts": [
              "The Vision Loop Your workflow for web tasks: 1. Screenshot the page → 2. Send to vision LLM → 3. Decide what to do → 4. Execute action → 5. Screenshot to verify → 6. Repeat Four Input Modes The unified /v1/run endpoint supports four input modes: URL Mode - Simple screenshot POST /v1/run {\"url\": \"https://example.com\"} Returns job_id, poll for completion. Good for single page captures. URLs Mode - Batch screenshots POST /v1/run {\"urls\": [\"https://example1.com\", \"https://example2.com\"]} Up to 10 URLs per batch. Cost-effective: one 30s minimum covers all. Steps Mode - JSON workflow (recommended for agents) POST /v1/run {\"steps\": [{\"goto\": \"https://example.com\"}, {\"click\": \".button\"}, {\"screenshot\": \"after-click\"}]} Structured JSON, no code strings. Easy for LLMs to generate. Script Mode - Full Playwright POST /v1/run {\"script\": \"await page.goto('https://example.com');\\nawait page.click('.button');\\nawait saveScreenshot('after-click');\"} Full Playwright access. Good for complex logic with loops and conditionals. Use steps mode for most agent tasks. Use script mode when you need loops or conditional logic. Viewport Strategy Size Use When 1920x1080 Default. Maximum information density. Dashboards, data tables. 800x600 Testing responsive layouts 375x667 Mobile experience. Triggers mobile CSS breakpoints. {\"url\": \"...\", \"options\": {\"viewport\": {\"width\": 375, \"height\": 667}}} Critical: fullPage defaults to true. Your screenshot height will expand to capture ALL content. Set fullPage: false if you want viewport-only. Zooming In on Details If you can't read small text or need to focus on a specific element, use element screenshots or clip regions: Steps Mode (recommended) // Element screenshot - capture just a specific element {\"screenshot\": {\"label\": \"form-detail\", \"selector\": \"form.checkout\"}} // Clipped region - capture a specific area {\"screenshot\": {\"label\": \"hero\", \"clip\": {\"x\": 0, \"y\": 0, \"width\": 1200, \"height\": 600}}} // Viewport only (not full scrollable page) {\"screenshot\": {\"label\": \"above-fold\", \"fullPage\": false}} Script Mode (for complex logic) await page.goto(\"https://example.com\"); const element = await page.$(\"form\"); // Option 1: Direct element screenshot (simplest) await element.screenshot({path: \"form.png\"}); // Option 2: Clip with padding for context const box = await element.boundingBox(); await page.screenshot({ path: \"form-with-context.png\", clip: { x: box.x - 20, y: box.y - 20, width: box.width + 40, height: box.height + 40 } }); Both modes give you focused views. Steps mode is recommended since your agent outputs JSON, not code strings. Debugging with console.json All jobs capture browser console output by default. Every console.log(), JavaScript error, and network failure is saved. Even simple screenshot jobs have logs. The job ID comes back in the X-Job-Id response header: Sync response includes X-Job-Id header curl -D - \"https://api.riddledc.com/v1/run\" \\ -H \"Authorization: Bearer YOUR_API_KEY\" \\ -d '{\"url\": \"https://example.com\"}' -o screenshot.png X-Job-Id: job_abc123 Check logs anytime: curl \"https://api.riddledc.com/v1/jobs/job_abc123/artifacts\" For script mode, add your own logging to trace execution: console.log(\"Step 1: Navigating...\"); await page.goto(url); console.log(\"Step 2: Looking for button...\"); const button = await page.$(\".submit-btn\"); console.log(\"Button found:\", button ? \"yes\" : \"no\"); if (button) { console.log(\"Step 3: Clicking...\"); await page.click(\".submit-btn\"); } await saveScreenshot(\"final-state\"); console.log(\"Done!\"); Debugging workflow: Screenshot looks wrong? Check console.json for JS errors. Page didn't load? Look for network failures. Selector not found? See what actually rendered. Authentication for Protected Pages Cookies (session-based auth) { \"url\": \"https://app.example.com/dashboard\", \"options\": { \"cookies\": [ {\"name\": \"session\", \"value\": \"abc123\", \"domain\": \"app.example.com\", \"path\": \"/\"} ] } } Headers (Bearer tokens, API keys) { \"url\": \"https://api.example.com/data\", \"options\": { \"headers\": { \"Authorization\": \"Bearer user-token-here\" } } } localStorage (SPAs with token storage) { \"url\": \"https://spa.example.com\", \"options\": { \"localStorage\": { \"authToken\": \"eyJhbG...\" } } } Tip: Cookie injection skips login flows entirely. This saves 10-30 seconds per job and reduces cost. Common Gotchas 1. fullPage is true by default // You request 375x667, you get 375x1800+ // Set fullPage: false if you want exact viewport {\"options\": {\"fullPage\": false, \"viewport\": {\"width\": 375, \"height\": 667}}} 2. Both saveScreenshot() and page.screenshot() work // Option 1: RiddleDC helper (recommended for clarity) await saveScreenshot(\"my\"); // Option 2: Standard Playwright - also works! Auto-saved to artifacts. await page.screenshot({path: \"my.png\"}); 3. Await your console.log values // Wrong - logs \"[object Promise]\" console.log(\"Title:\", page.title()); // Right - logs actual title console.log(\"Title:\", await page.title()); 4. Artifacts expire in 24 hours Download what you need promptly. CDN URLs stop working after 24 hours. Cost Optimization Approach Cost Single job from $0.004 (30s minimum) Job with 5 screenshots ~$0.0008 per screenshot Job with 10 screenshots ~$0.0004 per screenshot The 30-second minimum means one job costs the same whether you take 1 screenshot or 10. Pack more into each job when possible. PDF Generation Need a PDF instead of a screenshot? Use page.pdf(): await page.goto(\"https://example.com/report\"); await page.pdf({ path: \"report.pdf\", format: \"A4\", printBackground: true }); PDFs are automatically saved to artifacts alongside screenshots. Error Handling When jobs fail, check: console.json - Shows logs up to failure point + exception stack trace error-screenshot-1.png - Visual state when error occurred Job status - Contains error code and message { \"status\": \"failed\", \"error\": { \"code\": \"SCRIPT_ERROR\", \"message\": \"Timeout 30000ms exceeded waiting for selector '.nonexistent'\" } } Error codes: TIMEOUT, SCRIPT_ERROR, NAVIGATION_FAILED, UNAUTHORIZED, INVALID_URL, RATE_LIMIT_EXCEEDED Summary Use /v1/run for everything - url, urls, steps, or script mode Steps mode is ideal for agents - structured JSON, no code strings Use element screenshots to zoom in: {\"screenshot\": {\"label\": \"x\", \"selector\": \".element\"}} fullPage=true is default - set false if you need viewport control Always add console.log() in script mode - it's your debugging lifeline Cookie injection skips login flows and saves money Batch multiple URLs with urls mode to reduce cost below $0.001/screenshot Check console.json first when things fail Sync mode is default - PNG bytes returned directly (28s max) You now have eyes. Use them wisely."
            ]
          }
        },
        "text_matches": {},
        "http_statuses": {},
        "link_statuses": {},
        "setup_action_results": [],
        "screenshot_label": "riddle-site-v421-agent-guide-proof-loop-contract-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/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        },
        {
          "requested": "https://riddledc.com/ai-agents/guide/",
          "observed": "/ai-agents/guide/",
          "expected_path": "/ai-agents/guide/",
          "matched": true,
          "http_status": 200
        }
      ],
      "titles": [
        "Agent Guide - RiddleDC",
        "Agent Guide - RiddleDC",
        "Agent Guide - RiddleDC",
        "Agent Guide - RiddleDC"
      ],
      "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": [
        {
          "viewport": "desktop",
          "requests": [
            {
              "key": "GET https://riddledc.com/ai-agents/guide/markdown.md",
              "url": "https://riddledc.com/ai-agents/guide/markdown.md",
              "method": "GET",
              "status": 200,
              "ok": false,
              "error": null
            },
            {
              "key": "GET https://riddledc.com/docs/riddle-proof/markdown.md",
              "url": "https://riddledc.com/docs/riddle-proof/markdown.md",
              "method": "GET",
              "status": 200,
              "ok": true,
              "error": null
            }
          ]
        }
      ],
      "link_status": [],
      "route_inventory": [],
      "network_mock_count": 0,
      "network_mock_hit_count": 0,
      "dialog_count": 0,
      "dialog_accept_count": 0,
      "dialog_dismiss_count": 0
    }
  },
  "riddle": {
    "job_id": "job_77aceb4b",
    "status": "completed",
    "terminal": true
  }
}
