Skip to content

How we validate data

When you submit changes to the library directory, an automated system checks your data before it's added to the live dashboard. This ensures that all library records are accurate, complete, and formatted correctly.

What the validation system does

The system performs two levels of checking:

  1. Format check: Verifies that the data is properly structured
  2. Content check: Confirms that all information is complete and correct

Both checks happen automatically and provide instant feedback through GitHub. For optional authority identifiers and direct IIIF endpoints, the build also compares the proposed data with the pull request's target branch. Each added or changed link must have an exact matching row in the public assertion register. The row must include two distinct evidence URLs, check and review dates, a reviewer, and a DMMapp pull-request URL. The script cannot verify the factual relationship or authenticate the reviewer; maintainers do that in the pull request.

For target-type consistency, the assertion validator requires a Wikidata source containing the approved QID on www.wikidata.org, a GeoNames source containing the approved ID on a GeoNames domain, and an IIIF source equal to the direct endpoint. The linked-data build then uses the canonical Wikidata entity URI and GeoNames place URI as relationship targets.

scripts/validate_linked_data_pilot.py checks the 25-record pilot at field level. It requires a decision for every selected candidate, preserves the deliberate-absence control, and matches approved or corrected decisions to the assertion register. A well-formed pending candidate may remain unpublished when the corresponding catalogue field is empty; it cannot silently replace a populated value. A passing check may include pending decisions, so the command reports the pending count and the review packet reports publication state.

Identifiers and IIIF endpoints that were already public when this gate was introduced remain visible while maintainers audit them. They are not automatically approved, and they are not exported as linked-data relationships unless they have a row in the assertion register. The comparison prevents new unreviewed claims from enlarging this backlog.

Institution-name evidence check

The name check reads research/name-evidence.csv. It requires one preferred-name row for every record. Existing unreviewed labels remain pending; every published access_point_title or alternate name needs an exact verified row with a public source and check date. A pull-request history check also requires verified evidence when a preferred name, title, or alternate name is newly published. The checks cannot decide whether the source identifies the correct holding institution. Follow Institution-name research for the editorial review.

Identifier evidence check

The identifier evidence check reads research/identifier-evidence.csv and compares it with docs/assets/data.json. It requires exactly one isil, wikidata_qid, and geonames_id decision for every record.

For a verified decision, it checks that the value is present and identical in data.json, that the direct source is an absolute HTTP(S) URL, and that the date is a real YYYY-MM-DD date that is not in the future. Wikidata decisions also need a public corroborating institution or registry URL.

For an unresolved decision, it checks that no identifier or source URL was added to data.json and that the ledger explains why. It also rejects missing or duplicate rows, unknown record IDs, unknown fields, malformed CSV rows, and invalid status values.

This is a consistency check. It cannot establish that an external authority record describes the right institution or place. Reviewers must assess that match using the linked sources. See Identifier research for the research and review workflow.

IIIF evidence check

The IIIF evidence check reads research/iiif-evidence.csv. It requires one row for every direct Collection or representative Manifest published in data.json, plus every pending endpoint proposal in the linked-data pilot. Published endpoints use verified; unpublished pilot candidates use proposed.

The direct endpoint must be both value and source_url. Each row also needs a distinct, exact public catalogue, institutional, or item URL that corroborates the endpoint's scope, a real check date, and a safe evidence note. The check rejects missing, duplicate, extra, or malformed rows. It runs offline and cannot prove that the external JSON implements IIIF or describes the right resource. A maintainer must inspect both cited sources.

See IIIF endpoint research for the source criteria and review workflow.

When validation runs

The dedicated Data Guardrails workflow checks your data in two situations:

  • Automatically: When you submit a pull request that changes the library data
  • On demand: When you manually request a validation check from the GitHub Actions tab

The deployment workflow repeats the identifier, name, and link-assertion history checks when a release reaches master. This second gate compares the release with the previous master revision. It protects published IDs, aliases, and name evidence even if a change reaches the deployment workflow outside the normal pull-request path.

How the format check works

The first check ensures your data is properly formatted as a JSON file (a structured text format used to store information).

The system verifies:

  • All brackets and braces are correctly matched
  • Commas separate fields properly
  • Text is properly enclosed in quotation marks
  • There are no trailing commas or other syntax errors
  • The file uses standard UTF-8 encoding

Example: Missing comma

{
  "library": "Example"
  "nation": "Country"
}

The system finds the missing comma between fields and alerts you to fix it.

Example: Trailing comma

{
  "library": "Example",
}

Commas should not appear after the last field.

How the content check works

The second check ensures all your data follows the required structure and contains valid information.

The system verifies:

  1. All required fields are present, and no unrecognised fields are included
  2. Every library record must include: ID, name, country, city, website, copyright information, licence category, manuscript count, format support, license type, and aggregator memberships
  3. A record may also include optional fields—ISIL, Wikidata QID, GeoNames ID, IIIF endpoints, link status, and added/last-edited dates—see the Data Structure Guide for the full list
  4. Any field name outside that full list is rejected, so a typo in a field name is caught rather than silently ignored

  5. Data types are correct

  6. Text fields contain text (not numbers)
  7. Yes/No fields use true or false (not the words "true" or "false")
  8. Number fields contain whole numbers

  9. IDs are unique

  10. Every record's ID must be a positive whole number
  11. No two records may share the same ID
  12. The alias registry must contain the current slug for every ID and must not assign one historical slug to two records

  13. Websites are valid

  14. Website addresses follow proper format: https://example.com
  15. URLs start with http:// or https://
  16. Links are reachable and functional

  17. Aggregator memberships are consistent

  18. Every membership must provide both the aggregator name and its website
  19. A library must not list the same aggregator twice
  20. One aggregator name must use the same URL in every record that names it
  21. A library with no membership uses an empty list

  22. Categories use correct values

  23. Manuscript counts must be: "Few", "Dozens", "Hundreds", "Thousands", or "Unknown"
  24. Licence categories must be one of the values listed in the Data Structure Guide
  25. No other values are accepted

Example: Multiple violations

{
  "id": 1,
  "library": "Example Library",
  "nation": "Country",
  "city": "City",
  "website": "not-a-valid-url",
  "iiif": "false",
  "quantity": "Many"
}

Issues the system finds:

  • website: Format is invalid (missing protocol)
  • iiif: Should be true or false, not the word "false"
  • quantity: "Many" is not an allowed value

What you see on GitHub

After you submit changes, GitHub displays the validation results next to your pull request.

Validation passed

You see a green checkmark (✓) and a "Passed" status.

This means: - Your data format is correct - All required information is present - Content meets all standards - Your pull request can be reviewed and merged

Validation failed

You see a red X (✗) and a "Failed" status.

This means: - Your data has one or more errors - The system cannot process your changes until you fix the issues - Click the "Details" link to see what's wrong

Reading error messages

When validation fails, GitHub shows you exactly what needs to be fixed.

Format error

Expecting ',' delimiter: line 45 column 3 (char 1234)

What it means: Line 45 is missing a comma.
How to fix: Go to line 45 in your data and add a comma between fields.

Missing field

record 122 (id: 123): <record>: 'iiif' is a required property

What it means: The record at position 122 (with ID 123) is missing the IIIF field.
How to fix: Add "iiif": true or "iiif": false to that record.

Invalid website

record 455 (id: 456): website: 'not-a-valid-url' does not match '^https?://'

What it means: The website address in the record with ID 456 doesn't start with http:// or https://.
How to fix: Ensure the URL starts with https:// and is a working link. Example: "website": "https://example.com/manuscripts"

Incomplete aggregator information

record 788 (id: 789): aggregators/0/url: 'europeana.eu' does not match '^https?://'

What it means: The first aggregator listed on the record with ID 789 has a URL that doesn't start with http:// or https://.

How to fix: Provide the aggregator's full home page URL, or remove the membership and use an empty list ("aggregators": []).

Duplicate ID

record 583 (id: 42): duplicate id, already used by record 17

What it means: Two records share the same ID (42).
How to fix: Give the new record an ID that isn't already used anywhere in data.json.

Common errors and how to fix them

Missing required fields

Error message: is a required property

What went wrong: You forgot to include one or more required fields.

Example:

{
  "id": 1,
  "library": "Example"
}

Missing: country, city, website, copyright information, licence category, manuscript count, format support, license type, and aggregator memberships.

How to fix: Review the Data Structure Guide and add all required fields to your record.


Text written as "true" or "false" instead of true/false

Error message: must be boolean

What went wrong: You used quotation marks around true or false, making them text instead of yes/no values.

Example:

{
  "iiif": "false"
}

How to fix: Remove quotation marks:

{
  "iiif": false
}


Website URL missing the protocol

Error message: does not match '^https?://'

What went wrong: Your website address doesn't start with http:// or https://.

Example:

{
  "website": "www.example.com"
}

How to fix: Add the protocol:

{
  "website": "https://www.example.com"
}


Manuscript count not in the allowed list

Error message: must be equal to one of the allowed values

What went wrong: You used a value that's not in the approved list.

Example:

{
  "quantity": "Some"
}

Approved values: "Few", "Dozens", "Hundreds", "Thousands", "Unknown"

How to fix: Use one of the five approved values:

{
  "quantity": "Dozens"
}


Aggregator listed twice on one library

Error message: duplicate aggregator 'e-codices' listed more than once

What went wrong: The same aggregator appears twice in one library's list. Names are compared ignoring case and surrounding spaces, so e-codices and E-Codices count as the same aggregator.

Example:

{
  "aggregators": [
    { "name": "e-codices", "url": "https://www.e-codices.unifr.ch" },
    { "name": "E-Codices", "url": "https://www.e-codices.unifr.ch" }
  ]
}

How to fix: Keep one entry per aggregator:

{
  "aggregators": [
    { "name": "e-codices", "url": "https://www.e-codices.unifr.ch" }
  ]
}


One aggregator recorded under two URLs

Error message: aggregator 'e-codices' uses url 'https://e-codices.ch/', but record 12 (id: 13) uses 'https://www.e-codices.unifr.ch'

What went wrong: Another record already names this aggregator with a different URL. The URL is the aggregator's canonical home page, so it must be the same everywhere.

How to fix: Use the same URL the other records use. If the aggregator has genuinely moved, update every record that names it in the same change.


Duplicate ID

Error message: duplicate id, already used by record

What went wrong: You reused an ID that another record in data.json already has.

How to fix: Give your new record a whole number that isn't used anywhere else in the file. IDs don't need to be sequential — gaps are fine.


Unrecognised field

Error message: Additional properties are not allowed

What went wrong: Your record includes a field name that isn't one of the recognised fields (for example, a typo such as webiste instead of website).

How to fix: Check the field name against the Data Structure Guide and correct or remove it.

Manual validation check

To run a validation check on demand:

  1. Go to your GitHub repository
  2. Click the Actions tab
  3. Find Data Guardrails in the workflow list
  4. Click Run workflow
  5. Select your branch
  6. Click Run workflow

The system checks your data and displays results within 1-2 minutes.

When to use manual validation: - Before submitting a pull request to catch issues early - After making changes to schema rules - To verify data on a specific branch without a pull request

Performance

Typical validation time: 20-40 seconds

Cost: Free (included with GitHub)

Preventing validation failures

Before you submit changes

  1. Review the Data Structure Guide to understand all required fields
  2. Check the Update the Dashboard Data guide for step-by-step instructions
  3. Test your website links in a browser before submitting
  4. Verify spelling of library names, cities, and countries

If validation fails

  1. Read the error message carefully—it tells you exactly what's wrong
  2. Click the error details in GitHub to see which record and field have the problem
  3. Compare your data to the examples in the Data Structure Guide
  4. Fix the issue and resubmit
  5. Use the manual validation check to verify before creating a new pull request

Still stuck?

If you can't figure out the error:

  1. Review the "Common errors and how to fix them" section above
  2. Check the Data Structure Guide for field requirements
  3. Look at other records in the database to see examples of correct formatting
  4. Open an issue or contact @Dioscorides for help

Last Updated: August 1, 2026