← Documentation home

Canonical Markdown source · Oct 20, 2018

ActivityStreams Partner Quickstart

linked-art/activity-stream-partner-quickstart.md · 196 lines · SHA-256 6ea3aea16866

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: