> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useboom.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Relationship types

> Register the catalog of link shapes and the data you need to do it correctly.

A **relationship type** is the *shape* of a link, not a link itself. Every
relationship (e.g. person `user_123` → order `ord_998`) points at a
relationship type that defines its `role`, the object types involved, and
metadata like cardinality. Think of it as the schema for your edges.

Creating a relationship requires a `relationshipTypeId`, so registering
the type (or finding an existing one with `GET /relationship-types`) is
a **prerequisite for linking** over the API. Registering also lets you
attach `cardinality`, `description`, and `bidirectional` so your graph is
self-describing and queryable in Segments.

<Note>
  When you **register** a type, you reference custom object types by
  **name** (`order`, `line_item`). When you **create a relationship**, you
  reference the type by its **`relationshipTypeId`** (returned here), so
  register or list your types first. See [the FAQ below](#do-i-send-the-relationshiptypeid-when-creating-a-relationship).
</Note>

## What you need before you register

Registration validates against your existing catalog, so have these ready
first, in this order:

<Steps>
  <Step title="An organization API key">
    Sent as `Authorization: Bearer boom_org_…`. The organization is
    derived from the key. See [Authentication](/authentication).
  </Step>

  <Step title="The custom object type(s), already created">
    Both endpoints reference object types **by name**, and those types
    must already exist for your org. An unknown name returns `404` with
    fuzzy `suggestions`. Confirm with:

    ```bash theme={null}
    curl "$BASE/custom-objects/types" -H "Authorization: Bearer $BOOM_KEY"
    ```

    For `person_to_co` you need one type; for `co_to_co` you need both the
    parent and the child type.
  </Step>

  <Step title="A role and (recommended) the metadata">
    Decide the `role` label and, ideally, the `cardinality` and a short
    `description`. These are the fields documented in the catalog below.
  </Step>
</Steps>

## Field catalog

Required vs. optional fields per `kind`. `role`, `cardinality`,
`description`, and `bidirectional` are common to both.

### `kind: "person_to_co"`

| Field              | Required | Type             | Notes                                                  |
| ------------------ | -------- | ---------------- | ------------------------------------------------------ |
| `kind`             | ✅        | `"person_to_co"` | Discriminator.                                         |
| `role`             | ✅        | string (1–255)   | Label for the link, e.g. `placed`, `returned`.         |
| `customObjectType` | ✅        | string (1–255)   | Name of an **existing** custom object type.            |
| `cardinality`      | Optional | enum             | One of the [cardinality values](#cardinality-catalog). |
| `description`      | Optional | string (1–1000)  | Human-readable explanation.                            |
| `bidirectional`    | Optional | boolean          | Defaults to `false`.                                   |

### `kind: "co_to_co"`

| Field                    | Required | Type            | Notes                                                  |
| ------------------------ | -------- | --------------- | ------------------------------------------------------ |
| `kind`                   | ✅        | `"co_to_co"`    | Discriminator.                                         |
| `role`                   | ✅        | string (1–255)  | Label for the link, e.g. `contains`.                   |
| `parentCustomObjectType` | ✅        | string (1–255)  | Name of an **existing** type (parent side).            |
| `childCustomObjectType`  | ✅        | string (1–255)  | Name of an **existing** type (child side).             |
| `cardinality`            | Optional | enum            | One of the [cardinality values](#cardinality-catalog). |
| `description`            | Optional | string (1–1000) | Human-readable explanation.                            |
| `bidirectional`          | Optional | boolean         | Defaults to `false`.                                   |

<Note>
  Both `person_to_co` and `co_to_co` relationships can be created over the API.
  To link two objects, call **Link a relationship** with `kind: "co_to_co"`
  and `parentExternalId` / `childExternalId` (instead of `personExternalId` /
  `customObjectExternalId`).
</Note>

## Cardinality catalog

`cardinality` is optional but recommended. For `person_to_co`, read it as
person→object; for `co_to_co`, read it as parent→child.

| Value          | Meaning                    | Example (`person_to_co`)               |
| -------------- | -------------------------- | -------------------------------------- |
| `ONE_TO_ONE`   | One on each side.          | A person has exactly one `profile`.    |
| `ONE_TO_MANY`  | One subject, many objects. | A person `placed` many `order`s.       |
| `MANY_TO_ONE`  | Many subjects, one object. | Many people belong to one `household`. |
| `MANY_TO_MANY` | Many on both sides.        | People `favorited` many `product`s.    |

Any other value is rejected with `400`.

## Register a type

<CodeGroup>
  ```bash person_to_co theme={null}
  curl -X POST "$BASE/relationship-types" \
    -H "Authorization: Bearer $BOOM_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "kind": "person_to_co",
      "role": "placed",
      "customObjectType": "order",
      "cardinality": "ONE_TO_MANY",
      "description": "A person who placed an order"
    }'
  ```

  ```bash co_to_co theme={null}
  curl -X POST "$BASE/relationship-types" \
    -H "Authorization: Bearer $BOOM_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "kind": "co_to_co",
      "role": "contains",
      "parentCustomObjectType": "order",
      "childCustomObjectType": "line_item",
      "cardinality": "ONE_TO_MANY"
    }'
  ```
</CodeGroup>

The response echoes the resolved type, including `created` (`false` when
the type already existed and was refined in place):

```json theme={null}
{
  "kind": "person_to_co",
  "relationshipTypeId": "rt_2hk9",
  "role": "placed",
  "customObjectType": "order",
  "cardinality": "ONE_TO_MANY",
  "description": "A person who placed an order",
  "bidirectional": false,
  "created": true
}
```

<Tip>
  Registration is an **upsert**: re-post the same `(kind, role, type)` to
  refine metadata. Omitted optional fields are preserved, so you can patch
  just the `description` without clearing a previously-set `cardinality`.
</Tip>

## List your types

```bash theme={null}
curl "$BASE/relationship-types" -H "Authorization: Bearer $BOOM_KEY"
```

```json theme={null}
{
  "data": [
    { "kind": "person_to_co", "relationshipTypeId": "rt_2hk9", "role": "placed", "customObjectType": "order", "cardinality": "ONE_TO_MANY", "description": "A person who placed an order", "bidirectional": false },
    { "kind": "co_to_co", "relationshipTypeId": "rt_8fa1", "role": "contains", "parentCustomObjectType": "order", "childCustomObjectType": "line_item", "cardinality": "ONE_TO_MANY", "description": null, "bidirectional": false }
  ],
  "next_cursor": null
}
```

## Recommended order of operations

<Steps>
  <Step title="Create custom object types">
    `POST /custom-objects/types` (or via your dashboard / data sync). The
    public API never auto-creates object types.
  </Step>

  <Step title="Register relationship types">
    `POST /relationship-types` with cardinality + description. **Keep the
    `relationshipTypeId` from the response**: you'll need it to create
    relationships.
  </Step>

  <Step title="Upsert people and custom objects">
    `POST /people` and `POST /custom-objects`.
  </Step>

  <Step title="Create relationships">
    `POST /relationships`, passing the `relationshipTypeId` from step 2.
  </Step>
</Steps>

## FAQ

### Do I send the `relationshipTypeId` when creating a relationship?

**Yes, it's required.** `POST /relationships` takes the
`relationshipTypeId`, which fixes the link's `role` and which custom
object type it targets, so you don't pass those separately. Get the id
from the response of `POST /relationship-types` or from
`GET /relationship-types`.

```jsonc theme={null}
// POST /relationships, request
{ "personExternalId": "user_123", "customObjectExternalId": "ord_998",
  "relationshipTypeId": "rt_2hk9" }

// response
{ "created": true, "relationshipTypeId": "rt_2hk9" }
```

### What happens if I skip registration?

You can't. `POST /relationships` needs a `relationshipTypeId`, and the
only way to get one is to register a type (or list a type that already
exists from a prior data sync). So registering is a prerequisite for
linking over the API.

### Can I change a type's cardinality later?

Yes, re-`POST` the same type with the new value. It's an upsert and only
the fields you send are updated.

## Related

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Register a type and create a relationship in context.
  </Card>

  <Card title="Events" icon="bolt" href="/events">
    Record what your linked people and objects did.
  </Card>
</CardGroup>
