Digital Object Linkage Profile
Profile id: digital-object-linkage
Profile version: 0.6.0
Spec version: 0.33.0
Status: Normative
Dependencies: None at the endpoint level. When combined with Graph Traversal, the :InstantiationLinkedFromRecordShape full-graph SHACL shape (defined there) additionally verifies that every Instantiation is referenced by at least one Record.
Last updated: 2026-04-21
1. Purpose
Digital Object Linkage is the profile for the concrete surfaces records live on — digital files, images, PDFs, audio, video, and their physical counterparts — plus the business functions that produced them. Where Core Discovery answers what is this record, this profile answers where does the bitstream actually sit, and under which organisational function was it created.
A server implementing this profile commits to three things:
- Expose
rico:Instantiationentities — one per carrier (file, physical object, copy) — at stable URIs under/api/ric/v1/instantiations/{id}. Each Instantiation carries MIME type, carrier type, extent, and a link back to the Record it instantiates viarico:isInstantiationOf. - Expose
rico:Functionentities — ISDF-native business functions — at/api/ric/v1/functions/{id}. Each Function carries name, classification, and optional history / mandate. - Optionally expose file bytes and thumbnails —
POST /uploadwrites bytes;GET /thumbnail/{id}derives display-sized renditions for image Instantiations. These are conveniences for a working catalogue, not strictly required for profile claim.
Digital Object Linkage is orthogonal to every other profile. A server may claim it without Core Discovery (a pure object repository), without Authority & Context (files with no places or activities behind them), or without Graph Traversal (no /graph endpoint — Instantiations surfaced only via their per-record backlink). Combining with Graph Traversal is what enables store-wide hygiene checks over Instantiation ↔ Record linkage.
2. Scope
2.1 Required endpoints
A conformant server MUST expose:
| Verb | Path | Returns |
|---|---|---|
| GET | /api/ric/v1/instantiations |
Instantiation list, paginated |
| GET | /api/ric/v1/instantiations/{id} |
Single Instantiation as JSON-LD |
| GET | /api/ric/v1/functions |
Function list, paginated |
| GET | /api/ric/v1/functions/{id} |
Single Function as JSON-LD |
Pagination rules match Core Discovery §3.3 (default page size 50, max 200, openric:total/openric:page/openric:limit/openric:items).
2.2 Optional endpoints
| Verb | Path | Purpose |
|---|---|---|
| POST | /api/ric/v1/upload |
Multipart write of a single file → creates Instantiation + stores bytes. Requires X-API-Key with write scope. |
| GET | /api/ric/v1/thumbnail/{id} |
Derivative thumbnail for image Instantiations. First call generates + caches; subsequent calls serve from cache. Public (no auth). Returns the image bytes, not JSON. |
A server MAY additionally expose format-specific derivative endpoints (IIIF image tiles, video transcodes, etc.); anything outside the four required endpoints + the two listed optionals is outside the profile’s normative surface.
2.3 Forbidden without additional profile claims
- Write verbs on
/instantiations//functionsbeyondPOST /uploadfor creating Instantiations from uploaded files. Full CRUD lives in Round-Trip Editing. - Cross-entity graph hygiene checks (orphaned Instantiations, unlinked Records). Those live in Graph Traversal.
- Unbounded file-size uploads. Servers MUST enforce a per-request size cap and return
413 payload-too-largewith the cap in the problem+json body when exceeded.
2.4 Content types
/instantiations,/functions, and their per-id variants →application/ld+json/upload→ acceptsmultipart/form-datawith afilepart; returnsapplication/jsonwith the created Instantiation’sid,href,thumbnail_url(if applicable),mime,size,filename,path/thumbnail/{id}→ returns image bytes with appropriateContent-Type(typicallyimage/webporimage/jpeg), NOT JSON
Error responses MUST be application/problem+json per Core Discovery §4.
3. Response shapes
3.1 Instantiation — GET /instantiations/{id}
An Instantiation is a concrete carrier of the information in a Record — a specific file, a specific physical copy. At minimum: @id, @type: "rico:Instantiation", rico:title, and at least one of rico:hasCarrierType, rico:hasMimeType, rico:hasContentOfType, or rico:hasProductionTechnique (otherwise the Instantiation is a shell with no way to identify what it actually carries — SHACL Violation per §5).
{
"@context": { "rico": "https://www.ica.org/standards/RiC/ontology#",
"xsd": "http://www.w3.org/2001/XMLSchema#" },
"@id": "https://example.org/instantiation/910871",
"@type": "rico:Instantiation",
"rico:identifier": "marble_statue_ultra_high_res.tiff",
"rico:title": "marble_statue_ultra_high_res.tiff",
"rico:description": "MIME: image/tiff | Size: 73735.3 KB",
"rico:hasMimeType": "image/tiff",
"rico:hasCarrierType": "digital",
"rico:hasExtent": {
"@type": "rico:Extent",
"rico:quantity": 75504986,
"rico:extentType": "bytes"
},
"rico:technicalCharacteristics": "Checksum (sha256): b4156fc402504b621b0579635162f887ddd131045ee6eaa67c7c4b4a2d4e890f",
"rico:isInstantiationOf": {
"@id": "https://example.org/informationobject/title-of-object",
"@type": "rico:Record",
"rico:title": "Title of object"
}
}
Required: @id, @type: "rico:Instantiation", rico:title, AND at least one of rico:hasCarrierType / rico:hasMimeType / rico:hasContentOfType / rico:hasProductionTechnique.
Strongly recommended when present in the backing data:
| Field | Notes |
|---|---|
rico:identifier |
Typically the original filename |
rico:description |
Free-form, often auto-generated from MIME + size |
rico:hasMimeType |
MUST match ^[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+$ per SHACL |
rico:hasCarrierType |
digital, physical, born-digital, analog-converted, or a local vocabulary value |
rico:hasExtent |
Structured rico:Extent with rico:quantity (integer) + rico:extentType (bytes, pages, minutes, etc.) |
rico:technicalCharacteristics |
Free-form string. Checksum convention: "Checksum (sha256): <hex>" — structured checksum shape is a v0.7 candidate (see §9 Q2). |
rico:isInstantiationOf |
STRONGLY RECOMMENDED. Embedded stub of the Record this Instantiation carries. Without it, the Instantiation is floating (SHACL Warning at Graph Traversal level). |
3.2 Function — GET /functions/{id}
A Function is an ISDF business function — a recurring organisational activity-class under which records are produced and managed (“Acquisitions Management”, “Conservation Treatment”, “Permit Administration”). At minimum: @id, @type: "rico:Function", and a name (either rico:name shorthand OR rico:hasOrHadName).
{
"@id": "https://example.org/function/918002",
"@type": "rico:Function",
"rico:name": "Permit Administration",
"rico:history": "Established 1984 under Act 117/1983; moved from Ministry of Culture to Supreme Council of Antiquities in 2005.",
"rico:classification": "isdf:5.2.1",
"openric:localType": "administrative"
}
Required: @id, @type: "rico:Function", AND one of rico:name / rico:hasOrHadName.
Recommended (SHACL Info severity): rico:history, rico:classification (ISDF §5.2 code). Functions without either are accepted but carry no actionable semantics.
3.3 List envelopes
List responses follow the Core Discovery §3.3 shape:
/instantiations→rico:InstantiationList/functions→rico:FunctionList
Each envelope wraps openric:items as an array of stubs: @id, @type, rico:title (Instantiations) or rico:name (Functions), plus openric:localType when the server carries one. Full shapes only on single-entity GET.
3.4 Upload — POST /upload (optional)
POST /api/ric/v1/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=…
X-API-Key: …
The request body MUST be a multipart/form-data with a file part. Servers MAY accept additional parts (e.g., record_id to pre-link on create, description for metadata). On success:
{
"id": 910871,
"url": "https://example.org/uploads/abc123.tiff",
"thumbnail_url": "https://example.org/api/ric/v1/thumbnail/910871",
"mime": "image/tiff",
"size": 75504986,
"filename": "marble_statue_ultra_high_res.tiff",
"path": "uploads/2026/04/abc123.tiff"
}
Required on response: id, url, mime, size, filename. The thumbnail_url MUST be emitted when the upload was an image AND the server supports thumbnails. The path field is informational; clients SHOULD NOT parse it.
Error cases:
- Missing
filepart →400 bad-requestwithcode: no_file - Upload system-rejected (PHP upload error, etc.) →
422 validation-failedwithcode: invalid_file - File exceeds size cap →
413 payload-too-largewithmax_bytesin the problem+json body
3.5 Thumbnail — GET /thumbnail/{id} (optional)
Public, cacheable derivative for image Instantiations. The {id} is the Instantiation’s numeric ID (not the Record’s).
- If the Instantiation exists and is an image: returns the thumbnail bytes with
Content-Type: image/webp(orimage/jpegfor implementations without WebP). First call generates + caches on disk; subsequent calls serve from cache. - If the Instantiation doesn’t exist:
404 not-found - If the source file is missing on disk:
404 not-foundwithcode: source_missing - If the Instantiation exists but is not an image:
415 unsupported-media-typewith the actualmimein the problem+json body
Servers SHOULD emit Cache-Control: public, max-age=86400 or similar on successful thumbnail responses.
4. Error handling
Error responses follow Core Discovery §4 / §4.1 verbatim — application/problem+json with the nine registered error-type URIs. This profile uses:
404 not-found— missing Instantiation / Function ID, missing source file for thumbnail400 bad-request—POST /uploadwithout afilepart413 payload-too-large— upload exceeds size cap415 unsupported-media-type— thumbnail requested for a non-image Instantiation422 validation-failed— upload system-rejected401 authentication-required—POST /uploadwithout a validX-API-Key
No Digital Object Linkage-specific error types are defined.
5. SHACL shapes
Digital Object Linkage responses MUST validate against the profile-scoped shape file:
shapes/profiles/digital-object-linkage.shacl.ttl
| Shape | Target | Severity model |
|---|---|---|
:InstantiationShape |
rico:Instantiation |
sh:Violation on missing title; sh:Warning on invalid MIME pattern; sh:Violation if none of hasCarrierType / hasMimeType / hasContentOfType / hasProductionTechnique are present |
:FunctionShape |
rico:Function |
sh:Violation on missing name; sh:Info on missing history / classification |
Shapes are open — unknown predicates do not cause failure. The cross-entity check “Instantiation must be linked from a Record via rico:hasInstantiation” lives in shapes/profiles/graph-traversal.shacl.ttl (:InstantiationLinkedFromRecordShape) because it requires the full triple store to evaluate and produces false positives on single-document API responses.
6. Conformance testing
A server claims digital-object-linkage when:
- The four required endpoints from §2.1 return
application/ld+jsonwith HTTP 200 on happy paths. - Every Instantiation response validates against
:InstantiationShapeatsh:Violationseverity. - Every Function response validates against
:FunctionShapeatsh:Violationseverity. - Instantiations that are digital carriers emit a MIME type matching the SHACL pattern.
- Instantiations emit
rico:isInstantiationOfpointing at a real, dereferenceable Record (the cross-entity resolution is SHACL Warning, not Violation, at the per-endpoint level). - If
POST /uploadis implemented, the four error cases in §3.4 each return the matching problem+json type URI. - If
GET /thumbnail/{id}is implemented, the three error cases in §3.5 each return the matching problem+json type URI.
Run the conformance probe with --profile=digital-object-linkage to exercise only this profile’s checks against a live server.
7. Fixture pack
The manifest declares these four fixtures as normative for digital-object-linkage:
| Fixture | Status | What it pins |
|---|---|---|
instantiation-tiff |
done | Digital Instantiation — TIFF carrier with MIME, bytes-extent, sha256 checksum, backlink to Record |
instantiation-application |
done | Digital Instantiation — application/vnd.openxmlformats… MIME (exercises the SHACL MIME-pattern regex on a compound subtype) |
function-with-activities |
planned | ISDF Function with ≥2 linked Activities (cross-tags Authority & Context) |
record-in-container |
planned | Record held in a rico:Thing container (physical Instantiation cousin) |
Fixtures outside this list are NOT required for profile conformance.
8. Implementation checklist
- Expose the four required endpoints from §2.1
- Emit
@type: rico:Instantiationon every instantiation response - Emit at least one of
hasCarrierType/hasMimeType/hasContentOfType/hasProductionTechniqueper Instantiation - Emit
rico:isInstantiationOfpointing at a real Record (stub with@id+@type+rico:title) - Validate Instantiation responses against
:InstantiationShape— 0 Violations - Emit
@type: rico:Functionon every function response - Emit a name (
rico:nameorrico:hasOrHadName) on every Function - Add
digital-object-linkagetoopenric_conformance.profilesinGET / - Run the conformance probe with
--profile=digital-object-linkage— all shipped fixtures pass - Emit
/conformance/badge?profile=digital-object-linkagereturning shields.io JSON - (optional) Implement
POST /upload— handle all four error paths (no_file,invalid_file,too_large, auth) - (optional) Implement
GET /thumbnail/{id}— handlenot_found,source_missing,not_an_image
9. Design decisions
Five questions were flagged during drafting; all five carry resolutions.
Q1 — Why rico:Function in “Digital Object Linkage”?
Resolution: Historical grouping, preserved for stability.
Rationale: When the profile matrix was first laid out, rico:Function was grouped with rico:Instantiation under “things that qualify records but aren’t Agents, Places, or Rules” — both are ISDF/ISAAR-adjacent carriers of organisational context. Splitting Function out into its own profile (or folding it into Authority & Context) would be a cleaner taxonomy today, but the shapes file + manifest have been stable since v0.1, and implementations are already mapping Function under this profile. Treating the grouping as a historical artefact and preserving it is strictly less churn than re-homing Function across two profile cycles. A future v1 review may revisit this.
Q2 — Checksum as a free-form string vs structured sub-object?
Resolution: Free-form rico:technicalCharacteristics string now; structured rico:Checksum sub-object is a v0.7 candidate.
Rationale: The current convention "Checksum (sha256): <hex>" inside rico:technicalCharacteristics is what every reference implementation emits today. It is machine-parseable (single regex) without being overengineered. A structured shape ({@type: rico:Checksum, rico:algorithm: "sha256", rico:value: "<hex>"}) is cleaner for multi-algorithm storage and for PREMIS interop, but there is no current consumer that needs it. Adding the structured shape as OPTIONAL in v0.7 — allowed alongside the string form, not replacing it — is the planned path.
Q3 — Is /thumbnail/{id} required or optional?
Resolution: Optional.
Rationale: Thumbnails are an ergonomic layer, not a semantic one. A valid Digital Object Linkage claim can be made by a server that doesn’t generate derivatives at all (e.g., a pure archive that serves master files only). Forcing thumbnails would exclude implementations without image-processing toolchains and push them out of conformance for a reason unrelated to the profile’s meaning. Servers that do implement thumbnails MUST honour the three error paths in §3.5 so clients can depend on them.
Q4 — rico:hasCarrierType — fixed vocabulary or open strings?
Resolution: Open strings, with four conventional values RECOMMENDED.
Rationale: digital, physical, born-digital, analog-converted cover the common cases and SHOULD be preferred where they fit. But carrier taxonomies vary wildly by domain (archaeology vs film archive vs born-digital startup), and locking the vocabulary would bake a single discipline’s assumptions into the spec. Implementations with a narrower local vocabulary MAY surface it via openric:localType alongside the broader rico:hasCarrierType — the same pattern as rico:ruleType in Authority & Context §3.3.
Q5 — Upload size cap: spec-enforced minimum, or implementation choice?
Resolution: Implementation choice, with a MUST on the error path.
Rationale: A small-archive server on a $5 VPS has different capacity than a national archive on dedicated storage; spec-mandating any specific cap (100 MB? 2 GB?) would force implementations to either over-provision or artificially lie about the limit. The conformance requirement is behavioural: the server MUST return 413 payload-too-large with max_bytes in the problem+json body when the cap is hit, regardless of what that cap is. Clients can discover the cap either by reading the body of a failed upload or (optionally) by a future /service-description extension.