Dataset metadata
Excellent dataset metadata is a relatively low-effort way to demonstrate the transparency which underpins OpenSanctions. Write it considering the perspective of data users ranging from startup software developers and business analysts, to investigative journalists and researchers.
Remember to give the context that people from different countries need to make sense of systems they are not entirely familiar with. Share what you learned when figuring out what a source dataset represents.
Metadata fields are user-facing prose. Knowledge addressed to future maintainers of the crawler — failure modes, source quirks, the purpose of a lookup — belongs in YAML comments instead.
Use the .yml extension.
Title
As close as possible to an official title for what this dataset contains, starting with the name readers use to find it in a sorted list:
- Start with the short English name of the issuing country, never possessive:
Canada Members of Parliament, not "Members of the Canadian Parliament" or "Canada's Members of Parliament". - Datasets issued by international bodies start with the issuer's name or established acronym instead:
EU Financial Sanctions Files (FSF),UN Security Council Consolidated Sanctions,INTERPOL Red Notices. - Keep the agency acronym directly after the country when it identifies the list (
US OFAC Specially Designated Nationals (SDN) List,US SEC Litigation Releases), and keep established list acronyms or original-language names in parentheses (Mongolia Members of the State Great Khural,Slovakia Public Sector Partners Register (Register partnerov verejného sektora)). - If the dataset is a subset of its source data, try to capture that: e.g.
Plural Legislatorsif the Plural portal includes committees but the dataset only captures the legislators.
Summary
A short line (50–90 characters — aim just above the lower bound) shown with the title in search results and listings.
- One plain sentence or fragment, no trailing period.
- Complement the title: add what it doesn't already convey (kind of measure, scope, legal basis) rather than repeating title words.
- No humor, no editorializing.
Description
One to three short paragraphs describing the scope of the dataset. Write for a compliance or domain-expert reader, not an engineer.
- Open with what the dataset covers, and the institutional or legal context a reader from another country needs: what kind of body issues it, under what mandate, what inclusion on the list means.
- Describe scope boundaries: what is included and excluded, current-only vs. historical coverage, any period the dataset is limited to.
- Note significant limitations that affect how a reader should trust or interpret the data — for example a dataset maintained manually because the source is a PDF or behind an access block, or coverage that updates irregularly.
- Write time-agnostic prose (in the
summarytoo): describe the source's ongoing purpose ("Members of parliament, updated after each election"), not the current snapshot ("Members since the 2023 elections").
Keep out of the description:
- Per-record field listings ("records each person with their name, gender and date of birth") — that is visible from the data and its statistics, and drifts as the crawler changes.
- Routine ETL mechanics: fetching, parsing, pagination, lookups.
- Sourcing narration ("the data is sourced from the official website") — that is what
publisheranddata.urlconvey.
Maintainer notes (YAML comments)
Document technical knowledge about the crawler as # comments in the .yml, adjacent to the thing they explain:
- A top-of-file comment block for crawler-level notes: known failure modes and how to handle them, source publication quirks, runbooks for recurring warnings.
- A short comment above each lookup that is not a plain type lookup (
type.country,type.date, …), explaining its structure and purpose: what raw values it matches, and what the crawler does with the result.
Comments record known facts — observed failures, documented source behaviour, decisions that were made — never speculation. Good examples: datasets/cn/sanctions/cn_sanctions.yml (a runbook for handling designation-notice warnings), datasets/fr/tresor/fr_tresor_gels_avoir.yml (one-line annotations on the groups within a large lookup).
Properties
Basics
entry_pointe.g.crawler.py:crawl_peps- the file name, optionally followed by a method name called by the zavodcrawlcommand. Defaults to thecrawler.py:crawlcalling an entry point in the dataset directory.prefix- The prefix used by entity id helpers, e.g.gb-cohorofac- try to make this short but unique across datasets, unless you would like different datasets to intentionally generate overlapping keys. See the entity ID guide for the shape and stability rules that apply to IDs.url- the home page or most authoritative place where someone can read about this particular dataset at its source. E.g If a source publishes 5 different datasets, try to link to the page describing the data actually contained in this dataset.disabled- boolean, defaultfalse. Set totruefor sources that are not available any more or should not be crawled at the moment: no crawl job is deployed and the coverage frequency is forced tonever, but the metadata still gets published.hidden- boolean, defaultfalse. Set totrueto keep the dataset out of the website and other user interfaces while still running and publishing it.
Data Coverage
coveragefrequency- e.g.daily,weekly,monthly,never. This represents how often it is expected that this dataset will be updated. It conveys to users how often to expect updates, and is used to derive a crawling schedule unless one is defined explicitly. House defaults by dataset type:- sanctions and wanted lists:
daily - PEP sources (legislatures, governments):
monthly - company registries and other bulk sources:
weekly - frozen one-off dumps:
never, with an explicitschedule(usually@monthly) so exports stay consistent with FollowTheMoney updates.
- sanctions and wanted lists:
start- The date the dataset was first included in thedefaultcollection — i.e. the date the crawler was added to OpenSanctions. Use today's date when scaffolding a new crawler. A string in the formatYYYY-MM-DD. Do not set this to the date the source data begins covering (e.g. an election date or the start of a parliamentary term).end- The end date of a dataset which covers only a specific period in time, e.g. for a dataset specific to a data dump or parliamentary term. A string in the formatYYYY-MM-DD. Future dates imply an expected end to the maintenance and coverage period of the dataset. Past end dates result in the datasets last_change date being fixed to that date, while its last_exported date remains unchanged.schedule- a cron style schedule defining what time and frequency a crawler should run, e.g30 */6 * * *. The deployed schedule is resolved ascoverage.schedule, thendeploy.schedule, then the mapping offrequency(falling back to daily); the minute is randomized per dataset to spread load, so only pin an exact time when it matters (e.g. right after the source's own publication time).
manual_check- for datasets that need periodic human re-verification, e.g. manually maintained data or a source that changes without machine-readable signals. A maintenance script reports datasets whose check is due and bumpslast_checked.last_checked- quoted string"YYYY-MM-DD"(the exact format is required for the automatic update).interval- number of days between checks.message- what the reviewer should verify.
Publisher
publishername- The publisher's official name. If this is by default in a primary non-english language from the originating country, use that language here, and the english form inpublisher.name_en.name_en- Their name in English, ideally the official form, otherwise a translation.acronym- Add if there's an official acronym, e.g. check in their domain name, footer, about page.description- This can be one to two paragraphs of text. Use the publisher description field to explain to someone from a country other than the publisher who the publisher is, and why they do what they do.url- The home page of their official websitecountry- The Alpha-2 or two-letter ISO 3166-1official-trueif the publisher is an authority overseeing the subject data, generally a government entity releasing their sanctions list or legislator data, otherwisefalse.
Source data
dataurl- The link to a bulk download or API base URL or endpoint - ideally something you can use within the crawler viacontext.data_urlto request the data, and which ideally returns a useful response when followed by dataset users. It's not the end of the world if you make other requests to expand the data available to the crawler.formata string defining the format of the data at that URL, e.g.JSON,HTML,XML. A Zip file containing thousands of YAML files might be more usefully annoted withYAMLthanZIPbecause it conveys the structural syntax of the data.lang- ISO 639-3 code (e.g.deu,slk) of the source's primary language. It is applied as the default language of every statement the crawler emits, so set it when the source is predominantly in one non-English language.
Tags
tags are a controlled vocabulary used to categorize datasets by shared attributes such as legal basis, list type, target country, or sector. They support cross-referencing within specific scopes, such as distinguishing between sanctions, PEPs, and regulatory actions, and enable users to select the most relevant datasets for a given country, sector, or risk category.
Currently, tags cover the following dimensions:
- list type (e.g. list.sanction, list.pep);
- issuer and jurisdiction (e.g. issuer.west, juris.eu);
- target countries (e.g. target.ru, target.us)
- sectors (e.g. sector.financial, sector.maritime)
- risk themes (e.g. risk.klepto).
Tag matching is by exact string, not by prefix: a dataset tagged only list.pep.bulk does not match list.pep. Sub-tags qualify a base tag and should be applied alongside it — list.pep.bulk marks PEP datasets (also tagged list.pep) that are excluded from broad PEP cross-referencing, such as declaration registries and sub-national officeholder lists.
You can find a full overview of available tags here.
Deployment
The deploy section configures the Kubernetes job that runs the crawler in production. It is consumed by the deployment tooling, not by zavod itself. Override the defaults only when a crawler demonstrably needs it:
deploymemory/memory_limit- memory request and limit (defaults700Mi/1600Mi), e.g.2000Mifor crawlers that hold large source files in memory.cpu/cpu_limit- CPU request and limit (defaults200m/1600m).disk/disk_limit- scratch disk (default9Gi) for crawlers that download large source archives.premium-boolean- whether its compute instance may be evicted, restarting the job. Set totruefor jobs running for several hours.schedule- cron schedule; only consulted whencoverage.scheduleis not set.
Continuous Integration
ci_test- boolean, defaulttrue. If true, the crawler is run when its python or yaml is modified in CI. Set to false for extremely slow crawlers, or those that require credentials, and then take extra care when modifying them.
Exports
exports- An array of strings matching the export formats, e.g."targets.nested.json". The default is best for most cases.load_statements- Whether the statements should be loaded to a SQL table after the run. Usuallyfalsefor collections and enrichment targets like company registries, and true for normal datasets and enrichers.resolve- boolean, defaulttrue. Whether entities are resolved to canonical (deduplicated) IDs. Set tofalseonly for special-purpose datasets that must retain their local IDs.full_dataset- the name of the complete dataset a subset is derived from; required for datasets used as local enrichment targets, so that matches can be expanded from the full data.
Date formatting
dates- date formatting used by helpers.apply_date and apply_dates but also accessible via the context for use inhelpers.parse_date. See the date parsing guide for usage patterns and worked examples.formats: Array of date format strings for parsing dates into partial ISO datesmonths: Map where values likeMärzare translated into keys like"3"so that it could then be parsed by a format string like%m
HTTP options
HTTP requests for GET requests are automatically retried for connection and HTTP errors. Some of this retry behaviour can be configured from the dataset metadata if needed.
httpuser_agent: string, defaults to the value of the FTM_USER_AGENT setting. Set a custom value for theUser-Agentheader if needed.timeout: integer in seconds, default60. Connect and read timeout for the context HTTP session. Increase it for sources that are slow to respond.zyte_timeout: integer in seconds, default300. Connect and read timeout for Zyte API requests, which is separate because the timeout covers a whole proxied fetch — exit node selection, browser rendering and Zyte's own ban retries — rather than a single request to the source. Lower it only to fail faster on a source known to be unreachable; timing out below Zyte's own budget discards work it has already done.backoff_factor: float, default1. Scales the exponential backoff.max_retries: integer in seconds, default3retry_methods: List of strings, default['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE']retry_statuses: List of integers of HTTP error codes to retry, default[413, 429, 500, 502, 503, 504].
Validators
Besides the assertions below, zavod validate runs a set of validators over the whole
dataset once it is in the store. They are all enabled by default; the validators block
switches individual ones off for a dataset whose finding has been reviewed and accepted.
Record the reason in a YAML comment — a disabled validator leaves no trace in the run
output.
validatorsentity_reference: boolean, defaulttrue. Warns when an entity-type property references an id that isn't in the dataset, or references an entity whose schema doesn't match the property's declared range (e.g. anOwnership:assetpointing at a plainOrganizationrather than aCompany).
validators:
# Source models owned entities as plain Organizations; upgrading them is tracked in #1234.
entity_reference: false
Data assertions
Data assertions smoke-test the exported dataset against its expected shape. They run
during zavod run; zavod validate applies them to a development run. A failed min
assertion is an error and prevents export, while a failed max assertion emits a warning.
When creating count assertions, base them on a known healthy crawl. Set minima roughly 10–20% below the expected value to allow normal variation, unless the source has a known hard minimum. Set maxima around twice the expected value to leave room for ordinary growth while still catching duplication and runaway parsing.
Maintaining assertion bounds
A violated bound is a signal to explain the latest run, not an instruction to fit the envelope around any output. Compare the run with the last successful statistics and, when needed, its delta or source history.
When a count change is legitimate, move the bound outward from the observed value and round it in the safe direction:
- Lower a failing minimum to about 80% of the observed count.
- Raise a failing maximum to about twice the observed count.
Do not update a bound when the output collapsed, exploded or otherwise cannot be
explained. Repair the crawler or source access instead. For property_fill_rate, stay
within the 0–1 domain and choose a small margin that preserves the assertion's intended
quality signal; count rounding and doubling do not apply.
A basic assertion block can look like this:
assertions:
min:
schema_entities:
Person: 160 # at least 160 Person entities
Position: 30 # at least 30 Position entities
entities_with_prop:
Company:
taxNumber: 10 # at least 10 Companies have a tax number set
max:
schema_entities:
Person: 400 # at most 400 Person entities
Position: 80 # at most 80 Position entities
Assertion types
entity_count asserts on the total number of entities in the dataset.
schema_entities asserts on the number of entities of a given schema.
country_entities asserts on the number of entities associated with a country in any of its properties. All properties with type country are considered (among them the usual suspects such as country, jurisdiction and citizenship). Countries are given as ISO 3166-1 Alpha-2 country codes.
countries asserts on the number of distinct countries expected to appear in the dataset.
entities_with_prop asserts on the number of entities of a given schema that have a given property set.
property_fill_rate asserts on the proportion of entities of a given schema that have a given property set, expressed as a float between 0 and 1.