# Objects

The Objects module is the platform's generic content model. A merchant defines **object types** (content types such as "FAQ", "Page", or "Store location") in the client dashboard, each with an ordered list of typed **properties**. Content editors then create **records** (entries) of those types. Your storefront reads both through the public API: the type definitions tell you what shape to expect, the records carry the content.

This makes the module a natural fit for LLM-driven and headless storefronts: fetch `/v1/api/objects/types` once to learn the schema, then render records generically.

All three endpoints live under `/v1/api/objects`, are read-only, require the `objects:read` scope on your API key, and require the `objects` module to be enabled (`403 {"error":"Module not enabled: objects"}` otherwise).

## Object types and properties

An object type has a `key` (a unique slug used in URLs; the values `types`, `records`, `properties`, and `bulk` are reserved and never appear), a singular `name`, and a `plural_name`. Every type automatically carries a required `name` text property; merchants add the rest.

A property definition has:

| Field | Type | Description |
| --- | --- | --- |
| `key` | string | Machine key, matching `/^[a-z][a-z0-9_]*$/`. This is the key used inside record `values`. |
| `label` | string | Human label. |
| `type` | string | One of the property types below. |
| `options` | array of string or null | The allowed values for `select` properties; `null` for all other types. |
| `is_required` | boolean | Whether the property must have a value. |
| `position` | integer | Sort position within the type, 0-based. |

Property types and the value each produces inside `values`:

| Type | Value in `values` | Constraints |
| --- | --- | --- |
| `text` | string | Maximum 255 characters. |
| `textarea` | string | Maximum 10000 characters. |
| `number` | number | Integer or float. |
| `money` | integer | Non-negative, in cents. `15900` means 159.00 in the store currency. |
| `boolean` | boolean | |
| `select` | string | One of the property's `options`. |
| `date` | string | `YYYY-MM-DD`. |
| `customer` | string (uuid) | A customer id. |
| `sections` | array | Structured page-builder content; see below. |

Properties can be archived in the dashboard. Archived properties are invisible on the public API: they are omitted from the type's property list and their keys are stripped from record `values`.

## Records

A record is `{id, values, created_at}` where `values` is a JSON object keyed by property key. Properties without a value are simply absent from `values` (null values are dropped on save), so always treat missing keys as "no value". `created_at` is an ISO 8601 timestamp.

## Sections values

A property of type `sections` holds page-builder content: its value is a **list of section instances**, returned verbatim by the API. Each instance has this shape:

```json title="Section instance"
{
  "_key": "sec_a1b2c3d4e5f60718293a4b5c",
  "type": "hero",
  "look": "default",
  "v": 1,
  "visible": true,
  "data": { "title": { "en": "Welcome" }, "cta": { "url": "/products", "label": "Shop now" } }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `_key` | string | Stable instance key, `sec_` plus 24 hex characters. |
| `type` | string | The section type key, defined per store in the client dashboard. |
| `look` | string | The visual variant (look) key of that section type. |
| `v` | integer | Schema version of the section type at save time. |
| `visible` | boolean | Editors can hide a section without deleting it. Skip instances with `visible: false` when rendering. |
| `data` | object | Field values, keyed by the section type's field keys. |

Section instances are validated on write against the store's section type definitions (key, name, icon, looks, schema version, fields, item label field), which are managed in the client dashboard. The public API does not expose section type definitions; your storefront and the merchant agree on the section vocabulary out of band.

Field kinds that can appear inside `data`:

| Kind | Value shape | Constraints |
| --- | --- | --- |
| `text`, `textarea`, `number`, `money`, `boolean`, `select`, `date`, `customer` | Same as the property types above. | |
| `richtext` | string (HTML) | Maximum 50000 characters. Sanitized server-side to `p`, `br`, `strong`, `em`, `ul`, `ol`, `li`, `a[href]`, `h2`, `h3`, `h4`. Only `https://`, `http://`, root-relative `/`, `mailto:`, and `tel:` hrefs survive sanitization. |
| `media` | string (uuid) | A media library id. |
| `link` | object `{url, label}` | `url` required, maximum 2000 characters, same scheme allowlist as richtext hrefs; `label` optional, maximum 200 characters. |
| `icon` | string | Icon name matching `/^[a-z][a-z0-9-]{0,39}$/`. |
| `token_variant` | string | One of the field's configured option strings. |
| `record_ref_list` | array of string (uuid) | References to other records; maximum 24 entries. |
| `repeater` | array of row objects | Each row is `{"_key": "row_{24 hex}", ...scalar fields}`. Maximum 50 rows. Repeaters cannot nest, and a section has at most 40 fields. |

**Localized fields.** A field flagged as localized stores a locale map instead of a bare value: `{"en": "Hello", "nl": "Hallo"}` with locale tags matching `/^[a-z]{2}(-[A-Z]{2})?$/`. The API returns whatever was stored, so any consumer of record values must handle both bare values and locale maps for the same field kind.

**Limits.** A sections property holds at most 100 sections (or the property's configured maximum) and at most 256KB of JSON.

## Conventions

- All ids are UUIDs. All data is implicitly scoped to your store by the API key.
- Money is always integer cents. Dates in `date` fields and range filters are `YYYY-MM-DD` strings. `created_at` timestamps are ISO 8601.
- Only the record list endpoint is paginated; the types list returns a plain `data` array.

## Endpoints

| Endpoint | Purpose |
| --- | --- |
| `GET /v1/api/objects/types` | List all object types with their property definitions. See [Types](./types). |
| `GET /v1/api/objects/types/{key}/records` | Paginated, filterable record list for one type. See [Records](./records). |
| `GET /v1/api/objects/records/{id}` | Fetch a single record by id. See [Records](./records). |
