{
  "data": {
    "id": "UYaXoEvdhfwElKP1q",
    "actId": "OL8Hv3EeydbTSbCFR",
    "userId": "tnBVjt3CXmcUrEE3L",
    "startedAt": "2026-11-01T22:47:44.450Z",
    "finishedAt": "2026-11-01T22:47:56.877Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 12368,
      "runTimeSecs": 12.368,
      "computeUnits": 0.013742222222222222,
      "imageSizeBytes": 329650176
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "F2Aeb3gE2syjqz9xC",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Onion Directory Metadata Input\",\n  \"description\": \"Collect public onion directory entries and contact metadata from OnionTree, dark.fail and tor.taxi, or read Ahmia index results. Metadata only: no page bodies, credentials or marketplace listings.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"mode\": {\n      \"title\": \"Mode\",\n      \"type\": \"string\",\n      \"description\": \"What to collect. `directories` aggregates public onion directory entries; `search` returns Ahmia index-result metadata; `byUrls` reads directory pages you supply. Legacy `crawl` and `searchAndCrawl` values are accepted and mapped in code.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"directories\",\n        \"search\",\n        \"byUrls\",\n        \"crawl\",\n        \"searchAndCrawl\"\n      ],\n      \"enumTitles\": [\n        \"Aggregate onion directories\",\n        \"Search the Ahmia index\",\n        \"Read my directory page URLs\",\n        \"Legacy: read start URLs as directory pages\",\n        \"Legacy: aggregate directories\"\n      ],\n      \"default\": \"directories\",\n      \"prefill\": \"directories\"\n    },\n    \"source\": {\n      \"title\": \"Directory source\",\n      \"type\": \"string\",\n      \"description\": \"Directory aggregation source (mode=directories). `all` merges OnionTree, dark.fail and tor.taxi.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"all\",\n        \"onionTree\",\n        \"darkFail\",\n        \"torTaxi\"\n      ],\n      \"enumTitles\": [\n        \"All three directories\",\n        \"OnionTree (categories, descriptions, PGP contacts)\",\n        \"dark.fail (status claims)\",\n        \"tor.taxi (link table)\"\n      ],\n      \"default\": \"onionTree\",\n      \"prefill\": \"onionTree\"\n    },\n    \"categories\": {\n      \"title\": \"OnionTree categories\",\n      \"type\": \"array\",\n      \"description\": \"Keep OnionTree entries matching any selected category (mode=directories). Allowed values: archive, blockchain, blog, chat, dead, defcon, ebook, etherpad, forum, git, hacking, hosting, imageboard, jabber, journalism, keyserver, leaks, library, link-list, linux, mail, news, operating-system, pastebin, pgp, publishing, riseup, search, securedrop, security, social-media, tor, upload, video, vpn, vps, whistleblowing, wiki, xmpp. Empty means all neutral categories. This filter is ignored for dark.fail and tor.taxi rows because they carry no tags.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"searchQueries\": {\n      \"title\": \"Ahmia search queries\",\n      \"type\": \"array\",\n      \"description\": \"One or more keywords to query against the Ahmia index (mode=search). Each query is one request; result pages are never opened.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"maxResultsPerQuery\": {\n      \"title\": \"Max results per query\",\n      \"type\": \"integer\",\n      \"description\": \"Cap on Ahmia rows per query (mode=search). The global maxItems cap still applies.\",\n      \"minimum\": 1,\n      \"maximum\": 1000,\n      \"default\": 20,\n      \"prefill\": 3\n    },\n    \"startUrls\": {\n      \"title\": \"Directory page URLs\",\n      \"type\": \"array\",\n      \"description\": \"Clearnet or v3 onion directory/index pages to read (mode=byUrls). Only entry links and adjacent structured contact links are parsed; linked onion pages are not opened.\",\n      \"editor\": \"requestListSources\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"url\": {\n            \"type\": \"string\",\n            \"title\": \"Directory page URL\",\n            \"description\": \"Clearnet URL or valid v3 .onion directory/index page.\"\n          }\n        }\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"maxItems\": {\n      \"title\": \"Max output items\",\n      \"type\": \"integer\",\n      \"description\": \"Hard cap on records emitted to the dataset.\",\n      \"minimum\": 1,\n      \"maximum\": 5000,\n      \"default\": 100,\n      \"prefill\": 3\n    },\n    \"maxPages\": {\n      \"title\": \"Max directory pages\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum directory pages fetched in mode=byUrls.\",\n      \"minimum\": 1,\n      \"maximum\": 50,\n      \"default\": 5,\n      \"prefill\": 1\n    },\n    \"verifyLiveness\": {\n      \"title\": \"Verify onion liveness through Tor\",\n      \"type\": \"boolean\",\n      \"description\": \"Fetch each unique onion domain once through Tor and fill measured status, HTTP code, page title and check time. Applies to directories and byUrls; search mode never opens result pages.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"livenessTimeoutSecs\": {\n      \"title\": \"Liveness timeout (seconds)\",\n      \"type\": \"integer\",\n      \"description\": \"Per-domain timeout for the optional Tor liveness check.\",\n      \"minimum\": 5,\n      \"maximum\": 60,\n      \"default\": 12,\n      \"prefill\": 8\n    },\n    \"includeUnreachable\": {\n      \"title\": \"Include unreachable entries\",\n      \"type\": \"boolean\",\n      \"description\": \"When liveness is enabled, keep entries measured as timeout or unreachable. Applies only when verifyLiveness is true.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"extractEmails\": {\n      \"title\": \"Include contact emails\",\n      \"type\": \"boolean\",\n      \"description\": \"Include structured emails from OnionTree PGP user IDs and explicit mailto: links on directory pages. No free-text email mining.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"extractPhones\": {\n      \"title\": \"Include phone numbers\",\n      \"type\": \"boolean\",\n      \"description\": \"Include phone numbers from explicit tel: links on directory pages only. No free-text phone mining.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"extractSocialHandles\": {\n      \"title\": \"Include social profiles\",\n      \"type\": \"boolean\",\n      \"description\": \"Include handles from explicit X/Twitter, Instagram and Telegram profile links on directory pages only.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"extractPgpContacts\": {\n      \"title\": \"Include PGP contact metadata\",\n      \"type\": \"boolean\",\n      \"description\": \"Include OnionTree PGP fingerprint, key ID and contact email. The public key block itself is never stored.\",\n      \"default\": true,\n      \"prefill\": false\n    }\n  },\n  \"required\": [\n    \"mode\"\n  ]\n}",
    "readme": "# Onion Directory & Contact Metadata Scraper\n\nCollect public onion directory and contact metadata without crawling page bodies. The actor merges OnionTree, dark.fail and tor.taxi entries by v3 onion domain, keeps provenance, adds neutral categories, descriptions and PGP contact fields, and can measure liveness through a bundled Tor daemon. A separate search mode returns Ahmia index-result metadata only.\n\n## Scope and acceptable use\n\nThis actor is **metadata-only by design**. It reads public directory pages, OnionTree YAML records and Ahmia index results. It does **not** collect page text or HTML, credentials, API keys, passwords, cryptocurrency wallet addresses, marketplace listings, vendor/product data, breach data or illegal material. Search-mode result pages are never opened. Liveness checks only fetch the root of a directory entry's onion site to record status and title.\n\nUse it for lawful security research, OSINT triage, directory quality checks and threat-intelligence enrichment. Tor use and onward use of the data may be regulated in your jurisdiction; you are responsible for complying with applicable law and with each source's terms. Do not use it to facilitate crime, to target individuals, or to build a marketplace index. The marketplace name denylist is a best-effort safety filter, not a guarantee.\n\n## Modes\n\n| Mode | What it does | Opens onion pages? |\n|---|---|---|\n| `directories` (default) | Aggregates OnionTree, dark.fail and tor.taxi, dedupes by v3 onion domain and merges provenance. | Only when `verifyLiveness` is true, one root GET per domain. |\n| `search` | Queries the Ahmia index and returns title, URL, rank, snippet and last-seen claim. | Never. |\n| `byUrls` | Reads directory/index pages you supply and extracts their v3 onion entries plus adjacent structured contact links. | Fetches the directory pages themselves, never the linked onion entries. |\n\nLegacy mode values are mapped in code: `crawl` becomes `byUrls`, `searchAndCrawl` becomes `directories`, and an unknown mode falls back to `directories`.\n\n## Output fields\n\nEvery record is omit-empty: a field the source genuinely lacks is absent, never `null` or an empty array.\n\n**Directory rows (`recordType: \"directoryEntry\"`)**\n\n- `name`, `title` — entry name (`title` is the legacy alias)\n- `onionUrl`, `url`, `onionDomain` — primary URL and its v3 host (`url` is the legacy alias)\n- `urls[]` — all v3 mirror URLs for the entry (up to 10)\n- `links[]`, `onionLinks[]` — legacy aliases of `urls[]`\n- `description` — directory description, capped at 1,000 characters\n- `categories[]` — neutral OnionTree categories such as `news`, `mail`, `securedrop`\n- `source`, `sourceUrl` — primary provenance (`onionTree`, `darkFail`, `torTaxi`, `byUrls`)\n- `alsoListedIn[]` — other sources that listed the same onion domain\n- `sourceStatus` — dark.fail's own claim (`online`, `offline`, `unknown`), never used as measured status\n- `status`, `httpStatus`, `pageTitle`, `lastCheckedAt` — measured through Tor when `verifyLiveness` is true\n- `pgpFingerprint`, `pgpKeyId`, `contactEmail` — OnionTree public-key contact metadata\n- `emails[]`, `phones[]`, `misc` — structured contact values only (`mailto:`, `tel:`, explicit social profile links)\n- `scrapedAt` — ISO-8601 UTC timestamp\n\n**Search rows (`recordType: \"searchResult\"`)**\n\n`query`, `rank`, `title`, `onionUrl`, `onionDomain`, `snippet`, `lastSeenClaim`, `totalResults`, `searchKeywordFound`, `source`, `sourceUrl`, `scrapedAt`.\n\nThere is no `cryptoAddresses` field and no `misc.apiKeys` field. Both were removed for scope compliance and are never populated.\n\n## Input\n\n| Field | Type | Default | What it controls |\n|---|---|---|---|\n| `mode` | select | `directories` | `directories`, `search` or `byUrls` |\n| `source` | select | `onionTree` | `all`, `onionTree`, `darkFail` or `torTaxi` (directories mode) |\n| `categories` | string list | `[]` | OnionTree category filter; empty means all neutral categories |\n| `searchQueries` | string list | `[]` | Ahmia queries (search mode; max 20) |\n| `maxResultsPerQuery` | integer 1–1000 | `20` | Per-query Ahmia cap |\n| `startUrls` | URL list | `[]` | Directory/index pages (byUrls mode) |\n| `maxItems` | integer 1–5000 | `100` | Global emitted-record cap |\n| `maxPages` | integer 1–50 | `5` | Directory-page cap (byUrls mode) |\n| `verifyLiveness` | boolean | `true` | Measure each unique onion domain through Tor |\n| `livenessTimeoutSecs` | integer 5–60 | `12` | Per-domain liveness timeout |\n| `includeUnreachable` | boolean | `true` | Keep entries measured as `timeout`/`unreachable` |\n| `extractEmails` | boolean | `true` | Structured emails from PGP user IDs and `mailto:` links |\n| `extractPhones` | boolean | `true` | Phones from explicit `tel:` links only |\n| `extractSocialHandles` | boolean | `true` | Handles from explicit social profile links only |\n| `extractPgpContacts` | boolean | `true` | PGP fingerprint, key ID and contact email |\n\nNo cookies, API keys or proxy configuration are required or accepted.\n\n### Example: daily directory run (cheapest)\n\n```json\n{\n    \"mode\": \"directories\",\n    \"source\": \"onionTree\",\n    \"maxItems\": 3,\n    \"verifyLiveness\": false,\n    \"extractEmails\": false,\n    \"extractPgpContacts\": false\n}\n```\n\n### Example: merge all directories with liveness\n\n```json\n{\n    \"mode\": \"directories\",\n    \"source\": \"all\",\n    \"categories\": [\"news\", \"securedrop\", \"mail\"],\n    \"maxItems\": 50,\n    \"verifyLiveness\": true,\n    \"livenessTimeoutSecs\": 12,\n    \"includeUnreachable\": true\n}\n```\n\n### Example: Ahmia index metadata\n\n```json\n{\n    \"mode\": \"search\",\n    \"searchQueries\": [\"securedrop\", \"whistleblowing\"],\n    \"maxResultsPerQuery\": 10,\n    \"maxItems\": 20\n}\n```\n\n### Example: read your own directory page\n\n```json\n{\n    \"mode\": \"byUrls\",\n    \"startUrls\": [{ \"url\": \"https://example.org/onion-directory\" }],\n    \"maxPages\": 2,\n    \"maxItems\": 25\n}\n```\n\n## How liveness works\n\nWhen `verifyLiveness` is true the actor starts the bundled Tor daemon and makes one GET request per unique onion domain, with a concurrency of four and a short jitter between requests. Example valid v3 entry: `http://bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion`. Results are classified as `live`, `challenge` (Cloudflare/DDoS-Guard or a 403/429), `timeout` or `unreachable`. After six consecutive transport failures the circuit breaker stops checking and marks the remaining entries `unknown`. A liveness failure never discards an already-built directory row.\n\n## Limitations\n\n- Legacy v2 onions are dead in modern Tor. They are validated out and counted as skipped; only 56-character v3 addresses are emitted.\n- Directory data is stale by nature. Many listed services are offline; `status` is only meaningful when you enable `verifyLiveness`.\n- dark.fail and tor.taxi do not expose category tags, so the category filter applies to OnionTree rows only. The marketplace denylist for untagged sources is heuristic and covers known market, drug-tracking, exploit-market and darknet-market-guide brands.\n- Ahmia's search form uses a rotating hidden token and a session cookie. If the token cannot be read, search returns a clear status message instead of looping.\n- OnionTree's own website can be unreachable; the actor reads its public GitHub tarball mirror instead.\n- No actor can guarantee that a third-party directory entry is safe, lawful or current. Verify before use.\n\n## FAQ\n\n**Do I need to install Tor or configure a proxy?**\nNo. The container includes Tor. The actor starts it only when liveness is enabled or a `.onion` directory page is supplied.\n\n**Does this actor crawl onion pages or collect page content?**\nNo. It reads directory pages and index-result metadata. A liveness check fetches only the root of an onion site to measure status and title; page bodies are not stored or emitted.\n\n**Why are some directory entries missing?**\nMarketplace and vendor-tagged OnionTree records are excluded by scope, payment tags are stripped from categories, legacy v2-only records are skipped, and duplicate domains are merged into one row. A source that is blocked or unreachable is skipped without failing the run.\n\n**Why is a site marked `challenge` instead of `live`?**\nThe site answered with a Cloudflare/DDoS-Guard interstitial or a 403/429. It is reachable but not serving its normal page, so the actor reports the measured condition rather than assuming success.\n\n**What does `sourceStatus` mean?**\nIt is dark.fail's own claim about a site. It is kept separate from the measured `status` because directory claims can disagree with a live Tor check.\n\n**Why are PGP keys not included?**\nOnly the fingerprint, key ID and contact email are public contact metadata. The full public-key block is intentionally not stored.\n\n**Can I search more than one keyword?**\nYes. `searchQueries` accepts up to 20 queries; each query is one Ahmia request, and `maxResultsPerQuery` plus `maxItems` bound the output.\n\n**Why did an old task with `crawl` or `search` still run?**\nLegacy input keys and mode names are mapped in code. `crawl` now means `byUrls`, `searchAndCrawl` now means `directories`, and `extractCryptoAddresses`/`extractApiKeys` are accepted but ignored with a status note because those data types are outside this actor's scope.\n\n**Are empty fields returned as `null`?**\nNo. Fields with no value are omitted at every depth.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "tor-hidden-service-crawler-and-contact-extractor",
      "title": "Onion Directory & Contact Metadata Scraper",
      "description": "Collect public onion directory and contact metadata from OnionTree, dark.fail and tor.taxi: names, categories, descriptions, PGP contacts, provenance and optional Tor liveness. No page content, credentials or marketplace listings.",
      "version": "1.0",
      "buildTag": "latest",
      "input": {
        "title": "Onion Directory Metadata Input",
        "description": "Collect public onion directory entries and contact metadata from OnionTree, dark.fail and tor.taxi, or read Ahmia index results. Metadata only: no page bodies, credentials or marketplace listings.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "mode": {
            "title": "Mode",
            "type": "string",
            "description": "What to collect. `directories` aggregates public onion directory entries; `search` returns Ahmia index-result metadata; `byUrls` reads directory pages you supply. Legacy `crawl` and `searchAndCrawl` values are accepted and mapped in code.",
            "editor": "select",
            "enum": [
              "directories",
              "search",
              "byUrls",
              "crawl",
              "searchAndCrawl"
            ],
            "enumTitles": [
              "Aggregate onion directories",
              "Search the Ahmia index",
              "Read my directory page URLs",
              "Legacy: read start URLs as directory pages",
              "Legacy: aggregate directories"
            ],
            "default": "directories",
            "prefill": "directories"
          },
          "source": {
            "title": "Directory source",
            "type": "string",
            "description": "Directory aggregation source (mode=directories). `all` merges OnionTree, dark.fail and tor.taxi.",
            "editor": "select",
            "enum": [
              "all",
              "onionTree",
              "darkFail",
              "torTaxi"
            ],
            "enumTitles": [
              "All three directories",
              "OnionTree (categories, descriptions, PGP contacts)",
              "dark.fail (status claims)",
              "tor.taxi (link table)"
            ],
            "default": "onionTree",
            "prefill": "onionTree"
          },
          "categories": {
            "title": "OnionTree categories",
            "type": "array",
            "description": "Keep OnionTree entries matching any selected category (mode=directories). Allowed values: archive, blockchain, blog, chat, dead, defcon, ebook, etherpad, forum, git, hacking, hosting, imageboard, jabber, journalism, keyserver, leaks, library, link-list, linux, mail, news, operating-system, pastebin, pgp, publishing, riseup, search, securedrop, security, social-media, tor, upload, video, vpn, vps, whistleblowing, wiki, xmpp. Empty means all neutral categories. This filter is ignored for dark.fail and tor.taxi rows because they carry no tags.",
            "editor": "stringList",
            "default": [],
            "prefill": []
          },
          "searchQueries": {
            "title": "Ahmia search queries",
            "type": "array",
            "description": "One or more keywords to query against the Ahmia index (mode=search). Each query is one request; result pages are never opened.",
            "editor": "stringList",
            "default": [],
            "prefill": []
          },
          "maxResultsPerQuery": {
            "title": "Max results per query",
            "type": "integer",
            "description": "Cap on Ahmia rows per query (mode=search). The global maxItems cap still applies.",
            "minimum": 1,
            "maximum": 1000,
            "default": 20,
            "prefill": 3
          },
          "startUrls": {
            "title": "Directory page URLs",
            "type": "array",
            "description": "Clearnet or v3 onion directory/index pages to read (mode=byUrls). Only entry links and adjacent structured contact links are parsed; linked onion pages are not opened.",
            "editor": "requestListSources",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "title": "Directory page URL",
                  "description": "Clearnet URL or valid v3 .onion directory/index page."
                }
              }
            },
            "default": [],
            "prefill": []
          },
          "maxItems": {
            "title": "Max output items",
            "type": "integer",
            "description": "Hard cap on records emitted to the dataset.",
            "minimum": 1,
            "maximum": 5000,
            "default": 100,
            "prefill": 3
          },
          "maxPages": {
            "title": "Max directory pages",
            "type": "integer",
            "description": "Maximum directory pages fetched in mode=byUrls.",
            "minimum": 1,
            "maximum": 50,
            "default": 5,
            "prefill": 1
          },
          "verifyLiveness": {
            "title": "Verify onion liveness through Tor",
            "type": "boolean",
            "description": "Fetch each unique onion domain once through Tor and fill measured status, HTTP code, page title and check time. Applies to directories and byUrls; search mode never opens result pages.",
            "default": true,
            "prefill": false
          },
          "livenessTimeoutSecs": {
            "title": "Liveness timeout (seconds)",
            "type": "integer",
            "description": "Per-domain timeout for the optional Tor liveness check.",
            "minimum": 5,
            "maximum": 60,
            "default": 12,
            "prefill": 8
          },
          "includeUnreachable": {
            "title": "Include unreachable entries",
            "type": "boolean",
            "description": "When liveness is enabled, keep entries measured as timeout or unreachable. Applies only when verifyLiveness is true.",
            "default": true,
            "prefill": true
          },
          "extractEmails": {
            "title": "Include contact emails",
            "type": "boolean",
            "description": "Include structured emails from OnionTree PGP user IDs and explicit mailto: links on directory pages. No free-text email mining.",
            "default": true,
            "prefill": false
          },
          "extractPhones": {
            "title": "Include phone numbers",
            "type": "boolean",
            "description": "Include phone numbers from explicit tel: links on directory pages only. No free-text phone mining.",
            "default": true,
            "prefill": false
          },
          "extractSocialHandles": {
            "title": "Include social profiles",
            "type": "boolean",
            "description": "Include handles from explicit X/Twitter, Instagram and Telegram profile links on directory pages only.",
            "default": true,
            "prefill": false
          },
          "extractPgpContacts": {
            "title": "Include PGP contact metadata",
            "type": "boolean",
            "description": "Include OnionTree PGP fingerprint, key ID and contact email. The public key block itself is never stored.",
            "default": true,
            "prefill": false
          }
        },
        "required": [
          "mode"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Onion Directory & Contact Metadata Output",
        "description": "One dataset of public onion directory entries and Ahmia index-result metadata, with provenance, neutral categories, PGP contact fields and optional measured liveness.",
        "properties": {
          "directoryEntries": {
            "type": "string",
            "title": "Onion directory and index records",
            "description": "Dataset containing directoryEntry rows (directories/byUrls) and searchResult rows (search).",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "dockerfile": "../Dockerfile",
      "readme": "# Onion Directory & Contact Metadata Scraper\n\nCollect public onion directory and contact metadata without crawling page bodies. The actor merges OnionTree, dark.fail and tor.taxi entries by v3 onion domain, keeps provenance, adds neutral categories, descriptions and PGP contact fields, and can measure liveness through a bundled Tor daemon. A separate search mode returns Ahmia index-result metadata only.\n\n## Scope and acceptable use\n\nThis actor is **metadata-only by design**. It reads public directory pages, OnionTree YAML records and Ahmia index results. It does **not** collect page text or HTML, credentials, API keys, passwords, cryptocurrency wallet addresses, marketplace listings, vendor/product data, breach data or illegal material. Search-mode result pages are never opened. Liveness checks only fetch the root of a directory entry's onion site to record status and title.\n\nUse it for lawful security research, OSINT triage, directory quality checks and threat-intelligence enrichment. Tor use and onward use of the data may be regulated in your jurisdiction; you are responsible for complying with applicable law and with each source's terms. Do not use it to facilitate crime, to target individuals, or to build a marketplace index. The marketplace name denylist is a best-effort safety filter, not a guarantee.\n\n## Modes\n\n| Mode | What it does | Opens onion pages? |\n|---|---|---|\n| `directories` (default) | Aggregates OnionTree, dark.fail and tor.taxi, dedupes by v3 onion domain and merges provenance. | Only when `verifyLiveness` is true, one root GET per domain. |\n| `search` | Queries the Ahmia index and returns title, URL, rank, snippet and last-seen claim. | Never. |\n| `byUrls` | Reads directory/index pages you supply and extracts their v3 onion entries plus adjacent structured contact links. | Fetches the directory pages themselves, never the linked onion entries. |\n\nLegacy mode values are mapped in code: `crawl` becomes `byUrls`, `searchAndCrawl` becomes `directories`, and an unknown mode falls back to `directories`.\n\n## Output fields\n\nEvery record is omit-empty: a field the source genuinely lacks is absent, never `null` or an empty array.\n\n**Directory rows (`recordType: \"directoryEntry\"`)**\n\n- `name`, `title` — entry name (`title` is the legacy alias)\n- `onionUrl`, `url`, `onionDomain` — primary URL and its v3 host (`url` is the legacy alias)\n- `urls[]` — all v3 mirror URLs for the entry (up to 10)\n- `links[]`, `onionLinks[]` — legacy aliases of `urls[]`\n- `description` — directory description, capped at 1,000 characters\n- `categories[]` — neutral OnionTree categories such as `news`, `mail`, `securedrop`\n- `source`, `sourceUrl` — primary provenance (`onionTree`, `darkFail`, `torTaxi`, `byUrls`)\n- `alsoListedIn[]` — other sources that listed the same onion domain\n- `sourceStatus` — dark.fail's own claim (`online`, `offline`, `unknown`), never used as measured status\n- `status`, `httpStatus`, `pageTitle`, `lastCheckedAt` — measured through Tor when `verifyLiveness` is true\n- `pgpFingerprint`, `pgpKeyId`, `contactEmail` — OnionTree public-key contact metadata\n- `emails[]`, `phones[]`, `misc` — structured contact values only (`mailto:`, `tel:`, explicit social profile links)\n- `scrapedAt` — ISO-8601 UTC timestamp\n\n**Search rows (`recordType: \"searchResult\"`)**\n\n`query`, `rank`, `title`, `onionUrl`, `onionDomain`, `snippet`, `lastSeenClaim`, `totalResults`, `searchKeywordFound`, `source`, `sourceUrl`, `scrapedAt`.\n\nThere is no `cryptoAddresses` field and no `misc.apiKeys` field. Both were removed for scope compliance and are never populated.\n\n## Input\n\n| Field | Type | Default | What it controls |\n|---|---|---|---|\n| `mode` | select | `directories` | `directories`, `search` or `byUrls` |\n| `source` | select | `onionTree` | `all`, `onionTree`, `darkFail` or `torTaxi` (directories mode) |\n| `categories` | string list | `[]` | OnionTree category filter; empty means all neutral categories |\n| `searchQueries` | string list | `[]` | Ahmia queries (search mode; max 20) |\n| `maxResultsPerQuery` | integer 1–1000 | `20` | Per-query Ahmia cap |\n| `startUrls` | URL list | `[]` | Directory/index pages (byUrls mode) |\n| `maxItems` | integer 1–5000 | `100` | Global emitted-record cap |\n| `maxPages` | integer 1–50 | `5` | Directory-page cap (byUrls mode) |\n| `verifyLiveness` | boolean | `true` | Measure each unique onion domain through Tor |\n| `livenessTimeoutSecs` | integer 5–60 | `12` | Per-domain liveness timeout |\n| `includeUnreachable` | boolean | `true` | Keep entries measured as `timeout`/`unreachable` |\n| `extractEmails` | boolean | `true` | Structured emails from PGP user IDs and `mailto:` links |\n| `extractPhones` | boolean | `true` | Phones from explicit `tel:` links only |\n| `extractSocialHandles` | boolean | `true` | Handles from explicit social profile links only |\n| `extractPgpContacts` | boolean | `true` | PGP fingerprint, key ID and contact email |\n\nNo cookies, API keys or proxy configuration are required or accepted.\n\n### Example: daily directory run (cheapest)\n\n```json\n{\n    \"mode\": \"directories\",\n    \"source\": \"onionTree\",\n    \"maxItems\": 3,\n    \"verifyLiveness\": false,\n    \"extractEmails\": false,\n    \"extractPgpContacts\": false\n}\n```\n\n### Example: merge all directories with liveness\n\n```json\n{\n    \"mode\": \"directories\",\n    \"source\": \"all\",\n    \"categories\": [\"news\", \"securedrop\", \"mail\"],\n    \"maxItems\": 50,\n    \"verifyLiveness\": true,\n    \"livenessTimeoutSecs\": 12,\n    \"includeUnreachable\": true\n}\n```\n\n### Example: Ahmia index metadata\n\n```json\n{\n    \"mode\": \"search\",\n    \"searchQueries\": [\"securedrop\", \"whistleblowing\"],\n    \"maxResultsPerQuery\": 10,\n    \"maxItems\": 20\n}\n```\n\n### Example: read your own directory page\n\n```json\n{\n    \"mode\": \"byUrls\",\n    \"startUrls\": [{ \"url\": \"https://example.org/onion-directory\" }],\n    \"maxPages\": 2,\n    \"maxItems\": 25\n}\n```\n\n## How liveness works\n\nWhen `verifyLiveness` is true the actor starts the bundled Tor daemon and makes one GET request per unique onion domain, with a concurrency of four and a short jitter between requests. Example valid v3 entry: `http://bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion`. Results are classified as `live`, `challenge` (Cloudflare/DDoS-Guard or a 403/429), `timeout` or `unreachable`. After six consecutive transport failures the circuit breaker stops checking and marks the remaining entries `unknown`. A liveness failure never discards an already-built directory row.\n\n## Limitations\n\n- Legacy v2 onions are dead in modern Tor. They are validated out and counted as skipped; only 56-character v3 addresses are emitted.\n- Directory data is stale by nature. Many listed services are offline; `status` is only meaningful when you enable `verifyLiveness`.\n- dark.fail and tor.taxi do not expose category tags, so the category filter applies to OnionTree rows only. The marketplace denylist for untagged sources is heuristic and covers known market, drug-tracking, exploit-market and darknet-market-guide brands.\n- Ahmia's search form uses a rotating hidden token and a session cookie. If the token cannot be read, search returns a clear status message instead of looping.\n- OnionTree's own website can be unreachable; the actor reads its public GitHub tarball mirror instead.\n- No actor can guarantee that a third-party directory entry is safe, lawful or current. Verify before use.\n\n## FAQ\n\n**Do I need to install Tor or configure a proxy?**\nNo. The container includes Tor. The actor starts it only when liveness is enabled or a `.onion` directory page is supplied.\n\n**Does this actor crawl onion pages or collect page content?**\nNo. It reads directory pages and index-result metadata. A liveness check fetches only the root of an onion site to measure status and title; page bodies are not stored or emitted.\n\n**Why are some directory entries missing?**\nMarketplace and vendor-tagged OnionTree records are excluded by scope, payment tags are stripped from categories, legacy v2-only records are skipped, and duplicate domains are merged into one row. A source that is blocked or unreachable is skipped without failing the run.\n\n**Why is a site marked `challenge` instead of `live`?**\nThe site answered with a Cloudflare/DDoS-Guard interstitial or a 403/429. It is reachable but not serving its normal page, so the actor reports the measured condition rather than assuming success.\n\n**What does `sourceStatus` mean?**\nIt is dark.fail's own claim about a site. It is kept separate from the measured `status` because directory claims can disagree with a live Tor check.\n\n**Why are PGP keys not included?**\nOnly the fingerprint, key ID and contact email are public contact metadata. The full public-key block is intentionally not stored.\n\n**Can I search more than one keyword?**\nYes. `searchQueries` accepts up to 20 queries; each query is one Ahmia request, and `maxResultsPerQuery` plus `maxItems` bound the output.\n\n**Why did an old task with `crawl` or `search` still run?**\nLegacy input keys and mode names are mapped in code. `crawl` now means `byUrls`, `searchAndCrawl` now means `directories`, and `extractCryptoAddresses`/`extractApiKeys` are accepted but ignored with a status note because those data types are outside this actor's scope.\n\n**Are empty fields returned as `null`?**\nNo. Fields with no value are omitted at every depth.\n",
      "defaultRunOptions": {
        "build": "latest",
        "timeoutSecs": 1800,
        "memoryMbytes": 2048
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "overview": {
              "title": "Onion directory entries",
              "transformation": {
                "fields": [
                  "name",
                  "onionUrl",
                  "categories",
                  "source",
                  "status",
                  "description"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "name": {
                    "label": "Name",
                    "format": "text"
                  },
                  "onionUrl": {
                    "label": "Onion URL",
                    "format": "text"
                  },
                  "categories": {
                    "label": "Categories",
                    "format": "text"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "status": {
                    "label": "Measured status",
                    "format": "text"
                  },
                  "description": {
                    "label": "Description",
                    "format": "text"
                  }
                }
              }
            }
          }
        }
      },
      "environmentVariables": {}
    },
    "buildNumber": "1.0.2",
    "imageDigest": "d77d2f8efbad8255a76383756d91a4fc23b35423b83f4efa803d6b2b5a4aba8f",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.013742222222222222
    }
  }
}