ActivityStreams Partner Quickstart
Last updated: 2026-07-07
This quickstart helps an external partner read the MetaMuseum ActivityStreams feed. It is onboarding documentation only. It does not count as external adoption evidence until a real partner-owned consumer ID, public feed read, durable HTTPS subscription, recent 2xx callback verification, and refreshed syndication evidence packet are recorded.
What To Ask For
Ask the partner for:
- A stable partner-owned consumer ID, such as `museum-reader-prod`.
- The system or organization that owns that consumer ID.
- A public HTTPS callback URL if they can receive activity notifications.
- A contact who can confirm observed activity types and callback results.
- A real evidence reference, such as an issue, ticket, log export, or email thread.
Do not use demo, fixture, local, placeholder, sample, smoke, test, localhost, private-network, or MetaMuseum-owned IDs as strict evidence.
Read The Feed
Use the partner-owned ID in `x-linked-art-consumer-id`.
curl "https://www.metamuseum.org/api/activity?limit=100" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
For strict adoption evidence, read each required activity type with the same
declared header as well. If a type-filtered URL returns zero items, report that
explicitly so operators can distinguish missing feed content from missing
partner coverage.
curl "https://www.metamuseum.org/api/activity?limit=100&type=Create" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
curl "https://www.metamuseum.org/api/activity?limit=100&type=Update" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
curl "https://www.metamuseum.org/api/activity?limit=100&type=Delete" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
The response is an ActivityStreams `OrderedCollectionPage`. Each item includes:
- `type`: `Create`, `Update`, or `Delete`
- `object.id`: affected dereferenceable MetaMuseum Linked Art object URI
- `object.sourceId`: original provider/stored object URI when it differs from `object.id`
- `object.activityHref`: object-scoped feed URL
- `object.equivalent[]`: reconciliation hooks such as provider URIs or Wikidata QIDs when available
- `linkedArt`: embedded Linked Art JSON-LD projection; `linkedArt.id` is a dereferenceable `/api/events/{encodedSourceActivityId}` URL, embedded projections carry `_complete: false`, and `linkedArt.equivalent[]` preserves the source activity URI when available
- `source.kind`: `audit`, `record-backfill`, `record-semantic`, or `deletion`
- `endTime`: valid UTC sync timestamp; original placeholder times are retained under `extensions.sourceEndTime`
- `nextCursor`: stable cursor for incremental sync
Default cursor-feed reads collapse record-backed diagnostic rows to the latest
harvestable public row per object, matching the IIIF Change Discovery projection.
Use `source=record-semantic` or another `source=` filter when you need the
underlying semantic diagnostic rows instead of the deduped partner view.
Verify Update Linked Art Event Dereference
When `type=Update` returns at least one item, fetch the first row's
`orderedItems[0].linkedArt.id`. The response should return HTTP `200` with the
Linked Art JSON-LD context, `entity.id` equal to the requested URL,
`entity._complete: false`, and `entity.equivalent[]` preserving the source
activity URI.
curl "https://www.metamuseum.org/api/activity?limit=100&type=Update" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
curl "<orderedItems[0].linkedArt.id>" \
-H "accept: application/ld+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
If `type=Update` is zero, report zero and skip this dereference check. If
`type=Delete` is zero, report zero explicitly as well; MetaMuseum does not emit
Delete rows until a genuine upstream `404` or `410` tombstone exists.
Cursor Sync
Read the first page, store `nextCursor`, then request the next page with `cursor`.
curl "https://www.metamuseum.org/api/activity?limit=100&cursor=<nextCursor>" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
Useful filters:
- `type=Create`, `type=Update`, or `type=Delete`
- `source=record-backfill`, `source=record-semantic`, `source=audit`, or `source=deletion`
- `provider=met`, `provider=vanda`, `provider=museumsvictoria`, or another provider key
- `object=<encoded object URI>`
- `since=<ISO timestamp>`
- `until=<ISO timestamp>`
When `limit` is absent, the feed returns up to `25` items. Explicit limits are clamped to `1..100`. The canonical `next` link uses a cursor only when the page boundary has a valid timestamp; otherwise it falls back to the offset `nextPage` link instead of emitting a bad cursor.
IIIF Walk-Back Harvest
For IIIF Change Discovery-compatible consumers, discover the fixed-page collection first:
curl "https://www.metamuseum.org/api/activity/collection?limit=100" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
Start from `last.id`, process that page in reverse item order if your mirror wants newest-first application, then follow each page's `prev.id` until you reach a timestamp older than your last crawl. Send `x-linked-art-consumer-id` on the collection request and every page request; MetaMuseum records those walk-back reads in the same readiness ledger as cursor-feed reads, and page reads record the concrete activity types observed on that page. The page documents themselves list activities oldest-to-newest so the most recent activity on a page appears last, matching the IIIF Change Discovery API 1.0 consumption model. The same filters used by cursor sync, including `provider=`, are preserved in `first`, `last`, `prev`, and `next` links.
Discovery pages are thin by default: `linkedArt` is omitted, `endTime` is always a valid UTC timestamp, `object.equivalent[]` is present when stored records carry reconciliation hooks, and bare stored IDs are projected as dereferenceable `/api/records/{id}` object URLs with the original value preserved as `object.sourceId`. The cursor feed uses the same object/time projection while keeping `linkedArt` embedded. To include the richer Linked Art projection during traversal, add `embed=linked-art`:
curl "https://www.metamuseum.org/api/activity/collection?limit=100&embed=linked-art" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
Object Feed
For one object, use the encoded object URI:
curl "https://www.metamuseum.org/api/activity/object/https%3A%2F%2Fexample.org%2Fobject%2F123?limit=100" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod"
Record detail responses also advertise activity discovery with HTTP `Link`
headers. A client that dereferences `/api/records/{id}` can find:
- `rel="http://iiif.io/api/discovery/1#activity"` for `/api/activity/collection`
- `rel="alternate"` for the cursor feed and object-scoped activity feed
The same activity metadata is listed under `/api/providers/capabilities` in
`activitySyndication`.
Register A Callback
If the partner can receive events, register an HTTPS callback:
curl -X POST "https://www.metamuseum.org/api/activity/subscriptions" \
-H "content-type: application/json" \
-H "accept: application/activity+json" \
-H "x-linked-art-consumer-id: museum-reader-prod" \
--data '{"callbackUrl":"https://partner.example.org/metamuseum/activity","eventTypes":["Create","Update","Delete"],"signingRequired":true,"signingSecretRef":"museum-reader-prod-v1"}'
The callback URL must be public HTTPS and owned by the partner. When signing is enabled, the subscription stores only `signingSecretRef`; the actual HMAC secret is exchanged out of band. Callback deliveries are signed over `<timestamp>.<raw JSON payload>` with HMAC-SHA256 and include:
- `x-metamuseum-signature`: `sha256=<hex digest>`
- `x-metamuseum-signature-timestamp`: UTC signing timestamp
A subscription row alone is not enough for strict evidence; it also needs recent 2xx verification.
Verify Locally After Partner Reads
After a real partner reads the feed:
pnpm activity:adoption:matrix -- --base-url https://www.metamuseum.org --consumer-id museum-reader-prod
pnpm activity:subscription:verify -- --consumer-id museum-reader-prod --callback-url https://partner.example.org/metamuseum/activity --status-code 204 --verified-at <ISO timestamp> --evidence-ref <real-proof-ref>
pnpm activity:syndication:evidence
The evidence packet remains blocked until `consumerOnboarding.rows` shows the same partner consumer has feed-read telemetry, Create/Update/Delete type coverage, a durable subscription, callback verification, and strict packet refresh proof.
Partner Response Template
Consumer ID:
Organization/system:
Feed read timestamp:
Type-specific read timestamps:
Observed activity types:
Update linkedArt.id dereference:
Update linkedArt.id notes:
Object feed tested:
Callback URL:
Callback event types:
Callback verification status code:
Callback verification timestamp:
Evidence ref:
Contact: