Explore the RiC model - every entity, attribute and relation, plain and navigable. Open the model navigator ↗

Part of specification v0.55.1. Earlier versions are preserved as tagged releases.

Core Discovery Profile

Profile id: core-discovery Profile version: 0.7.0 Last revised in spec: v0.54.0 Status: Normative Dependencies: None Last updated: 2026-10-20


1. Purpose

Core Discovery is the minimum-viable OpenRiC conformance target. A server that implements this profile can be listed, browsed, searched, and dereferenced for records, agents, and repositories - but is not required to expose graph traversal, relations, authority entities, activities, digital objects, write verbs, or OAI-PMH harvesting.

This profile answers the question: “what is the smallest thing my system can expose and still call itself OpenRiC-compliant?”

Typical use cases:

2. Scope

2.1 Required endpoints

A server claiming Core Discovery conformance MUST implement all ten endpoints below under a single base URL:

# Method + path Purpose
1 GET / Service description with openric_conformance declaration
2 GET /health Liveness probe - {"status":"ok"}
3 GET /vocabulary rico:* term catalogue used by clients to render type labels
4 GET /records List and search records; pagination required
5 GET /records/{key} Single record by slug or numeric id
6 GET /agents List and search agents; pagination required
7 GET /agents/{key} Single agent
8 GET /repositories List repositories
9 GET /repositories/{key} Single repository
10 GET /autocomplete?q=&types= Cross-entity prefix search; types filter supports record,agent,repository

All endpoints are unauthenticated reads. Rate limiting is allowed but MUST return RFC 6585 429 Too Many Requests with a Retry-After header when applied.

2.2 Forbidden endpoints (without additional profile claims)

A server that claims only Core Discovery MUST NOT implement or reference:

A server MAY implement these if it also declares the relevant profile. Silently implementing them without declaration is a conformance failure because it misleads consumers about the capability surface.

2.3 Content types

All responses MUST support:

Servers MUST set Vary: Accept on every response that content-negotiates.

3. Response shapes

3.1 Service description - GET /

{
  "name": "string, human-readable service name",
  "version": "string, implementation version",
  "openric_conformance": {
    "spec_version": "string, e.g. \"0.55.1\"",
    "profiles": [
      {
        "id": "core-discovery",
        "version": "0.5.0",
        "conformance": "full"
      }
    ]
  }
}

The openric_conformance object is REQUIRED. At least one profile MUST be declared; core-discovery MUST appear if this profile is claimed.

3.2 Vocabulary - GET /vocabulary

Returns an ordered array of rico:* types with labels. Minimum shape:

{
  "@context": {
    "rico": "https://www.ica.org/standards/RiC/ontology#",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#"
  },
  "classes": [
    { "@id": "rico:Record", "rdfs:label": "Record" },
    { "@id": "rico:Agent", "rdfs:label": "Agent" }
  ],
  "properties": [
    { "@id": "rico:title", "rdfs:label": "Title" }
  ]
}

classes MUST be an array, and so MUST properties when it is sent. Before 0.7.0 the JSON Schema called the property list predicates while this section called it properties; properties is the name, and a server MAY send predicates beside it until it is removed (deprecated in v0.54.0, not before v0.55.0). @type is optional (the reference service sends ric:Vocabulary, the minimal server openric:Vocabulary). As for every response, the @context binds its prefixes inline (§3.3). Only classes and properties actually exposed by the server’s declared profiles need be listed. A Core-Discovery-only server lists Record, Agent, CorporateBody (for repositories), and the property set it emits.

3.3 List responses - GET /records, /agents, /repositories

Pagination envelope (profile 0.4.0):

{
  "@context": {
    "rico":     "https://www.ica.org/standards/RiC/ontology#",
    "openric":  "https://openric.org/ns/v1#",
    "openricx": "https://openric.org/ns/ext/v1#"
  },
  "@type": "openricx:RecordList",
  "openric:total": 713,
  "openric:page": 1,
  "openric:limit": 20,
  "openric:next": "https://example.org/api/ric/v1/records?page=2&limit=20",
  "openric:prev": null,
  "openric:items": [
    { "@id": "...", "@type": "rico:Record", "rico:title": "..." }
  ]
}

Before 0.4.0 this section showed bare total, limit, offset and items keys under a context URL (https://openric.org/ns/v1/context.jsonld) that has never been published, and required Link headers. No implementation followed it: the reference service paged with page and used openric: keys on some lists and ric: keys on others. 0.4.0 makes the openric: keys normative. A server MAY keep sending the old ric:total, ric:page, ric:limit and ric:items keys alongside them, with ric bound to https://openric.org/ns/v1# (the ric: alias); clients SHOULD read the openric: keys. See §9 for the migration note.

3.4 Single-entity responses

3.4.1 GET /records/{key}

Required fields:

Field Cardinality Notes
@id 1 Stable IRI, dereferenceable (SHOULD 303-redirect to content-negotiated representation)
@type 1 rico:Record (other subclasses allowed but MUST extend this)
rico:title OR rico:hasOrHadName 1 Multilingual via @language tags permitted
rico:identifier 1 String

Optional fields (permitted in this profile):

Field Cardinality Notes
rico:scopeAndContent 0..1 Scope and content, long-form prose (ISAD(G) 3.3.1). Language-tagged values permitted.
openricx:hasDateRangeSet 0..n openricx:DateRange objects; literal endpoints on rico:beginningDate / rico:endDate (xsd:gYear / xsd:date / xsd:dateTime), type on rico:hasDateType → a date-type concept (mapping §7.2)
rico:hasOrHadLanguage 0..n On a rico:Record only. A rico:RecordSet uses rico:hasOrHadAllMembersWithLanguage (one language) or rico:hasOrHadSomeMembersWithLanguage (several)
rico:hasOrHadHolder 0..1 Embedded partial repository object - @id, @type, rico:name only
rico:hasCreator 0..n Embedded partial agent objects - @id, @type, rico:name only

Forbidden without other profile claims:

3.4.2 GET /agents/{key}

Required fields:

Field Cardinality Notes
@id 1  
@type 1 One of rico:Agent, rico:Person, rico:CorporateBody, rico:Family
rico:name OR rico:hasOrHadAgentName 1 Either shorthand or canonical AgentName object permitted (v0.2+)

Optional fields:

Field Cardinality Notes
rico:history 0..1 Biographical / administrative prose
rico:date 0..1 Dates of existence as free text, when the source holds no structured dates
openricx:hasDateRangeSet 0..n Structured dates of existence, as for records

3.4.3 GET /repositories/{key}

Required fields:

Field Cardinality Notes
@id 1  
@type 1 rico:CorporateBody
rico:name 1  

Optional fields:

Field Cardinality Notes
rico:history 0..1  
openricx:contact 0..1 Single openricx:ContactPoint object; shape in §3.4.1

3.4.1 openricx:ContactPoint shape

The contact point is a single embedded object under openricx:contact, typed openricx:ContactPoint. Consistent with spec/mapping.md §5.2.2 (ISDIAH 5.2.2 → openricx:contact → openricx:ContactPoint). All subfields are OPTIONAL - emit only what you have; omit nulls. Implementations MUST NOT invent alternative property names.

{
  "openricx:contact": {
    "@type": "openricx:ContactPoint",
    "openricx:streetAddress": "Private Bag X236",
    "openricx:postalCode": "0001",
    "openricx:city": "Pretoria",
    "openricx:country": "ZA",
    "openricx:telephone": "+27 12 323 5300",
    "openricx:email": "info@example.org"
  }
}
Field Cardinality Notes
@type 1 MUST be openricx:ContactPoint
openricx:streetAddress 0..1 Free-form single-line address
openricx:postalCode 0..1  
openricx:city 0..1  
openricx:country 0..1 ISO 3166-1 alpha-2 RECOMMENDED
openricx:telephone 0..1 E.164 RECOMMENDED
openricx:email 0..1  

Rationale for openricx:ContactPoint over schema:ContactPoint. The Core Discovery profile is RiC-native and avoids cross-vocabulary dependencies in its normative surface. schema.org/ContactPoint is a valid alternative shape in a non-normative @context alias; implementations that want schema.org interop MAY emit the same object under schema:contactPoint in addition, but a Core-Discovery-conformant response MUST emit the openricx:contact → openricx:ContactPoint form.

3.5 Autocomplete - GET /autocomplete?q=&types=&limit=

The response is an array of matches, best first (schemas/autocomplete.schema.json):

[
  { "id": "https://example.org/actor/pieterse-johan", "label": "Pieterse, Johan", "type": "Agent",
    "@id": "https://example.org/actor/pieterse-johan", "@type": "rico:Person", "score": 0.95 }
]

Before 0.7.0 this section showed an object, {"query", "items"}, while the JSON Schema and the probe expected an array; implementations followed the schema.

4. Error handling

Errors MUST use the RFC 7807 Problem Details shape with Content-Type: application/problem+json:

{
  "type": "https://openric.org/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Record with key 'abc123' does not exist",
  "instance": "/api/ric/v1/records/abc123"
}

The five base fields (type, title, status, detail, instance) are REQUIRED. Implementations MAY emit additional fields alongside them - for example, an id of the missing entity, a code shorthand, a max_bytes upload ceiling, a validation example. Clients MUST tolerate unknown fields.

4.1 Error-type URIs

The type field is the stable identifier clients SHOULD dispatch on. Titles and details are human-readable and MAY be reworded or localised without breaking clients. The registered types, with their HTTP status codes, are:

Type URI HTTP status Used for
https://openric.org/errors/not-found 404 Entity does not exist
https://openric.org/errors/bad-request 400 Malformed request - missing or nonsense parameters
https://openric.org/errors/validation-failed 422 Request shape was fine; content did not pass validation
https://openric.org/errors/authentication-required 401 Protected route hit without credentials
https://openric.org/errors/forbidden 403 Credentials valid but insufficient scope
https://openric.org/errors/conflict 409 State conflict - e.g. delete blocked by descendants
https://openric.org/errors/not-acceptable 406 No representation the Accept header allows (the Export-Only export, for example)
https://openric.org/errors/payload-too-large 413 Upload exceeded the size ceiling
https://openric.org/errors/unsupported-media-type 415 Content-Type not accepted for this endpoint
https://openric.org/errors/internal-error 500 Unexpected server-side failure

Implementations MAY mint additional types for implementation-specific errors; such types MUST live under a URI prefix the implementer controls (NOT under https://openric.org/errors/), and the HTTP status MUST match the meaning in RFC 9110.

5. SHACL shapes

Core Discovery responses MUST validate against the profile-scoped shape file:

shapes/profiles/core-discovery.shacl.ttl

This file is a strict subset of the main shapes/openric.shacl.ttl containing only:

Shapes are open - unknown predicates do not cause failure. Implementations MAY emit private metadata.

6. Conformance testing

The conformance probe runs profile-scoped:

./probe.sh --profile core-discovery {base-url}

The probe MUST:

  1. Fetch GET / and verify openric_conformance.profiles contains core-discovery
  2. Hit each of the ten required endpoints
  3. Validate responses against their JSON Schemas
  4. Validate against shapes/profiles/core-discovery.shacl.ttl
  5. Verify no forbidden predicates appear in responses
  6. Verify pagination links on list endpoints
  7. Verify RFC 7807 error shape on a deliberate 404
  8. Emit a JSON report (--json=, schema openric-probe-result/1) with the profile, a verdict and every check

Steps 3 and 4 need a validator: the probe runs them when python3 with the jsonschema package (step 3) or pyshacl (step 4) is installed, and otherwise reports them as skipped, never as passed. Before v0.54.0 the probe did not run them at all.

A server passes Core Discovery conformance when all tests pass.

7. Fixture pack

The following existing fixtures exercise Core Discovery and ship in the profile manifest at fixtures/profiles/core-discovery/manifest.json:

  1. service-description - GET /
  2. vocabulary - GET /vocabulary
  3. record-list - GET /records list envelope
  4. fonds-minimal - GET /records/{key} minimal valid record
  5. fonds-with-children - GET /records/{key} with nested structure (fields trimmed to profile)
  6. record-multilingual - GET /records/{key} a record in its source culture, with scope and content (no @language tags yet; fonds-minimal carries them)
  7. agent-person-simple - GET /agents/{key} minimal Person
  8. agent-corporate-body - GET /agents/{key} CorporateBody
  9. agent-family - GET /agents/{key} Family
  10. autocomplete-egypt - GET /autocomplete cross-entity hit
  11. error-not-found - RFC 7807 error envelope

The manifest declares these eleven fixtures as normative for core-discovery. Fixtures outside this list are NOT required for profile conformance.

8. Implementation checklist

A server implementer works through:

9. Migration

From profile 0.6.0 to 0.7.0 (spec v0.54.0)

What changed Servers Clients
Autocomplete is an array of {id, label, type} matches (3.5), as the JSON Schema and probe already required Send the array; @id, @type and score MAY go on each match. A server sending {"query", "items"} moves to the array. Read the array.
A query under two characters is answered 200 with an empty array, not an error Answer it so. Expect an empty array.
The vocabulary’s property list is properties; the schema’s predicates is deprecated Send properties; predicates MAY be sent beside it until v0.55.0. Read properties, falling back to predicates.
not-acceptable (406) is a registered error type (4.1) Use it for a request whose Accept header allows no representation. Dispatch on it.
The probe validates responses against the JSON Schemas and the SHACL shapes (6) Responses that fail a schema or shape now fail the probe. None.

From profile 0.5.0 to 0.6.0 (spec v0.50.0)

RiC-O 1.1 separates datatype properties, which carry literals, from object properties, which point at other resources. Earlier versions of this profile and of the mapping gave literals to object properties. Each row below corrects one such case.

What changed Servers Clients
Literal dates Send literal dates on rico:beginningDate and rico:endDate, and free-text dates on rico:date. rico:hasBeginningDate and rico:hasEndDate now only point at a rico:Date node and never carry a literal. Read rico:beginningDate, rico:endDate and rico:date. Treat a literal on rico:hasBeginningDate from an older server as a beginning date.
Date type Send rico:hasDateType as a link to a concept in the date-type vocabulary, for example { "@id": "https://openric.org/vocab/date-type/existence" }. The old strings (existence, creation, accumulation, descriptive, custody) map one to one onto the concept IRIs.
Language on record sets On a rico:RecordSet, send rico:hasOrHadAllMembersWithLanguage for a single language or rico:hasOrHadSomeMembersWithLanguage for several; keep rico:hasOrHadLanguage for records and record parts. Read all three.
Legal status (agents) Send rico:hasOrHadLegalStatus as a rico:LegalStatus node with rico:name, not a string. Read the node’s rico:name.

From profile 0.4.0 to 0.5.0 (spec v0.49.0)

What changed Servers Clients
A record’s scope and content is rico:scopeAndContent only; openricx:description is no longer a record field (deprecated in v0.46.0) Stop sending openricx:description on records; send rico:scopeAndContent. Read rico:scopeAndContent. openricx:description remains a valid term for places, rules, activities, instantiations and functions.

From profile 0.3.0 to 0.4.0 (spec v0.44.0)

What changed Servers Clients
List envelope keys are openric:total, openric:page, openric:limit, openric:items (§3.3) Send them. Servers that sent ric:* keys MAY keep sending those too, with ric bound to https://openric.org/ns/v1#, until a later minor release removes the alias. Read the openric: keys; fall back to ric:* only for servers that have not moved.
Pagination is page and limit; offset is gone Accept ?page=; no change for servers already paging by page. Ask with ?page= instead of ?offset=.
Link headers are MAY, openric:next / openric:prev are SHOULD Nothing to remove. Follow openric:next.
@context binds every prefix inline Replace a context URL that serves no context document. None.

Fixture: record-list shows the 0.4.0 envelope with the ric: aliases still present.

From v0.2.0

Servers currently claiming L2-core conformance per the pre-v0.3 Viewing API §2 should transition as follows:

v0.2 claim v0.3 equivalent
L2-core {"id":"core-discovery", "version":"0.3.0", "level":"L2"}
L2-graph [{"id":"core-discovery", "version":"0.3.0"}, {"id":"graph-traversal", "version":"0.5.0"}]
L2-full All seven profiles once defined

Servers using the Heratio-style {"success":false, "error":"..."} error envelope will need to switch to RFC 7807 to claim Core Discovery. This is the one breaking change in the v0.2 → v0.3 transition.

10. Design decisions

Seven questions were flagged during drafting. All seven are resolved in the normative Core Discovery profile (v0.3.0 profile, cut in spec v0.30.0). Rationale is retained below for the record; any resolution can be re-opened via a GitHub discussion citing the question ID.

A note on numbering: Q2 was retired during early drafting; the remaining questions keep their original IDs (Q1, Q3-Q8) so cross-document references stay stable. Any resolution below can be re-opened via a GitHub discussion citing the question ID.

Q1 - “Core Discovery” name vs IIIF Level 0/1/2 numbering

Question: Should profiles follow IIIF’s numeric Level 0/1/2 convention, or keep named profiles?

Resolution: Keep named profiles. “Core Discovery”, “Authority & Context”, etc.

Rationale: IIIF’s numeric levels assume nested supersets - Level 1 contains Level 0. OpenRiC profiles are bounded capability axes that are largely orthogonal, not layered. A server might claim Core Discovery + Digital Object Linkage without ever implementing Round-Trip Editing, and numeric levels would falsely imply a ladder. Names preserve semantic flavour and avoid misrepresenting the design.

External review welcome on: whether one profile should nonetheless be declared the “floor” in governance.

Q3 - /autocomplete in Core Discovery or a separate Search profile?

Question: Is /autocomplete discovery, or is it search?

Resolution: Stays in Core Discovery.

Rationale: A discovery profile without type-ahead reads as navigation-only, which is a weaker position than IIIF Image API Level 0 (which at least delivers full images). Users evaluating whether to adopt OpenRiC will expect /autocomplete at the first step; carving it into a separate profile would make the default conformance target look incomplete. A richer Search profile can still add faceting, filters, and relevance tuning on top.

Q4 - Repositories as first-class in Core Discovery, or behind Authority & Context?

Question: Should GET /repositories/{key} and repository inclusion in lists require an A&C profile claim?

Resolution: First-class in Core.

Rationale: rico:hasOrHadHolder is emitted inline on every record in the list and detail shapes already. If repositories were A&C-only, every Core-only implementation would have to either strip the heldBy triple (losing material context) or leak a class the profile doesn’t cover (breaking conformance). First-classing repositories matches what real archival discovery always needs - “who holds this?” is a first-order discovery question, not an authority-control detail.

Q5 - Pagination mandatory on list endpoints?

Question: Must GET /records, /agents, /repositories paginate, or is unbounded allowed?

Resolution: Mandatory pagination. Default page size 50, max 200.

Rationale: Unbounded list responses are a client footgun: a /records endpoint with 100,000 rows tanks page loads, wastes bandwidth, and exposes every client to a memory-pressure bug nobody signed up for. Every known conformant implementation paginates anyway; codifying it avoids a failure mode where one impl ships unbounded and clients hand-written against it break against everyone else.

External review welcome on: the specific default (50) and max (200) page sizes.

Q6 - Error envelope shift to RFC 7807 in v0.3?

Question: Mandate application/problem+json in v0.3 and deprecate the existing {success: false, error: …} envelope, or accept both?

Resolution: Mandate RFC 7807 in v0.3. No migration window.

Rationale: There are no external v0.2 clients to accommodate - only the reference server itself was emitting the old envelope, and the migration landed in the reference implementation at the same time as this resolution (OpenRiC service v0.8.11, packages/ahg-ric/src/Support/ProblemDetails.php). Accepting both shapes indefinitely would lock every future implementer into emitting two error formats on every 4xx/5xx, which is strictly worse than a single IETF-standard shape. A migration window buys nothing when the only affected client is the one doing the migration. The nine registered error-type URIs are in §4.1; they cover the full surface the reference implementation currently returns. Implementations MAY mint additional types under a prefix they control, as §4.1 states.

Q7 - SHACL shapes open or closed?

Question: Should Core Discovery SHACL shapes use sh:closed true (reject unknown predicates) or leave shapes open?

Resolution: Open shapes.

Rationale: Closed shapes punish harmless additions - a reference server adding an implementation-specific predicate (e.g. myimpl:someInternalMarker) for operational reasons while still satisfying every Core Discovery requirement would fail validation under closed shapes. That turns every future spec extension into a coordinated flag day for every implementation. Open shapes let the ecosystem extend forward-compatibly: new predicates don’t break old validators, and closed-shape validation can still be offered as a stricter optional profile for implementers who want it.

Q8 - Profile lifecycle: tracks spec versions, or independent?

Question: Does Core Discovery v0.3 have to move to v0.4 when the spec does?

Resolution: Independent lifecycle.

Rationale: Spec version describes the HTTP contract (paths, response shapes, auth). Profile version describes a conformance-claim scope. An implementation might sit at Core Discovery v0.3 while the spec as a whole is at v0.4; the reverse is unlikely but permitted. Independent lifecycles mean we can revise Core Discovery without forcing a spec-wide freeze, and freezing the spec doesn’t rush unpolished profiles out. Matches IIIF’s multi-API approach - Image API and Presentation API ship on separate cadences.

External review welcome on: whether profile versions should declare a minimum spec version they require (likely yes) and a maximum (likely no - forward-compatible).


Comments on any of these - including arguments to reverse a draft resolution - are welcome at https://github.com/ArchiveHeritageGroup/openric-spec/discussions.


Copyright © 2026 The Archive and Heritage Digital Commons Group (Pty) Ltd and the OpenRiC contributors. Licensed under CC BY 4.0. Editor: Dr Johan Pieterse (johan@theahg.co.za).