This document extends the Linked Web Storage (LWS) protocol with server-managed services for type-based discovery: a Type Index Service that enumerates the distinct resource types present within a storage, and a Type Search Service that returns the resources matching a type filter. The specification defines how servers derive types, the query model and its error handling, and the authorization requirements that keep responses client-specific. Both services are discoverable via the storage description resource.

This is an unofficial proposal.

Introduction

The [[!LWS10-CORE|Linked Web Storage]] protocol defines a resource-centric storage system built on HTTP. While the core protocol supports creating, reading, updating, and deleting resources, and containers enumerate the resources they contain, it does not define a mechanism for a client to learn which kinds of resources exist within a storage, or to locate the resources of a given kind, other than by traversing the containment hierarchy one resource at a time.

This specification extends LWS with two server-managed services, discoverable via the storage description resource. The TypeIndexService provides a server-managed discovery mechanism to query the distinct resource types available within a storage, while the TypeSearchService allows clients to retrieve the specific resource URIs matching those types.

Terminology

The terms "storage", "storage description resource", "container", "data resource", "resource manager", and "requesting agent" are defined by [[!LWS10-CORE]].

This specification defines the following additional terms:

Discovery

A storage MAY support the Type Index Service, the Type Search Service, or both. A storage that supports one of these services MUST advertise it with a service object in the service array of its storage description resource: the entry for the Type Index Service MUST have a type equal to TypeIndexService, and the entry for the Type Search Service MUST have a type equal to TypeSearchService.

Each service object MUST include a serviceEndpoint property whose value is the URI of the corresponding service endpoint. Additional properties MAY be present on a service object.

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "id": "https://example.org/storage/alice/",
  "type": "Storage",
  "service": [
    {
      "type": "TypeIndexService",
      "serviceEndpoint": "https://example.org/types/index"
    },
    {
      "type": "TypeSearchService",
      "serviceEndpoint": "https://example.org/types/search"
    }
  ]
}
      

The storage description advertises only the service endpoints. In particular, the set of additional indexed relations a server supports is deliberately not enumerated there or anywhere else; see .

Type and Relation Derivation

To prevent inadvertent or malicious corruption by clients, these services are strictly populated and managed by the server. To mitigate security and performance risks associated with deep-parsing arbitrary resource bodies, servers are not required to parse resource content to discover types. Instead, servers SHOULD derive resource types from HTTP Link headers provided by the client during resource creation or modification, combined with the server's intrinsic knowledge of the resource's state (such as native LWS classes like https://www.w3.org/ns/lws#Container).

Servers MAY additionally derive types from the resource representation itself when they are able to parse it. Implementations are encouraged to do so where feasible, as richer type discovery improves the utility of the Type Index and Type Search services. When a server enriches the type index from resource content, the types it surfaces MUST be treated identically to those derived from Link headers for the purposes of indexing, search, and authorization filtering.

Type and indexed-relation membership in these services MAY be eventually consistent. Following a write that changes a resource's derivable types or relation targets (creation, update, or deletion), a server SHOULD reflect the change within a bounded, implementation-defined interval; until it does, a response MAY omit a newly matching resource or briefly retain one that no longer matches. Clients MUST NOT assume read-your-writes consistency and SHOULD tolerate transient staleness. This allowance applies only to derivation and MUST NOT be applied to authorization filtering (see ).

Because servers are not required to parse the resource body, the Type Search Service relies heavily on the types explicitly declared via HTTP headers or managed by the server. If a client includes a type exclusively within a resource's internal graph but fails to provide the corresponding Link header, that type may not be discoverable via these services.

Type Index Service

The Type Index Service provides a server-managed discovery mechanism to query the distinct resource types available within a storage.

GET [TypeIndexService]

Returns a paginated TypeIndex as application/lws+json, following the standard LWS pagination model defined by [[!LWS10-CORE]]: page URIs are conveyed in Link headers—not in the response body—and are opaque to the client. The body lists the unique types of resources that currently exist within the storage and that the authenticated user is authorized to see. This specification defines no query parameters for the endpoint: a client requests the advertised serviceEndpoint URI as-is, and any parameters appearing in server-issued pagination URIs (such as ?page= in the example below) are server-internal parts of those opaque URIs, not part of this interface. A page reference may expire or cease to be recognized; the pagination-expiry rule of applies to TypeIndex pages as it does to search result pages.

The Type Index takes no filter, so it is retrieved with a GET. The Type Search Service, which carries a filter, instead uses the HTTP QUERY method [[!RFC10008]] (see ).

Request
GET /types/index
Authorization: Bearer <token>

Response
HTTP/1.1 200 OK
Content-Type: application/lws+json
Cache-Control: private
Link: <https://example.org/types/index?page=1>; rel="first"
Link: <https://example.org/types/index?page=2>; rel="next"
Link: <https://example.org/types/index?page=4>; rel="last"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "TypeIndex",
  "totalItems": 142,
  "items": [
    { "id": "https://schema.org/Person" },
    { "id": "https://schema.org/Event" },
    { "id": "https://schema.org/Message" }
  ]
}
        

Type Search Service

The Type Search Service allows clients to retrieve descriptions of the resources within a storage matching a filter over types and indexed relations.

QUERY [TypeSearchService]

A client searches by sending an HTTP QUERY request [[!RFC10008]] to the service endpoint, carrying the filter in the request body. The query format is identified by the request's Content-Type header field, which MUST be present; per [[!RFC10008]], a server fails the request if the Content-Type is missing or inconsistent with the body (see ). Every server MUST support the application/lws-query+json filter format defined here as its baseline query format; a server MAY additionally accept other query formats, each identified by its own media type, and the authorization requirements of apply regardless of the format used. The QUERY method is both safe and idempotent [[!RFC10008]], in the sense of those terms as defined by [[!RFC9110]]: a search never alters server state, and it MAY be repeated, retried, and/or cached. Because the filter travels in the body rather than the request URI, it is not subject to the request-URI length limits that constrain a query string.

The baseline filter format is identified by the media type application/lws-query+json, registered in . A filter document is a single JSON object [[!RFC8259]] whose members express constraints: the type member and, optionally, one member per filtered link relation, each carrying the conjunctive-normal-form value grammar defined below. Unlike the application/lws+json representations defined by [[!LWS10-CORE]] — which this service still uses for its responses — a filter document is plain JSON rather than JSON-LD: it carries no @context, its member names are not JSON-LD terms, and its nested-array groups are not subject to JSON-LD constraints on @type values. A member whose name begins with @ (a JSON-LD keyword such as @context) MUST be ignored; it is neither a constraint nor a relation key.

A server SHOULD advertise the query formats it accepts using the Accept-Query response header field [[!RFC10008]] — for example in response to an OPTIONS request on the service endpoint, whose Allow header also signals that the QUERY method is supported. Accept-Query enumerates only the query-format media types the server understands; it MUST NOT be used to disclose which indexed relations or filter capabilities a server supports, which remain deliberately unobservable (see ). A client that receives a 415 (Unsupported Media Type) response MAY consult its Accept-Query value to select a supported format.

{
  "type": [
    ["https://schema.org/Person", "http://xmlns.com/foaf/0.1/Person"],
    "https://www.w3.org/ns/lws#DataResource"
  ]
}
        

The type key is OPTIONAL and selects resources by their rdf:type (as declared via rel="type" Link headers, server-intrinsic classes, or content; see ). Its value is an array whose elements are combined with logical AND; each element is either a single type IRI [[!RFC3987]] or an array of type IRIs combined with logical OR. The example above selects resources matching (schema:Person OR foaf:Person) AND lws:DataResource — the filter is a conjunction of disjunctions; that is, conjunctive normal form. A request whose body has no type key and no relation keys (or all of whose keys have values of empty arrays) matches all resources visible to the client.

A single-type query — a type value containing one IRI and no OR group, for example {"type": ["https://schema.org/Person"]} — matches every resource that declares that type, even if the resource also declares other types.

A type filter always denotes the type the matched resource itself bears; it never denotes the types of a container's members. The native LWS classes https://www.w3.org/ns/lws#DataResource and https://www.w3.org/ns/lws#Container are ordinary type values: filtering on them selects resources that are themselves data resources or containers, respectively (for example, a type of https://www.w3.org/ns/lws#Container matches container resources, and a filter of https://schema.org/Person AND https://www.w3.org/ns/lws#DataResource matches data resources that are also schema:Person).

This conjunctive-normal-form filter is the complete query expressiveness the service is required to provide. It is also the complete expressiveness of the application/lws-query+json format itself: a server MUST NOT extend this grammar. Richer expressiveness — such as nesting, negation, comparison, ordering, or text matching — is offered only through an alternate query format identified by its own media type and advertised in Accept-Query, and a portable client MUST NOT rely on any query format beyond this baseline. A type IRI that matches no resource yields no results and MUST NOT be treated as an error, and duplicate groups MUST be ignored. An empty group — an empty array appearing as an element of a filter key's value — MUST be rejected with 400 (Bad Request): an empty disjunction can match nothing, so ignoring it would silently broaden the query beyond its logical meaning (see ). A filter key whose entire value is an empty array expresses no constraint and is treated as if the key were absent.

The type key is the mandatory baseline and MUST be supported by every server that implements the TypeSearchService. A server MAY additionally index other descriptive link relations. Such a relation is filtered by using the relation itself as a key alongside type — a registered relation name per [[!RFC8288]] or, for an extension relation, its URI — with exactly the same conjunctive-normal-form semantics as type (a nested array is an OR group, sibling array elements form AND groups, target values matched against the link targets the resource declares for that relation). Groups belonging to different relations are combined with logical AND. Relation targets are derived by the server in the same way as types (HTTP Link headers, server-intrinsic state, or parsed content) and all sources MUST be treated identically.

A server MUST NOT enumerate its set of additional indexed relations — not in the storage description nor elsewhere. A relation the server does not index, like a target value that matches nothing, yields no results for that constraint: this MUST NOT be treated as an error, and the two cases MUST be indistinguishable to the client (consistent with the type-matching rule above). Only descriptive relations are eligible for indexing; servers MUST NOT index structural or protocol relations.

Withholding the set of indexed relations keeps the filter interface from serving as a discovery oracle for the server's configuration; see .

A successful search returns 200 (OK). The default response is an application/lws+json ContainerPage whose items describe the matching LWS resources, and every server MUST be able to produce it: a request that sends no Accept header field, or one that admits application/lws+json, receives this representation. A server MAY honor an Accept header field that requests a different response media type and return the result set in that format instead [[!RFC10008]]; if it can satisfy none of the media types the request finds acceptable, it responds 406 (Not Acceptable) (see ).

Each item in a ContainerPage result MUST carry at least the matched resource's id and its type, mirroring a container member; a server MAY include additional descriptive metadata (such as mediaType, size, or modified) but is not required to.

Whatever the response format, the result set is paginated according to the standard LWS pagination model defined by [[!LWS10-CORE]]: page URIs are conveyed in Link headers (not in the response body), are opaque to the client, and are dereferenced to retrieve subsequent pages. Aside from type and any additional relation keys described above, no other filter inputs are defined.

A ContainerPage returned by the TypeSearchService is a synthetic result set, not a representation of a container resource. The ContainerPage media type and pagination model are reused only for client convenience; the response does not identify a container, has no containment relationship to its members, is not retrievable or mutable as a resource, and its membership reflects the query rather than any stored hierarchy.

[[!RFC10008]] permits a response to carry a Content-Location header field identifying a resource that represents the query results. Because a Type Search result set is synthetic and client-specific (see the note above and ), this specification does not define such a resource; a server that nonetheless exposes one MUST subject it to the same authorization filtering on every access and MUST prevent its reuse across clients.

Errors

Servers indicate request outcomes using standard HTTP status codes [[!RFC9110]]. In particular:

Error responses may include a representation describing the problem; this specification does not define its format.

A QUERY response is cacheable [[!RFC10008]]. Because the filter travels in the request body, it forms part of the cache key itself ([[!RFC10008]], Section 2.7) and cannot be conveyed through the Vary header field, which names only request header fields. A server that negotiates the response representation on Accept (see ) MUST list Accept in Vary so that caches do not reuse a representation across response formats. Because every result set is authorization-filtered and therefore client-specific, the cross-client caching requirements in also apply.

Security and Authorization

Servers MUST enforce authorization. Responses from the TypeIndexService (a GET) and the TypeSearchService (a QUERY) include only types and resource URIs that the authenticated client is explicitly authorized to read.

Unauthorized entries MUST be omitted entirely. A server MUST NOT allow a client to discover, from the responses of these services, the existence of a specific resource instance, or that a specific type exists in the storage at all, without the required authorization. This implicit filtering yields a dynamically generated, client-specific type index and search result.

Any count a response exposes, including totalItems, MUST be computed over this client-specific, authorization-filtered view: it is the number of entries the requesting client is authorized to see, never a count of the types or resources present in the storage as a whole. A count that included unauthorized entries would itself reveal their existence and is therefore prohibited.

Authorization filtering MUST be evaluated against the requesting client's current access at the time the request is served, not against any cached or precomputed view. Once an authorization (such as an access grant) is revoked or narrowed, the affected types and resource URIs MUST NOT appear in any subsequent response; the eventual-consistency allowance for type and relation derivation MUST NOT be extended to authorization. A server MAY maintain a derived index for performance, but it MUST apply current authorization as a filter over that index on every request.

Responses from both services are authorization-filtered and therefore client-specific, yet both are cacheable by default: a TypeIndexService response under the ordinary HTTP caching rules for GET [[!RFC9110]], and a TypeSearchService response per [[!RFC10008]], whose cache key is computed over the request body rather than the requesting client. A server MUST ensure that an authorization-filtered response from either service is never served from a shared cache to a different client. A server MUST mark such responses so that they are not reused across clients (for example with Cache-Control: private or no-store) and MUST vary any cached entry on the credential that scopes the result.

Any extension query mechanism a server exposes over the storage — for example an alternative query format accepted by the TypeSearchService, or a separate query service — MUST uphold these authorization-filtering requirements and the indistinguishability requirements of this specification, so that no extension can become a discovery oracle for resources or types the client is not authorized to read.

Examples

Request
GET /types/index
Authorization: Bearer <token>

Response
HTTP/1.1 200 OK
Content-Type: application/lws+json
Cache-Control: private
Link: <https://example.org/types/index?page=1>; rel="first"
Link: <https://example.org/types/index?page=2>; rel="next"
Link: <https://example.org/types/index?page=4>; rel="last"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "TypeIndex",
  "totalItems": 142,
  "items": [
    { "id": "https://schema.org/Person" },
    { "id": "https://schema.org/Event" }
  ]
}
      
Request
OPTIONS /types/search

Response
HTTP/1.1 204 No Content
Allow: OPTIONS, QUERY
Accept-Query: application/lws-query+json

A server that also accepted other query formats would list them in Accept-Query,
for example: Accept-Query: application/lws-query+json, application/sparql-query
      
Request
QUERY /types/search
Authorization: Bearer <token>
Content-Type: application/lws-query+json

{
  "type": [
    "https://schema.org/Person",
    "https://www.w3.org/ns/lws#DataResource"
  ]
}

Response
HTTP/1.1 200 OK
Content-Type: application/lws+json
Cache-Control: private
Link: <https://example.org/types/search?cursor=8f2a1c>; rel="first"
Link: <https://example.org/types/search?cursor=b71e90>; rel="next"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 27,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4873" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-5882" }
  ]
}
      
Request
QUERY /types/search
Authorization: Bearer <token>
Content-Type: application/lws-query+json

{
  "type": ["https://www.w3.org/ns/lws#Container"]
}

Matches resources whose type includes lws:Container. This does NOT mean
"containers that contain something" - the service has no containment query.
The returned URIs are container resources because each is itself a lws:Container.

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": "Container", "id": "https://example.org/data/people/" },
    { "type": "Container", "id": "https://example.org/data/contacts/" }
  ]
}
      
Request (nested array = OR group, outer array = AND)
QUERY /types/search
Authorization: Bearer <token>
Content-Type: application/lws-query+json

{
  "type": [
    ["https://schema.org/Person", "http://xmlns.com/foaf/0.1/Person"],
    "https://www.w3.org/ns/lws#DataResource"
  ]
}

Selects resources matching (schema:Person OR foaf:Person) AND lws:DataResource.

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "http://xmlns.com/foaf/0.1/Person"], "id": "https://example.org/data/contact-2208" }
  ]
}
      
Request (type AND a 'describedby' relation, the relation name used as a key)
QUERY /types/search
Authorization: Bearer <token>
Content-Type: application/lws-query+json

{
  "type": ["https://schema.org/Person"],
  "describedby": ["https://shapes.example/PersonShape"]
}

Selects resources that are schema:Person AND declare a 'describedby' link to the given shape.
If the server does not index 'describedby', the constraint yields no results (not an error,
and indistinguishable from no resource declaring that link).

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-5012" }
  ]
}
      

Security Considerations

Privacy Considerations

Vocabulary

This document defines the following terms in the https://www.w3.org/ns/lws# namespace:

Term URI Description
TypeIndexService lws:TypeIndexService Service type for type index discovery
TypeSearchService lws:TypeSearchService Service type for type search discovery
TypeIndex lws:TypeIndex Document type returned by the Type Index Service

This document also uses the terms Storage, ContainerPage, service, serviceEndpoint, totalItems, and items defined by [[!LWS10-CORE]] and the LWS JSON-LD context.

IANA Considerations

The application/lws-query+json Media Type

This specification registers the application/lws-query+json media type, identifying the baseline Type Search filter format defined in .