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

# Property values

> What each property type returns when read and what to send when writing.

Keys in `properties` are property names. When writing, you can use either the property name or its ID as the key.

## Rich text items

`title` and `rich_text` are arrays of rich text items.

**When reading**

```json theme={null}
{
  "type": "text",
  "text": {
    "content": "Reviewed the January orders.",
    "link": null
  },
  "annotations": {
    "bold": false,
    "italic": false,
    "strikethrough": false,
    "underline": false,
    "code": false,
    "color": "default"
  },
  "plain_text": "Reviewed the January orders.",
  "href": null
}
```

An item's `type` is `text`, `mention`, or `equation`. `mention.type` is `user`, `page`, `database`, `date`, or `custom_emoji`. A mention of a document or database the integration can't read has `plain_text` set to `Untitled`, and a database mention like this comes back as a `page` mention. A person mention read by an integration with user information set to **None** is `@Anonymous`. Bot mentions keep their name.

Long text comes back as several items of up to 2,000 characters, so `rich_text[0]` holds only the first part. Join every item to read the whole text.

**When writing**

```json theme={null}
[
  {
    "text": {
      "content": "Read more",
      "link": {
        "url": "https://example.com"
      }
    }
  },
  {
    "text": {
      "content": " bold text"
    },
    "annotations": {
      "bold": true
    }
  },
  {
    "mention": {
      "user": {
        "id": "11111111-1111-4111-8111-111111111111"
      }
    }
  },
  {
    "mention": {
      "date": {
        "start": "2026-01-15"
      }
    }
  },
  {
    "equation": {
      "expression": "E = mc^2"
    }
  }
]
```

* Links accept http(s), mailto, and document-relative addresses. Anything else returns 400 `Invalid URL for link.`
* An item holds up to 2,000 characters, and an array up to 100 items. See [Values and request size](https://developers.bottari.ai/limits/values.md#long-text).
* Items with the same formatting are merged when stored. Reading returns them split every 2,000 characters.
* You can write rich text items back as you read them. Person mentions are the exception: send only `{ "id": "..." }`.
* A mention with an unknown document ID returns 200 and reads back as an `Untitled` document mention. An item with an unknown custom emoji ID is dropped.
* Empty text items aren't stored.
* A date mention whose end is earlier than its start loses the end.

## Writable properties

| Type | Value to send | Rules |
| - | - | - |
| `title` | Array of rich text items | An empty array sets an empty title. Line breaks become spaces, and leading and trailing spaces are removed. Subpages only accept this property. |
| `rich_text` | Array of rich text items | An empty array clears the value. |
| `number` | Number or `null` | A string returns 400. |
| `checkbox` | `true` or `false` | A string returns 400. |
| `select` | `{ "id" }`, `{ "name" }`, `{ "name", "color" }`, or `null` | See [Options](#options). |
| `multi_select` | Array of the above | Up to 100. |
| `status` | `{ "id" }`, `{ "name" }`, or `null` | Existing options only. `null` sets the property's default option. |
| `date` | `{ "start", "end", "time_zone" }` or `null` | See [Dates](#dates). |
| `people` | `[{ "id" }]` | Up to 100. Accepts the team's members, guests, and integration bots. |
| `relation` | `[{ "id" }]` | Up to 100. See [Relations](#relations). |
| `files` | See [Files](#files) | Up to 100. |
| `url` | Text or `null` | Up to 2,000 characters. The format isn't checked. Empty text returns 400; send `null` to clear. |
| `email`, `phone_number` | Text or `null` | Up to 100 characters. The format isn't checked. Empty text returns 400. |

## Read-only properties

`formula`, `rollup`, `unique_id`, `created_time`, `created_by`, `last_edited_time`, `last_edited_by`, and `button` can't be written. Writing them returns 400.

| Type | Value returned |
| - | - |
| `formula` | `{ "type": "string", "string": "..." }`, or `number`, `boolean`, `date`. Numbers are rounded at 12 decimal places. A formula that returns an empty text property gives `""`, and one that returns a date with a time gives UTC (`+00:00`). |
| `rollup` | `{ "type": "number", "number": 3, "function": "sum" }`, or `date`, `array`, `unsupported` |
| `unique_id` | `{ "prefix": "TASK", "number": 12 }` |
| `created_time`, `last_edited_time` | UTC time truncated to the minute |
| `created_by`, `last_edited_by` | User object or `null` |
| `button` | `{}` |

Session, linked pages, and PR properties don't appear in responses.

## Options

* When both `id` and `name` are sent, `id` wins. An unknown `id` returns 400.
* Names are trimmed and matched case-insensitively.
* If no name matches, `select` and `multi_select` create a new option. If the write fails, the new option isn't kept.
* A name with a comma returns 400.
* A color has to be one of `default`, `gray`, `brown`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink`, or `red`. Sending a different color for an existing option returns 400.
* `status` doesn't create options. An unknown name returns 400.

## Dates

**When reading**, `time_zone` is always `null`. Values with a time include an offset.

```json theme={null}
{ "start": "2026-01-15T09:30:00.000+09:00", "end": null, "time_zone": null }
```

**When writing**, these rules apply:

* An end earlier than the start returns 400, including when the start and end have different offsets. Times with no offset and date-only values are compared as UTC.
* To send `time_zone`, both start and end need a time, with no offset or a zero offset.
* Values with a time are normalized to one offset: the start's offset if the start has a time (`+00:00` if it has none), otherwise the end's offset.
* An invalid date returns 400 `... date.start should be a valid ISO 8601 date string ...`

## People

* Sending a user object as read returns 400. Send `{ "id": "..." }`.
* An unknown ID or a group returns 404 `Could not find user with ID: ...`
* Going over a property's person limit returns 400 `The request failed validation. Error: Invalid value for property with limit`
* People already in the property aren't checked again. With user information set to **None**, a person's ID returns 404, even if it's already in the property or in a mention. Integration bots and empty arrays are accepted.

## Relations

* A write replaces the whole value. A row response returns at most 25 relations, so if `has_more` is `true`, read them all first with [Retrieve a property value](https://developers.bottari.ai/api-reference/pages/property.md).
* An ID for a row the integration can't read returns 404.
* Only top-level rows of the target database are kept. Rows of other readable databases and subpages are silently dropped.
* Existing relations the integration can't see are kept.
* Sending more than one value to a property limited to 1 returns 400 `Relation property ... has a limit of 1, but ... values were provided.`
* Adding or removing a row of a database with a task agent attached returns 403.

## Files

**When reading**

```json theme={null}
[
  {
    "name": "contract.pdf",
    "type": "file",
    "file": {
      "url": "https://...",
      "expiry_time": "2026-01-15T03:30:00.000Z"
    }
  },
  {
    "name": "Guide",
    "type": "external",
    "external": {
      "url": "https://example.com/guide"
    }
  }
]
```

**When writing**

| Form | Behavior |
| - | - |
| `{ "name": "Guide", "external": { "url": "https://..." } }` | Adds an external URL. Only http(s) is accepted. A name is required, up to 100 characters. |
| `{ "name": "...", "file": { "url": "<signed URL you read>" } }` | Keeps a file already in the property. An expired URL also works. A URL matching no file in the property is dropped; a URL that isn't a Bottari signed URL returns 400. |

## Common validation errors

| Message | Cause |
| - | - |
| `<key> is not a property that exists.` | The property doesn't exist or is a hidden type. |
| `<key> is expected to be <type>.` | The property and value types differ. |
| `body failed validation. Fix one: ...` | The value's form is wrong. Each line lists an accepted form. |


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