> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/features/data-modelling/relationships.md).

# Relationships

ODD Platform tracks entity-to-entity relationships as first-class catalog objects. They show up two ways: as **ERD diagrams** between table-class entities (foreign-key-style edges) and as **graph relationships** between graph-store entities (Neo4j-style edges). Both are surfaced under [Data Modelling](/features/data-modelling.md) → Relationships in the UI and through the `/api/relationships` API.

## The two relationship classes

The platform's internal model defines a `DATA_RELATIONSHIP(9)` data-entity class that contains two types:

| Internal type             | API filter value | What it represents                                                                                                                                                                                           |
| ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ENTITY_RELATIONSHIP(25)` | `ERD`            | Foreign-key-style edges between two table-class entities. Cross-schema references are supported. The collector that catalogs the source emits one `ENTITY_RELATIONSHIP` per detected foreign-key constraint. |
| `GRAPH_RELATIONSHIP(26)`  | `GRAPH`          | Free-form graph edges between graph-store entities (e.g., relationships between nodes in a Neo4j database). Distinct from ERD because the cardinality model is graph-native, not relational.                 |

A relationship is a regular catalog entity — it has an ODDRN, an owner, a namespace inherited from its source / target, and shows up in search alongside datasets. The dedicated UI surface and `/api/relationships` API are for **list and detail navigation** of just the relationship-class entities.

## ERD cardinality model (ENTITY\_RELATIONSHIP)

ERD relationships carry the source-and-target table pair plus a cardinality classifier. Four classifiers are defined upstream:

* `ONE_TO_EXACTLY_ONE` — one source row maps to exactly one target row.
* `ONE_TO_ZERO_OR_ONE` — one source row maps to zero or one target row.
* `ONE_TO_ONE_OR_MORE` — one source row maps to one or more target rows.
* `ONE_TO_ZERO_ONE_OR_MORE` — one source row maps to zero, one, or many target rows.

These mirror the cardinality vocabulary defined in the `odd-collectors` monorepo's [Relationships section](https://github.com/opendatadiscovery/odd-collectors#relationships).

## UI walkthrough

Open **Data Modelling → Relationships** from the top-level navigation (`/data-modelling/relationships`). The list page shows every visible relationship across all data sources — as of 0.28.0 it applies the catalog's default visibility rules, so soft-deleted, hollow, and excluded-from-search relationship entities are filtered out (earlier releases listed them here even after every other catalog surface hid them). The page shows:

* **A table** with columns Name, Type (ERD or GRAPH), Namespace + Datasource, Source entity, Target entity.
* **A type tab strip** filtering by `ALL` / `ERD` / `GRAPH` (matches the API enum).
* **A search input** filtering by name across the visible set.
* **Infinite-scroll pagination** — page size is 30 by default.

Click a row to open the relationship's detail page; the platform routes to the `/api/relationships/erd/{id}` or `/api/relationships/graph/{id}` payload depending on the relationship type.

![Relationships list page — eight ENTITY\_RELATIONSHIP rows from a Snowflake sample-data source, each with its name, type (ENTITY RELATIONSHIP), namespace + datasource, source entity, and target entity. The All / ERD / Graph type-filter strip sits above the table; the right-rail switches between Query Examples and Relationships, the two Data Modelling sub-surfaces.](/files/VySae3Sx4hMRi2x3A5qY)

The same data is also surfaced **per-entity** — every dataset's detail page has a **Relationships** tab (in the same tab strip as Overview / Structure / Lineage / Test reports / Alerts / Query examples / Activity / Discussions) that lists only the relationships in which the current entity participates as Parent or Child:

![Per-entity Relationships tab on the ORDERS table (DS / TABLE) — two relationships: (1) ORDERS\_references\_CUSTOMER with CUSTOMER as Parent and ORDERS as Child, cardinality ONE TO ZERO ONE OR MORE, Is Identifying False; (2) LINEITEM\_references\_ORDERS with ORDERS as Parent and LINEITEM as Child, cardinality ONE TO ZERO ONE OR MORE, Is Identifying True. Each row carries an inline ERD-style cardinality glyph (crow's-foot notation) between the Parent and Child columns.](/files/4TIesWKioG3ElTGhXcAE)

The per-entity view is the operator-relevant perspective: when reading a table's documentation, you see only its incoming and outgoing relationships, not the whole catalog's. The Parent / Child columns and the cardinality glyph match the underlying `ENTITY_RELATIONSHIP` model and the same `/api/relationships/erd/{id}` payload surfaces both in this tab and on the global Data Modelling → Relationships list above. As of 0.28.0 this tab hides soft-deleted and hollow relationship entities, but — unlike the global list — it deliberately keeps `exclude_from_search`-flagged ones visible: that flag scopes discovery surfaces, and the dataset's own detail tab would be silently incomplete without its real relationships.

## API surface

The three endpoints exposed by `RelationshipController` are documented at [API Reference → Relationships](/developer-guides/api-reference/relationships.md) — `GET /api/relationships` (paginated list with optional `type=ERD|GRAPH|ALL` filter and free-text query) plus the two per-type detail endpoints. The same payloads also surface on the source and target dataset's detail pages under the dataset's relationships cluster.

## How relationships get into the catalog

Relationships are populated **by the ingesting collector** during the source-side metadata pull. The platform does not infer relationships from naming conventions or column-name similarity — only what the collector emits is surfaced.

Adapter coverage as of the time of writing (per the upstream [`odd-collectors` Relationships matrix](https://github.com/opendatadiscovery/odd-collectors#relationships)):

| Collector       | Adapter      | Relationship type           | How it's derived                                                                |
| --------------- | ------------ | --------------------------- | ------------------------------------------------------------------------------- |
| `odd-collector` | `postgresql` | `ENTITY_RELATIONSHIP` (ERD) | Foreign-key constraints in the database catalog. Cross-schema FKs are detected. |
| `odd-collector` | `snowflake`  | `ENTITY_RELATIONSHIP` (ERD) | Foreign-key constraints in the database catalog. Cross-schema FKs are detected. |

{% hint style="info" %}
**No other adapter currently emits relationships.** The `GRAPH_RELATIONSHIP` type is reserved in the platform model for graph-store sources (e.g., Neo4j edges), but no adapter in the published `odd-collectors` Relationships matrix currently surfaces graph relationships at the time of writing — operators using a Neo4j adapter today get nodes catalogued without their inter-node edges. This is an upstream collector-side gap; if your deployment relies on graph relationships, follow the upstream repo for adapter changes.
{% endhint %}

## Known operator caveats

Four behaviours of the Relationships surface are non-obvious from the UI alone — they are model contracts to know about, not defects. Two further code-side defects from earlier releases (the Target-column rendering and the unvalidated `?type=` parameter) were **fixed in 0.28.0**; the notes below describe them for operators on 0.27.x and earlier.

{% hint style="info" %}
**Fixed in 0.28.0 — the Target column previously showed the source entity.** On 0.27.x and earlier, the list-row renderer passed the source entity's data to both the Source and the Target cells, so every row displayed the same dataset twice and a discovery workflow of the form "find every relationship where `ORDERS` is the target" was impossible from this page. As of 0.28.0 the Target column renders the actual target entity. On affected releases, use the per-entity Relationships tab (which was never affected) for target-side questions.
{% endhint %}

{% hint style="warning" %}
**There is no RBAC gate on the Relationships endpoints — any authenticated caller can list every visible relationship in the catalog.** Neither `GET /api/relationships` nor the two per-type detail endpoints (`/api/relationships/erd/{id}` and `/api/relationships/graph/{id}`) are enumerated in the platform's authorization rules; the catch-all "any authenticated user" rule applies, and the query does not filter by owner or namespace. The catalog UI route mounts bare — contrast with `/data-modelling/query-examples`, which is wrapped in a permission-context provider. (Until 0.28.0 the listing also ignored the catalog's visibility flags — soft-deleted and excluded-from-search relationship entities stayed listed here after every other surface hid them; as of 0.28.0 the default visibility rules apply, so this caveat is now purely about access, not visibility.)

**Operator-visible consequence.** Every authenticated user reads every visible relationship in the catalog, across team boundaries. Under `auth.type=DISABLED`, the same listing is anonymous. Multi-team deployments expecting per-team isolation on relationships cannot enforce it through the platform's RBAC today.

**Mitigation today.** Treat the Relationships listing as catalog-read-collaborative — the same posture as the Owner / Namespace / Datasource directories. If your deployment requires per-team isolation on relationships specifically, enforce it at the network perimeter (reverse-proxy rules on `/api/relationships/*` plus the corresponding UI route).
{% endhint %}

{% hint style="warning" %}
**Clicking a row on the list opens the source entity's detail page — not a relationship-type-specific URL.** The row's Name link routes to `/dataentities/{id}` (the entity-detail-page route) on every click, regardless of relationship type. The two type-specific endpoints (`/api/relationships/erd/{id}` and `/api/relationships/graph/{id}`) are reached **from inside** the entity-detail page's relationship-card rendering, not from the row click on the list page. If you are scripting deep-links into "the relationship's detail view," use the per-entity Relationships tab on the source / target entity's page; there is no list-page row URL that lands on a type-specific relationship view today.
{% endhint %}

{% hint style="warning" %}
**The search input filters by relationship name only — not by source or target entity name.** Typing `orders` into the list-page search box matches relationships whose own `name` field contains the string `orders` (case-insensitive). It does **not** match relationships where the source or target entity is named `ORDERS`. The behaviour is consistent with how the platform models relationships as first-class entities, but the framing on the page above ("search input filtering by name across the visible set") does not call out the scope.

**To find every relationship that involves a specific dataset**, open that dataset's detail page → Relationships tab. That surface lists only the relationships in which the entity participates as Parent or Child, which is the question operators usually mean when they search.
{% endhint %}

{% hint style="info" %}
**Fixed in 0.28.0 — a mistyped `?type=` URL parameter previously rendered a dead, blank view.** On 0.27.x and earlier, the page sent the raw value to the backend, which rejected it with an opaque HTTP 400 from enum-binding; the tab strip showed no active tab and the list read "0 relationships overall" with no error message — indistinguishable from an empty catalog. As of 0.28.0 an unknown value (`?type=erd` lower-case, `?type=Erd` mixed-case, anything outside `ALL` / `ERD` / `GRAPH`) falls back to the `ALL` view with the All tab active. The accepted tokens are still exactly `ALL` / `ERD` / `GRAPH` (case-sensitive) — deep links using other spellings load the unfiltered list, not the intended tab.
{% endhint %}

{% hint style="info" %}
**The `relationship_id` path parameter on the API is the relationship's data-entity id, not the `relationships` table primary key.** The platform models a relationship as a regular catalog entity (entity-class id `9`, `DATA_RELATIONSHIP`). The API parameter named `relationship_id` is the corresponding `data_entity.id`; the underlying `relationships.id` row is internal and is not the value the API consumes.

The same trap exists **inside the API payload itself**: the details response exposes `erd_relationship.erd_relationship_id` and `graph_relationship.graph_relationship_id` — internal detail-record ids from two further tables. Feeding either back into `/api/relationships/{type}/{id}` returns a 404 (or, on a numeric coincidence with another relationship's data-entity id, the payload of an unrelated relationship). The only value that round-trips is the relationship's own `id` field from the list or details payload; the UI always uses it and is unaffected. As of 0.28.0 the OpenAPI spec states this on the `relationship_id` parameter and on both `*_relationship_id` fields.
{% endhint %}

## Where to next

* [Data Modelling overview](/features/data-modelling.md) — parent section; pairs Relationships with Query Examples.
* [Query Examples](/features/data-modelling/query-examples.md) — the other Data Modelling sub-surface.
* [Integrations → odd-collector](/integrations/integrations/odd-collector.md) — the generic collector that hosts the PostgreSQL and Snowflake adapters surfacing ERD relationships today; the per-adapter feature matrix on that page calls out ERD support explicitly.
* [Main Concepts](/introduction/main-concepts.md) — for the broader catalog vocabulary that names these entities.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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.opendatadiscovery.org/features/data-modelling/relationships.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.
