···10101111## Authentication
12121313-Most write operations require OAuth 2.0 authentication. Include the access token in the Authorization header:
1313+Most write operations require OAuth 2.0 authentication. Include the access token
1414+in the Authorization header:
14151516```
1617Authorization: Bearer YOUR_ACCESS_TOKEN
···22232324### Slice Management
24252525-#### `social.slices.slice.listRecords`
2626+#### `network.slices.slice.listRecords`
26272728List all slices.
28292930**Method**: GET
30313132**Parameters**:
3333+3234- `limit` (number, optional): Maximum records to return (default: 50)
3335- `cursor` (string, optional): Pagination cursor
3436- `sort` (string, optional): Sort field and order (e.g., `createdAt:desc`)
···3638- `authors` (string[], optional): Filter by multiple author DIDs
37393840**Response**:
4141+3942```json
4043{
4144 "records": [
4245 {
4343- "uri": "at://did:plc:abc/social.slices.slice/xyz",
4646+ "uri": "at://did:plc:abc/network.slices.slice/xyz",
4447 "cid": "bafyrei...",
4548 "did": "did:plc:abc",
4646- "collection": "social.slices.slice",
4949+ "collection": "network.slices.slice",
4750 "value": {
4851 "name": "My Slice",
4952 "domain": "com.example",
···5659}
5760```
58615959-#### `social.slices.slice.getRecord`
6262+#### `network.slices.slice.getRecord`
60636164Get a specific slice by URI.
62656366**Method**: GET
64676568**Parameters**:
6969+6670- `uri` (string, required): AT Protocol URI of the slice
67716872**Response**: Single record object (same structure as listRecords item)
69737070-#### `social.slices.slice.createRecord`
7474+#### `network.slices.slice.createRecord`
71757276Create a new slice.
7377···7680**Authentication**: Required
77817882**Body**:
8383+7984```json
8085{
8186 "slice": "at://your-slice-uri",
8287 "record": {
8383- "$type": "social.slices.slice",
8888+ "$type": "network.slices.slice",
8489 "name": "My New Slice",
8590 "domain": "com.example",
8691 "createdAt": "2024-01-01T00:00:00Z"
···9095```
91969297**Response**:
9898+9399```json
94100{
9595- "uri": "at://did:plc:abc/social.slices.slice/xyz",
101101+ "uri": "at://did:plc:abc/network.slices.slice/xyz",
96102 "cid": "bafyrei..."
97103}
98104```
99105100106### Slice Operations
101107102102-#### `social.slices.slice.stats`
108108+#### `network.slices.slice.stats`
103109104110Get statistics for a slice.
105111106112**Method**: POST
107113108114**Body**:
115115+109116```json
110117{
111118 "slice": "at://your-slice-uri"
···113120```
114121115122**Response**:
123123+116124```json
117125{
118126 "success": true,
···131139}
132140```
133141134134-#### `social.slices.slice.listSliceRecords`
142142+#### `network.slices.slice.listSliceRecords`
135143136144List records across multiple collections in a slice.
137145138146**Method**: POST
139147140148**Body**:
149149+141150```json
142151{
143152 "slice": "at://your-slice-uri",
···149158```
150159151160**Response**:
161161+152162```json
153163{
154164 "success": true,
···158168 "cid": "bafyrei...",
159169 "did": "did:plc:abc",
160170 "collection": "com.example.post",
161161- "value": { /* record data */ },
171171+ "value": {/* record data */},
162172 "indexedAt": "2024-01-01T00:00:00Z"
163173 }
164174 ],
···166176}
167177```
168178169169-#### `social.slices.slice.searchSliceRecords`
179179+#### `network.slices.slice.searchSliceRecords`
170180171181Search records across multiple collections in a slice by content.
172182173183**Method**: POST
174184175185**Body**:
186186+176187```json
177188{
178189 "slice": "at://your-slice-uri",
···185196```
186197187198**Response**:
199199+188200```json
189201{
190202 "success": true,
···194206 "cid": "bafyrei...",
195207 "did": "did:plc:abc",
196208 "collection": "com.example.post",
197197- "value": { /* record data */ },
209209+ "value": {/* record data */},
198210 "indexedAt": "2024-01-01T00:00:00Z"
199211 }
200212 ],
···202214}
203215```
204216205205-#### `social.slices.slice.syncUserCollections`
217217+#### `network.slices.slice.syncUserCollections`
206218207219Synchronously sync collections for the authenticated user.
208220···211223**Authentication**: Required
212224213225**Body**:
226226+214227```json
215228{
216229 "slice": "at://your-slice-uri",
···219232```
220233221234**Response**:
235235+222236```json
223237{
224238 "success": true,
···229243}
230244```
231245232232-#### `social.slices.slice.startSync`
246246+#### `network.slices.slice.startSync`
233247234248Start an asynchronous bulk sync job.
235249···238252**Authentication**: Required
239253240254**Body**:
255255+241256```json
242257{
243258 "slice": "at://your-slice-uri",
···249264```
250265251266**Response**:
267267+252268```json
253269{
254270 "success": true,
···257273}
258274```
259275260260-#### `social.slices.slice.codegen`
276276+#### `network.slices.slice.codegen`
261277262278Generate TypeScript client code.
263279264280**Method**: POST
265281266282**Body**:
283283+267284```json
268285{
269286 "target": "typescript",
···272289```
273290274291**Response**:
292292+275293```json
276294{
277295 "success": true,
···281299282300## Dynamic Collection Endpoints
283301284284-For each collection in your slice, the following endpoints are automatically generated:
302302+For each collection in your slice, the following endpoints are automatically
303303+generated:
285304286305### `[collection].listRecords`
287306···290309**Method**: GET
291310292311**Parameters**:
312312+293313- `slice` (string, required): Slice URI
294314- `limit` (number, optional): Maximum records (default: 50)
295315- `cursor` (string, optional): Pagination cursor
···304324**Method**: GET
305325306326**Parameters**:
327327+307328- `slice` (string, required): Slice URI
308329- `uri` (string, required): Record URI
309330···314335**Method**: GET
315336316337**Parameters**:
338338+317339- `slice` (string, required): Slice URI
318340- `query` (string, required): Search query
319341- `field` (string, optional): Specific field to search
···330352**Authentication**: Required
331353332354**Body**:
355355+333356```json
334357{
335358 "slice": "at://your-slice-uri",
336359 "record": {
337337- "$type": "collection.name",
360360+ "$type": "collection.name"
338361 /* record fields */
339362 },
340363 "rkey": "optional-key"
···350373**Authentication**: Required
351374352375**Body**:
376376+353377```json
354378{
355379 "slice": "at://your-slice-uri",
356380 "rkey": "record-key",
357381 "record": {
358358- "$type": "collection.name",
382382+ "$type": "collection.name"
359383 /* updated fields */
360384 }
361385}
···370394**Authentication**: Required
371395372396**Body**:
397397+373398```json
374399{
375400 "rkey": "record-key"
···378403379404## Lexicon Management
380405381381-### `social.slices.lexicon.listRecords`
406406+### `network.slices.lexicon.listRecords`
382407383408List lexicons in a slice.
384409···386411387412**Parameters**: Same as collection.listRecords
388413389389-### `social.slices.lexicon.createRecord`
414414+### `network.slices.lexicon.createRecord`
390415391416Add a lexicon to a slice.
392417···395420**Authentication**: Required
396421397422**Body**:
423423+398424```json
399425{
400426 "slice": "at://your-slice-uri",
401427 "record": {
402402- "$type": "social.slices.lexicon",
428428+ "$type": "network.slices.lexicon",
403429 "nsid": "com.example.post",
404430 "definitions": "{\"lexicon\": 1, ...}",
405431 "createdAt": "2024-01-01T00:00:00Z",
···410436411437## Actor Management
412438413413-### `social.slices.slice.getActors`
439439+### `network.slices.slice.getActors`
414440415441Get actors (users) in a slice.
416442417443**Method**: GET
418444419445**Parameters**:
446446+420447- `slice` (string, required): Slice URI
421448- `search` (string, optional): Search query
422449- `dids` (string[], optional): Filter by DIDs
···424451- `cursor` (string, optional): Pagination cursor
425452426453**Response**:
454454+427455```json
428456{
429457 "actors": [
···449477**Authentication**: Required
450478451479**Headers**:
480480+452481- `Content-Type`: MIME type of the blob
453482454483**Body**: Raw binary data
455484456485**Response**:
486486+457487```json
458488{
459489 "blob": {
···477507```
478508479509Common HTTP status codes:
510510+480511- `200`: Success
481512- `400`: Bad request
482513- `401`: Authentication required
···4935243. Continue until no cursor returned
494525495526Example:
527527+496528```javascript
497529let cursor = undefined;
498530do {
···508540Sort parameter format: `field:order` or `field1:order1,field2:order2`
509541510542Examples:
543543+511544- `createdAt:desc` - Newest first
512545- `name:asc` - Alphabetical
513546- `createdAt:desc,name:asc` - Newest first, then alphabetical
···516549517550- [SDK Usage](./sdk-usage.md) - Using generated TypeScript clients
518551- [Getting Started](./getting-started.md) - Build your first application
519519-- [Concepts](./concepts.md) - Understand the architecture552552+- [Concepts](./concepts.md) - Understand the architecture
+7-7
docs/concepts.md
···1010### Key Properties
11111212- **URI**: Unique AT Protocol URI (e.g.,
1313- `at://did:plc:abc123/social.slices.slice/3xyz`)
1313+ `at://did:plc:abc123/network.slices.slice/3xyz`)
1414- **Name**: Human-readable identifier
1515- **Domain**: Namespace for lexicons (e.g., `com.example`, `social.grain`)
1616- **Creation Date**: When the slice was created
···6565Lexicons follow reverse domain naming:
66666767- `com.example.post` - A post in the example.com namespace
6868-- `social.slices.slice` - Core slice record type
6868+- `network.slices.slice` - Core slice record type
6969- `app.bsky.actor.profile` - Bluesky profile (external)
70707171## Collections
···217217218218Built-in endpoints for slice management:
219219220220-- `social.slices.slice.stats` - Slice statistics
221221-- `social.slices.slice.records` - Browse records
222222-- `social.slices.slice.codegen` - Generate SDKs
223223-- `social.slices.slice.sync` - Trigger sync
220220+- `network.slices.slice.stats` - Slice statistics
221221+- `network.slices.slice.records` - Browse records
222222+- `network.slices.slice.codegen` - Generate SDKs
223223+- `network.slices.slice.sync` - Trigger sync
224224225225### Handler Authentication
226226···255255256256// Use nested structure matching lexicons
257257await client.com.example.post.listRecords();
258258-await client.social.slices.slice.stats();
258258+await client.network.slices.slice.stats();
259259await client.app.bsky.actor.profile.getRecord({ uri });
260260```
261261
+22-9
docs/getting-started.md
···3838Create `.env` files for both API and frontend:
39394040**API (`/api/.env`)**:
4141+4142```bash
4243DATABASE_URL=postgres://user:password@localhost:5432/slices
4344AUTH_BASE_URL=https://aip.your-domain.com
···4546```
46474748**Frontend (`/frontend/.env`)**:
4949+4850```bash
4951OAUTH_CLIENT_ID=your-client-id
5052OAUTH_CLIENT_SECRET=your-client-secret
···5254OAUTH_AIP_BASE_URL=https://aip.your-domain.com
5355SESSION_ENCRYPTION_KEY=your-32-char-key
5456API_URL=http://localhost:3000
5555-SLICE_URI=at://did:plc:your-did/social.slices.slice/your-slice-id
5757+SLICE_URI=at://did:plc:your-did/network.slices.slice/your-slice-id
5658DATABASE_URL=slices.db
5759```
5860···7072### 5. Start the Services
71737274Start the API server:
7575+7376```bash
7477cd api
7578cargo run
7679```
77807881Start the frontend:
8282+7983```bash
8084cd frontend
8185deno task dev
···9296### 2. Create a Slice
93979498Click "Create Slice" and provide:
9999+95100- **Name**: A friendly name for your slice
96101- **Domain**: Your namespace (e.g., `com.example`)
9710298103### 3. Define a Lexicon
99104100100-Navigate to your slice and go to the Lexicon tab. Create a lexicon for your first record type:
105105+Navigate to your slice and go to the Lexicon tab. Create a lexicon for your
106106+first record type:
101107102108```json
103109{
···140146141147### 4. Generate TypeScript Client
142148143143-Navigate to the Code Generation tab and click "Generate TypeScript Client". This creates a type-safe client library for your slice.
149149+Navigate to the Code Generation tab and click "Generate TypeScript Client". This
150150+creates a type-safe client library for your slice.
144151145152### 5. Use the Generated Client
146153···150157import { AtProtoClient } from "./generated-client.ts";
151158152159const client = new AtProtoClient(
153153- 'http://localhost:3000',
154154- 'at://did:plc:your-did/social.slices.slice/your-slice-id'
160160+ "http://localhost:3000",
161161+ "at://did:plc:your-did/network.slices.slice/your-slice-id",
155162);
156163157164// List posts
···162169 title: "My First Post",
163170 content: "Hello from Slices!",
164171 createdAt: new Date().toISOString(),
165165- tags: ["introduction", "slices"]
172172+ tags: ["introduction", "slices"],
166173});
167174168175// Get a specific post
169176const post = await client.com.example.post.getRecord({
170170- uri: newPost.uri
177177+ uri: newPost.uri,
171178});
172179```
173180···182189### 2. Configure Sync
183190184191Choose collections to sync:
192192+185193- **Primary Collections**: Your slice's lexicons
186194- **External Collections**: Bluesky or other AT Protocol collections
187195188196### 3. Start Sync
189197190190-Specify repositories (DIDs) to sync from, or leave empty to sync all available data.
198198+Specify repositories (DIDs) to sync from, or leave empty to sync all available
199199+data.
191200192201### 4. Monitor Progress
193202···203212## Troubleshooting
204213205214### Database Connection Issues
215215+206216- Verify PostgreSQL is running: `docker ps`
207217- Check DATABASE_URL format
208218- Ensure database exists
209219210220### OAuth Errors
221221+211222- Verify client ID and secret
212223- Check redirect URI matches configuration
213224- Ensure AIP server is accessible
214225215226### Sync Not Working
227227+216228- Check user has necessary permissions
217229- Verify lexicons are valid
218230- Check API server logs for errors
219231220232### Generated Client Issues
233233+221234- Regenerate client after lexicon changes
222235- Ensure API server is running
223223-- Check for TypeScript compilation errors236236+- Check for TypeScript compilation errors
+44-39
docs/sdk-usage.md
···1919```typescript
2020const client = new AtProtoClient(
2121 "https://api.your-domain.com",
2222- "at://did:plc:abc/social.slices.slice/your-slice-id",
2222+ "at://did:plc:abc/network.slices.slice/your-slice-id",
2323);
24242525// Read operations work without auth
···4343// Initialize API client with OAuth
4444const client = new AtProtoClient(
4545 "https://api.your-domain.com",
4646- "at://did:plc:abc/social.slices.slice/your-slice-id",
4646+ "at://did:plc:abc/network.slices.slice/your-slice-id",
4747 oauthClient,
4848);
4949```
···292292### Get Slice Statistics
293293294294```typescript
295295-const stats = await client.social.slices.slice.stats({
295295+const stats = await client.network.slices.slice.stats({
296296 slice: "at://your-slice-uri",
297297});
298298···311311312312```typescript
313313// Get all actors in the slice
314314-const actors = await client.social.slices.slice.getActors();
314314+const actors = await client.network.slices.slice.getActors();
315315316316// With pagination
317317-const page1 = await client.social.slices.slice.getActors({
317317+const page1 = await client.network.slices.slice.getActors({
318318 limit: 20,
319319});
320320-const page2 = await client.social.slices.slice.getActors({
320320+const page2 = await client.network.slices.slice.getActors({
321321 limit: 20,
322322 cursor: page1.cursor,
323323});
324324325325// Filter by specific DIDs
326326-const specificActors = await client.social.slices.slice.getActors({
326326+const specificActors = await client.network.slices.slice.getActors({
327327 where: {
328328 did: { in: ["did:plc:user1", "did:plc:user2"] },
329329 },
330330});
331331332332// Search by handle
333333-const searchByHandle = await client.social.slices.slice.getActors({
333333+const searchByHandle = await client.network.slices.slice.getActors({
334334 where: {
335335 handle: { contains: "alice" },
336336 },
337337});
338338339339// Filter by exact handle
340340-const exactHandle = await client.social.slices.slice.getActors({
340340+const exactHandle = await client.network.slices.slice.getActors({
341341 where: {
342342 handle: { eq: "alice.bsky.social" },
343343 },
···357357358358```typescript
359359// Get records from specific collections
360360-const records = await client.social.slices.slice.getSliceRecords({
360360+const records = await client.network.slices.slice.getSliceRecords({
361361 where: {
362362 collection: { eq: "com.example.post" },
363363 did: { eq: "did:plc:specific-author" }, // optional
···370370});
371371372372// Search across collections using specific fields
373373-const searchResults = await client.social.slices.slice.getSliceRecords({
373373+const searchResults = await client.network.slices.slice.getSliceRecords({
374374 where: {
375375 collection: { eq: "com.example.post" },
376376 title: { contains: "hello world" },
···380380});
381381382382// Global search across ALL fields in records
383383-const globalSearchResults = await client.social.slices.slice.getSliceRecords({
383383+const globalSearchResults = await client.network.slices.slice.getSliceRecords({
384384 where: {
385385 collection: { eq: "com.example.post" },
386386 json: { contains: "hello world" }, // Searches entire record content
···394394});
395395396396// Get records from any collection with global text search
397397-const allCollectionSearch = await client.social.slices.slice.getSliceRecords({
397397+const allCollectionSearch = await client.network.slices.slice.getSliceRecords({
398398 where: {
399399 json: { contains: "important content" }, // Searches ALL fields in ALL collections
400400 },
···451451452452```typescript
453453// Search for "tutorial" across all collections
454454-const crossCollectionSearch = await client.social.slices.slice.getSliceRecords({
455455- where: {
456456- json: { contains: "tutorial" },
454454+const crossCollectionSearch = await client.network.slices.slice.getSliceRecords(
455455+ {
456456+ where: {
457457+ json: { contains: "tutorial" },
458458+ },
457459 },
458458-});
460460+);
459461460462// Limit to specific collections
461461-const specificSearch = await client.social.slices.slice.getSliceRecords({
463463+const specificSearch = await client.network.slices.slice.getSliceRecords({
462464 where: {
463465 collection: { in: ["com.example.post", "com.example.article"] },
464466 json: { contains: "guide" },
···468470469471### OR Query Support
470472471471-You can use OR queries to find records that match any of multiple conditions using the separate `orWhere` parameter. This provides clean type safety and autocomplete for field names:
473473+You can use OR queries to find records that match any of multiple conditions
474474+using the separate `orWhere` parameter. This provides clean type safety and
475475+autocomplete for field names:
472476473477```typescript
474478// Find posts by either user1 OR user2
475479const posts = await client.com.example.post.getRecords({
476480 orWhere: {
477477- did: { in: ["did:plc:user1", "did:plc:user2"] }
478478- }
481481+ did: { in: ["did:plc:user1", "did:plc:user2"] },
482482+ },
479483});
480484481485// Find posts that either have "typescript" in title OR are by a specific user
482486const posts = await client.com.example.post.getRecords({
483487 orWhere: {
484488 title: { contains: "typescript" },
485485- did: { eq: "did:plc:alice" }
486486- }
489489+ did: { eq: "did:plc:alice" },
490490+ },
487491});
488492489493// Combining OR with regular AND conditions
490494const posts = await client.com.example.post.getRecords({
491495 where: {
492492- createdAt: { eq: "2025-09-03" }, // AND conditions
496496+ createdAt: { eq: "2025-09-03" }, // AND conditions
493497 },
494494- orWhere: { // OR conditions
498498+ orWhere: { // OR conditions
495499 title: { contains: "guide" },
496496- did: { eq: "did:plc:user1" }
497497- }
500500+ did: { eq: "did:plc:user1" },
501501+ },
498502});
499503// SQL: WHERE created_at = '2025-09-03' AND (title LIKE '%guide%' OR did = 'did:plc:user1')
500504501505// OR queries work with cross-collection searches too
502502-const crossCollectionOrSearch = await client.social.slices.slice.getSliceRecords({
503503- where: {
504504- collection: { eq: "com.example.post" },
505505- },
506506- orWhere: {
507507- title: { contains: "javascript" },
508508- tags: { contains: "tutorial" }
509509- }
510510-});
506506+const crossCollectionOrSearch = await client.network.slices.slice
507507+ .getSliceRecords({
508508+ where: {
509509+ collection: { eq: "com.example.post" },
510510+ },
511511+ orWhere: {
512512+ title: { contains: "javascript" },
513513+ tags: { contains: "tutorial" },
514514+ },
515515+ });
511516512517// You get full autocomplete and type safety for field names in both where and orWhere
513518const typedSearch = await client.com.example.post.getRecords({
···519524 // And also provides autocomplete here
520525 description: { contains: "tutorial" },
521526 tags: { contains: "guide" },
522522- }
527527+ },
523528});
524529```
525530···527532528533```typescript
529534// Sync current user's data (requires auth)
530530-const syncResult = await client.social.slices.slice.syncUserCollections({
535535+const syncResult = await client.network.slices.slice.syncUserCollections({
531536 timeoutSeconds: 30,
532537});
533538···593598const client = new AtProtoClient(apiUrl, sliceUri, oauthClient);
594599595600// OAuth tokens are automatically managed
596596-const profile = await client.social.slices.actor.profile.createRecord({
601601+const profile = await client.network.slices.actor.profile.createRecord({
597602 displayName: "New User",
598603 description: "My profile",
599604}, true); // useSelfRkey for profile