MetaMuseum Linked Art Activity Stream Profile
Last updated: 2026-07-07
MetaMuseum exposes `/api/activity` as an ActivityStreams 2.0 change feed with an embedded Linked Art projection on each activity item. The feed is designed for downstream collection-data consumers that need incremental synchronization while preserving Linked Art event semantics.
Endpoints
| Endpoint | Purpose |
|---|---|
| `/api/activity` | Global ActivityStreams `OrderedCollectionPage` over local write-audit, deletion, and semantic record activities. |
| `/api/activity/collection` | IIIF Change Discovery-compatible `OrderedCollection` with mandatory `first` and `last` page references. |
| `/api/activity/page/{page}` | Fixed ascending `OrderedCollectionPage` resources for walk-back harvesters. |
| `/api/activity/object/{encodedObjectId}` | Object-scoped `OrderedCollectionPage` for one Linked Art object URI. |
| `/api/activity/readiness` | External-consumer readiness ledger populated by declared feed reads. |
| `/api/activity/subscriptions` | Durable callback subscription registry and verification target for downstream aggregators. |
| `/api/providers/capabilities` | Provider and activity-syndication capability document, including discovery rels, endpoint templates, filters, and callback signing headers. |
All activity endpoints support `GET` and `OPTIONS`, CORS, and `application/activity+json`.
Item Shape
Each `/api/activity` cursor-feed `orderedItems[]` entry is an ActivityStreams activity with:
- `type`: `Create`, `Update`, or `Delete`
- `endTime`: valid UTC synchronization timestamp; original invalid source times are retained under `extensions.sourceEndTime`
- `object`: the affected object or tombstone, including dereferenceable `id`, optional `sourceId`, `activityHref`, and optional `equivalent[]` reconciliation links
- `source.kind`: `audit`, `record-backfill`, `record-semantic`, or `deletion`
- `linkedArt`: a Linked Art JSON-LD projection with `@context: https://linked.art/ns/v1/linked-art.json`; embedded `linkedArt.id` values dereference through `/api/events/{encodedSourceActivityId}`, carry `_complete: false`, and preserve source activity identity in `equivalent[]` when available
For semantic collection events, `linkedArt` preserves event-centric fields such as `type`, `timespan`, `_complete`, names, classifications, object-specific target links (`produced`, `created`, `modified`, `destroyed`, `removed`, `encountered`, `formed`, `dissolved`), `used_specific_object`, and `carried_out_by` when those fields are valid for the activity class. `Set` collection records emit `Creation`/`created` rows, not physical `Production` rows, and record detail responses preserve `member_of` on the member record rather than inventing inverse `member[]` lists on the Set. Provenance wrapper activities also preserve relative `before`/`after` ordering and bundled `part[]` entries such as `Encounter`, `Acquisition`, `RightAcquisition`, `TransferOfCustody`, indeterminate `Transfer`, `Move`, and `Payment`, including nested find/rediscovery, title-transfer, rights-establishment/invalidation, percentage ownership shares, copyright, custody-transfer, unknown-transfer, movement origin/destination, multiple-owner, agent, exchange, loan, theft/loss, and commission/service payment details. Auction activities preserve auction-house/place classifications, auction-of-lot identifiers, lot `Set` `used_specific_object` values, `part_of` links to the overall auction, and provenance purchases `caused_by` the lot auction. Exhibition activities preserve exhibiting classifications, venue and organizer metadata, source descriptions, object-list `Set` links, concept `PropositionalObject` influence, and travelling-exhibition `part_of` links. Conservation coverage keeps condition assessments as `AttributeAssignment` rows with `assigned_property` and `assigned` condition types, separate from conservation `Modification` rows with technique, actor, description, timespan, and shared project `part_of` links. `Birth` and `Death` projections intentionally suppress `carried_out_by` and `used_specific_object`, matching the Linked Art activity-class restrictions.
Default `/api/activity` cursor-feed responses and IIIF Change Discovery pages use the same partner-facing projection: valid UTC `endTime`, dereferenceable MetaMuseum `object.id` values, optional `object.sourceId` with the stored/provider record id when the projected id differs, `object.activityHref`, `source.kind`, and provider metadata. Default responses collapse duplicate rows for the same projected `object.id` to one latest-change harvest item, preferring deletion, audit, and `record-backfill` publication signals over historic `record-semantic` rows; explicit `source=` filters still expose source-specific rows for diagnostics and evidence checks. The cursor feed keeps embedded `linkedArt` by default, while IIIF Change Discovery pages are thin by default for immutable-page harvesters. Add `embed=linked-art` to a collection or page URL to include the richer `linkedArt` projection in fixed-page responses. Partners can verify the event handoff by reading `type=Update`, then dereferencing the first row's `linkedArt.id`; the returned `/api/events/...` entity should keep the same id, `_complete: false`, and source-preserving `equivalent[]`.
The feed includes a conservative `record-backfill` source: if a stored record is not already represented by an audit or deletion activity, MetaMuseum emits one thin `Create` publication activity for that record. Semantic collection events such as production, creation, encounter, or custody transfer remain separate and do not suppress this bootstrap publication row, because external harvesters need a current-record publication signal as well as historic object events. Backfill timestamps prefer record/source update or import metadata such as `_source.importedAt`; for initial production seed rows, these are import/republication times rather than original museum object-change times. Unresolved timestamps still fall back to the epoch marker described below. Activity objects also carry an `equivalent[]` array with cross-dataset `equivalent[]` identifiers derived from stored `equivalent[]`, provider record URLs, provider collection URLs, and provider object Wikidata URLs when available.
When an activity `object.id` dereferences through `/api/records/{id}`, the detail response serializes the record `id` as the canonical HTTP(S) detail URL, aligns the embedded record `_links.self.href` with that URL, and exposes the stored/provider identity in `equivalent[]`. This keeps a dereferenceable `object.id` on MetaMuseum Linked Art records while preserving the source record and import identity. Record detail responses also emit HTTP `Link` headers for autodiscovery: `rel="http://iiif.io/api/discovery/1#activity"` points at `/api/activity/collection`, while `rel="alternate"` points at the cursor feed and object-scoped feed.
Semantic Event Extraction
The feed extracts semantic events from these stored Linked Art record properties:
| Record property | Linked Art event type | ActivityStreams type |
|---|---|---|
| `produced_by` | `Production` | `Create` |
| `created_by` | `Creation` | `Create` |
| `attributed_by` | `AttributeAssignment` | `Update` |
| `modified_by` | `Modification` | `Update` |
| `destroyed_by` | `Destruction` | `Update` |
| `removed_by` | `PartRemoval` | `Create` |
| `encountered_by` | `Encounter` | `Update` |
| `formed_by` | `Formation` | `Create` |
| `dissolved_by` | `Dissolution` | `Update` |
| `born` | `Birth` | `Create` |
| `died` | `Death` | `Update` |
| `transferred_title_of` | `TransferOfTitle` | `Update` |
| `transferred_custody_of` | `TransferOfCustody` | `Update` |
| `used_for` | `Activity` | `Update` |
Semantic `Destruction`, `Dissolution`, and `Death` describe lifecycle facts inside the Linked Art record, so MetaMuseum publishes them as ActivityStreams `Update` rows. ActivityStreams `Delete` remains reserved for real record/tombstone deletions from the deletion evidence pipeline, especially genuine upstream `404` or `410` cases.
Write-audit and deletion events still emit a generic Linked Art `Activity` projection so every activity item has a Linked Art-facing payload, even when the source event is operational rather than semantic.
Synchronization
Consumers may use:
- `limit`: page size, clamped to `1..100`
- `offset`: backward-compatible page offset
- `cursor`: stable descending-time cursor from `nextCursor`
- `type`: `Create`, `Update`, or `Delete`
- `source`: `audit`, `record-backfill`, `record-semantic`, or `deletion`
- `provider`: exact provider key such as `met`, `vanda`, or `museumsvictoria`
- `object` / `objectId`: exact object URI filter
- `since` / `until`: timestamp range filters
The default page size is `25` when `limit` is absent, and explicit `limit` values are clamped to `1..100`.
The canonical `next` link prefers cursor sync only when the page boundary has a valid non-fallback timestamp. If a source event carries an invalid timestamp, `next` falls back to the offset-based `nextPage` link instead of emitting an unusable cursor. The `nextPage` alias remains offset-based for existing consumers.
IIIF Change Discovery Compatibility
This profile now includes a first IIIF Change Discovery API 1.0 compatibility layer. `/api/activity/collection` exposes a spec-shaped `OrderedCollection` with mandatory `first` and `last` page references, including the mandatory collection `last` link, and `/api/activity/page/{page}` exposes fixed ascending `OrderedCollectionPage` resources so IIIF/LOUD harvesters can start at `last`, walk `prev`, and stop at their last processed timestamp. Declared consumer IDs sent to the collection and page endpoints are recorded in the same readiness ledger as cursor-feed reads; page reads also merge the concrete activity types observed on that page. The original descending cursor API remains documented as a MetaMuseum extension for tail-following consumers.
Example walk-back seed:
curl "https://www.metamuseum.org/api/activity/collection?limit=100&provider=met" \
-H "accept: application/activity+json"
Each fixed page lists older activities first and newer activities last, matching the Change Discovery consumption model. Fixed pages omit absent `prev` and `next` links, normalize non-parseable source times to a valid UTC fallback with the original value preserved under `extensions.sourceEndTime`, and keep record-backed `object.id` values dereferenceable by pointing them at `/api/records/{encodedSourceRecordId}`. Epoch fallback timestamps are structurally valid but are not treated as production-quality incremental-sync evidence because they sort as the oldest possible changes; the evidence-grade production consumer run should wait for provider backfill with real event, import, or republication times and populated reconciliation hooks. Filter parameters such as `provider`, `type`, `source`, `object`, `since`, and `until` are preserved across collection, page, and cursor URLs; `embed=linked-art` is preserved across fixed-page traversal when requested.
Open partner compatibility work:
- Use the passing `metahistorybook-harvester-prod`, `daily-metahistorybook-prod`, and `wikidataexplorer-metamuseum-prod` reads plus their three verified callback rows as production-grade external-consumer evidence, then collect observed `Update`/`Delete` feed-read coverage for the strict syndication gate.
- Produce `Update` coverage only from a real reviewed record revision, which audits an existing-record POST as `record.update`; produce `Delete` coverage only from `pnpm activity:tombstone:evidence` after a canonical upstream record returns HTTP `404` or `410` with a real evidence reference.
- Expand cross-dataset `equivalent[]` identifiers beyond the current object-level provider URL and object-Wikidata mapping wherever additional providers expose safe object-level equivalents.
- Publish an explicit `Refresh` activity if a future full republication needs to mark a new corpus baseline.
- State delivery, deletion-tombstone retention, and cursor monotonicity guarantees with concrete retention windows.
- Collect evidence from a real signed callback delivery. Subscription records now declare HMAC-SHA256 signing metadata with `x-metamuseum-signature` and `x-metamuseum-signature-timestamp`, but strict evidence still requires an observed partner callback POST.
- Publish retry semantics for callback delivery.
External Evidence Contract
Partner-owned consumers identify themselves with `x-linked-art-consumer-id` when reading `/api/activity`, `/api/activity/collection`, `/api/activity/page/{page}`, or an object-scoped feed. A consumer counts toward strict evidence only after real feed-read telemetry, activity-type coverage, durable subscription evidence, callback verification, and the refreshed syndication-evidence packet are present. Community outreach and placeholder IDs remain context only.
Partner onboarding steps and copy/paste commands live in activity-stream-partner-quickstart.md(activity-stream-partner-quickstart.md).
Verification:
node --import tsx --test --test-concurrency=1 tests/api/activity.test.ts tests/api/activity-as2.test.ts
pnpm activity:syndication:evidence