Nimbryxo Studio

GOC Optician & Optometrist Register Scraper

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.

Input schema

Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.

FieldTypeRequiredDescription
modestringrequiredChoose 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).
surnamesarrayoptionalOne 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.
forenamestringoptionalOptional first-name filter applied to every surname search in registrant mode.
Show all input fields
FieldTypeRequiredDescription
modestringrequiredChoose 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).
surnamesarrayoptionalOne 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.
forenamestringoptionalOptional first-name filter applied to every surname search in registrant mode.
soundsLikeSurnamebooleanoptionalMatch surnames phonetically (e.g. 'Smyth' also finds 'Smith') instead of as a literal substring.
registerTypestringoptionalRestrict registrant searches to one registration category.
specialitystringoptionalRestrict registrant searches to one specialty annotation.
addressesarrayoptionalOne 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.
companyNamesarrayoptionalOne or more optical-business names to search in bodyCorporate mode. Each value runs its own search and results are merged. Latin-script values only.
gocNumbersarrayoptionalExact 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.
postcodestringoptionalUK postcode or outward code (SW1A 1AA or EH1) used with radiusMiles. Works for registrant and business searches. Only real postcodes return results.
radiusMilesintegeroptionalDistance in miles from the postcode. Only used when postcode is set.
sortBystringoptionalSort registrant results by surname. The GOC register ignores this for business searches.
fetchDetailsbooleanoptionalVisit 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.
maxDetailedItemsintegeroptionalOnly 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.
maxItemsintegeroptionalHard 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.

Download the published input schema

View complete JSON
{
  "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.",
  "properties": {
    "addresses": {
      "default": [],
      "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",
      "prefill": [],
      "title": "Address or town contains (one or more)",
      "type": "array"
    },
    "companyNames": {
      "default": [],
      "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",
      "prefill": [],
      "title": "Company names (one or more)",
      "type": "array"
    },
    "fetchDetails": {
      "default": true,
      "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.",
      "prefill": false,
      "sectionCaption": "Output options",
      "sectionDescription": "Control detail enrichment and the size of the run.",
      "title": "Fetch full profile details",
      "type": "boolean"
    },
    "forename": {
      "default": "",
      "description": "Optional first-name filter applied to every surname search in registrant mode.",
      "editor": "textfield",
      "prefill": "",
      "title": "First name contains",
      "type": "string"
    },
    "gocNumbers": {
      "default": [],
      "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",
      "prefill": [
        "01-12294"
      ],
      "title": "GOC number(s)",
      "type": "array"
    },
    "maxDetailedItems": {
      "default": 2000,
      "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.",
      "maximum": 3000,
      "minimum": 1,
      "prefill": 1,
      "title": "Maximum records with full details",
      "type": "integer"
    },
    "maxItems": {
      "default": 50,
      "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.",
      "maximum": 25000,
      "minimum": 1,
      "prefill": 1,
      "title": "Maximum records",
      "type": "integer"
    },
    "mode": {
      "default": "registrant",
      "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)"
      ],
      "prefill": "byGocNumber",
      "title": "What do you want to collect?",
      "type": "string"
    },
    "postcode": {
      "default": "",
      "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",
      "prefill": "",
      "title": "Postcode for a distance search",
      "type": "string"
    },
    "radiusMiles": {
      "default": 50,
      "description": "Distance in miles from the postcode. Only used when postcode is set.",
      "maximum": 100,
      "minimum": 1,
      "prefill": 50,
      "title": "Distance radius (miles)",
      "type": "integer"
    },
    "registerType": {
      "default": "",
      "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"
      ],
      "prefill": "",
      "title": "Registered as",
      "type": "string"
    },
    "sortBy": {
      "default": "",
      "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"
      ],
      "prefill": "",
      "title": "Sort order (registrant searches)",
      "type": "string"
    },
    "soundsLikeSurname": {
      "default": false,
      "description": "Match surnames phonetically (e.g. 'Smyth' also finds 'Smith') instead of as a literal substring.",
      "prefill": false,
      "title": "Sounds-like surname matching",
      "type": "boolean"
    },
    "speciality": {
      "default": "",
      "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"
      ],
      "prefill": "",
      "title": "Specialty",
      "type": "string"
    },
    "surnames": {
      "default": [],
      "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",
      "prefill": [],
      "sectionCaption": "Search filters",
      "sectionDescription": "Provide at least one filter for the search modes. Multiple surnames or addresses run separate searches.",
      "title": "Surnames (one or more)",
      "type": "array"
    }
  },
  "required": [
    "mode"
  ],
  "schemaVersion": 1,
  "title": "GOC Optician & Optometrist Register Scraper Input",
  "type": "object"
}

Output schema

Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.

FieldTypeRequiredDescription
registrantsstringoptionalDataset containing all scraped GOC register records.

Download the published output schema

View complete JSON
{
  "actorOutputSchemaVersion": 1,
  "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": {
      "description": "Dataset containing all scraped GOC register records.",
      "template": "{{links.apiDefaultDatasetUrl}}/items",
      "title": "GOC registrants / businesses",
      "type": "string"
    }
  },
  "title": "GOC Optician & Optometrist Register Scraper Output"
}

Dataset schema

Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.

Download the published dataset schema

View complete JSON
{
  "actorSpecification": 1,
  "views": {
    "overview": {
      "display": {
        "component": "table",
        "properties": {}
      },
      "title": "Optical registrants / businesses",
      "transformation": {
        "fields": [
          "name",
          "companyName",
          "gocNumber",
          "registerType",
          "registrationStatus",
          "registrationDate",
          "specialities",
          "qualifications",
          "town",
          "practiceAddresses",
          "ftpDecisions",
          "hasConditions",
          "isSuspended",
          "searchTerm",
          "sourceUrl"
        ]
      }
    }
  }
}

Key-value schema

Published key-value store definitions, when provided by this build.

This definition is not present in the default published build. See the published README for available documentation.

Published README

Read the full published documentation

GOC Optician & Optometrist Register Scraper

Search and export the General Optical Council's (GOC) public Specialist Register at 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.

What this actor does

  • Search individual registrants by surname, first name, register type, specialty, address/town, postcode radius, or exact GOC number.
  • Search optical businesses by company name, GOC number, or postcode radius.
  • 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.
  • Export the full registered-business list (~3,000 businesses) in one run.
  • Enrich with profile data — qualifications, town, every published practice address, practice postcodes, and FTP decision PDF links.
  • Use multiple surnames, addresses, or company names in one run; each value is searched separately and results are de-duplicated by GOC number.
  • Omit empty fields — every emitted field carries real, non-null data.

Modes

Mode Use it for
registrant Individuals: name, register type, specialty, address, postcode radius, GOC number
bodyCorporate Registered optical businesses: company name, GOC number, postcode radius
byGocNumber Bulk verification of exact GOC numbers; registrants and businesses mixed
allBusinesses Full registered-business export (~3,000 rows)

registrant and bodyCorporate need at least one search filter. byGocNumber needs gocNumbers. allBusinesses needs no filter.

Output fields

Registrant records (recordType: "registrant")

Field Description
gocNumber Official GOC registration number, e.g. 01-12294, D-9396
name Full registered name
registerType Optometrist, Dispensing Optician, Student Optometrist, etc.
registrationStatus Registered, Registered with Conditions, Suspended
registrationDate Date of most recent registration, ISO yyyy-mm-dd
specialities Raw specialty text, e.g. Contact Lens Specialty
specialityList Normalised specialty values, e.g. ["Contact lens"]
specialityCount Number of specialty annotations
qualifications Professional qualifications, when published (detail page)
town Registered town (detail page)
practiceAddresses First published practice address (detail page)
otherPracticeAddresses Additional practice addresses (detail page)
practiceAddressCount Total published practice addresses
practicePostcodes UK postcodes extracted from all practice addresses
primaryPracticePostcode First practice postcode
ftpDecisions FTP decision title(s), separated by ;
ftpDecisionDocuments {title, url} for each published FTP decision PDF
ftpDecisionCount Number of published FTP decisions
hasFtpDecision true when an FTP decision is published
hasConditions true when the status contains Conditions
isSuspended true when the status contains Suspended
isStudent true for student register types
searchTerm The surname/address that found the record, when the run used more than one search term
sourceUrl Canonical GOC register profile URL
recordType "registrant"
scrapedAt UTC ISO timestamp

Body corporate records (recordType: "bodyCorporate")

The 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.

Input

Field Type Default Description
mode select registrant registrant, bodyCorporate, byGocNumber, or allBusinesses
surnames array [] One or more surnames (registrant mode)
forename string – First-name contains filter (registrant mode)
soundsLikeSurname bool false Phonetic surname matching
registerType select – Registration category
speciality select – Specialty annotation
addresses array [] Address/town text searches (registrant mode)
companyNames array [] Company-name searches (business mode)
gocNumbers array [] Exact GOC numbers; mixed registrant/business in byGocNumber
postcode string – UK postcode or outward code for a distance search
radiusMiles int 50 Radius in miles (1–100)
sortBy select – Surname A-Z or Z-A (registrant searches)
fetchDetails bool true Fetch profile details; off is much faster. Ignored in allBusinesses, which is always list-only
maxDetailedItems int 2000 Cap on records with detail enrichment
maxItems int 50 Hard cap on emitted records (1–25,000)

Legacy tasks that send surname, companyName, or address still work: those keys are mapped in code to the new arrays.

Examples

Find optometrists named Smith or Jones, with full details:

{
  "mode": "registrant",
  "surnames": ["Smith", "Jones"],
  "registerType": "Optometrist",
  "fetchDetails": true,
  "maxDetailedItems": 100
}

Check a mixed list of GOC numbers:

{
  "mode": "byGocNumber",
  "gocNumbers": ["01-12294", "D-9396", "CO-4310"],
  "fetchDetails": false,
  "maxItems": 10
}

Find contact-lens dispensing opticians near Edinburgh:

{
  "mode": "registrant",
  "speciality": "Contact lens",
  "postcode": "EH1",
  "radiusMiles": 25,
  "fetchDetails": true
}

Export every registered optical business:

{
  "mode": "allBusinesses",
  "fetchDetails": false,
  "maxItems": 3000
}

How it works

The 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.

The 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.

Limits and accuracy

  • The GOC register is live. Counts and records change between runs.
  • 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.
  • 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.
  • 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.
  • No status filter exists upstream. The actor returns the raw registrationStatus and the derived hasConditions/isSuspended flags.
  • 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.
  • Body corporate sorting is ignored upstream. sortBy affects registrant searches only.
  • Historical/erased registrants are not in the public search. They cannot be retrieved from this source.

FAQ

Do I need cookies or an API key? No. The register is public and the actor uses the same public endpoint as the website.

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".

What does ftpDecisions mean? Fitness to Practise — a published regulatory decision against the registrant or business. Most records have none.

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.

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.

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.

Data source

General Optical Council public Specialist Register: https://str.optical.org. This actor is independent and not affiliated with the GOC.

Python quickstart

Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.

Open the step-by-step Python guide →

Source build UvNLj8gOebSn7KfpY. Documentation is regenerated from the default published build; private drafts are not included.