# Data Views API

{% hint style="success" %}
Added in version 2.7.0
{% endhint %}

The Data Views API enables fine-grained access control over JSON resources in ESS through GraphQL-based filtering. Data subjects can create filtered "View Resources" that expose only specific fields from their source data, allowing selective sharing without exposing sensitive information.

## Overview

Up to ESS 2.6, access control was available only at the resource level using [Access Control Policies (ACP)](https://docs.inrupt.com/reference/glossary#access-control-policies) or [Access Grants](https://docs.inrupt.com/reference/glossary#access-grant). If a resource owner wanted to share specific fields from a resource while keeping other fields private, they had to manually create separate resources for each sharing scenario. This approach makes data harder to manage and can lead to synchronization issues.

The Data Views API solves this problem by introducing **View Resources** - dynamic resources that present filtered views of source data. View Resources behave like regular Solid resources but their content is automatically derived from other resources (Source Resources) using GraphQL queries. When source data changes, View Resources update automatically.

### Key Benefits

* **Fine-grained sharing**: Share specific fields from resources without creating duplicates
* **Automatic synchronization**: View Resources update when source data changes
* **Reusable filters**: View Definitions can be applied to multiple resources
* **Standard Solid integration**: View Resources work with existing ACP, Access Grants, and Notifications
* **Audit support**: View operations generate audit events like regular resources

### Use Cases

* **Personal Data Sharing:** Share contact information (name and email) while keeping phone numbers and addresses private
* **Business Data:** Share transaction amounts and categories while hiding account numbers and merchant details
* **Collaborative Workflows:** Share project status information while keeping internal notes and budgets confidential

## Core Concepts

### View Definition

A **View Definition** is a shareable resource that defines how to filter JSON data using GraphQL. It consists of:

* **GraphQL Schema**: Defines the structure and fields available in the source data
* **GraphQL Query**: Specifies which fields to include in the view
* **Metadata**: Name, description, and purpose of the view

View Definitions are stored in a registry and can be reused across multiple resources. They're idempotent by name - creating a View Definition with the same name and content returns the existing definition.

### Source Resource and Source Container

* **Source Resource**: A regular Solid JSON resource that contains the original data
* **Source Container**: A regular Solid container with JSON resources

Source Resources are protected while View Bindings exist - attempting to delete a Source Resource with active bindings returns a 409 Conflict error.

### View Binding

A **View Binding** connects a View Definition to Source Resource(s) and specifies where to create the View Resource(s). There are two types:

* **`VIEW_RESOURCE`**: Filters a single Source Resource, creating one View Resource at an explicit destination URI
* **`VIEW_CONTAINER`**: Filters all JSON resources in a Source Container, creating View Resources in a destination container with matching relative paths

View Bindings are asynchronous - View Resources are created shortly after the binding (typically within 10 seconds).

### View Resource and View Container

* **View Resource**: A dynamic, read-only resource containing filtered JSON data from a Source Resource
* **View Container**: A container of View Resources created from a Source Container

View Resources automatically update when source data changes. They can be shared using ACP or Access Grants like regular resources, and support notifications for change tracking.

### Relationship Diagram

```
View Definition (reusable)
    ↓ (referenced by)
View Binding
    ↓ (connects)
Source Resource  →  View Resource (filtered, continuously synchronized)
```

## Data Views Endpoints

By default, the Data Views Service runs from the following root URL:

```
https://storage.<ESS Domain>/views
```

The Data Views API consists of the following endpoints:

| Endpoint                           | Description                                                             |
| ---------------------------------- | ----------------------------------------------------------------------- |
| **`POST /views/registry`**         | Create a new View Definition                                            |
| **`GET /views/registry`**          | List all View Definitions (paginated)                                   |
| **`GET /views/registry/{id}`**     | Retrieve a specific View Definition                                     |
| **`DELETE /views/registry/{id}`**  | Delete a View Definition                                                |
| **`POST /views/bindings`**         | Create a View Binding (`VIEW_RESOURCE` or `VIEW_CONTAINER`)             |
| **`POST /views/bindings/preview`** | Preview filtered data without creating a binding (`VIEW_RESOURCE` only) |
| **`DELETE {viewResource}`**        | Delete a View Resource and its binding                                  |

## View Registry Endpoints

### Create View Definition

Creates a new View Definition in the registry. View Definitions are idempotent by name - submitting the same name with identical content returns the existing definition (201 Created). Submitting the same name with different content returns a conflict error (409).

#### Input

| Field         | Value                                                         |
| ------------- | ------------------------------------------------------------- |
| Endpoint      | **`https://storage.{ESS Domain}/views/registry`**             |
| Method        | **`POST`**                                                    |
| Authorization | Access token (`DPoP` or `Bearer`) with registry authorization |
| Content-Type  | **`application/json`**                                        |
| Payload       | View Definition object                                        |

#### View Definition Structure

| Field             | Description                                                                                                                                |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **`type`**        | Required. The query type. Currently only **`"graphql"`** is supported (case-insensitive).                                                  |
| **`name`**        | Required. Human-readable name for the View Definition. Used for idempotency - definitions with the same name and content are deduplicated. |
| **`schema`**      | Required. GraphQL Schema Definition Language (SDL) defining the structure of the source data. Must be valid GraphQL SDL syntax.            |
| **`query`**       | Required. GraphQL query string specifying which fields to include in the view. Must be compatible with the provided schema.                |
| **`description`** | Optional. Detailed description of what the View Definition does and its intended use.                                                      |
| **`purpose`**     | Optional. Short identifier for the View Definition's purpose (e.g., "contact-sharing", "privacy", "reporting").                            |

#### Example Request

```http
POST /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Basic Contact Information",
  "description": "Filters personal data to only name and email for safe contact sharing",
  "purpose": "contact-sharing",
  "schema": "type Query { name: String, email: String, phone: String, ssn: String }",
  "query": "{ name, email }"
}
```

#### Output

Returns 201 Created with a `Location` header pointing to the new View Definition URI. The response body contains the complete View Definition including its unique identifier.

#### Example Response

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "type": "graphql",
  "name": "Basic Contact Information",
  "description": "Filters personal data to only name and email for safe contact sharing",
  "purpose": "contact-sharing",
  "schema": "type Query { name: String, email: String, phone: String, ssn: String }",
  "query": "{ name, email }"
}
```

#### Validation Rules

* **Schema validation**: The GraphQL schema must be valid SDL syntax. Invalid syntax returns 400 Bad Request.
* **Query validation**: The GraphQL query must be syntactically correct and compatible with the schema. References to fields not in the schema return 400 Bad Request.
* **Security limits**: Queries exceeding maximum depth or complexity limits return 400 Bad Request (see [Configuration](#configuration) section).
* **Idempotency**: Same name + same content returns existing definition (201 Created with existing URI).
* **Name conflicts**: Same name + different content returns 409 Conflict.

{% hint style="warning" %}
**Authorization Required**

Creating and deleting View Definitions requires special registry authorization. Only WebIDs in the registry authorization allow-list can create or delete View Definitions. For configuration details, see the [Authorization](#authorization) section.
{% endhint %}

### List View Definitions

Retrieves a paginated list of all View Definitions in the registry. Any authenticated user can list View Definitions.

#### Input

| Field            | Value                                                      |
| ---------------- | ---------------------------------------------------------- |
| Endpoint         | **`https://storage.{ESS Domain}/views/registry`**          |
| Method           | **`GET`**                                                  |
| Authorization    | Access token (`DPoP` or `Bearer`) - any authenticated user |
| Query Parameters | **`cursor`** (optional): Pagination cursor for next page   |

#### Example Request

```http
GET /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

#### Output

Returns 200 OK with a paginated list of View Definitions.

#### Example Response

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "definitions": [
    {
      "type": "graphql",
      "name": "Basic Contact Information",
      "description": "Filters personal data to only name and email",
      "purpose": "contact-sharing",
      "schema": "type Query { name: String, email: String, phone: String, ssn: String }",
      "query": "{ name, email }"
    },
    {
      "type": "graphql",
      "name": "Transaction Summary",
      "description": "Shows transaction amounts and categories only",
      "purpose": "reporting",
      "schema": "type Query { transactions: [Transaction] } type Transaction { amount: Float, category: String, accountNumber: String }",
      "query": "{ transactions { amount, category } }"
    }
  ],
  "hasMore": true,
  "nextCursor": "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9",
  "size": 2
}
```

#### Pagination

| Field             | Description                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------- |
| **`definitions`** | Array of View Definition objects for the current page.                                   |
| **`hasMore`**     | Boolean indicating if more pages are available.                                          |
| **`nextCursor`**  | Opaque cursor string to retrieve the next page. Only present when **`hasMore`** is true. |
| **`size`**        | Number of definitions in the current page.                                               |

To retrieve the next page, include the `cursor` query parameter:

```http
GET /views/registry?cursor=eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9 HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

### Retrieve View Definition

Retrieves a specific View Definition by its URI. Any authenticated user can retrieve View Definitions.

#### Input

| Field         | Value                                                            |
| ------------- | ---------------------------------------------------------------- |
| Endpoint      | **`https://storage.{ESS Domain}/views/registry/{definitionId}`** |
| Method        | **`GET`**                                                        |
| Authorization | Access token (`DPoP` or `Bearer`) - any authenticated user       |

#### Example Request

```http
GET /views/registry/550e8400-e29b-41d4-a716-446655440000 HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

#### Output

Returns 200 OK with the View Definition, or 404 Not Found if the definition doesn't exist.

#### Example Response

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "type": "graphql",
  "name": "Basic Contact Information",
  "description": "Filters personal data to only name and email for safe contact sharing",
  "purpose": "contact-sharing",
  "schema": "type Query { name: String, email: String, phone: String, ssn: String }",
  "query": "{ name, email }"
}
```

### Delete View Definition

Deletes a View Definition from the registry. Any agent on the registry authorization allow-list can delete View Definitions (requires registry authorization).

{% hint style="info" %}
**Existing Bindings Not Affected**

When a View Binding is created, it copies the View Definition's schema and query. Deleting a View Definition from the registry does not affect existing bindings that reference it - those bindings will continue to work with their copied definitions.
{% endhint %}

#### Input

| Field         | Value                                                            |
| ------------- | ---------------------------------------------------------------- |
| Endpoint      | **`https://storage.{ESS Domain}/views/registry/{definitionId}`** |
| Method        | **`DELETE`**                                                     |
| Authorization | Access token (`DPoP` or `Bearer`) with registry authorization    |

#### Example Request

```http
DELETE /views/registry/550e8400-e29b-41d4-a716-446655440000 HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

#### Output

Returns 204 No Content on successful deletion.

#### Example Response

```http
HTTP/1.1 204 No Content
```

## View Bindings Endpoints

### Preview View Binding

Allows you to test a View Binding before creating it by returning the filtered data immediately. This is useful for validating that your View Definition produces the expected output.

{% hint style="info" %}
**`VIEW_RESOURCE` Only**

Preview is only available for `VIEW_RESOURCE` bindings. Attempting to preview a `VIEW_CONTAINER` binding returns 400 Bad Request. For `VIEW_CONTAINER`, create the binding and inspect individual View Resources after async processing completes.
{% endhint %}

#### Input

| Field         | Value                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------------ |
| Endpoint      | **`https://storage.{ESS Domain}/views/bindings/preview`**                                        |
| Method        | **`POST`**                                                                                       |
| Authorization | Access token (`DPoP` or `Bearer`) with data subject permission for the Source Resource's storage |
| Content-Type  | **`application/json`**                                                                           |
| Payload       | View Binding request object                                                                      |

#### Example Request

```http
POST /views/bindings/preview HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/profile.json"
}
```

#### Output

Returns 200 OK with the filtered JSON data, or 400 Bad Request for validation errors.

#### Example Response

Assuming the Source Resource contains:

```json
{
  "name": "Alice Smith",
  "email": "alice@example.com",
  "phone": "+1-555-0123",
  "ssn": "123-45-6789"
}
```

The preview response would be:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com"
}
```

### Create View Binding

Creates a View Binding that connects a View Definition to Source Resource(s) and produces View Resource(s) at the specified destination. There are two types of View Bindings: `VIEW_RESOURCE` and `VIEW_CONTAINER`.

#### Input

| Field         | Value                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------------ |
| Endpoint      | **`https://storage.{ESS Domain}/views/bindings`**                                                |
| Method        | **`POST`**                                                                                       |
| Authorization | Access token (`DPoP` or `Bearer`) with data subject permission for the Source Resource's storage |
| Content-Type  | **`application/json`**                                                                           |
| Payload       | View Binding object                                                                              |

#### View Binding Types Comparison

| Aspect              | `VIEW_RESOURCE`                   | `VIEW_CONTAINER`                                            |
| ------------------- | --------------------------------- | ----------------------------------------------------------- |
| **Purpose**         | Filter a single Source Resource   | Filter all JSON resources in a container                    |
| **Source URI**      | Must NOT end with `/`             | Must end with `/` (container)                               |
| **Destination URI** | Must NOT end with `/`             | Must end with `/` (container)                               |
| **Result**          | One View Resource at explicit URI | Multiple View Resources with matching relative paths        |
| **Preview Support** | Yes, via `/bindings/preview`      | No preview available                                        |
| **Data Matching**   | N/A (single resource)             | Only creates views for data that produces non-empty results |

#### `VIEW_RESOURCE` Binding Structure

| Field                     | Description                                                                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **`type`**                | Required. Must be **`"VIEW_RESOURCE"`**.                                                                                                 |
| **`definitionUri`**       | Required. URI of the View Definition to apply. Must be a valid, existing View Definition in the registry.                                |
| **`sourceResource`**      | Required. URI of the source JSON resource to filter. Must NOT end with **`/`**. Must be a resource you have data subject permission for. |
| **`destinationResource`** | Required. URI where the View Resource will be created. Must NOT end with **`/`**. Can be any valid URI in your storage.                  |

#### Example `VIEW_RESOURCE` Request

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/private/profile.json",
  "destinationResource": "https://storage.example.com/alice/shared/contact-view"
}
```

#### Example `VIEW_RESOURCE` Response

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/shared/contact-view
Content-Type: application/json

{
  "id": "https://storage.example.com/alice/shared/contact-view",
  "viewResource": "https://storage.example.com/alice/shared/contact-view",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/private/profile.json",
  "destinationResource": "https://storage.example.com/alice/shared/contact-view",
  "type": "VIEW_RESOURCE"
}
```

#### `VIEW_CONTAINER` Binding Structure

| Field                     | Description                                                                                                                                                                                |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`type`**                | Required. Must be **`"VIEW_CONTAINER"`**.                                                                                                                                                  |
| **`definitionUri`**       | Required. URI of the View Definition to apply to all matching resources in the Source Container.                                                                                           |
| **`sourceResource`**      | Required. URI of the Source Container. Must end with **`/`**. All JSON resources in this container and its subcontainers will be processed.                                                |
| **`destinationResource`** | Required. URI of the destination container where View Resources will be created. Must end with **`/`**. View Resources will maintain the same relative path structure as Source Resources. |

#### Example `VIEW_CONTAINER` Request

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_CONTAINER",
  "definitionUri": "https://storage.example.com/views/registry/660e9511-f39c-52e5-b827-557766551111",
  "sourceResource": "https://storage.example.com/alice/transactions/",
  "destinationResource": "https://storage.example.com/alice/views/transactions/"
}
```

#### Example `VIEW_CONTAINER` Response

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/views/transactions/
Content-Type: application/json

{
  "id": "https://storage.example.com/alice/views/transactions/",
  "viewResource": "https://storage.example.com/alice/views/transactions/",
  "definitionUri": "https://storage.example.com/views/registry/660e9511-f39c-52e5-b827-557766551111",
  "sourceResource": "https://storage.example.com/alice/transactions/",
  "destinationResource": "https://storage.example.com/alice/views/transactions/",
  "type": "VIEW_CONTAINER"
}
```

#### `VIEW_CONTAINER` Path Structure

When a `VIEW_CONTAINER` binding is created, View Resources are created with the same relative paths as their Source Resources:

```
Source Container:
  https://storage.example.com/alice/transactions/
    2024-01.json
    2024-02.json
    archive/
      2023-12.json

Destination Container (after binding):
  https://storage.example.com/alice/views/transactions/
    2024-01.json (filtered view)
    2024-02.json (filtered view)
    archive/
      2023-12.json (filtered view)
```

{% hint style="info" %}
**Asynchronous Processing**

View Binding creation is asynchronous. The API returns 201 Created immediately, but View Resources are materialized shortly afterward (typically within 10 seconds). Use retry logic when accessing View Resources to account for processing time. See [Asynchronous Processing](#asynchronous-processing) for details.
{% endhint %}

#### Idempotency

View bindings are idempotent by destination, definition, and source. Submitting the same binding request multiple times succeeds (201 Created) and references the same view resource.

### Delete View Resource

Deletes a View Resource and its corresponding binding. If there are no more bindings on the associated Source Resource, this also removes its protection, allowing it to be deleted.

#### Input

| Field         | Value                                                                     |
| ------------- | ------------------------------------------------------------------------- |
| Endpoint      | **`{viewResourceUri}`**                                                   |
| Method        | **`DELETE`**                                                              |
| Authorization | Access token (DPoP or Bearer) with write permission for the View Resource |

#### Example Request

```http
DELETE /shared/contact-view HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

#### Output

Returns 204 No Content on successful deletion.

#### Example Response

```http
HTTP/1.1 204 No Content
```

## GraphQL Query Features

The Data Views API uses GraphQL to define how source data is filtered. This section explains the supported GraphQL features for creating View Definitions.

### Supported Data Types

#### Standard Scalars

GraphQL's standard scalar types are fully supported:

* **`String`**: Text values
* **`Int`**: 32-bit integer values
* **`Float`**: Floating-point numeric values
* **`Boolean`**: True or false values
* **`ID`**: Unique identifier values (treated as strings)

#### Extended Scalars

Data Views supports additional scalar types for common data formats:

* **`DateTime`**: ISO-8601 formatted date-time values (e.g., `"2024-03-15T10:30:00.000Z"`)
* **`Json`**: Arbitrary JSON objects or arrays

Extended scalars must be declared in the schema:

```graphql
scalar DateTime
scalar Json

type Query {
  dateField: DateTime
  metadata: Json
}
```

### Field Selection

The simplest use of GraphQL is selecting specific fields from source data.

#### Basic Field Selection

**Schema:**

```graphql
type Query {
  name: String
  email: String
  phone: String
  ssn: String
}
```

**Query:**

```graphql
{ name, email }
```

**Source Data:**

```json
{
  "name": "Alice Smith",
  "email": "alice@example.com",
  "phone": "+1-555-0123",
  "ssn": "123-45-6789"
}
```

**View Result:**

```json
{
  "name": "Alice Smith",
  "email": "alice@example.com"
}
```

#### Nested Object Selection

**Schema:**

```graphql
type Query {
  name: String
  address: Address
}

type Address {
  street: String
  city: String
  state: String
  zipCode: String
}
```

**Query:**

```graphql
{
  name
  address {
    city
    state
  }
}
```

**Source Data:**

```json
{
  "name": "Bob Jones",
  "address": {
    "street": "123 Main St",
    "city": "Boston",
    "state": "MA",
    "zipCode": "02101"
  }
}
```

**View Result:**

```json
{
  "name": "Bob Jones",
  "address": {
    "city": "Boston",
    "state": "MA"
  }
}
```

#### Array Field Selection

**Schema:**

```graphql
type Query {
  name: String
  emails: [String]
}
```

**Query:**

```graphql
{ name, emails }
```

**Source Data:**

```json
{
  "name": "Charlie Davis",
  "emails": ["charlie@work.com", "charlie@personal.com", "charlie@backup.com"]
}
```

**View Result:**

```json
{
  "name": "Charlie Davis",
  "emails": ["charlie@work.com", "charlie@personal.com", "charlie@backup.com"]
}
```

### Query Filters

GraphQL filters allow you to select specific items from arrays based on conditions. Filters are specified as arguments to fields.

#### Equality Filters

Filter array items where a field equals a specific value:

**Schema:**

```graphql
type Query {
  transactions(provider: String): [Transaction]
}

type Transaction {
  amount: Float
  provider: String
  description: String
}
```

**Query:**

```graphql
{ transactions(provider: "Acme Inc") { amount, description } }
```

**Source Data:**

```json
{
  "transactions": [
    { "amount": 50.0, "provider": "Acme Inc", "description": "Books" },
    { "amount": 75.0, "provider": "GlobalMart", "description": "Groceries" },
    { "amount": 120.0, "provider": "Acme Inc", "description": "Electronics" }
  ]
}
```

**View Result:**

```json
{
  "transactions": [
    { "amount": 50.0, "description": "Books" },
    { "amount": 120.0, "description": "Electronics" }
  ]
}
```

#### Numeric Comparison Filters

Filter numeric fields using comparison operators:

| Operator  | Description              |
| --------- | ------------------------ |
| **`eq`**  | Equal to                 |
| **`gt`**  | Greater than             |
| **`gte`** | Greater than or equal to |
| **`lt`**  | Less than                |
| **`lte`** | Less than or equal to    |

**Schema:**

```graphql
type Query {
  transactions(amount: AmountFilter): [Transaction]
}

input AmountFilter {
  eq: Float
  gt: Float
  gte: Float
  lt: Float
  lte: Float
}

type Transaction {
  amount: Float
  category: String
  date: String
}
```

**Query (transactions over $100):**

```graphql
{ transactions(amount: {gt: 100.0}) { amount, category } }
```

**Source Data:**

```json
{
  "transactions": [
    { "amount": 50.0, "category": "groceries", "date": "2024-03-01" },
    { "amount": 150.0, "category": "electronics", "date": "2024-03-05" },
    { "amount": 200.0, "category": "furniture", "date": "2024-03-10" }
  ]
}
```

**View Result:**

```json
{
  "transactions": [
    { "amount": 150.0, "category": "electronics" },
    { "amount": 200.0, "category": "furniture" }
  ]
}
```

**Query (transactions between $50 and $200):**

```graphql
{ transactions(amount: {gte: 50.0, lte: 200.0}) { amount, category } }
```

#### Array Membership Filters

Filter items where a field value is in a specified set.

**Schema:**

```graphql
type Query {
  transactions(amount: AmountFilter): [Transaction]
}

input AmountFilter {
  in: [Float]
}

type Transaction {
  amount: Float
  description: String
}
```

**Query:**

```graphql
{ transactions(amount: {in: [50.0, 100.0, 150.0]}) { amount, description } }
```

**Source Data:**

```json
{
  "transactions": [
    { "amount": 50.0, "description": "Subscription" },
    { "amount": 75.0, "description": "Groceries" },
    { "amount": 100.0, "description": "Utilities" },
    { "amount": 125.0, "description": "Gas" }
  ]
}
```

**View Result:**

```json
{
  "transactions": [
    { "amount": 50.0, "description": "Subscription" },
    { "amount": 100.0, "description": "Utilities" }
  ]
}
```

#### Date Range Filters

Filter `DateTime` fields using temporal comparisons:

| Operator     | Description                                          |
| ------------ | ---------------------------------------------------- |
| **`after`**  | `DateTime` is after (exclusive) the specified value  |
| **`before`** | `DateTime` is before (exclusive) the specified value |

**Schema:**

```graphql
scalar DateTime

type Query {
  transactions(date: DateFilter): [Transaction]
}

input DateFilter {
  after: DateTime
  before: DateTime
}

type Transaction {
  date: DateTime
  amount: Float
  category: String
}
```

**Query (March 2024 transactions):**

```graphql
{
  transactions(date: {
    after: "2024-02-29T23:59:59.999Z",
    before: "2024-04-01T00:00:00.000Z"
  }) {
    date
    amount
    category
  }
}
```

**Source Data:**

```json
{
  "transactions": [
    { "date": "2024-02-15T10:30:00.000Z", "amount": 50.0, "category": "groceries" },
    { "date": "2024-03-10T14:20:00.000Z", "amount": 150.0, "category": "electronics" },
    { "date": "2024-03-25T09:45:00.000Z", "amount": 200.0, "category": "furniture" },
    { "date": "2024-04-05T16:15:00.000Z", "amount": 75.0, "category": "clothing" }
  ]
}
```

**View Result:**

```json
{
  "transactions": [
    { "date": "2024-03-10T14:20:00.000Z", "amount": 150.0, "category": "electronics" },
    { "date": "2024-03-25T09:45:00.000Z", "amount": 200.0, "category": "furniture" }
  ]
}
```

#### Multiple Filter Combination

Multiple filters on the same field are combined with `AND` logic.

**Schema:**

```graphql
scalar DateTime

type Query {
  transactions(
    provider: String
    amount: AmountFilter
    date: DateFilter
  ): [Transaction]
}

input AmountFilter {
  gt: Float
  lt: Float
}

input DateFilter {
  after: DateTime
}

type Transaction {
  date: DateTime
  amount: Float
  provider: String
  description: String
}
```

**Query (Acme Inc purchases over $100 in March 2024):**

```graphql
{
  transactions(
    provider: "Acme Inc",
    amount: {gt: 100.0},
    date: {after: "2024-02-29T23:59:59.999Z"}
  ) {
    date
    amount
    description
  }
}
```

All specified filters must match for a transaction to be included in the View.

## Complete Workflows

### Workflow 1: Basic Contact Filtering (`VIEW_RESOURCE`)

This workflow demonstrates creating a filtered View of a single resource containing personal contact information.

#### Step 1: Create Source Resource

Create a JSON resource with complete contact information:

```http
PUT /private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com",
  "phone": "+1-555-0123",
  "address": {
    "street": "123 Main St",
    "city": "Boston",
    "state": "MA",
    "zipCode": "02101"
  },
  "ssn": "123-45-6789"
}
```

**Response**:

```http
HTTP/1.1 201 Created
```

#### Step 2: Create View Definition

Create a View Definition that filters to only name and email. Note that the schema only needs to define the fields used in the query - it doesn't need to include all fields from the Source Resource:

```http
POST /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Basic Contact Info",
  "description": "Shares only name and email for contact purposes",
  "purpose": "contact-sharing",
  "schema": "type Query { name: String, email: String }",
  "query": "{ name, email }"
}
```

**Response**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "type": "graphql",
  "name": "Basic Contact Info",
  "description": "Shares only name and email for contact purposes",
  "purpose": "contact-sharing",
  "schema": "type Query { name: String, email: String }",
  "query": "{ name, email }"
}
```

#### Step 3: Preview the Binding (Optional)

Test the View Definition before creating the binding:

```http
POST /views/bindings/preview HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/private/profile.json"
}
```

Response:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com"
}
```

The preview confirms that phone, address, and ssn are filtered out as expected.

#### Step 4: Create View Binding

Create the binding to produce the View Resource:

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/private/profile.json",
  "destinationResource": "https://storage.example.com/alice/shared/contact-view"
}
```

**Response**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/shared/contact-view
Content-Type: application/json

{
  "id": "https://storage.example.com/alice/shared/contact-view",
  "viewResource": "https://storage.example.com/alice/shared/contact-view",
  "definitionUri": "https://storage.example.com/views/registry/550e8400-e29b-41d4-a716-446655440000",
  "sourceResource": "https://storage.example.com/alice/private/profile.json",
  "destinationResource": "https://storage.example.com/alice/shared/contact-view",
  "type": "VIEW_RESOURCE"
}
```

#### Step 5: Access View Resource

Access the View Resource (with retry for async processing):

```http
GET /shared/contact-view HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

Initial response (if processing not complete):

```http
HTTP/1.1 404 Not Found
```

Retry after a few seconds.

**Response** (after processing completes):

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com"
}
```

#### Step 6: Share View Resource

Now you can share the View Resource using ACP or Access Grants. External users will only see name and email. They will never see the phone, address, or SSN fields from the Source Resource.

#### Step 7: Update Source Resource

Update the Source Resource with new data:

```http
PUT /private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "name": "Alice Johnson",
  "email": "alice.johnson@example.com",
  "phone": "+1-555-9999",
  "address": {
    "street": "456 Oak Ave",
    "city": "Cambridge",
    "state": "MA",
    "zipCode": "02138"
  },
  "ssn": "123-45-6789"
}
```

**Response**:

```http
HTTP/1.1 204 No Content
```

#### Step 8: View Auto-Updates

After a short delay, the View Resource automatically reflects the changes:

```http
GET /shared/contact-view HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "Alice Johnson",
  "email": "alice.johnson@example.com"
}
```

The View Resource updated automatically. Note that phone and address changes are not visible in the view.

#### Step 9: Delete View Binding

To stop sharing and remove the View Resource:

```http
DELETE /shared/contact-view HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 204 No Content
```

After a short delay, the View Resource is deleted and accessing it returns 404 Not Found. The Source Resource remains unchanged and can now be deleted if needed.

### Workflow 2: Bulk Transaction Filtering (`VIEW_CONTAINER`)

This workflow demonstrates creating filtered Views of multiple resources in a container.

#### Step 1: Create Source Container

Create a container for transaction records:

```http
PUT /transactions/ HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: text/turtle
```

**Response**:

```http
HTTP/1.1 201 Created
```

#### Step 2: Add Transaction Resources

Add multiple transaction records to the container.

**January Transactions:**

```http
PUT /transactions/2024-01.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-01-05T10:30:00.000Z",
      "amount": 50.0,
      "category": "groceries",
      "provider": "FreshMarket",
      "accountNumber": "****1234"
    },
    {
      "date": "2024-01-15T14:20:00.000Z",
      "amount": 150.0,
      "category": "electronics",
      "provider": "TechHub",
      "accountNumber": "****1234"
    }
  ]
}
```

**February Transactions:**

```http
PUT /transactions/2024-02.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-02-10T09:45:00.000Z",
      "amount": 200.0,
      "category": "furniture",
      "provider": "FurnitureCo",
      "accountNumber": "****1234"
    },
    {
      "date": "2024-02-20T16:15:00.000Z",
      "amount": 75.0,
      "category": "clothing",
      "provider": "GlobalMart",
      "accountNumber": "****1234"
    }
  ]
}
```

**Responses**:

```http
HTTP/1.1 201 Created
```

#### Step 3: Create View Definition for Transactions

Create a View Definition that filters out account numbers:

```http
POST /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Transaction Summary",
  "description": "Shows transaction details without sensitive account information",
  "purpose": "reporting",
  "schema": "scalar DateTime type Query { transactions: [Transaction] } type Transaction { date: DateTime, amount: Float, category: String, provider: String, accountNumber: String }",
  "query": "{ transactions { date, amount, category, provider } }"
}
```

**Response**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/views/registry/660e9511-f39c-52e5-b827-557766551111
```

#### Step 4: Create `VIEW_CONTAINER` Binding

Create a Binding for the entire container:

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_CONTAINER",
  "definitionUri": "https://storage.example.com/views/registry/660e9511-f39c-52e5-b827-557766551111",
  "sourceResource": "https://storage.example.com/alice/transactions/",
  "destinationResource": "https://storage.example.com/alice/views/transactions/"
}
```

**Response**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/views/transactions/
```

#### Step 5: Access View Resources

After async processing completes, access the filtered View Resources:

**January View:**

```http
GET /views/transactions/2024-01.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response:**

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-01-05T10:30:00.000Z",
      "amount": 50.0,
      "category": "groceries",
      "provider": "Whole Foods"
    },
    {
      "date": "2024-01-15T14:20:00.000Z",
      "amount": 150.0,
      "category": "electronics",
      "provider": "Best Buy"
    }
  ]
}
```

**February View:**

```http
GET /views/transactions/2024-02.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-02-10T09:45:00.000Z",
      "amount": 200.0,
      "category": "furniture",
      "provider": "FurnitureCo"
    },
    {
      "date": "2024-02-20T16:15:00.000Z",
      "amount": 75.0,
      "category": "clothing",
      "provider": "GlobalMart"
    }
  ]
}
```

Note that `accountNumber` is filtered out from all View Resources.

#### Step 6: Add New Source Resource

Add a new transaction file to the Source Container:

```http
PUT /transactions/2024-03.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-03-10T11:00:00.000Z",
      "amount": 300.0,
      "category": "travel",
      "provider": "United Airlines",
      "accountNumber": "****1234"
    }
  ]
}
```

**Response**:

```http
HTTP/1.1 201 Created
```

#### Step 7: New View Auto-Created

After async processing, a View Resource is automatically created for the new file:

```http
GET /views/transactions/2024-03.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "transactions": [
    {
      "date": "2024-03-10T11:00:00.000Z",
      "amount": 300.0,
      "category": "travel",
      "provider": "United Airlines"
    }
  ]
}
```

#### Step 8: Delete Source Resource

Delete one of the Source Resources:

```http
DELETE /transactions/2024-01.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 204 No Content
```

#### Step 9: Corresponding View Auto-Deleted

After async processing, the corresponding View Resource is automatically deleted:

```http
GET /views/transactions/2024-01.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 404 Not Found
```

The other View Resources (2024-02.json and 2024-03.json) remain available.

## Key Behaviors and Constraints

### Asynchronous Processing

{% hint style="warning" %}
**All View Operations Are Asynchronous**

View resource creation, updates, and deletions happen asynchronously after API operations complete. The API returns success immediately, but materialization occurs shortly afterward.
{% endhint %}

#### Typical Processing Times

* **View Resource creation**: 5-15 seconds after binding creation
* **View Resource updates**: 5-15 seconds after Source Resource changes
* **View Resource deletion**: 5-15 seconds after binding deletion

Actual times depend on system load and data size.

{% hint style="info" %}
**Client Retry Logic**

Due to the asynchronous nature of View Resource creation and deletion, clients should implement retry logic when accessing newly created View Resources or verifying deletion completion.
{% endhint %}

### Source Protection

{% hint style="danger" %}
**Source root resources cannot be deleted while bindings exist**

Only the source root resource (for `VIEW_RESOURCE` bindings) or source root container (for `VIEW_CONTAINER` bindings) is protected from deletion while active bindings exist. Attempting to delete a protected source returns a 409 Conflict error.

* **`VIEW_RESOURCE`**: The specific Source Resource referenced in the binding cannot be deleted
* **`VIEW_CONTAINER`**: The Source Container itself cannot be deleted, but nested child resources within the container can be deleted (which will trigger automatic deletion of their corresponding View Resources)
  {% endhint %}

#### Protected Deletion Example - `VIEW_RESOURCE`

```http
DELETE /alice/private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response** (when View Binding exists):

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "status": 409,
  "title": "Conflict",
  "detail": "Cannot remove resource with existing direct views. Please remove the views first to delete the resource. Refer to rel=https://w3id.org/inrupt/namespace/vocab/storage/hasViewResource Link headers to discover the views.",
  "instance": "/alice/private/profile.json"
}
```

#### Protected Deletion Example - `VIEW_CONTAINER`

When a `VIEW_CONTAINER` binding exists for `https://storage.example.com/alice/transactions/`:

**Attempting to delete the Source Container (BLOCKED):**

```http
DELETE /alice/transactions/ HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "status": 409,
  "title": "Conflict",
  "detail": "Cannot remove resource with existing direct views. Please remove the views first to delete the resource. Refer to rel=https://w3id.org/inrupt/namespace/vocab/storage/hasViewResource Link headers to discover the views.",
  "instance": "/alice/transactions/"
}
```

**Deleting a nested child resource (ALLOWED):**

```http
DELETE /alice/transactions/2024-01.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 204 No Content
```

The nested child resource is deleted successfully, and the corresponding View Resource at `/alice/views/transactions/2024-01.json` is automatically deleted asynchronously.

#### Discovering View Bindings

To identify View Bindings on a Source Resource, inspect the `Link` headers in the resource's HTTP response. Source Resources with active bindings include a `Link` header with the relation type `https://w3id.org/inrupt/namespace/vocab/storage/hasViewResource`.

**Example Request:**

```http
HEAD /alice/private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Example Response:**

```http
HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://storage.example.com/alice/shared/contact-view>; rel="https://w3id.org/inrupt/namespace/vocab/storage/hasViewResource"
Link: <https://storage.example.com/alice/public/basic-info>; rel="https://w3id.org/inrupt/namespace/vocab/storage/hasViewResource"
```

In this example, the Source Resource has two View Bindings pointing to:

* `https://storage.example.com/alice/shared/contact-view`
* `https://storage.example.com/alice/public/basic-info`

These URIs identify the View Resources that must be deleted before the source can be deleted.

#### Resolution

To delete a protected Source Root Resource or Container:

1. **Discover View Bindings**: Inspect the `Link` headers on the Source Resource to identify all View Resources
2. **Delete each View Resource**: Send DELETE requests to each View Resource URI (which deletes the binding)
3. **Delete the source**: Once all bindings are removed, delete the source root resource or container

**Step 1: Discover bindings**

```http
HEAD /alice/private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

Response shows two View Bindings in the `Link` headers.

**Step 2: Delete View Resources**

```http
DELETE /alice/shared/contact-view HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

```http
DELETE /alice/public/basic-info HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Step 3: Delete Source Resource**

After all View Resources are deleted, the source can be deleted successfully:

```http
DELETE /alice/private/profile.json HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response**:

```http
HTTP/1.1 204 No Content
```

### Idempotency

#### View Definition Idempotency

View Definitions are idempotent by name and content:

**Scenario 1: Identical Name and Content**

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Contact Info",
  "schema": "type Query { name: String, email: String }",
  "query": "{ name, email }"
}
```

**First request response**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/views/registry/abc-123
```

**Repeat request with identical content**:

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/views/registry/abc-123
```

The same Definition URI is returned. No duplicate is created.

**Scenario 2: Same Name, Different Content**

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Contact Info",
  "schema": "type Query { name: String, email: String, phone: String }",
  "query": "{ name, email, phone }"
}
```

**Response**:

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "status": 409,
  "title": "Conflict",
  "detail": "View definition with name 'Contact Info' already exists with different content",
  "instance": "/views/registry"
}
```

#### View Binding Idempotency

View Bindings are idempotent by destination, definition, and source:

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/shared/view"
}
```

**First request:**

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/shared/view
```

**Repeat request:**

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/shared/view
```

The same Binding is returned. No duplicate is created.

### Data Matching

#### `VIEW_CONTAINER` Filtering Behavior

When a `VIEW_CONTAINER` binding is created, not all Source Resources necessarily produce View Resources. The View Definition's GraphQL query acts as a filter.

**Resources That Produce Views:**

* JSON resources where the query produces non-empty results

**Resources That Don't Produce Views:**

* Non-JSON resources (text files, images, etc.)
* JSON resources where the query produces empty results (no matching data)
* Resources with malformed JSON
* Resources with data that doesn't match the schema

#### Example: Selective View Creation

**Source Container:**

```
/transactions/
  2024-01.json - Contains Acme Inc transactions
  2024-02.json - Contains GlobalMart transactions only
  2024-03.json - Contains Acme Inc transactions
  readme.txt - Text file
```

**View Definition:**

```graphql
type Query {
  transactions(provider: String): [Transaction]
}

type Transaction {
  amount: Float
  provider: String
}

Query: { transactions(provider: "Acme Inc") { amount } }
```

**Result in View Container:**

```
/views/transactions/
  2024-01.json - Contains filtered Acme Inc transactions
  2024-03.json - Contains filtered Acme Inc transactions
  # 2024-02.json NOT created (no Acme Inc transactions)
  # readme.txt NOT created (not JSON)
```

### Non-JSON Source Resources

#### Behavior with Non-JSON Resources

When a View Binding references a non-JSON Source Resource:

**`VIEW_RESOURCE`:**

* Binding creation succeeds (201 Created)
* View resource is NOT created
* Accessing the View Resource URI returns 404 Not Found

**`VIEW_CONTAINER`:**

* Binding creation succeeds (201 Created)
* Non-JSON resources are silently skipped
* Only JSON resources that match the query produce View Resources

#### Example: Text File Source

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/document.txt",
  "destinationResource": "https://storage.example.com/alice/views/document-view"
}
```

**Response:**

```http
HTTP/1.1 201 Created
Location: https://storage.example.com/alice/views/document-view
```

**Accessing the view:**

```http
GET /views/document-view HTTP/1.1
Host: storage.example.com
```

**Response:**

```http
HTTP/1.1 404 Not Found
```

The binding exists but no View Resource is materialized because the source is not JSON.

## Authorization

### Authentication

All Data Views API requests require authentication using either `DPoP` (Demonstrating Proof-of-Possession) or `Bearer` tokens.

For details on authentication methods, see [Authentication in Solid](https://docs.inrupt.com/guides/authentication-in-solid).

### View Registry Authorization

#### Read Operations (`GET`)

Any authenticated user can list and retrieve View Definitions:

```http
GET /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token>
DPoP: <dpop-proof>
```

**Response:**

```http
HTTP/1.1 200 OK
```

#### Write Operations (POST, DELETE)

Creating and deleting View Definitions requires special registry authorization. Only WebIDs in the registry authorization allow-list can perform these operations.

**Authorized Request:**

```http
POST /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <authorized-access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Test View",
  "schema": "type Query { field: String }",
  "query": "{ field }"
}
```

**Response:**

```http
HTTP/1.1 201 Created
```

**Unauthorized Request:**

```http
POST /views/registry HTTP/1.1
Host: storage.example.com
Authorization: DPoP <unauthorized-access-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Test View",
  "schema": "type Query { field: String }",
  "query": "{ field }"
}
```

**Response:**

```http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "status": 403,
  "title": "Forbidden",
  "detail": "Access has been denied",
  "instance": "/views/registry"
}
```

#### Configuration

Registry authorization is configured via environment variables or application properties. Only ESS Administrators are allowed to edit the allow-list. See [Registry Authorization Configuration](#registry-authorization) for details.

### View Binding Authorization

#### Creating Bindings

To create a View Binding, you must have data subject permission for the storage containing the Source Resource. This means you must be the owner of the Pod where the Source Resource resides.

**Authorized Request (own Storage):**

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token-for-alice>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 201 Created
```

**Unauthorized Request (not data subject):**

```http
POST /views/bindings HTTP/1.1
Host: storage.example.com
Authorization: DPoP <access-token-for-bob>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "status": 403,
  "title": "Forbidden",
  "detail": "Access has been denied",
  "instance": "/views/bindings"
}
```

#### Accessing View Resources

View Resources are regular Solid resources and respect standard access control:

* **ACP Policies**: Apply ACP policies to View Resources to control access
* **Access Grants**: Issue Access Grants for View Resources
* **Read Permission**: Users need read permission to access View Resources

View Resources do NOT inherit access control from Source Resources. You must explicitly grant access to View Resources.

## Error Responses

The Data Views API uses [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807) format for error responses.

### View Definition Errors

#### Invalid GraphQL Schema

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Invalid Schema",
  "schema": "type Query { field String }",
  "query": "{ field }"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/registry",
  "violations": [
    {
      "field": "schema",
      "in": "body",
      "message": "Invalid GraphQL schema: GraphQL schema validation failed: errors=[[@1:12] The schema definition text contains a non schema definition language (SDL) element 'OperationDefinition']"
    }
  ]
}
```

#### Query-Schema Mismatch

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Mismatch",
  "schema": "type Query { name: String, email: String }",
  "query": "{ name, phone }"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/registry",
  "violations": [
    {
      "field": "query",
      "in": "body",
      "message": "GraphQL schema and query validation failed: Query validation failed: Validation error (FieldUndefined@[phone]) : Field 'phone' in type 'Query' is undefined"
    }
  ]
}
```

#### View Definition Name Conflict

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Existing Name",
  "schema": "type Query { different: String }",
  "query": "{ different }"
}
```

**Response:**

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "status": 409,
  "title": "Conflict",
  "detail": "View definition with name 'Existing Name' already exists with different content",
  "instance": "/views/registry"
}
```

#### Exceeds Security Limits

```http
POST /views/registry HTTP/1.1
Content-Type: application/json

{
  "type": "graphql",
  "name": "Deep Query",
  "schema": "type Query { a: A } type A { b: B } type B { c: C } ...",
  "query": "{ a { b { c { d { e { f { g { h { i { j { k { l } } } } } } } } } } } }"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/registry",
  "violations": [
    {
      "field": "query",
      "in": "body",
      "message": "GraphQL schema and query validation failed: Query validation failed: maximum query depth exceeded 16 > 10"
    }
  ]
}
```

### View Binding Errors

#### Missing Required Fields

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/bindings",
  "violations": [
    {
      "field": "sourceResource",
      "in": "body",
      "message": "must not be null"
    },
    {
      "field": "destinationResource",
      "in": "body",
      "message": "must not be null"
    }
  ]
}
```

#### Source and Destination on Different Storages

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/bob/views/alice-profile"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/bindings",
  "violations": [
    {
      "field": "sourceResource",
      "in": "body",
      "message": "Source Resource and destination resource must be on the same storage"
    },
    {
      "field": "destinationResource",
      "in": "body",
      "message": "Source Resource and destination resource must be on the same storage"
    }
  ]
}
```

#### Invalid Binding Type

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "INVALID_TYPE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/bindings",
  "violations": [
    {
      "field": "type",
      "in": "body",
      "message": "must be VIEW_RESOURCE or VIEW_CONTAINER"
    }
  ]
}
```

#### `VIEW_CONTAINER` URI Requirements

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_CONTAINER",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/transactions",
  "destinationResource": "https://storage.example.com/alice/views/transactions/"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "instance": "/views/bindings",
  "violations": [
    {
      "field": "sourceResource",
      "in": "body",
      "message": "VIEW_CONTAINER type requires sourceResource URI to end with '/' (container)"
    }
  ]
}
```

#### Non-Existent View Definition

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/nonexistent",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "detail": "View definition not found",
  "instance": "/views/bindings"
}
```

#### Source Resource Not Found

```http
POST /views/bindings HTTP/1.1
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/nonexistent.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "status": 400,
  "title": "Bad Request",
  "detail": "Source Resource not found",
  "instance": "/views/bindings"
}
```

### Authorization Errors

#### Insufficient Registry Authorization

```http
POST /views/registry HTTP/1.1
Authorization: DPoP <unauthorized-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "graphql",
  "name": "Test",
  "schema": "type Query { field: String }",
  "query": "{ field }"
}
```

**Response:**

```http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "status": 403,
  "title": "Forbidden",
  "detail": "Access has been denied",
  "instance": "/views/registry"
}
```

#### Insufficient Data Subject Permission

When the source and destination are on the same storage but the agent making the request is not the data subject:

```http
POST /views/bindings HTTP/1.1
Authorization: DPoP <wrong-user-token>
DPoP: <dpop-proof>
Content-Type: application/json

{
  "type": "VIEW_RESOURCE",
  "definitionUri": "https://storage.example.com/views/registry/abc-123",
  "sourceResource": "https://storage.example.com/alice/profile.json",
  "destinationResource": "https://storage.example.com/alice/views/profile-view"
}
```

**Response:**

```http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "status": 403,
  "title": "Forbidden",
  "detail": "Access has been denied",
  "instance": "/views/bindings"
}
```

## Configuration

The Data Views Service is configured via environment variables or application properties files. Only ESS Administrators can adjust these settings.

### Security Limits

These settings control GraphQL query complexity to prevent resource exhaustion attacks.

#### Maximum Query Depth

Limits the maximum nesting depth of GraphQL queries.

```bash
inrupt.storage.dataviews.graphql.max-query-depth=10
```

**Default**: `10`

**Example**: A query with depth 12 would be rejected:

```graphql
{
  level1 {
    level2 {
      level3 {
        level4 {
          level5 {
            level6 {
              level7 {
                level8 {
                  level9 {
                    level10 {
                      level11 {
                        level12  # REJECTED: exceeds depth limit
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

#### Maximum Query Complexity

Limits the total complexity score of GraphQL queries. Complexity is calculated based on field count and nesting.

```bash
inrupt.storage.dataviews.graphql.max-query-complexity=1000
```

**Default**: `1000`

Queries with complexity scores exceeding this limit are rejected with a 400 Bad Request error.

#### Maximum List Size

Limits the maximum number of items that can be processed in array filtering operations.

```bash
inrupt.storage.dataviews.graphql.max-list-size=10000
```

**Default**: `10000`

If a Source Resource contains an array with more than this many items, the query is rejected.

### GraphQL Schema Caching

View Definitions' GraphQL schemas are parsed and cached to improve performance. These settings control cache behavior.

#### Cache Maximum Size

Maximum number of parsed schemas to keep in cache.

```bash
quarkus.cache.caffeine."graphql-schema-cache".maximum-size=100
```

**Default**: `100`

When the cache reaches this size, least-recently-used schemas are evicted.

#### Cache Expiry Time

How long parsed schemas remain in cache after last access.

```bash
quarkus.cache.caffeine."graphql-schema-cache".expire-after-write=1h
```

**Default**: `1h` (1 hour)

**Valid values**: Time duration string (e.g., `30m`, `2h`, `1d`)

After this time, schemas are evicted and must be re-parsed on next use.

### Registry Authorization

Controls which WebIDs are authorized to create and delete view definitions in the registry. Only WebIDs in the allow-list can perform write operations (`POST`, `DELETE`) on `/views/registry`. Read operations (`GET`) are available to all authenticated users.

#### Allow List Configuration

Specify authorized WebIDs using a comma-separated list:

```bash
inrupt.storage.dataviews.registry.agent.allow-list=https://id.example.com/alice,https://id.example.com/bob
```

**Default**: Empty (no users authorized for write operations)

**Format**: Comma-separated list of WebID URIs

**Example with multiple WebIDs:**

```bash
inrupt.storage.dataviews.registry.agent.allow-list=https://pod.example.com/admin,https://id.inrupt.com/service-account,https://pod.example.com/data-steward
```

{% hint style="warning" %}
**Authorization Required for Write Operations**

If the allow-list is empty or not configured, all attempts to create or delete View Definitions will return 403 Forbidden. Ensure at least one WebID is configured to enable View Definition management.
{% endhint %}

## Additional Information

### Related Concepts

* [**Access Control Policies (ACP)**](https://docs.inrupt.com/ess/latest/services/service-authorization): Use ACP to control who can access View Resources
* [**Access Grants**](https://docs.inrupt.com/ess/latest/services/service-access-grant): Issue Access Grants for View Resources to enable consent-based data sharing
* [**Notifications**](https://docs.inrupt.com/ess/latest/services/service-notification): Subscribe to notifications for View Resources to track changes
* [**Auditing Service**](https://docs.inrupt.com/ess/latest/services/service-auditing): View operations generate audit events for compliance

### Best Practices

#### Security

* **Minimize View Definitions**: Create View Definitions that expose only necessary fields, following the principle of least privilege
* **Review GraphQL queries**: Ensure queries don't inadvertently expose sensitive data through complex filtering logic
* **Use short-lived Access Grants**: When sharing View Resources, use Access Grants with appropriate expiration times
* **Monitor audit logs**: Regularly review audit events for view operations to detect unauthorized access attempts

#### Performance

* **Reuse View Definitions**: Create generic View Definitions that can be applied to multiple resources instead of creating specific definitions for each resource
* **Limit query complexity**: Keep GraphQL queries simple to reduce processing overhead
* **Use `VIEW_CONTAINER` sparingly**: `VIEW_CONTAINER` bindings process all resources in a container, which can be resource-intensive for large containers

#### Data Management

* **Document View Definitions**: Include clear descriptions and purposes for View Definitions to help future maintenance
* **Plan destination URIs**: Use consistent naming conventions for View Resources to make them easy to discover and manage
* **Clean up unused bindings**: Regularly delete View Bindings that are no longer needed to free resources and simplify management

#### Development Workflow

* **Use preview before binding**: Always preview `VIEW_RESOURCE` bindings before creating them to verify the output matches expectations
* **Test with sample data**: Create View Definitions using sample data that includes edge cases (null values, empty arrays, malformed dates)
* **Implement retry logic**: Always use retry logic when accessing View Resources to account for asynchronous processing

### Further Reading

* **GraphQL Language**: <https://graphql.org/learn/>
* **GraphQL Schema Definition**: <https://graphql.org/learn/schema/>
* **Quarkus Configuration**: <https://quarkus.io/guides/all-config>
* **DPoP Specification**: [RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession](https://www.rfc-editor.org/rfc/rfc9449.html)
* **RFC 7807 Problem Details**: <https://www.rfc-editor.org/rfc/rfc7807.html>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.inrupt.com/ess/latest/services/service-pod-management/data-views-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
