···11-[{"path":"/","title":"Introduction","group":"Getting Started","content":"Quickslice > \\1 > This project is in early development. APIs may change without notice. Quickslice is a quick way to spin up an \\1 for AT Protocol applications. Import your Lexicon schemas and you get a GraphQL API with OAuth authentication, real-time sync from the network, and joins across record types without setting up a database or writing any backend code. ## The Problem Building an AppView from scratch means writing a lot of infrastructure code: - Jetstream connection and event handling - Record ingestion and validation - Database schema design and normalization - XRPC API endpoints for querying and writing data - OAuth session management and PDS writes - Efficient batching when resolving related records This adds up before you write any application logic. ## What Quickslice Does Quickslice handles all of that automatically: - \\1 and tracks the record types defined in your Lexicons - \\1 relevant records into a database (SQLite or Postgres) - \\1 queries, mutations, and subscriptions from your Lexicon definitions - \\1 and writes records back to the user's PDS - \\1 by DID, URI, or strong reference, so you can query a status and its author's profile in one request ## When to Use It - You want to skip the AppView boilerplate - You want to prototype Lexicon data structures quickly - You want OAuth handled for you - You want to ship your AppView already ## Next Steps \\1: A hands-on tutorial showing what Quickslice handles for you","headings":[]},{"path":"/tutorial","title":"Tutorial","group":"Getting Started","content":"Tutorial: Build Statusphere with Quickslice Let's build Statusphere, an app where users share their current status as an emoji. This is the same app from the \\1, but using Quickslice as the AppView. Along the way, we'll show what you'd write manually versus what Quickslice handles automatically. \\1 A working example is running at \\1, connected to a slice at \\1 with the lexicon. ## What We're Building Statusphere lets users: - Log in with their AT Protocol identity - Set their status as an emoji - See a feed of everyone's statuses with profile information By the end of this tutorial, you'll understand how Quickslice eliminates the boilerplate of building an AppView. ## Step 1: Project Setup and Importing Lexicons Every AT Protocol app starts with Lexicons. Here's the Lexicon for a status record: Importing this Lexicon into Quickslice triggers three automatic steps: 1. \\1: Quickslice tracks records from the network 2. \\1: Quickslice creates a normalized table with proper columns and indexes 3. \\1: Quickslice generates query, mutation, and subscription types | Without Quickslice | With Quickslice | |---|---| | Write Jetstream connection code | Import your Lexicon | | Filter events for your collection | | | Validate incoming records | | | Design database schema | Quickslice handles the rest. | | Write ingestion logic | | ## Step 2: Querying Status Records Query indexed records with GraphQL. Quickslice generates a query for each Lexicon type using Relay-style connections: The and pattern comes from \\1, a GraphQL pagination specification. Each contains a (the record) and a for pagination. You can filter with clauses: | Without Quickslice | With Quickslice | |---|---| | Design query API | Query is auto-generated: | | Write database queries | | | Handle pagination logic | | | Build filtering and sorting | | ## Step 3: Joining Profile Data Here Quickslice shines. Every status record has a field identifying its author. In Bluesky, profile information lives in records. Join directly from a status to its author's profile: The field is a \\1. It follows the on the status record to find the profile authored by that identity. Quickslice: - Collects DIDs from the status records - Batches them into a single database query (DataLoader pattern) - Joins profile data efficiently | Without Quickslice | With Quickslice | |---|---| | Collect DIDs from status records | Add join to your query: | | Batch resolve DIDs to profiles | | | Handle N+1 query problem | | | Write batching logic | | | Join data in API response | | ### Other Join Types Quickslice also supports: - \\1: Follow a URI or strong ref to another record - \\1: Find all records that reference a given record See the \\1 for complete documentation. ## Step 4: Writing a Status (Mutations) To set a user's status, call a mutation: Quickslice: 1. \\1: Creates the record in their personal data repository 2. \\1: The record appears in queries immediately, before Jetstream confirmation 3. \\1: Uses the authenticated session to sign the write | Without Quickslice | With Quickslice | |---|---| | Get OAuth session/agent | Call the mutation: | | Construct record with $type | | | Call putRecord XRPC on the PDS | | | Optimistically update local DB | | | Handle errors | | ## Step 5: Authentication Quickslice bridges AT Protocol OAuth. Your frontend initiates login; Quickslice manages the authorization flow: 1. User enters their handle (e.g., ) 2. Your app redirects to Quickslice's OAuth endpoint 3. Quickslice redirects to the user's PDS for authorization 4. User approves the app 5. PDS redirects back to Quickslice with an auth code 6. Quickslice exchanges the code for tokens and establishes a session For authenticated queries and mutations, include auth headers. The exact headers depend on your OAuth flow (DPoP or Bearer token). See the \\1 for details. ## Step 6: Deploying to Railway Deploy quickly with Railway: 1. Click the deploy button in the \\1 2. Generate an OAuth signing key with 3. Paste the key into the environment variable 4. Generate a domain and redeploy 5. Create your admin account by logging in 6. Upload your Lexicons See \\1 for detailed instructions. ## What Quickslice Handled Quickslice handled: - \\1: firehose connection, event filtering, reconnection - \\1: schema checking against Lexicons - \\1: tables, migrations, indexes - \\1: filtering, sorting, pagination endpoints - \\1: efficient related-record resolution - \\1: indexing before Jetstream confirmation - \\1: token exchange, session management, DPoP proofs Focus on your application logic; Quickslice handles infrastructure. ## Next Steps - \\1: Filtering, sorting, and pagination - \\1: Forward, reverse, and DID joins - \\1: Creating, updating, and deleting records - \\1: Setting up OAuth - \\1: Production configuration","headings":[]},{"path":"/guides/queries","title":"Queries","group":"Guides","content":"Queries Quickslice generates a GraphQL query for each Lexicon record type. Queries are public; no authentication required. ## Relay Connections Queries return data in the \\1 format: - : Array of results, each containing a (the record) and (for pagination) - : Pagination metadata - : Total number of matching records ## Filtering Use the argument to filter records: ### Filter Operators | Operator | Description | Example | |----------|-------------|---------| | | Equal to | | | | Not equal to | | | | In array | | | | String contains (case-insensitive) | | | | Greater than | | | | Less than | | | | Greater than or equal | | | | Less than or equal | | ### Multiple Conditions Combine multiple conditions (they're ANDed together): ## Sorting Use to order results: ### Multi-Field Sorting Sort by multiple fields (applied in order): ## Pagination ### Forward Pagination Use to limit results and to get the next page: ### Backward Pagination Use and to paginate backward: ### PageInfo Fields | Field | Description | |-------|-------------| | | More items exist after this page | | | More items exist before this page | | | Cursor of the first item | | | Cursor of the last item | ## Complete Example Combining filtering, sorting, and pagination: Variables:","headings":[]},{"path":"/guides/joins","title":"Joins","group":"Guides","content":"Joins AT Protocol data lives in collections. A user's status records ( ) occupy one collection, their profile ( ) another. Quickslice generates joins that query across collections—fetch a status and its author's profile in one request. ## Join Types Quickslice generates three join types automatically: | Type | What it does | Field naming | |------|--------------|--------------| | \\1 | Follows a URI or strong ref to another record | | | \\1 | Finds all records that reference a given record | | | \\1 | Finds records by the same author | | ## Forward Joins Forward joins follow references from one record to another. When a record has a field containing an AT-URI or strong ref, Quickslice generates a field that fetches the referenced record. ### Example: Resolving a Favorite's Subject A favorite record has a field containing an AT-URI. The field fetches the actual record: Forward joins return a union type because the referenced record could be any type. Use inline fragments ( ) for type-specific fields. ## Reverse Joins Reverse joins work oppositely: given a record, find all records that reference it. Quickslice analyzes your Lexicons and generates reverse join fields automatically. Reverse joins return paginated connections supporting filtering, sorting, and cursors. ### Example: Comments on a Photo Find all comments that reference a specific photo: ### Sorting and Filtering Reverse Joins Reverse joins support the same sorting and filtering as top-level queries: ## DID Joins DID joins connect records by author identity. Every record has a field identifying its creator. Quickslice generates fields to find related records by the same author. ### Example: Author Profile from a Status Get the author's profile alongside their status: ### Unique vs Non-Unique DID Joins Some collections have one record per DID (like profiles with a key). These return a single object: Other collections can have multiple records per DID. These return paginated connections: ### Cross-Lexicon DID Joins DID joins work across different Lexicon families. Get a user's Bluesky profile alongside their app-specific data: ## Common Patterns ### Profile Lookups The most common pattern: joining author profiles to any record type. ### Engagement Counts Use reverse joins to count likes, comments, or other engagement: ### User Activity Get all records by a user across multiple collections: ## How Batching Works Quickslice batches join resolution to avoid the N+1 query problem. When querying 100 photos with author profiles: 1. Fetches 100 photos in one query 2. Collects all unique DIDs from those photos 3. Fetches all profiles in a single query: 4. Maps profiles back to their photos All join types batch automatically.","headings":[]},{"path":"/guides/mutations","title":"Mutations","group":"Guides","content":"Mutations Mutations write records to the authenticated user's repository. All mutations require authentication. ## Creating Records Quickslice: 1. Writes the record to the user's PDS 2. Indexes locally for immediate query availability ### Custom Record Keys By default, Quickslice generates a TID (timestamp-based ID) for the record key. You can specify a custom key: Some Lexicons require specific key patterns. For example, profiles use as the record key. ## Updating Records Update an existing record by its record key: The update replaces the entire record. Include all required fields, not just the ones you're changing. ## Deleting Records Delete a record by its record key: ## Working with Blobs Records can include binary data like images. Upload the blob first, then reference it. ### Upload a Blob The field accepts base64-encoded binary data. The response includes a (CID) for use in your record. ### Use the Blob in a Record See the \\1 for more details on blob handling and URL presets. ## Error Handling Common mutation errors: | Error | Cause | |-------|-------| | | Missing or invalid authentication token | | | Invalid input (missing required fields, wrong types) | | | Record doesn't exist (for update/delete) | | | Trying to modify another user's record | ## Authentication Mutations require authentication. Headers depend on the OAuth flow: \\1 (recommended for browser apps): \\1: See the \\1 for flow details and token acquisition.","headings":[]},{"path":"/guides/authentication","title":"Authentication","group":"Guides","content":"Authentication Quickslice proxies OAuth between your app and users' Personal Data Servers (PDS). Your app never handles AT Protocol credentials directly. ## How It Works 1. User clicks login in your app 2. Your app redirects to Quickslice's endpoint 3. Quickslice redirects to the user's PDS for authorization 4. User enters credentials and approves your app 5. PDS redirects back to Quickslice with an auth code 6. Quickslice exchanges the code for tokens 7. Quickslice redirects back to your app with a code 8. Your app exchanges the code for an access token The access token authorizes mutations that write to the user's repository. ## Setting Up OAuth ### Generate a Signing Key Quickslice needs a private key to sign OAuth tokens. Generate one with : Set the output as your environment variable. ### Register an OAuth Client 1. Open your Quickslice instance and navigate to \\1 2. Scroll to \\1 and click \\1 3. Fill in the form: - \\1: Your app's name - \\1: Public (browser apps) or Confidential (server apps) - \\1: Where users return after auth (e.g., ) - \\1: Leave as 4. Copy the \\1 ### Public vs Confidential Clients | Type | Use Case | Secret | |------|----------|--------| | \\1 | Browser apps, mobile apps | No secret (client cannot secure it) | | \\1 | Server-side apps, backend services | Secret (stored securely on server) | ## Using the Client SDK The Quickslice client SDK handles OAuth, PKCE, DPoP, token refresh, and GraphQL requests. ### Install Or via CDN: ### Initialize ### Login ### Handle the Callback After authentication, the user returns to your redirect URI: ### Check Authentication State ### Logout ## Making Authenticated Requests ### With the SDK The SDK adds authentication headers automatically: ### Without the SDK Without the SDK, include headers based on your OAuth flow: \\1 (public clients): \\1 (confidential clients): ## The Viewer Query The query returns the authenticated user: Returns when not authenticated (no error thrown). ## Security: PKCE and DPoP The SDK implements two security mechanisms for browser apps: \\1 prevents authorization code interception. Before redirecting, the SDK generates a random secret and sends only its hash to the server. When exchanging the code for tokens, the SDK proves it initiated the request. \\1 binds tokens to a cryptographic key in your browser. Each request includes a signed proof. An attacker who steals your access token cannot use it without the key. ## OAuth Endpoints - - Start the OAuth flow - - Exchange authorization code for tokens - - Server metadata - - Client metadata","headings":[]},{"path":"/guides/deployment","title":"Deployment","group":"Guides","content":"Deployment Deploy Quickslice to production. Railway with one-click deploy is fastest. ## Railway (Recommended) ### 1. Deploy Click the button to deploy Quickslice with SQLite: \\1](https://railway.com/deploy/quickslice?referralCode=Ofii6e&utm\\1source=template&utm\\1medium=integration&utm\\1campaign=generic) This template provisions a PostgreSQL database alongside Quickslice. The is automatically configured.","headings":[]},{"path":"/guides/patterns","title":"Patterns","group":"Guides","content":"Common Patterns Recipes for common use cases when building with Quickslice. ## Profile Lookups Join author profiles to any record type to display names and avatars. The field works on all records because every record has a field. ## User Timelines Fetch all records by a specific user using DID joins from their profile. ## Engagement Counts Use reverse joins with to show likes, comments, or other engagement metrics. ## Feed with Nested Data Build a rich feed by combining multiple join types. ## Paginated Lists Implement infinite scroll or \"load more\" with cursor-based pagination. First request: Subsequent requests: Continue until is . ## Filtered Search Combine multiple filters for search functionality. ## Date Range Queries Filter records within a time period. Variables: ## Current User's Data Use the query to get the authenticated user's records. ## Real-Time Updates Subscribe to new records and update your UI live. Combine with an initial query to show existing data, then append new records as they arrive via subscription. ## Aggregations Get statistics like top items or activity over time.","headings":[]},{"path":"/guides/troubleshooting","title":"Troubleshooting","group":"Guides","content":"Troubleshooting Common issues and how to resolve them. ## OAuth Errors ### \"Invalid redirect URI\" The redirect URI in your OAuth request doesn't match any registered URI. \\1 Ensure the redirect URI in your app exactly matches one in Settings > OAuth Clients. URIs must match protocol, host, port, and path. ### \"Invalid client ID\" The client ID doesn't exist or was deleted. \\1 Verify the client ID in Settings > OAuth Clients. If it was deleted, register a new client. ### \"PKCE code verifier mismatch\" The code verifier sent during token exchange doesn't match the code challenge from authorization. \\1 The code verifier wasn't stored correctly between authorization redirect and callback. If using the SDK, call in the same browser session that initiated login. ### \"DPoP proof invalid\" The DPoP proof header is missing, malformed, or signed with the wrong key. \\1 If using the SDK, this is handled automatically. If implementing manually, ensure: - The DPoP header contains a valid JWT - The JWT is signed with the same key used during token exchange - The and claims match the request method and URL ## GraphQL Errors ### \"Cannot query field X on type Y\" The field doesn't exist on the queried type. \\1 Check your query against the schema in GraphiQL. Common causes: - Typo in field name - Field exists on a different type (use inline fragments for unions) - Lexicon wasn't imported yet ### \"Variable $X of type Y used in position expecting Z\" Type mismatch between your variable declaration and how it's used. \\1 Check variable types in your query definition. Common issues: - Using instead of for date fields - Missing for required variables - Using wrong scalar type ### \"Record not found\" The record you're trying to update or delete doesn't exist. \\1 Verify the record key (rkey). Query for the record first to confirm it exists. ## Jetstream Issues ### Records not appearing after creation Records created via mutation should appear immediately due to optimistic indexing. If they don't: \\1 1. Was the mutation successful? Check the response for errors. 2. Is the record in the user's PDS? Use to verify. 3. Is Jetstream connected? Check the logs for connection errors. ### Old records missing Records created before you deployed Quickslice won't appear until backfilled. \\1 Trigger a backfill from the admin UI or wait for the scheduled backfill to complete. ### Backfill stuck or slow \\1 1. Memory usage - backfill is memory-intensive. See \\1 for tuning. 2. Network connectivity to PDS endpoints 3. Logs for specific PDS errors ## Database Issues ### \"Database is locked\" SQLite can't acquire a write lock. Caused by long-running queries or concurrent access. \\1 - Ensure only one Quickslice instance writes to the database - Check for stuck queries in logs - Restart the service if needed ### Disk space full SQLite needs space for WAL files and vacuuming. \\1 Expand your volume. See your hosting platform's documentation. ## Debugging Tips ### Check if records are being indexed Query for recent records: ### Verify OAuth is working Query the viewer: Returns if not authenticated. Returns user info if authenticated. ### Inspect the GraphQL schema Use GraphiQL at to explore types, queries, and mutations. The Docs panel shows all fields and types. ### Check Lexicon registration Use the MCP endpoint or admin UI to list registered Lexicons: Look for types matching your Lexicon NSIDs (e.g., ). ## FAQ ### \"Why aren't my records showing up?\" 1. \\1 Should appear immediately. Check mutation response for errors. 2. \\1 Needs backfill. Trigger from admin UI. 3. \\1 Ensure the Lexicon is registered in your instance. ### \"Why is my mutation failing?\" 1. \\1 Token expired or invalid. Re-authenticate. 2. \\1 Trying to modify another user's record. 3. \\1 Check input against Lexicon schema. Required fields missing? ### \"How do I check what Lexicons are loaded?\" Go to Settings in the admin UI, or query via MCP:","headings":[]},{"path":"/reference/aggregations","title":"Aggregations","group":"Reference","content":"Aggregations Every record type has an aggregation query: . For example, aggregate records with . Aggregation queries are public; no authentication required. ## Basic Aggregation Group by a field to count occurrences: ## Filtering & Sorting Use to filter records, to sort by count, and to cap results. Get a user's top 10 artists for 2025: ## Multiple Fields Group by multiple fields. Get top tracks with their artists: ## Date Truncation Group datetime fields by time intervals: , , , or . Get plays per month: ## Reference ### Query Structure - - Aggregation query for any record type - (required) - Array of fields to group by, with optional for datetime fields - (optional) - Filter conditions - (optional) - Sort by ( or ) - (optional) - Maximum groups to return (default: 100) ### Available Columns Beyond record fields, group by: , , , , , ### Validation - Date intervals can only be applied to datetime fields - Maximum 5 groupBy fields per query","headings":[]},{"path":"/reference/subscriptions","title":"Subscriptions","group":"Reference","content":"Subscriptions Subscriptions deliver real-time updates when records are created, updated, or deleted. The server pushes events over WebSocket instead of requiring polling. Connect to using the \\1 protocol. ## Basic Subscription ## Field Selection Request only the fields you need: Response: ## Named Subscription ## Subscription Types Each collection has three subscription fields: - - Fires when a new record is created - - Fires when a record is updated - - Fires when a record is deleted ### Examples ## With Joins Subscriptions support joins like queries: Response: ## WebSocket Protocol ### 1. Connect ### 2. Initialize ### 3. Subscribe ### 4. Receive Events ### 5. Unsubscribe","headings":[]},{"path":"/reference/blobs","title":"Blobs","group":"Reference","content":"Working with Blobs Blobs store binary data like images, videos, and files. Upload separately and reference by CID (Content Identifier). ## Upload Blob Upload binary data encoded as base64: Response: ## Blob Reference A blob reference contains: - : CID of the blob content - : MIME type (e.g., , ) - : Size in bytes ## Using Blobs in Records ### Profile Avatar ### Profile Banner ## Blob URLs Blobs generate CDN URLs automatically. Use the field with optional presets: ### Default URL Returns: ### Avatar Preset Returns: ### Banner Preset Returns: ## Available Presets - - Optimized for profile avatars (square, small) - - Optimized for profile banners (wide, medium) - - Thumbnails in feed view - - Full size images in feed (default) ## Complete Example: Update Profile with Images ### Step 1: Upload Avatar Variables: Response: ### Step 2: Upload Banner Variables: Response: ### Step 3: Update Profile Response: ## JavaScript Example","headings":[]},{"path":"/reference/variables","title":"Variables","group":"Reference","content":"Variables GraphQL variables parameterize queries and mutations for reusability and security. ## Basic Variables ### Query with Variables Variables: ### Mutation with Variables Variables: ## Multiple Variables Variables: ## Optional Variables Use default values for optional variables: Variables: Or omit variables to use defaults: ## Blob Upload with Variables Variables: ## Update Profile with Variables Variables: ## Using in HTTP Requests Send variables in the HTTP request body:","headings":[]},{"path":"/reference/mcp","title":"MCP","group":"Reference","content":"MCP Server Quickslice provides an MCP (Model Context Protocol) server that lets AI assistants query ATProto data directly. ## Endpoint Every Quickslice instance exposes MCP at . For example: ## Setup ### Claude Code ### Other MCP Clients Point any MCP-compatible client at the endpoint using HTTP transport. ## Available Tools | Tool | Description | |------|-------------| | | List all registered lexicons | | | Get full lexicon definition by NSID | | | List available GraphQL queries | | | Get OAuth flows, scopes, and endpoints | | | Get server version and features | | | Get full GraphQL schema | | | Execute a GraphQL query | ## Example Prompts Once connected, you can ask things like: - \"What lexicons are registered?\" - \"Show me the schema for xyz.statusphere.status\" - \"Query the latest 10 statusphere statuses\" - \"What GraphQL queries are available?\" - \"What OAuth scopes does this server support?\"","headings":[]}]