suo
Guides

Push app content with per-user partitions

Index content behind a login, one partition per user, the way Toread indexes each reader's library.

This guide walks through the pattern Toread uses for its reading library: an index where every record belongs to exactly one user, and a user only ever searches their own records.

1. Create the index with a partition key

PUT /1/indexes/library/settings
{ "indexTime": { "partitionKey": "userId" } }

Set this once, before you write any records — changing partitionKey on an index that already has data is not supported; create a new index instead.

2. Push records with the partition attribute

Every record needs the attribute named by partitionKey:

POST /1/indexes/library/records
{
	"operations": [
		{
			"action": "upsert",
			"record": {
				"objectID": "item-482",
				"userId": "user_9f2",
				"title": "The gentle art of note-taking",
				"url": "https://example.com/read/item-482",
				"version": 1732550400000
			}
		}
	]
}

Push on the events that change what should be searchable: an item becomes ready, its content updates, it is archived or deleted, or a highlight or note is added or changed. Use your own updatedAt as version — see Versions and last-writer-wins.

3. Mint a scoped token per reader

Your server mints a token pinned to that one partition, using your write (or a dedicated signing) key — never expose that key to the browser:

import type { ScopedTokenClaims } from '@suo/protocol';

const claims: ScopedTokenClaims = {
	keyId: process.env.SUO_KEY_ID,
	indexes: ['library'],
	partition: `user_${session.userId}`,
	filters: [],
	exp: Math.floor(Date.now() / 1000) + 300,
	ratePerMinute: null,
};

Mint a fresh token per session or page load with a short expiry — see Scoped tokens for the full claims shape, how signing works today, and how expiry is enforced.

4. Search with the token, not a shared key

<suo-search host="https://api.usesuo.com" scoped-token="{{token}}" index-name="library"></suo-search>

Because the token pins the partition, there is no filters parameter for the client to get wrong — a bug in your front end cannot leak another reader's records.

5. Handle account deletion

Clear the whole partition in one call when an account is deleted:

DELETE /1/indexes/library/partitions/user_9f2

See Clear a partition — this removes every record and tombstone in that partition, with no undo.

Backfilling existing users

For a partitioned index created after your app already has users, push a full backfill batch per user once, then switch to incremental pushes on the events above going forward.

On this page