Skip to content

Adding a Source

Adding a source is the single highest-impact contribution you can make. Most sources take 30–90 minutes end to end.

Two paths, depending on how standard the API is:

  1. Manifest-only — for ordinary REST APIs that fit the generic adapter. Just drop a JSON file in insposearch/sources/ and you're done
  2. Manifest + custom adapter — for APIs that need auth quirks, two-step fetches, response mangling, or CORS proxying. Add a fetch* function in src/fetchers.js and register it in the ADAPTERS map

Both paths share the same manifest schema and test protocol.

1. The manifest

One JSON file per source, dropped in insposearch/sources/. File name matches the id field: example-museum.json.

json
{
  "id": "example-museum",
  "name": "Example Museum",
  "category": "museums",
  "homepage": "https://example.org",
  "apiBase": "https://api.example.org/v1",
  "keyRequired": false,
  "keyParam": "",
  "searchEndpoint": "/search",
  "queryParam": "q",
  "perPage": 40,
  "pageParam": "page",
  "resultsPath": "data.items",
  "mapping": {
    "title": "title",
    "imageUrl": "images.web",
    "thumbUrl": "images.thumbnail",
    "sourceUrl": "links.self",
    "artist": "artist_display",
    "date": "date_display",
    "medium": "medium_display"
  }
}

Required fields

FieldTypeNotes
idstringUnique, kebab-case. Used as the filename and the registry key
namestringHuman-readable display name
categorystringOne of: museums, historical, art-design, photography, nature, maps, fashion, science
homepagestringInstitution homepage URL
apiBasestringBase URL for API calls
searchEndpointstringPath appended to apiBase
queryParamstringName of the search query parameter
resultsPathstringDotted path to the results array in the response
mappingobjectMaps response fields → InspoSearch's normalised schema

Optional fields

FieldDefaultNotes
keyRequiredfalseSet to true and list the source on API Keys in your PR
keyParam""Query parameter name for the key (or leave empty and use a header)
perPage40Results per page
pageParam"page"Pagination parameter name
headers{}Custom headers — use this for Authorization: Bearer … style keys
rateLimit0Minimum milliseconds between requests. Use for APIs that throttle aggressively

The normalised schema

Every result in InspoSearch's grid conforms to this shape. Your mapping just tells the generic adapter how to get there from the source's response.

json
{
  "id": "sourceId-recordId",
  "sourceId": "example-museum",
  "title": "Artwork title",
  "imageUrl": "https://full-resolution-url",
  "thumbUrl": "https://thumbnail-url",
  "sourceUrl": "https://link-to-original-record",
  "artist": "Creator name",
  "date": "Date or range",
  "medium": "Medium / materials",
  "description": "Longer description if available"
}

Anything you can't map, omit. Empty fields are fine; wrong fields aren't.

2. If you need a custom adapter

Go this route when:

  • The API requires a two-step fetch (search returns IDs, then one call per record — like the Met)
  • Response structure has arrays-of-single-element weirdness you need to flatten
  • CORS requires proxying via our image proxy worker
  • Rate limiting needs custom backoff
  • Image URLs are pattern-constructed ({server}/{id}_{secret}.jpg — Flickr)

Write a fetchExampleMuseum(query, opts) function in src/fetchers.js and register it in ADAPTERS. Look at fetchMet, fetchFlickr, or fetchEuropeana as templates. If you also add display metadata, update BADGE_META, SOURCE_META, SOURCE_GROUPS, and SOURCE_DOMAINS in src/state.js.

3. The 3-term test protocol

Every source must pass these three queries before it ships.

TermWhat it tests
landscapeCommon term — should return many diverse results with thumbnails
vermeerSpecific artist — tests relevance, artist metadata, and diacritic handling
quiltNiche term — tests empty / low-result edge cases

For each query, verify:

  • Results load without console errors
  • Thumbnails render (not 404, not broken CORS)
  • Clicking a result opens a valid source URL
  • title, artist, date populate when the source has them
  • Empty or sparse results don't crash the grid or spin forever

Capture screenshots of all three. Attach them to your PR.

4. Test the adapter in isolation

bash
node scripts/test-source.js example-museum

Runs the 3-term protocol programmatically and prints per-term counts plus a sample record. Useful for fast iteration before opening the app.

5. Validate

bash
npm run validate

CI runs this on every PR. It checks manifest structure, category validity, URL formatting, and duplicate IDs. Fix any errors before pushing.

6. PR checklist

  • [ ] Manifest file at insposearch/sources/<id>.json
  • [ ] id is unique and kebab-case
  • [ ] Category is one of the eight valid values
  • [ ] apiBase uses HTTPS
  • [ ] Mapping tested with all three terms
  • [ ] Thumbnails render, source URLs open
  • [ ] npm run validate passes locally
  • [ ] Screenshots of the 3-term test attached to the PR description
  • [ ] Link to the API documentation in the PR description
  • [ ] No API keys committed — if the source needs one, set keyRequired: true and add it to API Keys in the same PR
  • [ ] If custom adapter: fetch* function registered in ADAPTERS, metadata added to BADGE_META / SOURCE_META / SOURCE_GROUPS / SOURCE_DOMAINS

Tips

  • Understand the API first. Use your browser's Network tab or curl with pretty-printed JSON. Read their docs on pagination, rate limits, and image URL construction
  • Favour the highest-quality image URL you can get. Some APIs return both a thumbnail and a full-res — always map the full-res to imageUrl
  • IIIF sources get deep-zoom for free. If the source has IIIF support, prefer the IIIF endpoint — users get pan/zoom with no extra work from you
  • If a source throttles, set rateLimit. Better to queue requests than get the source temporarily disabled by our health tracker
  • Mention CORS issues in your PR. If the API refuses browser requests, flag it — we may need to fetch server-side via the nightly job instead

· AGPL-3.0 · app · github