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

# Rank a catalog

> Order your own items by how well they fit what the person saved, on the device or on your server.

There are two ways to fit your catalog to a person. Ranking on the device needs nothing new from them. Ranking on your server needs a summary to leave the phone, so it has its own permission.

## On the device

Hand PoolKit the items you are about to show, such as a catalog page or a starter grid. They come back in the order that fits what the person saved.

```swift theme={null}
let ranking = try await pool.context.rank([
    RankCandidate(id: "sku-1", text: "Leather tote, Polène", imageData: toteJPEG),
    RankCandidate(id: "sku-2", text: "Nike Air Max 90 White"),
], using: grant)

for item in ranking.items {
    print(item.id, item.score, item.reasons)
}
```

* A candidate is your `id` plus whatever you have: `text`, `imageData` (PNG, JPEG or HEIC), or an `embedding` you computed yourself. Up to 500 candidates per call.
* Only your ids, scores and reasons come back. No photo, no capture id and no library content.
* It needs the `query` operation, like any other read.

| Field | Meaning |
| - | - |
| `items` | Every candidate, best fit first, each with a `score` from 0 to 1 and its `reasons` |
| `reasons` | `looksLikeSaved`, `sameThing` (names a thing they saved), `sameBrand` |
| `usedImageModel` | False until the engine's models are installed. Until then ranking uses words only. |
| `basedOnCaptures` | How many saved captures the ranking drew on |
| `coverage`, `asOf` | How much of the library was read, and when |

A score orders the candidates of one call. It is not a probability and not a measure you can compare across people. Unlike [classify](https://docs.pool.day/classify.md#probabilities), rank scores are not fitted to labelled data in this version.

## On your server

To search a large catalog, ask for a summary of the person's visual taste and run the search yourself.

```swift theme={null}
let grant = try await pool.authorization.requestAccess(
    AccessRequest(
        data: [.products],
        operations: [.query, .exportTaste],
        purpose: "Share a summary of the style of what you save, not your photos, to build your feed"
    )
)

let taste = try await pool.context.tasteVectors(using: grant)
upload(taste.modelSignature, taste.centres.map { ($0.vector, $0.weight) })
```

* `centres` holds six vectors by default, each with a `weight`. The weights sum to 1. Pass `clusters:` for 1 to 12.
* They are centres of groups of captures, never a vector for one capture, and nothing in them points back to a photo.
* `modelSignature` names the model the vectors come from. Embed your catalog with the same model, and check the signature before you mix vectors from two calls.
* With fewer than 20 saved captures there are no centres, and `basedOnCaptures` says how many there were.
* Captures that Pool marks as sensitive never take part.

`exportTaste` is a separate operation because these vectors leave the device. Say so in the `purpose` you show the person. A grant without it makes `tasteVectors` fail with `PoolError.operationNotGranted`.

## What ranking never says

PoolKit orders your items and exports vectors. It never states that a person likes something, and it has no labels for mood, style or personality.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.