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

# Items

> The primitive — typed records with system fields, properties, and a metadata layer

An item is the primitive. Every record in Marfa is an item, regardless of whether it represents a note, a book, a person, a photo, or a custom type registered by an app.

Every item, regardless of type, has four layers.

## System fields

Set by the server (or SDK in modes without a server), immutable from client request bodies. The [Item schema in the API reference](/api-reference/items/get-an-item) is the source of truth for field names, types, and nullability.

A few fields carry semantics that are worth knowing before reading the spec:

* **`id`** — UUIDv7. Time-sortable. Generated server-side.
* **`timestamp`** vs **`created_at`** — `timestamp` is the user-meaningful time (when a photo was taken, when an event occurred). `created_at` is when the item arrived in Marfa. `timestamp` defaults to `created_at`.
* **`source`** — stamped from the credential server-side in server-synced mode and not forgeable. See [Provenance](/concepts/provenance).
* **`source_id`** — optional. The upstream system's stable identifier for this item — an RSS entry id, a Calendar event id, an external row id. Combined with the credential-stamped `source`, it forms the natural key behind the `POST /items` upsert behavior described below. The sync agent stores each file's root-relative path here — see [Folder structure](/clients/sync#folder-structure) for how that shapes folder queries and cross-device behavior.
* **`version`** — starts at 1, increments on every update. Used for optimistic concurrency; `PATCH /items/{id}` takes it for [conflict detection](/api/errors#version-conflict).
* **`schema_version`** — which version of the type schema the item was validated against. See [Types](/concepts/types).
* **`device`**, **`capture_latitude`**, **`capture_longitude`** — optional, client-supplied, preserved as written, not validated.

Each of these system fields is reserved across every type — a custom-type schema cannot redeclare them as properties. Registration is rejected `400 property_shadows_field`. See [Reserved field names](/concepts/authoring-types#reserved-field-names) for the full list and the rationale.

## Properties

Type-schema-validated JSON. The item's content — what makes one type different from another.

* **Schema-defined fields** are enforced on write. Missing required fields produce a validation error.
* **Unknown fields are preserved** — forward compatibility for schemas that evolve. Servers and SDKs must not strip unknown properties.
* **Properties are type-specific.** A `core.note` has `body`; a `core.media.book` has `title`, `author`, `isbn`; their shapes don't overlap.

See [Types](/concepts/types) for the full core type set and how custom types work.

## Metadata — the structural universal layer

Every item carries the metadata layer regardless of type. Three fields:

| Field   | Type             | Purpose                                                                                            |
| ------- | ---------------- | -------------------------------------------------------------------------------------------------- |
| `tags`  | array of strings | Flat labels. Kebab-case. Reserved prefixes carry conventions (see [Metadata](/concepts/metadata)). |
| `tier`  | enum             | `library` or `feed`. The curated-library vs. high-volume-feed axis. See [Tier](/concepts/library). |
| `state` | enum             | `active` / `archived` / `trashed`. See [Lifecycle](/concepts/lifecycle).                           |

<Info>
  The metadata layer is **closed** — types can't add to it. The three fields hold concepts with system-wide consequences: labels, the curation axis, and lifecycle. Additional always-present fields belong in `properties` on the type. The metadata layer applies only to items — edges and attachments don't carry it.
</Info>

## Extensions

Namespaced JSON objects attached to an item. Extensions are **sidecar state** — not structured content with its own identity.

```json theme={null}
"extensions": {
  "readwise-reader.reading_progress": { "offset": 1234, "percent": 45 },
  "user.last_viewed": { "at": "2026-04-12T14:30:00Z" }
}
```

Extensions live at dedicated endpoints (`/items/{id}/extensions/{namespace}`) and respect per-namespace permissions. See [Extensions](/concepts/extensions) for when to use them vs a custom item type.

## Attachments

Every item has an `attachments` array. Each entry:

```json theme={null}
{
  "blob_ref": "sha256:abc123…",
  "mime_type": "image/png",
  "title": "original-filename.png",
  "role": "screenshot"
}
```

* **`blob_ref`** — content-addressed reference (SHA-256). Storage deduplicates naturally; identical bytes have identical hashes.
* **`mime_type`** — MIME type.
* **`title`** — optional human-readable label.
* **`role`** — optional. Well-known vocabulary: `banner`, `screenshot`, `pdf`, `archive`, `readable`, `video`, `transcript`, `thumbnail`, `cover`, `original-html`. Custom roles allowed in an app namespace.

**Attachments vs primary blob.** A `core.file.*` item carries its primary binary in a type-specific property (`blob_ref` is required on `core.file`). `attachments[]` is for **secondary artifacts** — a `core.file.video` might have `attachments[]` entries for a transcript and thumbnail; the video file itself sits in `properties.blob_ref`.

### When to use `attachments[]` vs the `attached-to` edge

Both attach secondary content to an item, but they have different semantics. The choice turns on whether the secondary content has its own identity.

| Mechanism                                         | Use when                                                                                                                                                                                          | Example                                                                                                                                                                           |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`attachments[]` array on the item**             | The artifact is bound to the host's lifecycle (deleting the host deletes the attachment), it doesn't need to be queryable as an item on its own, and a `role` describes what it is.               | A `core.media.book` with a cover image as `attachments[{role: "cover", ...}]`. The cover isn't a `core.file.image` item in its own right — it's a secondary artifact on the book. |
| **`attached-to` edge** ([Edges](/concepts/edges)) | The artifact is its own item (a `core.file.audio`, a `core.file.image`), you want it queryable on its own (e.g. "find every image in this space"), or it might be linked from more than one host. | An audio recording stored as a `core.file.audio` item, with an `attached-to` edge pointing at the host item. Both are first-class items; the edge models the relationship.        |

If in doubt: secondary content that only makes sense in the host's context → `attachments[]`. Independent item that happens to belong to a host → `attached-to` edge.

## Edges

Relationships to other items live as edges, not as fields on the item itself. `about` references, parent-child hierarchies, thread membership, annotations — all edges. See [Edges](/concepts/edges).

Single-item reads hydrate edges inline:

```json theme={null}
{
  "item": {
    "id": "...",
    "type": "core.note",
    "properties": { "body": "..." },
    "edges": {
      "about": { "edges": [...], "has_more": false },
      "parent-of": { "edges": [...], "has_more": false }
    }
  }
}
```

List reads opt into edges via `?include=edges`.

## Lists are lean, opt into extras

`GET /items` returns the minimum item shape by default — no edges, no metadata, no extensions. That's deliberate: a naive "fetch the extras per item" loop is an N+1 trap. The `?include=` parameter is the escape hatch: name what you need and the server batches it inline.

Currently supported values (comma-separated):

* `?include=edges` — hydrates outbound edges per item, grouped by edge type. Same shape as single-item responses.
* `?include=metadata` — wraps each entry as `{ item, metadata }` so tags travel inline.
* `?include=extensions` — hydrates extension namespaces per item, filtered by caller permissions (same rule as `GET /items/:id/extensions`).

Combinations work. `?include=edges,extensions` is the typical "rendering a rich list view" shape: one request, everything the UI needs, no per-item follow-ups.

The TypeScript SDK exposes typed helpers — `client.items.listWithMetadata`, `client.items.listWithExtensions` — that call these under the hood. Prefer them over assembling raw query strings.

## Atomic item + edges write

A single `POST /items` call creates an item and its outbound edges in one transaction. See [Edges](/concepts/edges#atomic-item--edges-write) for the request shape.

## Natural-key upsert

`POST /items` is idempotent on `(source, source_id)`. When both are present and resolve a non-trashed row in the caller's space, the request short-circuits to update — properties shallow-merge, tags replace if provided, edges replace per-type if provided. Fields that only matter at create time (id, state, capture\_\*) are ignored on the update branch.

The realised effect is reflected in the HTTP status:

* **`201 created`** — a new row was created. No matching `(source, source_id)` existed.
* **`200 ok`** — an existing row was updated via natural-key match. The response body is the updated item.

Without `source_id`, behavior is unchanged — every call creates a new row. With a `source_id` that resolves no row, the create path runs as before.

The contract lets inbound integration handlers recover from whole-batch retries (handler succeeded, cursor write failed, the queue redelivers the same items) without producing duplicates. Same shape as the resource-level idempotency in mature integration platforms.

## Item response shape

```json theme={null}
{
  "item": {
    "id": "019d9154-fc91-7d0b-ad4c-dcf3eb96ddbb",
    "type": "core.note",
    "tenant_id": "...",
    "properties": { "body": "...", "title": "..." },
    "tier": "library",
    "state": "active",
    "tags": ["work"],
    "timestamp": "2026-04-15T13:28:35.125Z",
    "created_at": "2026-04-15T13:28:35.125Z",
    "updated_at": "2026-04-15T13:28:35.125Z",
    "source": "My App",
    "version": 1,
    "schema_version": 1,
    "edges": { ... }
  }
}
```

The `metadata` layer fields (`tags`, `tier`, `state`) are flattened onto the item object. `extensions` are fetched separately via the extensions endpoints — or, on list reads, hydrated inline with `?include=extensions`.
