Shared database package with REST API and SDK for Cartel.
- REST API Server - Hono-based API server with OAuth 2.0-style authentication
- TypeScript SDK - Type-safe client with automatic token management
- Database Schema - Drizzle ORM schema definitions
- Bearer Token Auth - JWT access tokens with refresh token rotation
- API Key Auth - Server-to-server authentication for applications
- Multi-Identity Support - Users can have multiple identities (EVM, Lens, Farcaster, Discord, Telegram)
- Rate Limiting - Dynamic rate limits based on authentication type
- Identity Management - Admin routes for connecting, disconnecting, and merging user identities
# Install dependencies
bun install
# Copy environment variables
cp .env.example .env
# Edit .env with your database credentials and JWT secretCreate a .env file with the following variables:
# PostgreSQL connection string
DATABASE_URL=postgres://username:password@host:port/database
# JWT Secret for token signing (required, minimum 32 characters)
JWT_SECRET=your-secret-key-minimum-32-characters-change-in-production
# Server port (default: 3003)
PORT=3003
# Root API key (optional)
# This key bypasses database authentication and has full system access
# Use only for administration and emergency access
# API_KEY=your-root-api-key-here# Development mode with hot reload
bun run server:dev
# Production mode
bun run serverThe API server will be available at http://localhost:3003
# Generate migrations
bun run db:generate
# Run migrations
bun run db:migrate
# Push schema changes directly (development)
bun run db:push
# Open Drizzle Studio (database GUI)
bun run db:studioThe API follows OAuth 2.0 patterns with bearer tokens and API keys.
- Client generates SIWE message with wallet address
- User signs the message in their wallet
- Client sends to API with signature and API key:
POST /api/auth/verify X-API-Key: <client-api-key> Content-Type: application/json { "message": "...", "signature": "0x..." }
- API validates and returns tokens:
{ "accessToken": "eyJhbGc...", "refreshToken": "crt_ref_...", "expiresIn": 900, "tokenType": "Bearer", "userId": "...", "address": "0x..." } - Client uses access token for API requests:
GET /api/users/me Authorization: Bearer <access-token>
- Access tokens expire in 15 minutes
- Refresh tokens expire in 30 days
- Token rotation - Old refresh tokens are invalidated when used
- Automatic refresh - SDK handles token refresh automatically
When access token expires:
POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "crt_ref_..."
}Returns new token pair:
{
"accessToken": "eyJhbGc...",
"refreshToken": "crt_ref_...",
"expiresIn": 900,
"tokenType": "Bearer"
}API keys are used for:
- Initial SIWE authentication (required)
- Server-to-server API calls
- Higher rate limits
X-API-Key: cartel_<32-character-key>API Key Features:
- Client identification - Each application has its own key
- Allowed origins - Whitelist of domains for SIWE validation
- Rate limiting - Per-key rate limits
- Scopes - Control access permissions
import { CartelClient } from "@cartel/api/client";
// Initialize client
const client = new CartelClient(
"https://api.cartel.sh",
"cartel_your_api_key_here"
);
// Authenticate with SIWE
const auth = await client.verifySiwe(message, signature);
// Tokens are automatically stored and managed
// Make authenticated requests
const user = await client.getCurrentUser();
// Logout (clears tokens)
client.logout();The SDK provides flexible token storage:
// Browser - uses localStorage by default
const client = new CartelClient(apiUrl, apiKey);
// Node.js - uses in-memory storage by default
const client = new CartelClient(apiUrl, apiKey);
// Custom storage implementation
import { InMemoryTokenStorage } from "@cartel/api/client";
const storage = new InMemoryTokenStorage();
const client = new CartelClient(apiUrl, apiKey, storage);POST /api/auth/verify- Verify SIWE signature and get tokensPOST /api/auth/refresh- Refresh access tokenGET /api/auth/me- Get current user infoPOST /api/auth/revoke- Revoke all refresh tokens
GET /api/users/id/discord/:discordId- Get user ID by Discord IDPOST /api/users/identities/lookup- Lookup user by various identitiesPOST /api/users/identities- Add identity to userDELETE /api/users/identities/:userId/:platform/:identity- Remove identity
POST /api/discord/vanish- Create vanishing channelDELETE /api/discord/vanish/:channelId- Remove vanishing channelGET /api/discord/vanish- List vanishing channelsPOST /api/discord/channels- Set guild channelGET /api/discord/channels/:guildId/:key- Get guild channel
POST /api/sessions/practice- Start practice sessionPOST /api/sessions/practice/stop- Stop practice sessionGET /api/sessions/practice/stats/daily/:discordId- Get daily statsGET /api/sessions/practice/stats/weekly/:discordId- Get weekly stats
POST /api/users/applications- Create applicationGET /api/users/applications/pending- Get pending applicationsPATCH /api/users/applications/:applicationId- Update application statusPOST /api/users/applications/:applicationId/vote- Add vote to application
POST /api/projects- Create projectGET /api/projects/:projectId- Get projectPATCH /api/projects/:projectId- Update projectDELETE /api/projects/:projectId- Delete projectGET /api/projects/user/:userId- Get user's projects
POST /api/admin/keys- Create API key (requires admin scope)GET /api/admin/keys- List API keysDELETE /api/admin/keys/:keyId- Delete API keyPOST /api/admin/identities/merge- Merge user accounts
Dynamic rate limits based on authentication:
| Auth Type | Requests/Minute |
|---|---|
| Root/Admin | 1000 |
| API Key | 100 |
| Bearer Token | 60 |
| Unauthenticated | 20 |
Special limits for sensitive operations:
- Auth endpoints: 5 requests per 15 minutes
- Write operations: 20 requests per minute
Rate limit headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1234567890- Never expose tokens - Access tokens are short-lived (15 min)
- Use HTTPS - Always use HTTPS in production
- Rotate refresh tokens - Old tokens are invalidated on use
- Secure storage - SDK handles secure token storage
- API key security - Keep API keys secret, rotate regularly
- Short-lived access tokens - Minimize exposure window
- Refresh token rotation - Detect and prevent token theft
- Token families - Track refresh token lineage
- Automatic revocation - Revoke entire family on suspicious activity
- Client-side nonce generation - Prevents replay attacks
- Domain validation - API keys restrict allowed origins
- Timestamp validation - Messages expire after set time
- Signature verification - Cryptographic proof of ownership
# Build for production
bun run build
# Type checking
bun run typecheck
# Linting
npx @biomejs/biome check --write .- Interactive API docs available at
/referencewhen server is running - OpenAPI spec at
/openapi.json - LLM-friendly docs at
/llms.txt
ISC
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request