For museums, galleries, archives and collectors

Publishing API

One data standard for publishing a collection. Map your spreadsheet once, validate as often as you like for free, and keep control of what is public and under which licence.

Schema version 1.0.0 · base URL https://www.artheonmuseum.org

How it works

Your data stays yours and your catalogue is authoritative. You send records matching the schema below. We never ask for your spreadsheet, your collection management system, or a migration project.

Everything arrives as a draft. Nothing becomes public until someone at your institution reviews and publishes it.

  1. Validate your mapping with dry_run — no account needed.
  2. Get an API key from us.
  3. Request upload URLs and send images straight to storage.
  4. Publish records in batches of up to 500.
  5. Review and publish from your dashboard.

Start without an account

A dry run validates records and writes nothing, so you can get the mapping right before you talk to us at all.

curl -X POST https://www.artheonmuseum.org/api/v1/publish -H "Content-Type: application/json" -d '{"dry_run": true, "records": [ ... ]}'

Problems come back per record, naming the field, a stable code, what we received, and what was expected:

{
  "index": 47,
  "external_id": "1998.45",
  "ok": false,
  "errors": [{
    "field": "date.start_year",
    "code": "unparseable",
    "message": "date.start_year must be a whole year as a number. Leave it out if the date is unknown and keep the original text in date.text.",
    "got": "n.d."
  }]
}

A record

Three things are required: external_id, title, and rights.license. Everything else is optional. A sparse record is normal, and far better than an invented one.

{
  "external_id": "1971.86.11",
  "title": "Les Amoureux de Paris",
  "title_lang": "fr",
  "artist": { "display": "Robert Delaunay", "birth_year": 1885, "death_year": 1941 },
  "date": { "text": "1928", "start_year": 1928, "end_year": 1928 },
  "medium": "color lithograph",
  "dimensions": { "text": "23 x 45 cm", "height_cm": 23, "width_cm": 45 },
  "classification": "Print",
  "department": "Modern Prints",
  "identifiers": { "wikidata": "Q28100129" },
  "credit_line": "Gift of Mr. and Mrs. Burton Tremaine",
  "rights": {
    "license": "public-domain",
    "holder": "National Gallery of Art, Washington, D.C.",
    "source_url": "https://www.nga.gov/collection/art-object-page.53045.html"
  },
  "images": [{ "key": "publishers/<your-id>/<uuid>-1971.86.11.tif", "role": "primary" }]
}

The full field list and vocabularies are served as JSON at /api/v1/schema, so your tooling can read them instead of hardcoding them.

external_id is permanent

This is your own accession number, and it is how we recognise a record you have sent before. Publishing the same external_id again updates that record rather than creating a second one, so a re-run after a failure is safe. It also means changing an external_id later orphans the original and creates a duplicate. Choose something stable.

Licensing

rights.license is required and never inferred. Age does not imply public domain, and a museum photograph of a public-domain painting may itself be restricted. If you are unsure, use all-rights-reserved and leave the records as drafts. That is recoverable; publishing under the wrong licence is not.

public-domaincc0cc-bycc-by-sacc-by-ndcc-by-nccc-by-nc-sacc-by-nc-ndall-rights-reserved

Dates that are not simple

Keep your own string in date.text exactly as written. Set numeric years only when you can parse them confidently, and omit them when the date is genuinely unknown.

  • Circa. c. 1650 becomes start_year: 1645, end_year: 1655, circa: true.
  • BCE years are negative. Many systems store 1550–1196 BC as positive 1550 and 1196, which reads as a date running backwards. Use -1550 to -1196.
  • Reproductions are not ranges. A 1903 reproduction of a 1790 print is not a 113-year span. The object is 1903; 1790 goes in date.original_year.
  • Unknown. Keep the text, omit both years.

Images and 3D models

Ask for upload URLs, then send files straight to storage. They never pass through our servers, so a folder of large TIFFs is not a problem.

curl -X POST https://www.artheonmuseum.org/api/v1/uploads -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d '{"files": [{"filename": "1998.45.tif", "content_type": "image/tiff", "size": 41234567}]}'

Then PUT each file to its returned upload_url with the same content type you declared, and use the returned key in the record:

curl -X PUT "UPLOAD_URL_FROM_RESPONSE" -H "Content-Type: image/tiff" --data-binary @1998.45.tif

URLs expire after an hour, so request them in batches as you upload. Accepted types are JPEG, PNG, TIFF, WebP, AVIF and glTF/GLB, up to 512 MB per file. Uploading is not publishing — a file nothing references is simply unused.

Publishing

curl -X POST https://www.artheonmuseum.org/api/v1/publish -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d '{"records": [ ... ]}'

Up to 500 records per request. A batch is all-or-nothing: if any record is invalid, nothing is written, so you can fix and resend without wondering what got through. The response reports how many were created and how many updated.

Verification

A verified publisher sits at the same level as any other institution on Artheon. There is no second tier for material that arrived through this API. Verification claims one thing only: that the account belongs to the named institution. It is not a claim about the authenticity or attribution of any individual work, which remains your scholarship and your credit.

Using an AI assistant

If your data is in spreadsheets with images in folders, an assistant can do the mapping. We publish a skill that walks it through profiling your columns, matching images to records, normalising dates and dimensions, and running the dry-run loop until it is clean. It runs on your machine, so your files stay under your control until you choose to publish.

Getting a key

Write to [email protected] with your institution and roughly how large the collection is. Keys are scoped to your organisation and can be revoked at any time. Until then, you can validate everything with dry_run.

    Send Feedback

    We use this only to reply to your feedback.