API Documentation

One endpoint: /v1/run. Send JSON, get screenshots. Sync by default.

Quick Start

Your First Screenshot

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}' \
  -o screenshot.png

Get your API key from your dashboard. PNG bytes returned directly.

1

Create Account

Sign up

2

Get API Key

Dashboard

3

Make Request

See examples below

Authentication

Two options: API keys (recommended) or JWT tokens.

API Keys (Recommended)

Create from your Dashboard or via API:

# Create an API key
curl -X POST "https://api.riddledc.com/billing/api-keys" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production Key"}'

# Response
{
  "apiKey": "rdc_live_abc123********************xyz789",
  "keyId": "key_0844fb72799dd7b3",
  "warning": "Save this key now - you will not be able to see it again!"
}

Use in the Authorization header:

Authorization: Bearer rdc_live_abc123********************xyz789

JWT Tokens (Alternative)

Authenticate via AWS Cognito for short-lived tokens:

curl -X POST "https://cognito-idp.us-east-1.amazonaws.com/" \
  -H "Content-Type: application/x-amz-json-1.1" \
  -H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
  -d '{
    "AuthFlow": "USER_PASSWORD_AUTH",
    "ClientId": "7u1bt3r20v613j7eaki9dkbhve",
    "AuthParameters": {
      "USERNAME": "your-email@example.com",
      "PASSWORD": "your-password"
    }
  }'

# Use the IdToken from the response
When to use which: API keys for production (never expire, can rotate). JWT for quick testing (expires in 1 hour).

Input Modes

Four ways to use /v1/run. Pick based on complexity.

ModeUse CaseExample
urlSingle screenshot{ "url": "https://example.com" }
urlsBatch screenshots{ "urls": ["https://a.com", "https://b.com"] }
stepsWorkflows (recommended for agents){ "steps": [{ "goto": "..." }, { "click": "..." }] }
scriptFull Playwright (loops, conditionals){ "script": "await page.goto(...)" }

URL Mode

Simplest option. Returns PNG directly.

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}' -o screenshot.png

Batch Mode

Multiple URLs in one job. More cost-efficient.

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com",
      "https://example.com/pricing",
      "https://example.com/docs"
    ]
  }'

Steps Mode (Recommended)

JSON-based workflows. Best for AI agents—no code generation needed.

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "steps": [
      { "goto": "https://example.com/login" },
      { "fill": { "selector": "#email", "value": "user@example.com" } },
      { "fill": { "selector": "#password", "value": "secret" } },
      { "click": "button[type=submit]" },
      { "waitForUrl": "**/dashboard**" },
      { "screenshot": "dashboard" }
    ]
  }'

Script Mode

Full Playwright when you need loops, try/catch, or complex logic.

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "script": "await page.goto('\''https://example.com'\''); const title = await page.title(); console.log('\''Title:'\''', title); await saveScreenshot('\''homepage'\''');"
  }'

Script Helpers

Scripts can wait on window hooks and emit structured JSON artifacts.

await waitForWindow("appReady", 30000);
const data = await page.evaluate(() => window.appState);
await saveJson("state", data);

To return a JSON value directly in the response, set options.returnResult: true in the request.

Common Options

These work with any input mode:

{
  "url": "https://example.com",
  "timeout_sec": 60,
  "options": {
    "viewport": { "width": 1920, "height": 1080 },
    "fullPage": true,
    "cookies": [{ "name": "session", "value": "abc", "domain": "example.com" }],
    "headers": { "Authorization": "Bearer APP_TOKEN" },
    "localStorage": { "token": "xyz" }
  }
}

Steps Reference

All available step types for steps mode.

StepDescriptionExample
gotoNavigate to URL{ "goto": "https://example.com" }
screenshotCapture screenshot{ "screenshot": "step1" }
clickClick element{ "click": "button.submit" }
fillFill input field{ "fill": { "selector": "#email", "value": "test@example.com" } }
typeType key-by-key{ "type": { "selector": "#search", "text": "query" } }
selectSelect dropdown{ "select": { "selector": "select#country", "value": "US" } }
checkCheck checkbox{ "check": "#agree" }
uncheckUncheck checkbox{ "uncheck": "#newsletter" }
hoverHover element{ "hover": ".dropdown" }
pressPress key{ "press": "Enter" }
waitForWait for selector{ "waitFor": ".loaded" }
waitForUrlWait for URL match{ "waitForUrl": "**/success**" }
waitForTimeoutWait milliseconds{ "waitForTimeout": 2000 }
waitForLoadStateWait for load state{ "waitForLoadState": "networkidle" }
assertAssert conditions{ "assert": [{ "selectorExists": ".success" }] }
saveHtmlSave page HTML{ "saveHtml": "page" }
logLog message{ "log": "Starting flow" }
evalRun Playwright code{ "eval": "await page.evaluate(() => localStorage.clear())" }

Assertions

Check page state without screenshotting. Abort early on failure.

{
  "steps": [
    { "goto": "https://example.com/checkout" },
    { "assert": [{ "selectorExists": ".checkout-form" }], "onFail": ["screenshot", "abort"] },
    { "fill": { "selector": "#card", "value": "4242424242424242" } },
    { "click": "button.pay" },
    { "assert": [{ "urlIncludes": "/confirmation" }], "onFail": ["screenshot", "abort"] },
    { "screenshot": "confirmation" }
  ]
}

// Assertion types: selectorExists, selectorMissing, urlIncludes, urlExcludes, textContains

Eval Context

The eval step runs in Node.js with Playwright's page object. Browser code is auto-wrapped:

// Both work - browser code auto-wrapped:
{ "eval": "document.title" }
{ "eval": "await page.evaluate(() => document.title)" }

// Playwright APIs work directly:
{ "eval": "await page.click('.submit')" }

Screenshot Options

Control what and how you capture.

Simple (Full Page)

{ "screenshot": "homepage" }

Element Screenshot

Capture just a specific element:

{ "screenshot": { "label": "navbar", "selector": "nav.main-nav" } }

Clipped Region

{ "screenshot": { "label": "hero", "clip": { "x": 0, "y": 0, "width": 1200, "height": 600 } } }

Viewport Only

{ "screenshot": { "label": "above-fold", "fullPage": false } }

Format Options

// JPEG with quality
{ "screenshot": { "label": "compressed", "type": "jpeg", "quality": 75 } }

// Retina scale
{ "screenshot": { "label": "retina", "scale": "device" } }

// Transparent background
{ "screenshot": { "label": "transparent", "omitBackground": true } }

All Options

OptionTypeDescription
labelstringFilename for screenshot
selectorstringCSS selector to capture
clipobject{ x, y, width, height }
fullPagebooleanFull page (true) or viewport (false)
typestring"png" or "jpeg"
qualitynumberJPEG quality 0-100
scalestring"css" or "device"
omitBackgroundbooleanTransparent background

Response & Debugging

Understanding what comes back and how to debug issues.

Sync Mode (Default)

Waits up to 28 seconds for completion, returns PNG directly. For longer jobs, use sync: false to get a job_id and poll.

Default Response (PNG)

HTTP/1.1 200 OK
Content-Type: image/png
X-Job-Id: job_abc123
X-Duration-Ms: 4200

[PNG bytes]

Enhanced Response with include

Get screenshot + console logs + network HAR + script results in one call:

curl -X POST "https://api.riddledc.com/v1/run" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "url": "https://example.com",
    "include": ["console", "har", "result"]
  }'

Shortcut: set inlineConsole, inlineHar, or inlineResult to true for sync responses.

Response:

{
  "status": "completed",
  "job_id": "job_abc123",
  "duration_ms": 4200,
  "screenshot": {
    "name": "result.png",
    "data": "data:image/png;base64,iVBORw0K...",
    "size": 45678
  },
  "console": {
    "summary": { "total_entries": 3, "error_count": 1 },
    "entries": { "log": [...], "error": [...] }
  },
  "result": { "score": 42 },
  "har": {
    "log": { "entries": [...] }
  }
}
Best for AI agents: Use include: ["console", "har"] to debug failures without extra API calls.

Async Mode

For jobs >28 seconds, use sync: false:

// 1. Submit
const { job_id } = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: { "Authorization": "Bearer KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com", sync: false })
}).then(r => r.json());

// 2. Poll until status is "completed"
const status = await fetch(`https://api.riddledc.com/v1/jobs/${job_id}`, {
  headers: { "Authorization": "Bearer KEY" }
}).then(r => r.json());

// 3. Get artifacts
const artifacts = await fetch(`https://api.riddledc.com/v1/jobs/${job_id}/artifacts`, {
  headers: { "Authorization": "Bearer KEY" }
}).then(r => r.json());

Artifacts

Every job produces artifacts stored for 24 hours:

ArtifactDescription
screenshot.pngYour captured screenshot(s)
console.jsonAll console.log, warn, error output
network.harFull network trace (HAR format)
result.jsonStructured data returned by scripts (when enabled)
proof.jsonExecution proof with timing/metrics

Debugging Workflow

  1. Screenshot looks wrong? Check console.json for JavaScript errors
  2. Page didn't load? Check network.har for failed requests
  3. Selector not found? Use saveHtml step to see actual DOM

Webhooks

Get notified when jobs complete instead of polling.

{
  "url": "https://example.com",
  "webhook_url": "https://your-server.com/riddle-webhook"
}

Payload

{
  "event": "job.completed",
  "job_id": "job_abc123",
  "status": "completed",
  "duration_ms": 5693,
  "artifacts": [
    { "name": "screenshot.png", "url": "https://cdn.riddledc.com/..." }
  ],
  "billing": { "actual_seconds": 30, "actual_cost_dollars": 0.004167 }
}

Requirements

  • HTTPS recommended (HTTP allowed for dev)
  • Must return 2xx within 10 seconds
  • 2 automatic retries on failure

Recipes

Copy-paste examples for common scenarios.

Feed Vision LLM

import requests, base64, openai

# Get screenshot
response = requests.post(
    "https://api.riddledc.com/v1/run",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"url": "https://example.com"}
)
screenshot_b64 = base64.b64encode(response.content).decode()

# Send to GPT-4V
openai.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What's on this page?"},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{screenshot_b64}"}}
        ]
    }]
)

Authenticated Screenshot

// With session cookie
{
  "url": "https://app.example.com/dashboard",
  "options": {
    "cookies": [{ "name": "session_id", "value": "abc123", "domain": "app.example.com" }]
  }
}

// With Bearer token
{
  "url": "https://api.example.com/page",
  "options": {
    "headers": { "Authorization": "Bearer APP_TOKEN" }
  }
}

Mobile Screenshot

{
  "url": "https://example.com",
  "options": { "viewport": { "width": 375, "height": 667 } }
}

Common: iPhone (375x667), Android (360x640), iPad (768x1024)

Batch for Cost Savings

// 4 screenshots, 1 job = ~$0.001 each instead of ~$0.004
{
  "steps": [
    { "goto": "https://example.com" }, { "screenshot": "home" },
    { "goto": "https://example.com/pricing" }, { "screenshot": "pricing" },
    { "goto": "https://example.com/docs" }, { "screenshot": "docs" },
    { "goto": "https://example.com/about" }, { "screenshot": "about" }
  ]
}

Wait for SPA

{
  "steps": [
    { "goto": "https://spa-app.com" },
    { "waitForLoadState": "networkidle" },
    { "waitFor": "text=Dashboard" },
    { "screenshot": "loaded" }
  ]
}

Multi-Step Form

{
  "steps": [
    { "goto": "https://signup.example.com" },
    { "fill": { "selector": "input[name=email]", "value": "test@example.com" } },
    { "click": "text=Continue" },
    { "screenshot": "step1" },
    { "waitFor": "input[name=name]" },
    { "fill": { "selector": "input[name=name]", "value": "Test User" } },
    { "click": "text=Next" },
    { "screenshot": "step2" }
  ]
}

Lambda Handler

const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");

exports.handler = async (event) => {
  const response = await fetch("https://api.riddledc.com/v1/run", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.RIDDLE_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ url: event.url })
  });

  const imageBuffer = Buffer.from(await response.arrayBuffer());

  await new S3Client({}).send(new PutObjectCommand({
    Bucket: process.env.BUCKET,
    Key: `screenshots/${Date.now()}.png`,
    Body: imageBuffer
  }));

  return { statusCode: 200 };
};

API Reference

POST/v1/run

Take screenshots, run workflows. Accepts url, urls, steps, or script.

GET/v1/jobs

List recent jobs. Query: ?limit=50

GET/v1/jobs/{job_id}

Get job status. Returns queued, running, completed, or failed.

GET/v1/jobs/{job_id}/artifacts

Get artifacts (screenshots, console.json, network.har). Available for 24 hours.

GET/v1/balance

Check account balance in seconds and dollars.

POST/billing/api-keys

Create API key. Requires JWT auth.

GET/billing/api-keys

List your API keys.

DELETE/billing/api-keys/{keyId}

Revoke an API key.

Pricing

$0.50/hour browser time, billed per second. 30s minimum per job (~$0.004).

Full pricing details →

Error Codes

Check the error.code field in failed responses.

CodeMeaningFix
UNAUTHORIZEDInvalid/missing API keyCheck Authorization header
TIMEOUTJob exceeded timeoutIncrease timeout_sec or optimize
NAVIGATION_FAILEDPage failed to loadCheck URL, network errors
SCRIPT_ERRORScript threw exceptionCheck console.json
INVALID_URLURL blocked/malformedNo localhost, internal IPs
INSUFFICIENT_BALANCENot enough creditsAdd funds
RATE_LIMIT_EXCEEDEDToo many requestsExponential backoff
INTERNAL_ERRORSystem errorRetry, contact support if persists