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.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.
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 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.timestampvscreated_at—timestampis the user-meaningful time (when a photo was taken, when an event occurred).created_atis when the item arrived in Marfa.timestampdefaults tocreated_at.source— stamped from the credential server-side in server-synced mode and not forgeable. See 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-stampedsource, it forms the natural key behind thePOST /itemsupsert behavior described below. The sync agent stores each file’s root-relative path here — see 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.schema_version— which version of the type schema the item was validated against. See Types.device,capture_latitude,capture_longitude— optional, client-supplied, preserved as written, not validated.
400 property_shadows_field. See 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.notehasbody; acore.media.bookhastitle,author,isbn; their shapes don’t overlap.
Metadata — the structural universal layer
Every item carries the metadata layer regardless of type. Three fields: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.Extensions
Namespaced JSON objects attached to an item. Extensions are sidecar state — not structured content with its own identity./items/{id}/extensions/{namespace}) and respect per-namespace permissions. See Extensions for when to use them vs a custom item type.
Attachments
Every item has anattachments array. Each entry:
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.
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) | 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. |
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.
Single-item reads hydrate edges inline:
?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 asGET /items/:id/extensions).
?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 singlePOST /items call creates an item and its outbound edges in one transaction. See Edges 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.
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
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.