# Core concepts

The reference pages describe every field the API has. This page describes what those fields are *about*: the handful of objects the whole surface is built from, and how they relate. It’s background rather than a procedure — nothing here is a step to follow.

If you’ve used the Archive app, most of this will be familiar under different names. The [glossary](#ui--api-glossary) at the end maps the two vocabularies, including the places where the same word means different things on each side.

## Workspaces

A **workspace** is one brand, or one region of a brand — the boundary the `WORKSPACE-ID` header selects. Every operation except `workspaces` reads from exactly one workspace, and nothing crosses that line: an item, a creator, or a saved view belongs to a workspace and is invisible from any other.

An organization can hold many workspaces. A token issued to an organization reaches all of them, one per request, but they stay separate in every other respect — including rate limits, where each workspace draws from its own bucket rather than sharing the organization’s. See [Workspaces](/api/v2/docs/guides/workspaces) for the header mechanics and [Rate limits & plans](/api/v2/docs/guides/rate-limits) for the bucket model.

A workspace is also what defines the content Archive captures for you. `Workspace` exposes the `hashtags`, `mentions`, and `keywords` it tracks, plus its `integrations` — the connected social accounts. Archive monitors those and captures what matches, which is why `items` returns your tracked universe rather than the whole platform.

> **The API calls a workspace a “shop” when it fails**
>
> An unknown or unreachable workspace returns HTTP 404 with `{"errors":"Shop not found"}`. “Shop” is the older internal name for the same object; treat the two words as interchangeable when you’re reading an error.

## Items: the content

An **Item** is a single captured piece of social content — a post, a Reel, a Story, a TikTok video, a YouTube Short. It carries what the platform published (`caption`, `hashtags`, `mentions`, `takenAt`, `location`) alongside what Archive derived from it (`transcriptions`, `currentEngagement`, `customAttributes`).

Items have more than one identifier, and they don’t mean the same thing:

- `id` — the item in Archive. This is what you pass to other operations.
- `externalId` — the platform’s own ID for the content.
- `mediaItemId` — the parent post. Carousel frames that belong to one post share it, so use it to regroup them.
- `originalUrl` — the post on the platform. It’s `null` for content with no public URL, such as Instagram Stories, and it stops resolving when the post is deleted or the account goes private.
- `archivePublicUrl` — Archive’s permalink, which keeps resolving after the original doesn’t.

### Platform and format are separate

`provider` is the platform (`INSTAGRAM`, `TIKTOK`, `YOUTUBE`, `INTERNAL`). `type` is the format (`POST`, `REEL`, `SHORT`, `STORY`). They’re independent: a TikTok video comes back as `provider: TIKTOK` with whichever format value fits, not as a single fused value.

Filtering does fuse them, and this is the most common cause of a query that returns nothing:

> **Reading an item's type and filtering on it use two different enums**
>
> `Item.type` is `ItemType` — `POST`, `REEL`, `SHORT`, `STORY`. Format only.
>
> `ItemFilterInput.itemTypes` is `FilterItemType` — `POST`, `REEL`, `STORY`, `TIKTOK`, `TIKTOK_STORY`, `YOUTUBE`, `YOUTUBE_SHORT`. Platform and format fused, where the bare values mean Instagram.
>
> So a value you read back is not necessarily a value you can filter by. `SHORT` isn’t a filter value at all, and `POST` as a filter means *an Instagram post* — which is why pairing `provider: TIKTOK` with `itemTypes: [“POST”]` asks for content that is both TikTok and Instagram, and matches nothing. Filter by one or the other, never by a conflicting pair.

### Engagement

`currentEngagement` is the latest metrics snapshot for an item; [`engagementHistory`](/api/v2/docs/queries/engagement-history) returns the series behind it. Two fields there are easy to misread:

- `views` and `impressions` are **not** the same field. `views` is the platform’s view count; `impressions` covers views, plays, or estimated impressions depending on the source. Don’t assume they agree, and don’t substitute one for the other.
- `earnedMediaValue` is **in cents**. Divide by 100 before you show it as currency.

### Transcriptions

Video items carry `transcriptions` — the spoken content as plain text, keyed to the media it came from by `mediaContentId`. This is what makes speech searchable: the `FUZZY_TRANSCRIPTION` super-search mode matches against these, not against captions. See [Filtering & search](/api/v2/docs/guides/filtering).

## Creators and social profiles

These two are distinct objects, and confusing them is the most expensive mistake in this data model.

A **Social Profile** is one account on one platform: `accountName` (the handle), `provider`, `followers`, `verified`, and any contact details Archive has (`email`, `phoneNumbers`).

A **Creator** is the person behind those accounts — one unified record per workspace, with `socialProfiles` listing every account linked to them. The link runs both ways: `SocialProfile.creator` points back, though it’s nullable, because a profile that has no creator record in this workspace returns `null`.

Items reference both. `Item.socialProfile` is the account that posted; `Item.creator` is the person behind it.

> **Join on creator id, never on handle**
>
> `accountName` is not an identity. One workspace can hold two creator records that share a handle, and the same handle on two platforms is frequently two different people. Matching creators by handle silently merges records that aren’t the same person and splits ones that are.
>
> Use `creator.id`. It’s the only stable join key between content and the people who made it.

## Collections, views, and view groups

Three different ways to organize a workspace, easily confused because the app presents them side by side.

A **Collection** is a manual grouping. Membership is explicit — you put items in with [`addItemToCollections`](/api/v2/docs/mutations/add-item-to-collections) and take them out with [`removeItemFromCollections`](/api/v2/docs/mutations/remove-item-from-collections) — and `itemCount` tells you how many are in it. A Collection never evaluates a rule; it holds exactly what you put there.

A **view** is the opposite: a saved query, stored once and re-run every time you read it. There are three kinds, one per object they list — `ContentView`, `SocialProfileView`, and `CreatorView`. A view’s `id` is a filter-preset UUID, which is what you pass as `presetId`. Deleting a view never deletes content; the items survive and lose only that one grouping.

A **View Group** is a folder. One group can hold Collections and all three kinds of view at once, each in its own ordered list.

### Views don’t all filter the same way

The three view types store the same fields but don’t honor them equally on read:

| View type           | Read with                                                           | What `presetId` actually applies                                     |
| ------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `ContentView`       | [`items(presetId:)`](/api/v2/docs/queries/items)                    | `filters`, `customAttributeConditions`, and sort                     |
| `SocialProfileView` | [`socialProfiles(presetId:)`](/api/v2/docs/queries/social-profiles) | `customAttributeConditions` and sort **only** — `filters` is ignored |
| `CreatorView`       | [`creators(presetId:)`](/api/v2/docs/queries/creators)              | `customAttributeConditions` and sort **only** — `filters` is ignored |

For Social Profile and Creator views, `filters` is stored and echoed back to you verbatim, but never evaluated. A view that narrows correctly in the app can therefore return an unnarrowed list through the API. To build one that actually filters, express the narrowing as `customAttributeConditions`.

Note also that `presetId` is exclusive on `items`: supply it and the `filter`, `sorting`, and `customAttributeConditions` arguments are ignored, because the saved view defines the query entirely. [Filtering & search](/api/v2/docs/guides/filtering) covers both behaviors in more detail.

## Custom attributes and AI Filters

`customAttributes` is a free-form JSON object carried by both items and creators — your own metadata, the CRM layer on top of captured content. Because it’s opaque JSON, the keys inside it are described separately, by [`customAttributeSchemas`](/api/v2/docs/queries/custom-attribute-schemas): each entry gives a key, a human-readable `name`, and a `type`.

> **Select-type values are option IDs, not labels**
>
> When a schema entry has `options`, the value stored in `customAttributes` is the option’s **ID** — not the text shown in the app. Map it back through `options` to get the label.
>
> When `options` is `null`, the value is stored directly, as text, a number, or a date. Reading a select attribute without the mapping step gives you an opaque identifier where you expected a word.

Some attributes are filled in by **AI Filters** rather than by a person. A schema entry marks these with `aiGenerated: true`, and the item’s `aiFilterReasons` carries the model’s explanation for the value it assigned, paired to the attribute by `key`. An explanation can be `null` when the run recorded none.

To query on custom attributes, use the top-level `customAttributeConditions` argument — a sibling of `filter`, not a field inside it. See [Filtering & search](/api/v2/docs/guides/filtering).

## UI ↔ API glossary

Where the app and the API use different words for the same thing, or the same word for different things:

| In the app                | In the API                                                                     | Watch out for                                                               |
| ------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Workspace                 | `workspace`, the `WORKSPACE-ID` header — and **“shop”** in error strings       | `{"errors":"Shop not found"}` means the workspace is unknown or unreachable |
| Views (the metric)        | `currentEngagement.views` and `currentEngagement.impressions`                  | Two distinct fields, and neither is a saved **View**                        |
| EMV                       | `currentEngagement.earnedMediaValue`                                           | **In cents**                                                                |
| Saved view, filter preset | `ContentView`, `SocialProfileView`, `CreatorView`; `presetId`, `filterPresets` | The view’s `id` *is* the preset ID                                          |
| Group, folder             | `ViewGroup`                                                                    | Holds Collections and all three view types together                         |
| Collection                | `Collection`                                                                   | Manual membership — never a stored rule                                     |
| Creator                   | `Creator`                                                                      | The person. The account is a `SocialProfile`                                |
| Handle, username          | `SocialProfile.accountName`                                                    | Not unique, and not an identity — join on `creator.id`                      |
| Post type                 | `Item.type` (`ItemType`) on read, `itemTypes` (`FilterItemType`) on filter     | Different enums with overlapping names                                      |
| Date, time                | `DateTime`, `DateTimeWithZone`                                                 | Input must be UTC (`…Z`); output is always UTC                              |

## Where to go next

- [Getting started](/api/v2/docs/guides/getting-started) — your first three requests, end-to-end.
- [Filtering & search](/api/v2/docs/guides/filtering) — putting the item and custom-attribute model to work.
- [Workspaces](/api/v2/docs/guides/workspaces) — the `WORKSPACE-ID` model in full.
- [API reference](/api/v2/docs/queries) — every query, mutation, and type.
- [MCP overview](/api/v2/docs/mcp/overview) — the same objects, exposed to agents as tools.
