# Publishing task actor test (`lukasdurec/ui-styling-validator`) Actor

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 1234567812333

- **URL**: https://securitybyobscurity.apify.com/lukasdurec/ui-styling-validator.md
- **Developed by:** [Lukas Durec](https://securitybyobscurity.apify.com/lukasdurec) (community)
- **Categories:** Agents, News, SEO tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Screenshot style QA

An Apify Actor that takes screenshots of UI, analyzes all visible text against the Apify style guide using Claude Vision, and writes results to a Notion database.

Built for the Apify marketing team to systematically audit Console copy for style guide compliance.

---

### What it does

1. Accepts one or more publicly accessible screenshot URLs as input
2. Sends each screenshot to Claude (claude-opus-4-6) with the full Apify style guide rules
3. Claude identifies every visible text element and checks it for violations
4. Results (score, issues, good points, description) are written as a new page in a Notion database
5. Results are also pushed to the Apify dataset for easy review in the Console

---

### Style rules checked

The Actor checks against the full Apify style guide, including:

- **"Actor" capitalization** - always uppercase; "task" and "schedule" are always lowercase
- **Product names** - "Apify Console" (not "the Apify Console"), "Apify Store" (not "the Apify Store"), "the Apify platform" (with "the", lowercase "p")
- **Currency format** - "$49" or "USD 49", never "49 USD" or "49$"
- **Oxford comma** - "A, B, and C" not "A, B and C"
- **Sentence case** - headings and titles in sentence case, not Title Case
- **Ampersand** - use "and" in body text, "&" only in UI labels or brand names
- **Em dash** - use " - " (hyphen with spaces), not "—"
- **US spelling** - "analyze" not "analyse", "color" not "colour"
- **UI element formatting** - UI elements in **bold**, not italics
- **Tone** - direct and active voice, no blame language, no robotic phrases like "Please note that" or "in order to"
- **Gender-neutral language** - use "they/theirs"
- And many more from the full Apify style guide

---

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `screenshots` | array | Yes | List of `{ url, name? }` objects. URLs must be publicly accessible. |
| `notionApiKey` | string | Yes | Notion integration token (create at notion.so/my-integrations). |
| `notionDatabaseId` | string | No | ID of an existing Notion database to append results to. |
| `notionParentPageId` | string | No* | ID of a Notion page to create a new results database under. Required if `notionDatabaseId` is not provided. |
| `anthropicApiKey` | string | No | Anthropic API key. Falls back to `ANTHROPIC_API_KEY` environment variable. |
| `additionalRules` | string | No | Extra rules to check beyond the built-in style guide, one per line. |
| `scoreThreshold` | integer | No | Minimum score for "Needs review" status. Default: 70. Below threshold = Fail, 70-79 = Needs review, 80+ = Pass. |

\* Either `notionDatabaseId` or `notionParentPageId` must be provided.

#### Example input

```json
{
  "screenshots": [
    {
      "url": "https://storage.googleapis.com/my-bucket/actor-detail.png",
      "name": "Actor detail page"
    },
    {
      "url": "https://storage.googleapis.com/my-bucket/billing-modal.png",
      "name": "Billing upgrade modal"
    }
  ],
  "notionApiKey": "secret_abc123",
  "notionParentPageId": "1234567890abcdef1234567890abcdef",
  "scoreThreshold": 70
}
````

***

### Output

#### Notion database

Each screenshot gets a new page in the database with:

- **Name** - screenshot name or filename
- **Score** - 0-100 compliance score
- **Status** - Pass / Needs review / Fail
- **Screenshot URL** - link to the original image
- **Issues count** - number of style violations found
- **Analyzed at** - timestamp

Each page also contains blocks with:

- The screenshot embedded
- Context (what UI screen this is)
- Description (what the user sees)
- Summary (overall assessment)
- Issues list - each with: violated rule category, exact problematic text, what rule it breaks, and suggested fix
- Good points list (what's done correctly)

#### Apify dataset

Each item in the dataset contains:

```json
{
  "screenshotUrl": "https://...",
  "name": "Actor detail page",
  "context": "Actor detail page in Apify Console",
  "description": "Shows the Actor overview tab with title, description, and run button",
  "score": 82,
  "status": "Pass",
  "issuesCount": 2,
  "issues": [
    {
      "type": "capitalization",
      "text": "the Apify Console",
      "problem": "Should be 'Apify Console' without 'the'",
      "suggestion": "Apify Console"
    }
  ],
  "goodPoints": [
    "Correct use of Oxford comma in the feature list"
  ],
  "summary": "Good overall compliance with minor capitalization issues.",
  "analyzedAt": "2026-04-01T10:00:00.000Z"
}
```

***

### Setup

#### 1. Create a Notion integration

1. Go to [notion.so/my-integrations](https://www.notion.so/my-integrations)
2. Create a new integration with read/write access to pages and databases
3. Copy the integration token (starts with `secret_`)
4. Open the Notion page where you want results, click **Share**, and add your integration

#### 2. Get the Anthropic API key

Get your key from [console.anthropic.com](https://console.anthropic.com). The Actor uses `claude-opus-4-6`.

Set it as the `ANTHROPIC_API_KEY` environment variable in the Actor's environment settings on the Apify platform, or pass it as the `anthropicApiKey` input field.

#### 3. Prepare screenshots

Screenshots must be at publicly accessible URLs. Options:

- Upload to Google Cloud Storage, AWS S3, or similar with public read access
- Use Apify's key-value store and make the records public
- Use any CDN or image hosting service

#### 4. Deploy to Apify

```bash
apify login
apify push
```

***

### Running locally

```bash
npm install
npm run build

## Put your input in storage/key_value_stores/default/INPUT.json
## Set your Anthropic API key
export ANTHROPIC_API_KEY=sk-ant-...

apify run
```

***

### Notes

- The Actor uses `claude-opus-4-6` for best vision accuracy on UI screenshots
- Each screenshot is one API call to Claude - costs depend on image resolution and response length
- Screenshots are processed sequentially to respect Notion's API rate limits
- Screenshots with no visible text receive a null score with an explanation in the context field

# Actor input Schema

## `screenshots` (type: `array`):

Upload the screenshot images you want to analyze. You can upload files directly or provide URLs to publicly accessible images.

## `notionApiKey` (type: `string`):

Your Notion integration token. Create one at https://www.notion.so/my-integrations and share your target page/database with it.

## `notionDatabaseId` (type: `string`):

ID of an existing Notion database to append results to. If not provided, a new database will be created under notionParentPageId.

## `notionParentPageId` (type: `string`):

ID of the Notion page under which a new results database will be created. Required if notionDatabaseId is not provided.

## `anthropicApiKey` (type: `string`):

Your Anthropic API key for Claude Vision. If not provided, the ANTHROPIC\_API\_KEY environment variable is used.

## `additionalRules` (type: `string`):

Any extra rules to check beyond the built-in Apify style guide. Write each rule on a new line.

## `scoreThreshold` (type: `integer`):

Screenshots with a score below this value are marked 'Fail'. Screenshots at or above this value but below 80 are 'Needs review'. Scores 80 and above are 'Pass'.

## Actor input object example

```json
{
  "scoreThreshold": 70
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `output` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("lukasdurec/ui-styling-validator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("lukasdurec/ui-styling-validator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call lukasdurec/ui-styling-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=lukasdurec/ui-styling-validator",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Publishing task actor test",
        "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 1234567812333",
        "version": "0.0",
        "x-build-id": "Le9mhP98bZWfl7A0m"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/lukasdurec~ui-styling-validator/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-lukasdurec-ui-styling-validator",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/lukasdurec~ui-styling-validator/runs": {
            "post": {
                "operationId": "runs-sync-lukasdurec-ui-styling-validator",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/lukasdurec~ui-styling-validator/run-sync": {
            "post": {
                "operationId": "run-sync-lukasdurec-ui-styling-validator",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "screenshots",
                    "notionApiKey"
                ],
                "properties": {
                    "screenshots": {
                        "title": "Screenshots",
                        "type": "array",
                        "description": "Upload the screenshot images you want to analyze. You can upload files directly or provide URLs to publicly accessible images."
                    },
                    "notionApiKey": {
                        "title": "Notion API key",
                        "type": "string",
                        "description": "Your Notion integration token. Create one at https://www.notion.so/my-integrations and share your target page/database with it."
                    },
                    "notionDatabaseId": {
                        "title": "Notion database ID (optional)",
                        "type": "string",
                        "description": "ID of an existing Notion database to append results to. If not provided, a new database will be created under notionParentPageId."
                    },
                    "notionParentPageId": {
                        "title": "Notion parent page ID",
                        "type": "string",
                        "description": "ID of the Notion page under which a new results database will be created. Required if notionDatabaseId is not provided."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API key (optional)",
                        "type": "string",
                        "description": "Your Anthropic API key for Claude Vision. If not provided, the ANTHROPIC_API_KEY environment variable is used."
                    },
                    "additionalRules": {
                        "title": "Additional style rules (optional)",
                        "type": "string",
                        "description": "Any extra rules to check beyond the built-in Apify style guide. Write each rule on a new line."
                    },
                    "scoreThreshold": {
                        "title": "Score threshold for 'Needs review'",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Screenshots with a score below this value are marked 'Fail'. Screenshots at or above this value but below 80 are 'Needs review'. Scores 80 and above are 'Pass'.",
                        "default": 70
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
