# Entity connections

> The two ways entities connect, structural links and semantic relations, and when to use each.

URL: https://doc.synap.live/docs/concepts/entity-connections · Last reviewed against the code: 2026-11-04

Records in your pod connect to each other in two ways. A **structural link** is a field on a record that points to
another record, such as the contact on a deal. A **semantic relation** is a typed edge in the pod's graph, such as a
note that references a decision. You read both together, in one list.

## The two systems

| | Structural links | Semantic relations |
|---|---|---|
| **Stored as** | A field whose value is another record's id (`valueType: "entity_id"`) | A relation with a type, between two records |
| **Defined by** | The kind's fields, set up with the space or its template | Any time, by a person, an agent or an automation |
| **Direction** | From the record that holds the field | Readable from either end |
| **Good for** | The core data model: "this deal's primary contact" | Knowledge found later: "this note references that decision" |

## Structural links

A structural link is a field of type `entity_id` on a kind. Its value is the id of another record. For example, a
`deal` kind could have a `contact` field; every deal then shows it as a field people fill in.

Use a structural link when:

- the link is part of what the record is, and most records of that kind have it;
- you want it to appear as a field in forms;
- the link has a clear direction ("a deal has one primary contact");
- you are writing a template: model each relationship your kinds need as a field.

A field can also name a relation type. Writing that field then creates the matching relation in the graph, and
clearing it removes the relation, so the link shows up in graph queries too.

## Semantic relations

A semantic relation joins two records with a type, such as `references`, `mentions` or `blocks`. No field defines it:
a person, an agent or an automation creates one between any two records when it finds the connection.

```json
{
  "sourceEntityId": "<note-id>",
  "targetEntityId": "<decision-id>",
  "type": "references"
}
```

Use a semantic relation when:

- the connection is found after the fact, not modelled up front;
- it joins records of any kinds, with no field behind it;
- you want to walk the graph, such as everything two hops from a record;
- it is many-to-many and does not fit a field;
- an agent is annotating what it found.

The type must be one the pod knows. Every pod has a set of default types, such as `relates_to`, `references`,
`mentions`, `parent_of`, `depends_on`, `blocks`, `assigned_to`, `created_by` and `works_at`, and a space or its
template can add its own. `synap_list_profiles` returns the relation types you may use.

## When both could work

A deal could have a `contact` field, or a `relates_to` relation to the same person. The rule of thumb:

> **Use fields for what the kind's schema owns. Use relations for what you discover.**

An assignee on a task is a field: it is part of the task. A `references` relation between two documents is a
relation: it came from reading them.

## One list of connections

`GET /api/hub/entities/{id}/connections` (scope `hub-protocol.read`) returns every connection around a record, from
both systems and from the rest of the pod, in one list. Each item has a `source`:

| `source` | What it is |
|---|---|
| `graph` | A semantic relation, with its `relationType` and a `direction` of `outgoing` or `incoming` |
| `property` | A structural link, with its `propertySlug` and `propertyLabel` |
| `thread` | A conversation that used, created, updated or referenced the record |
| `context_channel` | A conversation about the record |
| `focus_session` | A session whose subject is the record |

The answer is `{ connections, counts }`. `limit` caps the items read from each source (default 50, at most 200), and
`workspaceId` reads through one space. The typed client wraps it: `client.getConnections(entityId)` from
`@synap-core/hub-rest-client`.

`GET /api/hub/relations` reads only the graph, so a structural link whose field names no relation type is missing
there. Use `/connections` when you want every link without knowing where it is stored.

## For agents

- **Set a field** when the link is a defined field (an assignee, the contact on a deal).
- **Create a relation** for a connection you discover: `synap_link_entities` over MCP, or `POST /api/hub/relations`.
- **Read the connections** to see every link around a record: `GET /api/hub/entities/{id}/connections`, or
  `synap_get_relations` over MCP for the graph edges alone.

## Related

- [Kind and facets](/docs/concepts/kind-and-facets)
- [Profile schemas](/docs/concepts/profile-schemas)
- [Hub REST API](/docs/reference/hub-api)
