# Brand Handle Monitor (`ironstark_daysky/brand-handle-monitor`) Actor

Monitor usernames and brand handles across public platforms and detect when handles become claimed, released, or change status between runs.

- **URL**: https://apify.com/ironstark\_daysky/brand-handle-monitor.md
- **Developed by:** [Caio Fábio M Silva](https://apify.com/ironstark_daysky) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 platform checks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

Check **username availability** across public platforms and **monitor brand
handles** over time — detect when a handle is newly **claimed**, **released**,
or changes status between runs.

### What does Brand Handle Monitor do?

Brand Handle Monitor is a **username checker** that does not stop at one
lookup. It tells you whether a handle is **taken or free** on public platforms
such as [GitHub](https://github.com), [X](https://x.com),
[YouTube](https://youtube.com), [Telegram](https://telegram.org),
[Medium](https://medium.com) and 400+ more — and then **remembers the answer
and tells you what changed since the last run**.

That second half is what turns a username lookup into **social media username
monitoring**. Run it once and you get an availability snapshot. Run it on a
schedule with a `monitorId` and you get a monitor: the Actor stores a baseline,
compares every new run against it, and flags each handle as `new_claim`,
`released`, `status_changed`, `unchanged` or `unknown`.

Because it runs on the Apify platform you get scheduling, a **username
monitoring API**, webhooks, MCP/agent access, integrations and run history for
free — just hit **Start** to try it.

### Why use this brand handle monitor?

- **Catch handle squatting early.** Brand monitoring that tells you the day
  someone registers your brand name on a platform you do not yet use.
- **Grab a handle the moment it frees up.** A `released` event means the name
  you wanted is available right now.
- **Track a whole watchlist.** Monitor your brand, your common misspellings and
  your competitors' handles in one scheduled run.
- **Audit your own footprint.** Confirm every official account still resolves.
- **Reserve names before a launch.** Check availability across the default
  platform set in seconds before you commit to a product name.

### How to check username availability across platforms

1. Open the Actor and put your handles into **Usernames** — `acme`, `@acme` and
   `https://github.com/acme` all work.
2. Leave **Platforms** empty to use the curated default set, or list your own.
3. Click **Start**. The first run finishes in a few seconds and gives you a
   snapshot.
4. To turn it into a monitor: set **Mode** to `monitor`, give it a **Monitor
   ID** such as `acme-brand`, and save it as a scheduled task (daily is a good
   start). The first monitor run records the baseline; every later run reports
   what changed.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `usernames` | array of strings | `["apify"]` | Handles to check. Accepts bare handles, `@handles` and profile URLs. De-duplicated case-insensitively, max 50 per run. |
| `mode` | `scan` | `monitor` | `scan` | `scan` is a stateless snapshot; `monitor` compares against the stored baseline. |
| `monitorId` | string | — | Identifies the baseline. **Required in `monitor` mode.** |
| `platforms` | array of strings | `[]` | Platform names. Empty means the curated default set; any of the 400+ catalogue entries can be named explicitly. |
| `includeAvailable` | boolean | `true` | When `false`, available handles are left out of the dataset (the baseline still records them). |
| `timeoutSeconds` | integer 5–60 | `15` | Per-request timeout. |
| `maxConcurrency` | integer 1–30 | `10` | Parallel requests. `20` roughly halves run time. |

#### Scan mode

```json
{
    "usernames": ["acme", "acme-official", "@acmehq"],
    "mode": "scan"
}
```

Nothing is read from or written to the baseline. Use it for one-off
availability research before a launch.

#### Monitor mode

```json
{
    "usernames": ["acme", "acme-official", "@acmehq"],
    "mode": "monitor",
    "monitorId": "acme-brand",
    "platforms": ["X", "GitHub", "YouTube", "Telegram", "Medium"]
}
```

The first run with a given `monitorId` creates the baseline and reports every
handle as `unchanged` with a `null` `previousStatus`. Every later run compares
against it and fills in `previousStatus`, `changed` and `changeType`.

### Output

One dataset item per handle/platform pair:

```json
{
    "username": "apify",
    "platform": "GitHub",
    "profileUrl": "https://www.github.com/apify",
    "currentStatus": "CLAIMED",
    "previousStatus": "AVAILABLE",
    "changed": true,
    "changeType": "new_claim",
    "checkedAt": "2026-09-06T14:47:19Z",
    "firstDetectedAt": "2026-09-06T14:47:10Z",
    "lastChangedAt": "2026-09-06T14:47:19Z",
    "httpStatus": 200,
    "error": null
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

A run summary is written to the key-value store under the `SUMMARY` key:

```json
{
    "usernamesChecked": 3,
    "platformsChecked": 16,
    "claimed": 27,
    "available": 21,
    "unknown": 0,
    "changesDetected": 1,
    "newClaims": 1,
    "releasedHandles": 0,
    "checksPerformed": 48,
    "billableChecks": 48,
    "recordsPushed": 48,
    "skippedIncompatible": 0,
    "executionTimeSeconds": 6.2,
    "mode": "monitor",
    "monitorId": "acme-brand",
    "baselineUpdated": true,
    "baselineCreated": false
}
```

`checksPerformed` is the number of username/platform pairs actually checked,
and it is exactly what the run bills for. Pairs a platform's own username rules
reject — X caps handles at 15 characters, for example — are counted in
`skippedIncompatible` and are never charged.

#### Data fields

| Field | Description |
|---|---|
| `username` | The normalized handle that was checked. |
| `platform` | Platform name, e.g. `GitHub`. |
| `profileUrl` | Public profile URL for that handle. |
| `currentStatus` | `CLAIMED`, `AVAILABLE` or `UNKNOWN`. |
| `previousStatus` | The status recorded by the previous run, or `null` on a baseline run. |
| `changed` | Whether this is a real transition between two confirmed statuses. |
| `changeType` | `new_claim`, `released`, `status_changed`, `unchanged` or `unknown`. |
| `checkedAt` | UTC ISO 8601 timestamp of this check. |
| `firstDetectedAt` | When the handle was first seen as claimed by this monitor. |
| `lastChangedAt` | When the status last changed. |
| `httpStatus` | HTTP status behind the verdict, for debugging. |
| `error` | Why a result is `UNKNOWN`, when applicable. |

### Status and change model

Three statuses, on purpose:

| Status | Meaning |
|---|---|
| `CLAIMED` | The profile exists. |
| `AVAILABLE` | The platform confirmed there is no such profile. |
| `UNKNOWN` | The check was inconclusive — timeout, rate limit, anti-bot challenge, DNS or parser error. |

**An error is never reported as `AVAILABLE`.** A blocked or throttled response
produces `UNKNOWN`, which is explicitly *not* a change and never overwrites a
good stored observation. This is what stops a Cloudflare hiccup from paging you
about a handle that was never released.

| `changeType` | Transition |
|---|---|
| `new_claim` | `AVAILABLE` → `CLAIMED`: someone took the handle. |
| `released` | `CLAIMED` → `AVAILABLE`: the handle is free again. |
| `unchanged` | Same confirmed status as last run, or a first observation. |
| `unknown` | This run could not determine the status. |
| `status_changed` | Reserved for future statuses; not produced today. |

### Persistence behaviour

- Baselines live in a **named** key-value store, `brand-handle-monitor`, so they
  survive between runs. The per-run default store is not used for state.
- One record per `monitorId`, keyed as `baseline--<slug>--<hash>`. Two different
  monitor IDs can never collide, even if they slugify to the same string.
- Records carry a `schemaVersion`. A record from an incompatible version, or
  from a different `monitorId`, is refused rather than misread.
- Updates **merge**. Handles and platforms that were not part of a run keep
  their stored state.
- A run in which more than half the checks were inconclusive is **not allowed to
  write** the baseline, so a bad network day cannot destroy good history.
- `scan` mode never reads or writes the baseline.

### Scheduling

Save the Actor as a task with `mode: "monitor"` and a stable `monitorId`, then
attach a schedule (Apify Console → **Schedules**). Daily is a sensible default;
hourly is fine too — the run is cheap.

Attach a webhook on `ACTOR.RUN.SUCCEEDED` to push changes into Slack, email or
your own service. Filter the dataset on `changed = true` to get only the events.

### API and MCP usage

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR-USERNAME~brand-handle-monitor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "usernames": ["acme"],
        "mode": "monitor",
        "monitorId": "acme-brand"
      }'
```

The Actor is batch/run-based: every run reads its input, scans, writes the
dataset and the summary, and exits. The same shape backs the Scheduler and
agentic/MCP execution — there is no persistent HTTP server to keep warm.

### Pricing: how much does it cost to monitor a username?

You pay per **platform check** — one username checked on one platform. That is
the work the Actor does, so the price scales with what you ask for instead of
with a flat fee.

| What you run | Checks | You pay |
|---|---|---|
| 1 handle, default platform set | 16 | $0.008 |
| 5 handles, default platform set | 80 | $0.040 |
| 10 handles, default platform set | 160 | $0.080 |
| 20 handles, daily monitor | 320/day | ~$4.80/month |
| 1 handle, 5 platforms you pick | 5 | $0.0025 |

Narrowing `platforms` is the direct lever on cost: five platforms cost a third
of the default sixteen. Handles a platform's own rules reject are skipped and
never charged, and an internal retry never charges twice.

Every run also carries Apify's standard $0.00005 Actor start charge, which is
rounding error next to the checks themselves.

Platform compute is included in the event price — there is no separate usage
bill to reason about, and no surprise line item after the run.

### Tips

- **Batch your handles.** The fixed per-run overhead dominates a
  single-username run; ten handles cost barely more than one.
- **Raise `maxConcurrency` to 20** to roughly halve run time.
- **Narrow `platforms`** to the ones you actually care about — fewer checks,
  faster runs, fewer inconclusive results.
- **Set `includeAvailable: false`** on large watchlists to keep the dataset
  focused on handles that exist.
- **Use one `monitorId` per watchlist**, and keep it stable. Changing it starts
  a new baseline from scratch.

### Accuracy limitations

- Results are point-in-time signals from public pages. Platforms change their
  markup, status codes and anti-bot rules without notice.
- **The default set contains only platforms verified to answer reliably from
  Apify's cloud environment.** Every other platform in the catalogue stays
  available — just name it in `platforms`. Some of those answer differently to
  datacenter IPs than to a home connection and may return `UNKNOWN` for that
  reason rather than because anything is wrong with the handle.
- A handle can be *reserved*, *suspended* or *deleted but not released*. The
  Actor reports what the public page says, which may not be the same thing.
- Which platforms were measured, and why each one is or is not in the default
  set, is documented in [`docs/BENCHMARK.md`](docs/BENCHMARK.md).
- A handful of catalogue platforms are detected by whether the profile URL
  redirects. On those, a site that redirects claimed profiles to a canonical
  address can read as available. None of them are in the default set.
- Two runs of the *same* `monitorId` overlapping — a schedule firing while you
  start a manual run — both read and write the same baseline, and the last one
  to finish wins. Give concurrent watchlists separate monitor IDs.

### Troubleshooting

| Symptom | What it means | What to do |
|---|---|---|
| Everything is `UNKNOWN` | The Actor was blocked or the network failed | Lower `maxConcurrency`, raise `timeoutSeconds`, or narrow `platforms`. The baseline was left untouched. |
| `previousStatus` is always `null` | You are in `scan` mode, or this is the first run for this `monitorId` | Set `mode: "monitor"` and reuse the same `monitorId`. |
| Run fails with "monitorId is required" | `monitor` mode without an ID | Provide a stable `monitorId`. |
| A handle you own shows `AVAILABLE` | The platform hides that profile from anonymous visitors | Remove that platform from `platforms`; please also open an issue. |
| Fewer handles were checked than you asked for | The run's maximum cost could not cover them all. `usernamesSkippedForBudget` in the summary says how many were dropped. | Raise the maximum cost for the run, or narrow `platforms`. |
| A platform you selected is always `UNKNOWN` with HTTP 403 | That platform blocks datacenter IP ranges, which is where the Actor runs. Measured cases include Reddit, npm and Codepen — they are in the catalogue but not in the default set for this reason. | Drop it from `platforms`, or run the Actor through a residential proxy. |
| Some handles are missing from the output | The platform's own username rules reject them (e.g. X caps handles at 15 characters) | Expected — those pairs are skipped and counted in `skippedIncompatible`. |

### Privacy and responsible use

This Actor only requests **public profile pages, anonymously**. It does not log
in, does not use cookies or credentials, does not bypass authentication or
captchas, and does not read messages, followers, emails or any personal data. It
records one thing per URL: whether the page exists.

Use it to protect your own brand, to find available names, and to audit your own
accounts. Do not use it to track individuals.

### Disclaimer

The results are **technical signals** derived from public pages and services.
The Actor detects whether a public profile exists and whether that changed
between runs. It does **not** determine identity or intent, and a result is not
a statement that any account is fraudulent, impersonating anyone, infringing a
trademark, or unlawful. Those are legal conclusions that require human judgment
and, where appropriate, legal advice.

### Sherlock attribution

Platform definitions come from the
[Sherlock Project](https://github.com/sherlock-project/sherlock) manifest
(`sherlock_project/resources/data.json`, v0.16.0), vendored at
`my_actor/resources/sherlock_sites.json` under the MIT License. The original
license is preserved at `my_actor/resources/SHERLOCK_LICENSE`.

> Copyright (c) 2019 Sherlock Project — MIT License

**This Actor is not affiliated with or endorsed by the Sherlock Project.** It is
not a Sherlock wrapper either: Sherlock's code is not executed. Only the
declarative site manifest is reused, under an independent async scanning engine
built for this Actor, with different — more conservative — error handling: any
blocked, throttled or ambiguous response becomes `UNKNOWN`, never `AVAILABLE`.

A small number of definitions are maintained by this project rather than by
Sherlock, where the upstream entry is stale; they are declared in
`EXTRA_SITES` in `my_actor/sites.py`.

To refresh the manifest, replace `my_actor/resources/sherlock_sites.json` with
the current upstream file and re-run `python scripts/benchmark.py` plus
`pytest -m integration` to re-validate the default platform set.

### FAQ

#### Is checking username availability legal?

The Actor requests public profile pages anonymously, exactly as a browser does
when you type a profile URL. It does not log in, bypass authentication, or
access anything a signed-out visitor cannot see. Whether any particular use is
appropriate depends on your jurisdiction and purpose; if you are acting on a
trademark matter, take legal advice rather than treating a scan result as
evidence.

#### Why does a handle I own show as `AVAILABLE`?

Some platforms hide profiles from signed-out visitors, or serve a different
page to datacenter IP ranges. Remove that platform from `platforms` and please
open an Issue — reports like that are what keep the default set accurate.

#### Why is a platform always `UNKNOWN`?

It is refusing the request, usually with HTTP 403 or 429. Platforms that do
this consistently from Apify's IP ranges are deliberately excluded from the
default set, though they remain selectable. `UNKNOWN` never counts as a change
and never overwrites a stored result.

#### How often should I run the monitor?

Daily suits most brand watchlists. Handle turnover is measured in days, not
minutes, and a daily schedule keeps the cost trivial. Hourly works too if you
are waiting for a specific handle to free up.

#### Does the first run report changes?

No. The first run for a `monitorId` records the baseline and reports every
handle as `unchanged` with a `null` `previousStatus`. Comparisons start with
the second run.

#### Can I check more than the default platforms?

Yes — name any of the 400+ catalogue entries in `platforms`. Expect a higher
`UNKNOWN` rate outside the default set, and remember that cost scales with the
number of checks.

#### What happens if a run fails halfway?

Nothing is corrupted. The baseline is only written when a run is conclusive
enough to be trusted, updates merge rather than replace, and a run that was
mostly inconclusive leaves the previous baseline untouched.

### Support and feedback

Found a platform that reports the wrong status, or want one added to the default
set? Open an issue on the Actor's **Issues** tab with the handle and platform —
those reports are what keep the default set accurate.

### Development

```bash
pip install -r requirements-dev.txt
pytest -m "not integration"   # fast, hermetic
pytest -m integration         # a handful of real network checks
ruff check .
apify run                     # local run using storage/key_value_stores/default/INPUT.json
```

### License notes

This Actor's own source code is released under the MIT License (see `LICENSE`).
The vendored Sherlock site manifest is MIT-licensed by the Sherlock Project and
retains its own copyright notice at `my_actor/resources/SHERLOCK_LICENSE`.

# Actor input Schema

## `usernames` (type: `array`):

Handles to check. Accepts a bare handle (`acme`), an @handle (`@acme`) or a profile URL (`https://github.com/acme`) - all are reduced to the bare handle. Duplicates are removed, up to 50 handles per run.

## `mode` (type: `string`):

`scan` is a stateless snapshot. `monitor` compares this run against the stored baseline for the same monitor ID and reports what changed since the previous run.

## `monitorId` (type: `string`):

Identifies the baseline to compare against. Required in monitor mode. Use one stable ID per brand or watchlist, for example `acme-brand`. Two different IDs never share state.

## `platforms` (type: `array`):

Platform names to check. Leave empty to use the curated default set, which contains only platforms verified to answer reliably from the Apify cloud. Any of the 400+ catalogue names is accepted, for example `GitHub`, `X`, `Reddit`, `Twitch` - note that some of those may return UNKNOWN because they block datacenter IP ranges.

## `includeAvailable` (type: `boolean`):

When off, only claimed and inconclusive results reach the dataset. The baseline still records everything, so change detection is unaffected.

## `timeoutSeconds` (type: `integer`):

How long to wait for a single platform before giving up. A timeout is reported as UNKNOWN, never as an available handle.

## `maxConcurrency` (type: `integer`):

Upper bound on parallel requests. Higher is faster but more likely to be rate limited, which produces UNKNOWN results.

## Actor input object example

```json
{
  "usernames": [
    "apify"
  ],
  "mode": "scan",
  "monitorId": "my-brand",
  "platforms": [],
  "includeAvailable": true,
  "timeoutSeconds": 15,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "usernames": [
        "apify"
    ],
    "monitorId": "my-brand"
};

// Run the Actor and wait for it to finish
const run = await client.actor("ironstark_daysky/brand-handle-monitor").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 = {
    "usernames": ["apify"],
    "monitorId": "my-brand",
}

# Run the Actor and wait for it to finish
run = client.actor("ironstark_daysky/brand-handle-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "usernames": [
    "apify"
  ],
  "monitorId": "my-brand"
}' |
apify call ironstark_daysky/brand-handle-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ironstark_daysky/brand-handle-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uBw7S5yjVwL4SUlt0/builds/sm1IYaert7NsKilpi/openapi.json
