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

# Linking referenced records

> Reference a record by ID, match it by attribute value, or create it as part of the same request

[Record reference](/rest-api/attribute-types/attribute-types-record-reference) attributes point at other records. When you write one, you have to tell Attio which record you mean. This guide covers the three ways to do that, and how to create the referenced record when it does not exist yet.

## The three write formats

| Format | Shape | Use it when |
| - | - | - |
| By ID | `{"target_object": "companies", "target_record_id": "<uuid>"}` | You already know the record ID. |
| By matching attribute | `{"target_object": "companies", "domains": [{"domain": "company.com"}]}` | You know a unique value, such as a domain or an email address. |
| `target_record_reference` | `{"target_object": "companies", "target_record_reference": {…}}` | You want to create the referenced record if it's missing or match an existing one. |

The first two formats fail if they do not find exactly one record. The third format lets you choose what happens when no record or more than one record matches, and can create the record for you. With both matching formats, the matching attribute does not have to be unique, but we recommend a unique attribute, such as a domain, an email address or an ID from your own system.

All three formats are write-only. Responses always return the resolved record as `target_object` and `target_record_id`.

## The target record reference format

<CodeGroup>
  ```json Link the company with the domain company.com, and create it if it is missing theme={"system"}
  {
    "associated_company": [
      {
        "target_object": "companies",
        "target_record_reference": {
          "match": {"domains": ["company.com"]},
          "if_unmatched": "create",
          "if_multiple_matches": "error",
          "create": {"name": "Company"}
        }
      }
    ]
  }
  ```
</CodeGroup>

| Key | Required | Default | Description |
| - | - | - | - |
| `match` | Yes | — | The attribute and value used to find the record. |
| `if_unmatched` | No | `"error"` | What to do when no record matches. One of `error`, `create` or `ignore`. |
| `if_multiple_matches` | No | `"error"` | What to do when more than one record matches. One of `error` or `match_all`. |
| `create` | No | {} | Additional values for the record, used only with `if_unmatched: "create"`. |

`target_object` stays alongside `target_record_reference`, exactly as in the other two formats.

## Matching a record

`match` takes exactly one attribute, identified by its slug or ID, and exactly one value. You can write the value on its own or as an array with one element. Both of these are the same:

<CodeGroup>
  ```json A single value theme={"system"}
  {
    "match": {"domains": "company.com"}
  }
  ```

  ```json The same value in an array theme={"system"}
  {
    "match": {"domains": ["company.com"]}
  }
  ```
</CodeGroup>

You can match on domain, email address, number, phone number and text attributes. Like the shorthand format, the attribute does not have to be unique, but we recommend a unique attribute, such as a domain, an email address or an ID from your own system. `if_multiple_matches` decides what happens when the value matches more than one record.

Two or more keys in `match`, or two or more values for one key, are rejected with a `400`.

## When no record matches

<CodeGroup>
  ```json error: fail the request (default) theme={"system"}
  {
    "target_object": "companies",
    "target_record_reference": {
      "match": {"domains": ["company.com"]},
      "if_unmatched": "error"
    }
  }
  ```

  ```json create: create the record theme={"system"}
  {
    "target_object": "companies",
    "target_record_reference": {
      "match": {"domains": ["company.com"]},
      "if_unmatched": "create"
    }
  }
  ```

  ```json ignore: continue without the value theme={"system"}
  {
    "target_object": "companies",
    "target_record_reference": {
      "match": {"domains": ["company.com"]},
      "if_unmatched": "ignore"
    }
  }
  ```
</CodeGroup>

* `error` is the default. The request fails with a `400` naming the value that was not found. This is how the other two write formats behave.
* `create` creates the record from the `match` value and, if you supply it, `create`.
* `ignore` drops the value and lets the rest of the request succeed. Use it when the link is nice to have but the record itself must be written either way.

## When several records match

<CodeGroup>
  ```json error: fail the request (default) theme={"system"}
  {
    "target_object": "people",
    "target_record_reference": {
      "match": {"phone_numbers": "+15558675309"},
      "if_multiple_matches": "error"
    }
  }
  ```

  ```json match_all: link every match theme={"system"}
  {
    "target_object": "people",
    "target_record_reference": {
      "match": {"phone_numbers": "+15558675309"},
      "if_multiple_matches": "match_all"
    }
  }
  ```
</CodeGroup>

* `error` is the default. More than one match fails the request with a `400`, instead of picking a record for you.
* `match_all` links every matching record, up to **50** records per value. Above that, the request fails and you should use a more specific value or reference the records by ID.

`match_all` only applies to multiselect ("many") attributes. On a single-value attribute, more than one match always fails with a `400`, so that Attio never has to choose between them.

## Creating the referenced record

`create` carries the values the new record is created with. The `match` value is added automatically, so do not repeat it:

<CodeGroup>
  ```json Create the person if no one has this email address theme={"system"}
  {
    "team": [
      {
        "target_object": "people",
        "target_record_reference": {
          "match": {"email_addresses": ["person@company.com"]},
          "if_unmatched": "create",
          "create": {"job_title": "VP Engineering"}
        }
      }
    ]
  }
  ```
</CodeGroup>

* `create` is ignored unless `if_unmatched` is `"create"`.
* Creation is one level deep. The values in `create` cannot themselves create further records, so a referenced record can be created, but the records *it* references must already exist.
* Records that already match are never updated. `create` applies only when nothing matches.
* Identical references in one request are merged, so the record is created once even if several attributes point at it. If the same reference appears twice with different `create` values, the request fails.

### Order of writes and retries

Referenced records are not created atomically with the main record. Attio creates them first, before it writes the main record, so that they exist when the main record references them. If the main write then fails, the referenced records that were created are kept.

This means that it is safe to retry a request that failed, for example with a `5xx` error. On the retry, `match` finds the records that were created on the first attempt and links them, so no duplicates are created. The retry does not update those records, so values in `create` that differ from the first attempt are not applied.

## Limits

| Limit | Value |
| - | - |
| Records linked per matching value | 50 |
| Records created per request | 10 |
| Attributes in `match` | 1 |
| Values per `match` attribute | 1 |
| Depth of record creation | 1 |

The 10 records per request are counted after identical references are merged.

## Required scopes

Creating referenced records writes to records of another object, so the token needs `record_permission:read-write` and `object_configuration:read` on top of the scopes the endpoint itself requires. Without them the request fails with a `403`. Matching without creating does not need the write scope.

## Rate limits

Every referenced record that Attio creates counts as one more write request. See [handling rate limits](/rest-api/guides/rate-limiting) for how this affects your budget.

## Supported endpoints

The `target_record_reference` format is accepted on these endpoints only:

* [Create a record](/rest-api/endpoint-reference/records/create-a-record) and [upsert a record](/rest-api/endpoint-reference/records/upsert-a-record)
* Update a record, both [append](/rest-api/endpoint-reference/records/update-a-record-append-multiselect-values) and [overwrite](/rest-api/endpoint-reference/records/update-a-record-overwrite-multiselect-values)
* The equivalent [activity record](/rest-api/guides/activities) endpoints


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.