{
  "data": {
    "id": "UvNLj8gOebSn7KfpY",
    "actId": "nM2vtMcH1bZsKtNf3",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-11-05T12:29:32.591Z",
    "finishedAt": "2026-11-05T12:29:40.755Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 8083,
      "runTimeSecs": 8.083,
      "computeUnits": 0.008981111111111112,
      "imageSizeBytes": 309440000
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"GOC Optician & Optometrist Register Scraper Input\",\n  \"description\": \"Search the General Optical Council's public Specialist Register (str.optical.org) for registered optometrists, dispensing opticians, students, and optical businesses across the UK. Look up exact GOC numbers, search by name, specialty, company, or postcode, or export the full business register.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"mode\": {\n      \"title\": \"What do you want to collect?\",\n      \"type\": \"string\",\n      \"description\": \"Choose the task. `registrant` searches individuals by name, register type, specialty, address, postcode, or GOC number. `bodyCorporate` searches registered businesses. `byGocNumber` checks a list of exact GOC numbers and detects registrants vs businesses automatically. `allBusinesses` exports the full registered-business list (~3,000 records).\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"registrant\",\n        \"bodyCorporate\",\n        \"byGocNumber\",\n        \"allBusinesses\"\n      ],\n      \"enumTitles\": [\n        \"Search individual registrants (name, register type, specialty, location)\",\n        \"Search optical businesses (company name, GOC number, location)\",\n        \"Look up exact GOC number(s) — registrants and/or businesses\",\n        \"All registered optical businesses (full business register, list-only)\"\n      ],\n      \"default\": \"registrant\",\n      \"prefill\": \"byGocNumber\"\n    },\n    \"surnames\": {\n      \"title\": \"Surnames (one or more)\",\n      \"type\": \"array\",\n      \"description\": \"One or more surnames to search. Each surname runs its own search and results are merged without duplicates. Leave empty if you search by company, register type, specialty, address, postcode, or GOC number instead. Latin-script values only: the GOC search ignores non-Latin text.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": [],\n      \"sectionCaption\": \"Search filters\",\n      \"sectionDescription\": \"Provide at least one filter for the search modes. Multiple surnames or addresses run separate searches.\"\n    },\n    \"forename\": {\n      \"title\": \"First name contains\",\n      \"type\": \"string\",\n      \"description\": \"Optional first-name filter applied to every surname search in registrant mode.\",\n      \"editor\": \"textfield\",\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"soundsLikeSurname\": {\n      \"title\": \"Sounds-like surname matching\",\n      \"type\": \"boolean\",\n      \"description\": \"Match surnames phonetically (e.g. 'Smyth' also finds 'Smith') instead of as a literal substring.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"registerType\": {\n      \"title\": \"Registered as\",\n      \"type\": \"string\",\n      \"description\": \"Restrict registrant searches to one registration category.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"\",\n        \"Optometrist\",\n        \"Dispensing Optician\",\n        \"Student Optometrist\",\n        \"Student Dispensing Optician\",\n        \"Dispensing Optician, Student Optometrist\",\n        \"Student Dispensing Optician, Student Optometrist\"\n      ],\n      \"enumTitles\": [\n        \"Any register type\",\n        \"Optometrist\",\n        \"Dispensing Optician\",\n        \"Student Optometrist\",\n        \"Student Dispensing Optician\",\n        \"Dispensing Optician + Student Optometrist\",\n        \"Student Dispensing Optician + Student Optometrist\"\n      ],\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"speciality\": {\n      \"title\": \"Specialty\",\n      \"type\": \"string\",\n      \"description\": \"Restrict registrant searches to one specialty annotation.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"\",\n        \"Additional supply\",\n        \"Contact lens\",\n        \"Independent prescribing\",\n        \"Supplementary prescribing\"\n      ],\n      \"enumTitles\": [\n        \"Any specialty\",\n        \"Additional supply\",\n        \"Contact lens\",\n        \"Independent prescribing\",\n        \"Supplementary prescribing\"\n      ],\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"addresses\": {\n      \"title\": \"Address or town contains (one or more)\",\n      \"type\": \"array\",\n      \"description\": \"One or more address, town, or postcode-area text searches for registrant mode. Each value runs its own search and results are merged. Latin-script values only.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"companyNames\": {\n      \"title\": \"Company names (one or more)\",\n      \"type\": \"array\",\n      \"description\": \"One or more optical-business names to search in bodyCorporate mode. Each value runs its own search and results are merged. Latin-script values only.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"gocNumbers\": {\n      \"title\": \"GOC number(s)\",\n      \"type\": \"array\",\n      \"description\": \"Exact GOC registration numbers, e.g. 01-12294, D-9396, SO-44231, CO-4310. In byGocNumber mode this is the whole lookup list (registrants and businesses can be mixed). Missing hyphens, lowercase, and a missing leading zero are normalised. In search modes the numbers are combined (AND) with the other filters.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": [\n        \"01-12294\"\n      ]\n    },\n    \"postcode\": {\n      \"title\": \"Postcode for a distance search\",\n      \"type\": \"string\",\n      \"description\": \"UK postcode or outward code (SW1A 1AA or EH1) used with radiusMiles. Works for registrant and business searches. Only real postcodes return results.\",\n      \"editor\": \"textfield\",\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"radiusMiles\": {\n      \"title\": \"Distance radius (miles)\",\n      \"type\": \"integer\",\n      \"description\": \"Distance in miles from the postcode. Only used when postcode is set.\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"default\": 50,\n      \"prefill\": 50\n    },\n    \"sortBy\": {\n      \"title\": \"Sort order (registrant searches)\",\n      \"type\": \"string\",\n      \"description\": \"Sort registrant results by surname. The GOC register ignores this for business searches.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"\",\n        \"Registrant Surname (a - z)\",\n        \"Registrant Surname (z - a)\"\n      ],\n      \"enumTitles\": [\n        \"Default register order\",\n        \"Surname A-Z\",\n        \"Surname Z-A\"\n      ],\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"fetchDetails\": {\n      \"title\": \"Fetch full profile details\",\n      \"type\": \"boolean\",\n      \"description\": \"Visit each result's detail page to capture qualifications, town, every practice address, postcodes, and FTP decision documents. Slower and dependent on the GOC's flaky detail pages; disable for a fast list-only run. Ignored in allBusinesses mode, which is always list-only.\",\n      \"default\": true,\n      \"prefill\": false,\n      \"sectionCaption\": \"Output options\",\n      \"sectionDescription\": \"Control detail enrichment and the size of the run.\"\n    },\n    \"maxDetailedItems\": {\n      \"title\": \"Maximum records with full details\",\n      \"type\": \"integer\",\n      \"description\": \"Only used when fetchDetails is on. The first N records are enriched; the rest are emitted with list-level fields. Keep this modest for large runs. Ignored in allBusinesses mode.\",\n      \"minimum\": 1,\n      \"maximum\": 3000,\n      \"default\": 2000,\n      \"prefill\": 1\n    },\n    \"maxItems\": {\n      \"title\": \"Maximum records\",\n      \"type\": \"integer\",\n      \"description\": \"Hard cap on records emitted across all searches. The registrant register is about 33,359 rows at 5 per page, so a complete registrant export cannot finish in one default run. The full business register is about 2,973 rows and allBusinesses is list-only.\",\n      \"minimum\": 1,\n      \"maximum\": 25000,\n      \"default\": 50,\n      \"prefill\": 1\n    }\n  },\n  \"required\": [\n    \"mode\"\n  ]\n}",
    "readme": "# GOC Optician & Optometrist Register Scraper\r\n\r\nSearch and export the General Optical Council's (GOC) public **Specialist Register** at [str.optical.org](https://str.optical.org) — the statutory register of every optometrist, dispensing optician, student optometrist/student dispensing optician, and registered optical business permitted to practise in the UK. No login, API key, cookie, or proxy is required. The actor selects its own network route automatically; you do not configure a proxy.\r\n\r\n## What this actor does\r\n\r\n- **Search individual registrants** by surname, first name, register type, specialty, address/town, postcode radius, or exact GOC number.\r\n- **Search optical businesses** by company name, GOC number, or postcode radius.\r\n- **Check a list of exact GOC numbers** — registrants and businesses can be mixed in one list; the actor detects the prefix, batches the lookups, and reports numbers that were not found.\r\n- **Export the full registered-business list** (~3,000 businesses) in one run.\r\n- **Enrich with profile data** — qualifications, town, every published practice address, practice postcodes, and FTP decision PDF links.\r\n- **Use multiple surnames, addresses, or company names** in one run; each value is searched separately and results are de-duplicated by GOC number.\r\n- **Omit empty fields** — every emitted field carries real, non-null data.\r\n\r\n## Modes\r\n\r\n| Mode | Use it for |\r\n|---|---|\r\n| `registrant` | Individuals: name, register type, specialty, address, postcode radius, GOC number |\r\n| `bodyCorporate` | Registered optical businesses: company name, GOC number, postcode radius |\r\n| `byGocNumber` | Bulk verification of exact GOC numbers; registrants and businesses mixed |\r\n| `allBusinesses` | Full registered-business export (~3,000 rows) |\r\n\r\n`registrant` and `bodyCorporate` need at least one search filter. `byGocNumber` needs `gocNumbers`. `allBusinesses` needs no filter.\r\n\r\n## Output fields\r\n\r\n### Registrant records (`recordType: \"registrant\"`)\r\n\r\n| Field | Description |\r\n|---|---|\r\n| `gocNumber` | Official GOC registration number, e.g. `01-12294`, `D-9396` |\r\n| `name` | Full registered name |\r\n| `registerType` | `Optometrist`, `Dispensing Optician`, `Student Optometrist`, etc. |\r\n| `registrationStatus` | `Registered`, `Registered with Conditions`, `Suspended` |\r\n| `registrationDate` | Date of most recent registration, ISO `yyyy-mm-dd` |\r\n| `specialities` | Raw specialty text, e.g. `Contact Lens Specialty` |\r\n| `specialityList` | Normalised specialty values, e.g. `[\"Contact lens\"]` |\r\n| `specialityCount` | Number of specialty annotations |\r\n| `qualifications` | Professional qualifications, when published (detail page) |\r\n| `town` | Registered town (detail page) |\r\n| `practiceAddresses` | First published practice address (detail page) |\r\n| `otherPracticeAddresses` | Additional practice addresses (detail page) |\r\n| `practiceAddressCount` | Total published practice addresses |\r\n| `practicePostcodes` | UK postcodes extracted from all practice addresses |\r\n| `primaryPracticePostcode` | First practice postcode |\r\n| `ftpDecisions` | FTP decision title(s), separated by `; ` |\r\n| `ftpDecisionDocuments` | `{title, url}` for each published FTP decision PDF |\r\n| `ftpDecisionCount` | Number of published FTP decisions |\r\n| `hasFtpDecision` | `true` when an FTP decision is published |\r\n| `hasConditions` | `true` when the status contains `Conditions` |\r\n| `isSuspended` | `true` when the status contains `Suspended` |\r\n| `isStudent` | `true` for student register types |\r\n| `searchTerm` | The surname/address that found the record, when the run used more than one search term |\r\n| `sourceUrl` | Canonical GOC register profile URL |\r\n| `recordType` | `\"registrant\"` |\r\n| `scrapedAt` | UTC ISO timestamp |\r\n\r\n### Body corporate records (`recordType: \"bodyCorporate\"`)\r\n\r\nThe same common fields where applicable, with `companyName` instead of `name`. `registerType`, `specialities`, and `qualifications` are omitted because the GOC does not publish them for businesses.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `mode` | select | `registrant` | `registrant`, `bodyCorporate`, `byGocNumber`, or `allBusinesses` |\r\n| `surnames` | array | `[]` | One or more surnames (registrant mode) |\r\n| `forename` | string | – | First-name contains filter (registrant mode) |\r\n| `soundsLikeSurname` | bool | `false` | Phonetic surname matching |\r\n| `registerType` | select | – | Registration category |\r\n| `speciality` | select | – | Specialty annotation |\r\n| `addresses` | array | `[]` | Address/town text searches (registrant mode) |\r\n| `companyNames` | array | `[]` | Company-name searches (business mode) |\r\n| `gocNumbers` | array | `[]` | Exact GOC numbers; mixed registrant/business in `byGocNumber` |\r\n| `postcode` | string | – | UK postcode or outward code for a distance search |\r\n| `radiusMiles` | int | `50` | Radius in miles (1–100) |\r\n| `sortBy` | select | – | Surname A-Z or Z-A (registrant searches) |\r\n| `fetchDetails` | bool | `true` | Fetch profile details; off is much faster. Ignored in `allBusinesses`, which is always list-only |\r\n| `maxDetailedItems` | int | `2000` | Cap on records with detail enrichment |\r\n| `maxItems` | int | `50` | Hard cap on emitted records (1–25,000) |\r\n\r\nLegacy tasks that send `surname`, `companyName`, or `address` still work: those keys are mapped in code to the new arrays.\r\n\r\n### Examples\r\n\r\nFind optometrists named Smith or Jones, with full details:\r\n\r\n```json\r\n{\r\n  \"mode\": \"registrant\",\r\n  \"surnames\": [\"Smith\", \"Jones\"],\r\n  \"registerType\": \"Optometrist\",\r\n  \"fetchDetails\": true,\r\n  \"maxDetailedItems\": 100\r\n}\r\n```\r\n\r\nCheck a mixed list of GOC numbers:\r\n\r\n```json\r\n{\r\n  \"mode\": \"byGocNumber\",\r\n  \"gocNumbers\": [\"01-12294\", \"D-9396\", \"CO-4310\"],\r\n  \"fetchDetails\": false,\r\n  \"maxItems\": 10\r\n}\r\n```\r\n\r\nFind contact-lens dispensing opticians near Edinburgh:\r\n\r\n```json\r\n{\r\n  \"mode\": \"registrant\",\r\n  \"speciality\": \"Contact lens\",\r\n  \"postcode\": \"EH1\",\r\n  \"radiusMiles\": 25,\r\n  \"fetchDetails\": true\r\n}\r\n```\r\n\r\nExport every registered optical business:\r\n\r\n```json\r\n{\r\n  \"mode\": \"allBusinesses\",\r\n  \"fetchDetails\": false,\r\n  \"maxItems\": 3000\r\n}\r\n```\r\n\r\n## How it works\r\n\r\nThe actor sends the same JSON search request as the public register (`POST /registrant/list`) and parses the returned HTML fragment. Detail enrichment fetches `GET /Registrant/{guid}`. FTP decision links point at public PDFs on `mygoc.azurewebsites.net`.\r\n\r\nThe register returns 5 results per page. Large runs are bounded by `maxItems` and the platform timeout. The actor streams records, retries transient list errors, caps detail fetches, and falls back to list-only records when the detail pages keep failing.\r\n\r\n## Limits and accuracy\r\n\r\n- **The GOC register is live.** Counts and records change between runs.\r\n- **Detail pages are flaky.** The GOC's own detail pages intermittently return HTTP 500 or take tens of seconds. The actor uses short timeouts, a circuit breaker, and list-only fallback, so a run can still complete with list-level fields for the records whose detail page failed.\r\n- **A full registrant export does not fit one default run.** The register returned 33,359 registrants on 2026-11-05 at a fixed 5 records per page: 6,672 page requests. At the measured cloud rate of about 1.36 s/page that is roughly 151 minutes, far above the default 3,600 s timeout. Even one register type (`Optometrist`, 19,501) is 3,901 pages, about 88 minutes. Raising `maxItems` does not remove the page ceiling, and the actor does not ship a chunk/resume cursor; use narrow filters or accept a partial export. The full business register is about 2,973 rows (595 pages, roughly 13.5 minutes) and `allBusinesses` is deliberately list-only.\r\n- **No contact details are published.** The GOC register does not expose email, phone, website, gender, languages, home visits, registration expiry, first-registration date, or conditions text. Those fields are not emitted.\r\n- **No status filter exists upstream.** The actor returns the raw `registrationStatus` and the derived `hasConditions`/`isSuspended` flags.\r\n- **Non-Latin search text is rejected.** The GOC search silently ignores CJK/emoji/non-Latin text and would return the whole register; the actor refuses those values before sending a request.\r\n- **Body corporate sorting is ignored upstream.** `sortBy` affects registrant searches only.\r\n- **Historical/erased registrants are not in the public search.** They cannot be retrieved from this source.\r\n\r\n## FAQ\r\n\r\n**Do I need cookies or an API key?** No. The register is public and the actor uses the same public endpoint as the website.\r\n\r\n**Why are some fields missing?** Qualifications, town, practice addresses, and FTP decisions are published conditionally. `fetchDetails` must be on for those fields, except in `allBusinesses`, which is list-only. Detail pages can also fail; the actor then emits list-level fields for the affected records. Empty values are omitted, never emitted as `null` or `\"None\"`.\r\n\r\n**What does `ftpDecisions` mean?** Fitness to Practise — a published regulatory decision against the registrant or business. Most records have none.\r\n\r\n**Can I combine filters?** Yes. Surnames, address text, register type, specialty, and postcode are combined with AND on the register. Multiple surnames or addresses run separate searches and are merged.\r\n\r\n**Why did my GOC number not appear?** `byGocNumber` reports missing numbers in the run status. A number that is not on the current register, or is formatted in a way the actor cannot normalise, will not return a record.\r\n\r\n**Why does a `sourceUrl` sometimes show a server error?** It points at the live GOC page, which is intermittently unavailable. Reload it; the scraped data is unaffected.\r\n\r\n## Data source\r\n\r\nGeneral Optical Council public Specialist Register: https://str.optical.org. This actor is independent and not affiliated with the GOC.\r\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "goc-optician-and-optometrist-register-scraper",
      "title": "GOC Optician & Optometrist Register Scraper",
      "description": "Search the General Optical Council's Specialist Register (str.optical.org) for UK optometrists, dispensing opticians, students and optical businesses - by name, exact GOC number, specialty, company or postcode radius, with practice addresses and FTP decision documents.",
      "version": "2.0",
      "buildTag": "latest",
      "dockerfile": "./Dockerfile",
      "readme": "# GOC Optician & Optometrist Register Scraper\r\n\r\nSearch and export the General Optical Council's (GOC) public **Specialist Register** at [str.optical.org](https://str.optical.org) — the statutory register of every optometrist, dispensing optician, student optometrist/student dispensing optician, and registered optical business permitted to practise in the UK. No login, API key, cookie, or proxy is required. The actor selects its own network route automatically; you do not configure a proxy.\r\n\r\n## What this actor does\r\n\r\n- **Search individual registrants** by surname, first name, register type, specialty, address/town, postcode radius, or exact GOC number.\r\n- **Search optical businesses** by company name, GOC number, or postcode radius.\r\n- **Check a list of exact GOC numbers** — registrants and businesses can be mixed in one list; the actor detects the prefix, batches the lookups, and reports numbers that were not found.\r\n- **Export the full registered-business list** (~3,000 businesses) in one run.\r\n- **Enrich with profile data** — qualifications, town, every published practice address, practice postcodes, and FTP decision PDF links.\r\n- **Use multiple surnames, addresses, or company names** in one run; each value is searched separately and results are de-duplicated by GOC number.\r\n- **Omit empty fields** — every emitted field carries real, non-null data.\r\n\r\n## Modes\r\n\r\n| Mode | Use it for |\r\n|---|---|\r\n| `registrant` | Individuals: name, register type, specialty, address, postcode radius, GOC number |\r\n| `bodyCorporate` | Registered optical businesses: company name, GOC number, postcode radius |\r\n| `byGocNumber` | Bulk verification of exact GOC numbers; registrants and businesses mixed |\r\n| `allBusinesses` | Full registered-business export (~3,000 rows) |\r\n\r\n`registrant` and `bodyCorporate` need at least one search filter. `byGocNumber` needs `gocNumbers`. `allBusinesses` needs no filter.\r\n\r\n## Output fields\r\n\r\n### Registrant records (`recordType: \"registrant\"`)\r\n\r\n| Field | Description |\r\n|---|---|\r\n| `gocNumber` | Official GOC registration number, e.g. `01-12294`, `D-9396` |\r\n| `name` | Full registered name |\r\n| `registerType` | `Optometrist`, `Dispensing Optician`, `Student Optometrist`, etc. |\r\n| `registrationStatus` | `Registered`, `Registered with Conditions`, `Suspended` |\r\n| `registrationDate` | Date of most recent registration, ISO `yyyy-mm-dd` |\r\n| `specialities` | Raw specialty text, e.g. `Contact Lens Specialty` |\r\n| `specialityList` | Normalised specialty values, e.g. `[\"Contact lens\"]` |\r\n| `specialityCount` | Number of specialty annotations |\r\n| `qualifications` | Professional qualifications, when published (detail page) |\r\n| `town` | Registered town (detail page) |\r\n| `practiceAddresses` | First published practice address (detail page) |\r\n| `otherPracticeAddresses` | Additional practice addresses (detail page) |\r\n| `practiceAddressCount` | Total published practice addresses |\r\n| `practicePostcodes` | UK postcodes extracted from all practice addresses |\r\n| `primaryPracticePostcode` | First practice postcode |\r\n| `ftpDecisions` | FTP decision title(s), separated by `; ` |\r\n| `ftpDecisionDocuments` | `{title, url}` for each published FTP decision PDF |\r\n| `ftpDecisionCount` | Number of published FTP decisions |\r\n| `hasFtpDecision` | `true` when an FTP decision is published |\r\n| `hasConditions` | `true` when the status contains `Conditions` |\r\n| `isSuspended` | `true` when the status contains `Suspended` |\r\n| `isStudent` | `true` for student register types |\r\n| `searchTerm` | The surname/address that found the record, when the run used more than one search term |\r\n| `sourceUrl` | Canonical GOC register profile URL |\r\n| `recordType` | `\"registrant\"` |\r\n| `scrapedAt` | UTC ISO timestamp |\r\n\r\n### Body corporate records (`recordType: \"bodyCorporate\"`)\r\n\r\nThe same common fields where applicable, with `companyName` instead of `name`. `registerType`, `specialities`, and `qualifications` are omitted because the GOC does not publish them for businesses.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `mode` | select | `registrant` | `registrant`, `bodyCorporate`, `byGocNumber`, or `allBusinesses` |\r\n| `surnames` | array | `[]` | One or more surnames (registrant mode) |\r\n| `forename` | string | – | First-name contains filter (registrant mode) |\r\n| `soundsLikeSurname` | bool | `false` | Phonetic surname matching |\r\n| `registerType` | select | – | Registration category |\r\n| `speciality` | select | – | Specialty annotation |\r\n| `addresses` | array | `[]` | Address/town text searches (registrant mode) |\r\n| `companyNames` | array | `[]` | Company-name searches (business mode) |\r\n| `gocNumbers` | array | `[]` | Exact GOC numbers; mixed registrant/business in `byGocNumber` |\r\n| `postcode` | string | – | UK postcode or outward code for a distance search |\r\n| `radiusMiles` | int | `50` | Radius in miles (1–100) |\r\n| `sortBy` | select | – | Surname A-Z or Z-A (registrant searches) |\r\n| `fetchDetails` | bool | `true` | Fetch profile details; off is much faster. Ignored in `allBusinesses`, which is always list-only |\r\n| `maxDetailedItems` | int | `2000` | Cap on records with detail enrichment |\r\n| `maxItems` | int | `50` | Hard cap on emitted records (1–25,000) |\r\n\r\nLegacy tasks that send `surname`, `companyName`, or `address` still work: those keys are mapped in code to the new arrays.\r\n\r\n### Examples\r\n\r\nFind optometrists named Smith or Jones, with full details:\r\n\r\n```json\r\n{\r\n  \"mode\": \"registrant\",\r\n  \"surnames\": [\"Smith\", \"Jones\"],\r\n  \"registerType\": \"Optometrist\",\r\n  \"fetchDetails\": true,\r\n  \"maxDetailedItems\": 100\r\n}\r\n```\r\n\r\nCheck a mixed list of GOC numbers:\r\n\r\n```json\r\n{\r\n  \"mode\": \"byGocNumber\",\r\n  \"gocNumbers\": [\"01-12294\", \"D-9396\", \"CO-4310\"],\r\n  \"fetchDetails\": false,\r\n  \"maxItems\": 10\r\n}\r\n```\r\n\r\nFind contact-lens dispensing opticians near Edinburgh:\r\n\r\n```json\r\n{\r\n  \"mode\": \"registrant\",\r\n  \"speciality\": \"Contact lens\",\r\n  \"postcode\": \"EH1\",\r\n  \"radiusMiles\": 25,\r\n  \"fetchDetails\": true\r\n}\r\n```\r\n\r\nExport every registered optical business:\r\n\r\n```json\r\n{\r\n  \"mode\": \"allBusinesses\",\r\n  \"fetchDetails\": false,\r\n  \"maxItems\": 3000\r\n}\r\n```\r\n\r\n## How it works\r\n\r\nThe actor sends the same JSON search request as the public register (`POST /registrant/list`) and parses the returned HTML fragment. Detail enrichment fetches `GET /Registrant/{guid}`. FTP decision links point at public PDFs on `mygoc.azurewebsites.net`.\r\n\r\nThe register returns 5 results per page. Large runs are bounded by `maxItems` and the platform timeout. The actor streams records, retries transient list errors, caps detail fetches, and falls back to list-only records when the detail pages keep failing.\r\n\r\n## Limits and accuracy\r\n\r\n- **The GOC register is live.** Counts and records change between runs.\r\n- **Detail pages are flaky.** The GOC's own detail pages intermittently return HTTP 500 or take tens of seconds. The actor uses short timeouts, a circuit breaker, and list-only fallback, so a run can still complete with list-level fields for the records whose detail page failed.\r\n- **A full registrant export does not fit one default run.** The register returned 33,359 registrants on 2026-11-05 at a fixed 5 records per page: 6,672 page requests. At the measured cloud rate of about 1.36 s/page that is roughly 151 minutes, far above the default 3,600 s timeout. Even one register type (`Optometrist`, 19,501) is 3,901 pages, about 88 minutes. Raising `maxItems` does not remove the page ceiling, and the actor does not ship a chunk/resume cursor; use narrow filters or accept a partial export. The full business register is about 2,973 rows (595 pages, roughly 13.5 minutes) and `allBusinesses` is deliberately list-only.\r\n- **No contact details are published.** The GOC register does not expose email, phone, website, gender, languages, home visits, registration expiry, first-registration date, or conditions text. Those fields are not emitted.\r\n- **No status filter exists upstream.** The actor returns the raw `registrationStatus` and the derived `hasConditions`/`isSuspended` flags.\r\n- **Non-Latin search text is rejected.** The GOC search silently ignores CJK/emoji/non-Latin text and would return the whole register; the actor refuses those values before sending a request.\r\n- **Body corporate sorting is ignored upstream.** `sortBy` affects registrant searches only.\r\n- **Historical/erased registrants are not in the public search.** They cannot be retrieved from this source.\r\n\r\n## FAQ\r\n\r\n**Do I need cookies or an API key?** No. The register is public and the actor uses the same public endpoint as the website.\r\n\r\n**Why are some fields missing?** Qualifications, town, practice addresses, and FTP decisions are published conditionally. `fetchDetails` must be on for those fields, except in `allBusinesses`, which is list-only. Detail pages can also fail; the actor then emits list-level fields for the affected records. Empty values are omitted, never emitted as `null` or `\"None\"`.\r\n\r\n**What does `ftpDecisions` mean?** Fitness to Practise — a published regulatory decision against the registrant or business. Most records have none.\r\n\r\n**Can I combine filters?** Yes. Surnames, address text, register type, specialty, and postcode are combined with AND on the register. Multiple surnames or addresses run separate searches and are merged.\r\n\r\n**Why did my GOC number not appear?** `byGocNumber` reports missing numbers in the run status. A number that is not on the current register, or is formatted in a way the actor cannot normalise, will not return a record.\r\n\r\n**Why does a `sourceUrl` sometimes show a server error?** It points at the live GOC page, which is intermittently unavailable. Reload it; the scraped data is unaffected.\r\n\r\n## Data source\r\n\r\nGeneral Optical Council public Specialist Register: https://str.optical.org. This actor is independent and not affiliated with the GOC.\r\n",
      "input": {
        "title": "GOC Optician & Optometrist Register Scraper Input",
        "description": "Search the General Optical Council's public Specialist Register (str.optical.org) for registered optometrists, dispensing opticians, students, and optical businesses across the UK. Look up exact GOC numbers, search by name, specialty, company, or postcode, or export the full business register.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "mode": {
            "title": "What do you want to collect?",
            "type": "string",
            "description": "Choose the task. `registrant` searches individuals by name, register type, specialty, address, postcode, or GOC number. `bodyCorporate` searches registered businesses. `byGocNumber` checks a list of exact GOC numbers and detects registrants vs businesses automatically. `allBusinesses` exports the full registered-business list (~3,000 records).",
            "editor": "select",
            "enum": [
              "registrant",
              "bodyCorporate",
              "byGocNumber",
              "allBusinesses"
            ],
            "enumTitles": [
              "Search individual registrants (name, register type, specialty, location)",
              "Search optical businesses (company name, GOC number, location)",
              "Look up exact GOC number(s) — registrants and/or businesses",
              "All registered optical businesses (full business register, list-only)"
            ],
            "default": "registrant",
            "prefill": "byGocNumber"
          },
          "surnames": {
            "title": "Surnames (one or more)",
            "type": "array",
            "description": "One or more surnames to search. Each surname runs its own search and results are merged without duplicates. Leave empty if you search by company, register type, specialty, address, postcode, or GOC number instead. Latin-script values only: the GOC search ignores non-Latin text.",
            "editor": "stringList",
            "default": [],
            "prefill": [],
            "sectionCaption": "Search filters",
            "sectionDescription": "Provide at least one filter for the search modes. Multiple surnames or addresses run separate searches."
          },
          "forename": {
            "title": "First name contains",
            "type": "string",
            "description": "Optional first-name filter applied to every surname search in registrant mode.",
            "editor": "textfield",
            "default": "",
            "prefill": ""
          },
          "soundsLikeSurname": {
            "title": "Sounds-like surname matching",
            "type": "boolean",
            "description": "Match surnames phonetically (e.g. 'Smyth' also finds 'Smith') instead of as a literal substring.",
            "default": false,
            "prefill": false
          },
          "registerType": {
            "title": "Registered as",
            "type": "string",
            "description": "Restrict registrant searches to one registration category.",
            "editor": "select",
            "enum": [
              "",
              "Optometrist",
              "Dispensing Optician",
              "Student Optometrist",
              "Student Dispensing Optician",
              "Dispensing Optician, Student Optometrist",
              "Student Dispensing Optician, Student Optometrist"
            ],
            "enumTitles": [
              "Any register type",
              "Optometrist",
              "Dispensing Optician",
              "Student Optometrist",
              "Student Dispensing Optician",
              "Dispensing Optician + Student Optometrist",
              "Student Dispensing Optician + Student Optometrist"
            ],
            "default": "",
            "prefill": ""
          },
          "speciality": {
            "title": "Specialty",
            "type": "string",
            "description": "Restrict registrant searches to one specialty annotation.",
            "editor": "select",
            "enum": [
              "",
              "Additional supply",
              "Contact lens",
              "Independent prescribing",
              "Supplementary prescribing"
            ],
            "enumTitles": [
              "Any specialty",
              "Additional supply",
              "Contact lens",
              "Independent prescribing",
              "Supplementary prescribing"
            ],
            "default": "",
            "prefill": ""
          },
          "addresses": {
            "title": "Address or town contains (one or more)",
            "type": "array",
            "description": "One or more address, town, or postcode-area text searches for registrant mode. Each value runs its own search and results are merged. Latin-script values only.",
            "editor": "stringList",
            "default": [],
            "prefill": []
          },
          "companyNames": {
            "title": "Company names (one or more)",
            "type": "array",
            "description": "One or more optical-business names to search in bodyCorporate mode. Each value runs its own search and results are merged. Latin-script values only.",
            "editor": "stringList",
            "default": [],
            "prefill": []
          },
          "gocNumbers": {
            "title": "GOC number(s)",
            "type": "array",
            "description": "Exact GOC registration numbers, e.g. 01-12294, D-9396, SO-44231, CO-4310. In byGocNumber mode this is the whole lookup list (registrants and businesses can be mixed). Missing hyphens, lowercase, and a missing leading zero are normalised. In search modes the numbers are combined (AND) with the other filters.",
            "editor": "stringList",
            "default": [],
            "prefill": [
              "01-12294"
            ]
          },
          "postcode": {
            "title": "Postcode for a distance search",
            "type": "string",
            "description": "UK postcode or outward code (SW1A 1AA or EH1) used with radiusMiles. Works for registrant and business searches. Only real postcodes return results.",
            "editor": "textfield",
            "default": "",
            "prefill": ""
          },
          "radiusMiles": {
            "title": "Distance radius (miles)",
            "type": "integer",
            "description": "Distance in miles from the postcode. Only used when postcode is set.",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "prefill": 50
          },
          "sortBy": {
            "title": "Sort order (registrant searches)",
            "type": "string",
            "description": "Sort registrant results by surname. The GOC register ignores this for business searches.",
            "editor": "select",
            "enum": [
              "",
              "Registrant Surname (a - z)",
              "Registrant Surname (z - a)"
            ],
            "enumTitles": [
              "Default register order",
              "Surname A-Z",
              "Surname Z-A"
            ],
            "default": "",
            "prefill": ""
          },
          "fetchDetails": {
            "title": "Fetch full profile details",
            "type": "boolean",
            "description": "Visit each result's detail page to capture qualifications, town, every practice address, postcodes, and FTP decision documents. Slower and dependent on the GOC's flaky detail pages; disable for a fast list-only run. Ignored in allBusinesses mode, which is always list-only.",
            "default": true,
            "prefill": false,
            "sectionCaption": "Output options",
            "sectionDescription": "Control detail enrichment and the size of the run."
          },
          "maxDetailedItems": {
            "title": "Maximum records with full details",
            "type": "integer",
            "description": "Only used when fetchDetails is on. The first N records are enriched; the rest are emitted with list-level fields. Keep this modest for large runs. Ignored in allBusinesses mode.",
            "minimum": 1,
            "maximum": 3000,
            "default": 2000,
            "prefill": 1
          },
          "maxItems": {
            "title": "Maximum records",
            "type": "integer",
            "description": "Hard cap on records emitted across all searches. The registrant register is about 33,359 rows at 5 per page, so a complete registrant export cannot finish in one default run. The full business register is about 2,973 rows and allBusinesses is list-only.",
            "minimum": 1,
            "maximum": 25000,
            "default": 50,
            "prefill": 1
          }
        },
        "required": [
          "mode"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "GOC Optician & Optometrist Register Scraper Output",
        "description": "Registrants and optical businesses from the General Optical Council's public Specialist Register — GOC number, name or company, register type, status, registration date, specialties, qualifications, town, every practice address and postcode, FTP decision documents, and source URL.",
        "properties": {
          "registrants": {
            "type": "string",
            "title": "GOC registrants / businesses",
            "description": "Dataset containing all scraped GOC register records.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "overview": {
              "title": "Optical registrants / businesses",
              "transformation": {
                "fields": [
                  "name",
                  "companyName",
                  "gocNumber",
                  "registerType",
                  "registrationStatus",
                  "registrationDate",
                  "specialities",
                  "qualifications",
                  "town",
                  "practiceAddresses",
                  "ftpDecisions",
                  "hasConditions",
                  "isSuspended",
                  "searchTerm",
                  "sourceUrl"
                ]
              },
              "display": {
                "component": "table",
                "properties": {}
              }
            }
          }
        }
      },
      "environmentVariables": {},
      "defaultRunOptions": {
        "build": "latest",
        "timeoutSecs": 3600,
        "memoryMbytes": 1024
      }
    },
    "buildNumber": "2.0.1",
    "imageDigest": "c4424efca97b006c59f3655ca4e0053647a2580e0ae3f761012ee07c770c26c2",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "2.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.008981111111111112
    }
  }
}