# Organizing and finding assets

A library is only as useful as what you can retrieve from it. This page covers the
querying half of the media library: the filters, the two endpoints that accept them, and
how to save a query rather than rebuild it.

## Two listing endpoints

They are not interchangeable, and picking the wrong one is a common early mistake:

| Endpoint | For |
|---|---|
| `GET /files` | Any repository. **`repo_type` is required**: files are always scoped to one, never listed across the workspace. Plain column filters: name, type, size, status, dates. |
| `GET /media-files` | **The media library** (`repo_type=media`) or generated media (`repo_type=generated`). The full filter vocabulary below, each file's album membership, and semantic and face search. |

For asset-management work, use `GET /media-files`. It returns album membership alongside
each file rather than listing one album; that is `af_album`, one filter among many.

### Repository parameters

`repo_type` names what kind of thing owns the files, and `repo_id` names which one:

| `repo_type` | `repo_id` | Lists |
|---|---|---|
| `media` | — | The shared media library, what people uploaded. A library takes no id. |
| `generated` | — | What the platform produced: AI images and infographics, wherever they were made. |
| `ds` | knowledge base id | The documents a knowledge base indexes. |
| `thread` | conversation id | What was attached to a conversation, and what was generated in it. |
| `design` | design id | A design's assets and rendered previews. |
| `prompt` | prompt id | The references attached to a saved prompt. |

A file can sit in more than one repository at once, and either containment is readable:
an image generated inside a conversation carries both `generated` and its `thread`, so it
is returned by both listings.

Reading a repository means reading the object that owns it, so that object's access rules
apply on top of each file's. Several repositories have their own collection, which is the
more direct way to ask: `GET /knowledge-bases/{id}/files`, `GET /threads/{id}/files`,
`GET /designs/{id}/files`.

Both endpoints answer with the standard [collection envelope](/pagination):

```json
{ "data": [ … ], "has_more": true, "next_cursor": "eyJzIjoi…" }
```

Each row in the media library adds `file_albums`: every album the file belongs to,
resolved, so you do not need a second call to render membership:

```json
{
  "file_id": "8a1b6e5c-…",
  "file_name": "harbor-sunset.jpg",
  "file_status": "ready",
  "file_albums": [
    { "album_id": "3f2a9c1e-…", "album_name": "Summer campaign", "af_created": "2026-07-02T14:11:03Z" }
  ]
}
```

## The filter vocabulary

Filters combine with AND. Where a filter takes a list, the values inside it combine with
OR, so `tags=harbor&tags=sunset` means "either tag", and adding `orientation=Landscape`
narrows that to landscape files.

### Core filters

| Filter | |
|---|---|
| `search` | Free text over the file name and, for generated images, the prompt that made them. |
| `af_album` | Files in one album. |
| `favorite=true` | The **calling user's** favorites. Favorites are per person, not per workspace. |
| `fileType` | `Image`, `Video`, `Audio` or `Document`. Repeatable. |
| `file_created`, `photoTakenDate` | Time ranges. `photoTakenDate` is when the photo was taken; `file_created` is when it arrived. They differ for anything imported from an archive. |
| `file_state_item` | Files sitting at one state of your review workflow. |
| `aiGenerated` | Whether the platform generated the image. |
| `excludeWorkflow=true` | Hides intermediate files produced by workflow runs. |

### Tags

Two systems, deliberately kept separate:

- **`tags`**: what the AI identified. Free-form labels from analysis. Filter with
  `tags=harbor&tags=boat`.
- **`mediaTags`**: what your organization defined. Structured groups you define, passed
  as a JSON object mapping group to values:

  ```
  GET /media-files?repo_type=media&mediaTags={"campaign":["summer-2026"],"usage":["print"]}
  ```

Write media tags with `PUT /files/{id}/media-tags` for one file, or `POST
/files/actions/set-media-tags` for many. The bulk form merges by default and removes when
you pass `remove: true`, and it reports what it actually changed:

```json
{ "successCount": 412, "totalRequested": 420 }
```

The difference is files you cannot write to. Compare the two numbers rather than treating
a 200 as confirmation that all of them were updated.

### Analysis filters

These read the AI's structured output, so they match only files that reached `ready`. See
[AI enrichment](/ai-enrichment) for what produces them.

| Filter | |
|---|---|
| `flags` | Anything the analysis flagged for attention. Repeatable. |
| `photoRating` | Overall quality score, **1 to 5**, as a range: `photoRating[0]=4&photoRating[1]=5`. |
| `peopleCount` | Number of people in the shot, as a range. |
| `gender`, `role`, `age` | Attributes of the people in the shot. `age` is a range. |
| `orientation` | `Portrait` or `Landscape`, computed from EXIF dimensions. |
| `dpi` | Pixel density range, the filter for whether a file is good enough to print. |
| `custom` | Fields from your own analysis schemas, as `{"field":["value"]}`. |

### Faces

| Filter | |
|---|---|
| `has_faces=true` | Any photo containing a countable face. |
| `face_person` | Photos containing one named person. |
| `face_embedding` | Photos containing a face resembling one you supply. See [people and faces](/people-and-faces). |

### Meaning

`embedding_search` searches by what an image is *of* rather than what it is called,
and works on files whose analysis has produced embeddings:

```
GET /media-files?repo_type=media&embedding_search=boats%20at%20golden%20hour
```

Matching rows carry a `similarity` score, and results are ordered by it.
`similarityThreshold` tightens or loosens the cutoff. [AI enrichment](/ai-enrichment)
covers this in full.

### Ranges

Range filters take two values, either repeated (`age=25&age=40`) or as
`{"min":25,"max":40}`. Both ends are inclusive and both are required; there is no
open-ended form.

## Albums

**Manual albums** are explicit lists. Create one, then add and remove files:

<CodeTabs syncKey="lang">

```bash title="cURL"
curl -X POST https://api.genuineai.app/api/v1/albums \
  -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>" \
  -H "Content-Type: application/json" \
  -d '{"album_name": "Summer campaign"}'

curl -X POST https://api.genuineai.app/api/v1/albums/<album-id>/files \
  -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>" \
  -H "Content-Type: application/json" \
  -d '{"file_ids": ["8a1b6e5c-…", "3f2a9c1e-…"]}'
```

```js title="JavaScript"
const album = await fetch("https://api.genuineai.app/api/v1/albums", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ album_name: "Summer campaign" }),
}).then(r => r.json());

const result = await fetch(
  `https://api.genuineai.app/api/v1/albums/${album.album_id}/files`,
  {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ file_ids: fileIds }),
  },
).then(r => r.json());
```

```python title="Python"
album = requests.post(
    "https://api.genuineai.app/api/v1/albums",
    headers=headers,
    json={"album_name": "Summer campaign"},
).json()

result = requests.post(
    f"https://api.genuineai.app/api/v1/albums/{album['album_id']}/files",
    headers=headers,
    json={"file_ids": file_ids},
).json()
```

</CodeTabs>

Adding is idempotent and reports the result:

```json
{ "added": 18, "skipped": 2, "missing": 0 }
```

`skipped` counts files already in the album, and `missing` counts files that do not exist
or are not visible to you. Re-sending the same list is safe.

You can also skip this step entirely: `album_ids` on the [upload
request](/uploading-files) files a file at the moment it is created.

**Smart albums** are a saved filter rather than a list. Their contents are whatever
currently matches, so files join and leave automatically:

```bash
curl -X POST https://api.genuineai.app/api/v1/albums/smart \
  -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "album_name": "Print-ready harbor shots",
    "query_search": {
      "tags": ["harbor"],
      "dpi": {"min": 300, "max": 1200},
      "photoRating": {"min": 4, "max": 5}
    }
  }'
```

`query_search` is the same filter object you would have sent as query parameters. `PATCH
/albums/{id}/smart` changes it later, and reading a smart album returns the filter as
`album_query_search` so you can show people what it selects on.

Albums nest through `album_parent`, and `GET /albums?album_parent=<id>` walks one level.

A smart album is how you save a filter. There is no separate saved-search resource on this
surface: to keep a query, save it as a smart album and read it back as one.

## Bulk actions

Three bulk endpoints take a `file_ids` array and apply one change:

| | |
|---|---|
| `POST /files/actions/set-media-tags` | Add or remove media tags. |
| `POST /files/actions/transition` | Move files to a `target_state_id` in the media status flow. |
| `POST /files/actions/reanalyze` | Re-run [AI analysis](/ai-enrichment). Consumes credits. |

Each skips files you cannot write to rather than failing the batch, and each reports
counts. Most housekeeping integrations combine a filtered list with one of these actions:
query for what is wrong, then hand the ids to the action.

## Sorting and paging

`sort=-file_created` is newest first, and `sort=file_name` is A to Z. The media library
adds a deterministic tiebreaker of its own, so equal sort values do not reshuffle between
pages and infinite scroll does not show duplicates.

Page with `cursor` rather than `offset` (see [pagination](/pagination)). Media libraries
are exactly the case cursors exist for: a deep offset gets slower the further in you go.

## Next steps

- [AI enrichment](/ai-enrichment) explains what fills in the analysis filters above.
- [People and faces](/people-and-faces) covers the face filters, and naming who is in a photo.
- [Delivering assets](/delivering-assets) is how you get the files you found back out.
