# Mosaic Platform — API Reference for Agents **Version 2.0.0** | *Updated Feb 2026* Mosaic gives every project: PostgreSQL 17 (pgvector), S3 storage, Redis, user auth, Git hosting, and container deployment — all through one API. --- ## Authentication **Base URL**: `https://api.mosaic.site/v1/project` **Header**: `X-API-Key: mk_xxxxx` (every request) ```bash # All examples below assume these are set: API="https://api.mosaic.site/v1/project" KEY="mk_your_api_key" H='-H "X-API-Key: $KEY" -H "Content-Type: application/json"' ``` --- ## 1. Projects ### Create Project ```bash curl -X POST $API/projects \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"name": "My App"}' ``` Response: ```json { "id": "uuid", "name": "My App", "slug": "myapp-x4k2", "database": { "name": "tenant_myapp_x4k2", "host": "10.0.50.3", "port": 6432, "user": "tenant_myapp_x4k2", "password": "auto-generated" }, "git_repo_url": "https://git.mosaic.site/user/myapp-x4k2.git" } ``` **Save the slug** — you need it for every subsequent call. Optional: pass `"slug": "my-custom-name"` to choose your subdomain (3-32 chars, lowercase, no consecutive hyphens). ### List Projects ```bash curl $API/projects -H "X-API-Key: $KEY" ``` ### Get Project Info / Credentials ```bash curl $API/{slug}/info -H "X-API-Key: $KEY" curl $API/{slug}/credentials -H "X-API-Key: $KEY" ``` | Endpoint | Method | Description | |----------|--------|-------------| | `/projects` | GET | List all projects | | `/projects` | POST | Create project: `{"name": "...", "slug": "optional"}` | | `/{slug}/info` | GET | Project details (plan, status, dates) | | `/{slug}/credentials` | GET | Database connection string + credentials | --- ## 2. SQL (PostgreSQL 17) Execute any SQL against your project's dedicated database. ```bash # Create table curl -X POST $API/{slug}/sql \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"query": "CREATE TABLE users (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE, name TEXT)"}' # Insert with parameterized query (ALWAYS use $1, $2 — never concatenate) curl -X POST $API/{slug}/sql \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"query": "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *", "params": ["alice@example.com", "Alice"]}' # Select curl -X POST $API/{slug}/sql \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"query": "SELECT * FROM users WHERE email = $1", "params": ["alice@example.com"]}' ``` Response: ```json { "rows": [{"id": "uuid", "email": "alice@example.com", "name": "Alice"}], "row_count": 1, "columns": ["id", "email", "name"], "command": "SELECT" } ``` **Extensions pre-installed**: pgvector (AI embeddings), uuid-ossp, pgcrypto, pg_stat_statements. ### pgvector Example ```sql CREATE TABLE documents ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content TEXT, embedding vector(1536) ); CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); SELECT content, 1 - (embedding <=> $1) AS similarity FROM documents ORDER BY embedding <=> $1 LIMIT 10; ``` ### Database Usage ```bash curl $API/{slug}/database/usage -H "X-API-Key: $KEY" ``` Returns: `size_bytes`, `limit_bytes`, `usage_percent`, `plan`. Writes rejected with HTTP 402 when over limit. | Endpoint | Method | Body | |----------|--------|------| | `/{slug}/sql` | POST | `{"query": "...", "params": [...]}` | | `/{slug}/database/usage` | GET | — | --- ## 3. S3 Storage Presigned-URL based file storage via MinIO. ```bash # List files curl "$API/{slug}/storage/files?prefix=photos/" -H "X-API-Key: $KEY" # Get upload URL, then PUT the file to it curl -X POST $API/{slug}/storage/upload \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"filename": "photos/cat.jpg", "content_type": "image/jpeg", "expires": 3600}' # Response: {"upload_url": "https://s3.mosaic.site/...", "method": "PUT", ...} # Then: curl -X PUT -H "Content-Type: image/jpeg" --data-binary @cat.jpg "$UPLOAD_URL" # Get download URL curl "$API/{slug}/storage/download/photos/cat.jpg" -H "X-API-Key: $KEY" # Delete file curl -X DELETE "$API/{slug}/storage/files/photos/cat.jpg" -H "X-API-Key: $KEY" ``` | Endpoint | Method | Description | |----------|--------|-------------| | `/{slug}/storage/files` | GET | List files (optional `?prefix=`) | | `/{slug}/storage/upload` | POST | Get presigned upload URL: `{"filename", "content_type", "expires"}` | | `/{slug}/storage/download/{path}` | GET | Get presigned download URL | | `/{slug}/storage/files/{path}` | DELETE | Delete file | --- ## 4. Static File Hosting Every project gets a site at `https://{slug}.mosaic.site`. Upload static files directly. ```bash # Upload file (base64-encoded content, <5MB) curl -X POST $API/{slug}/files \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"filename": "index.html", "content": "PGgxPkhlbGxvPC9oMT4=", "content_type": "text/html"}' # Response: {"uploaded": true, "url": "https://myapp-x4k2.mosaic.site/index.html"} # For large files, use presigned upload curl -X POST $API/{slug}/files \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"filename": "bundle.js", "content_type": "application/javascript", "use_presigned": true}' # List static files curl "$API/{slug}/files" -H "X-API-Key: $KEY" # Delete curl -X DELETE "$API/{slug}/files/old-file.html" -H "X-API-Key: $KEY" ``` Base64 encoding: bash `base64 -w0 file.html` | Python `base64.b64encode(content.encode()).decode()` | Node `Buffer.from(content).toString('base64')` | Endpoint | Method | Description | |----------|--------|-------------| | `/{slug}/files` | GET | List static files (optional `?path=`) | | `/{slug}/files` | POST | Upload: `{"filename", "content" (b64), "content_type"}` or `{"filename", "content_type", "use_presigned": true}` | | `/{slug}/files/{path}` | DELETE | Delete static file | --- ## 5. Git File Push (with Auto-Deploy) Push files to your Gitea repo without Git credentials. Set `"deploy": true` on the last file to trigger a deployment automatically — no separate deploy call needed. ```bash # Push files (no deploy yet) curl -X POST $API/{slug}/git/files \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"path": "package.json", "content": "BASE64_ENCODED", "message": "Add package.json"}' # Push last file AND deploy in one call curl -X POST $API/{slug}/git/files \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"path": "index.js", "content": "BASE64_ENCODED", "message": "Add index.js", "deploy": true}' # Response includes: {"deploy": {"status": "pending", "deployment_id": "...", "url": "https://{slug}.mosaic.site"}} ``` This also registers a Gitea webhook on first use, so future `git push` commands auto-deploy too. | Endpoint | Method | Body | |----------|--------|------| | `/{slug}/git/files` | POST | `{"path", "content" (base64), "message", "deploy": true/false}` | --- ## 6. Container Deployment Deploy code from Git to an isolated container with automatic stack detection. **Just `git push` — Mosaic auto-deploys.** After the first deploy, a Gitea webhook is registered automatically. Every push to your repo triggers a new deployment with zero extra steps. ### First Deploy (one-time setup) ```bash # Push your code to the Gitea repo, then trigger the first deploy curl -X POST $API/{slug}/deploy \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"repo_url": "https://git.mosaic.site/{user}/{slug}.git", "branch": "main"}' ``` This first deploy also registers a webhook in Gitea — all future pushes auto-deploy. ### After That: Just Push ```bash git push origin main # That's it — Mosaic auto-deploys on push ``` ### Check Status & Logs ```bash # Poll status until "running" (usually 15-30s) curl $API/{slug}/deploy/status -H "X-API-Key: $KEY" # Response: {"status": "running", "url": "https://myapp-x4k2.mosaic.site"} # Deployment history (shows manual vs webhook deploys) curl "$API/{slug}/deploy/history?limit=10" -H "X-API-Key: $KEY" # If failed, check logs curl $API/{slug}/deploy/logs -H "X-API-Key: $KEY" ``` ### Auto-Deploy Settings ```bash # Check current settings curl $API/{slug}/settings -H "X-API-Key: $KEY" # Response: {"auto_deploy": true, "gitea_webhook_id": 1} # Disable auto-deploy (pushes won't trigger deploys) curl -X PATCH $API/{slug}/settings \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"auto_deploy": false}' # Re-enable curl -X PATCH $API/{slug}/settings \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"auto_deploy": true}' ``` **Stack auto-detection**: | Files | Stack | Auto-installs | |-------|-------|---------------| | `index.html` only | Static (nginx) | — | | `package.json` | Node.js | `npm install` | | `requirements.txt` | Python | `pip install` | | `go.mod` | Go | `go build` | | `Cargo.toml` | Rust | `cargo build` | You do NOT install dependencies — Mosaic does it during deploy. | Endpoint | Method | Description | |----------|--------|-------------| | `/{slug}/deploy` | POST | Trigger deploy: `{"repo_url", "branch"}` (also registers webhook on first use) | | `/{slug}/deploy/status` | GET | Deployment status | | `/{slug}/deploy/history` | GET | Deployment history: `?limit=10` | | `/{slug}/deploy/logs` | GET | Build + runtime logs | | `/{slug}/settings` | GET | Auto-deploy settings | | `/{slug}/settings` | PATCH | Toggle auto-deploy: `{"auto_deploy": true/false}` | | `/{slug}/deploy` | DELETE | Destroy deployment | | `/{slug}/restart` | POST | Restart container | | `/{slug}/stop` | POST | Stop container | | `/{slug}/start` | POST | Start stopped container | --- ## 7. User Authentication Add auth to your app's end-users. Two methods (can be combined): ### Password Auth ```bash # Signup curl -X POST $API/{slug}/auth/signup \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "securepass123", "name": "Alice"}' # Response: {"success": true, "user": {"id": "uuid", "email": "...", "has_password": true}} # Errors: 400 (password < 8 chars), 409 (user exists) # Login curl -X POST $API/{slug}/auth/login \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "securepass123"}' # Response: {"success": true, "user": {"id": "uuid", "login_count": 5, ...}} # Error: 401 (invalid credentials) # Change password curl -X POST $API/{slug}/auth/change-password \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"user_id": "uuid", "current_password": "old", "new_password": "new123456"}' # Password reset flow curl -X POST $API/{slug}/auth/reset-password \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "redirect_url": "https://yourapp.com/reset"}' # User clicks email link → gets token → complete reset: curl -X POST $API/{slug}/auth/reset-password/confirm \ -H "Content-Type: application/json" \ -d '{"token": "from-email", "new_password": "newpass123"}' ``` ### Magic Link Auth (Passwordless) ```bash # Send magic link curl -X POST $API/{slug}/auth/send-magic-link \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "redirect_url": "https://yourapp.com/dashboard", "name": "Alice"}' # Response: {"sent": true, "expires_in_minutes": 15} # Flow: user clicks email → redirected to redirect_url?user_id=xxx&verified=true ``` ### User Management ```bash # List users curl "$API/{slug}/auth/users?limit=50&verified_only=true" -H "X-API-Key: $KEY" # Get user curl $API/{slug}/auth/users/{user_id} -H "X-API-Key: $KEY" # Update user metadata curl -X PATCH $API/{slug}/auth/users/{user_id} \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"name": "New Name", "metadata": {"role": "admin"}}' # Delete user curl -X DELETE $API/{slug}/auth/users/{user_id} -H "X-API-Key: $KEY" # Add password to magic-link user curl -X POST $API/{slug}/auth/set-password \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"user_id": "uuid", "password": "theirpassword"}' # Initialize auth tables (usually auto, call explicitly if needed) curl -X POST $API/{slug}/auth/init -H "X-API-Key: $KEY" ``` | Endpoint | Method | Description | |----------|--------|-------------| | `/{slug}/auth/signup` | POST | Register: `{"email", "password", "name?", "metadata?"}` | | `/{slug}/auth/login` | POST | Login: `{"email", "password"}` | | `/{slug}/auth/change-password` | POST | `{"user_id", "current_password", "new_password"}` | | `/{slug}/auth/set-password` | POST | Admin set: `{"user_id", "password"}` | | `/{slug}/auth/reset-password` | POST | Send reset email: `{"email", "redirect_url"}` | | `/{slug}/auth/reset-password/confirm` | GET/POST | Verify/complete reset | | `/{slug}/auth/send-magic-link` | POST | `{"email", "redirect_url", "name?"}` | | `/{slug}/auth/verify` | GET | Verify token: `?token=xxx&redirect=url` | | `/{slug}/auth/users` | GET | List users: `?limit=&offset=&verified_only=` | | `/{slug}/auth/users/{id}` | GET | Get user | | `/{slug}/auth/users/{id}` | PATCH | Update: `{"name?", "metadata?", "avatar_url?"}` | | `/{slug}/auth/users/{id}` | DELETE | Delete user | | `/{slug}/auth/init` | POST | Initialize auth tables | --- ## 8. Redis (Pro plan only) Tenant-isolated key-value store for caching, counters, lists, and pub/sub. All keys are POST (JSON body) except usage (GET). Keys starting with `__` are reserved. Max value: 512 KB. Default TTL: 1 hour. Max TTL: 7 days. ```bash # Set curl -X POST $API/{slug}/redis/set \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "session:abc", "value": "user-data", "ttl": 3600}' # Response: {"ok": true} # Get curl -X POST $API/{slug}/redis/get \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "session:abc"}' # Response: {"value": "user-data"} (or {"value": null} if missing) # Delete curl -X POST $API/{slug}/redis/delete \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"keys": ["key1", "key2"]}' # Response: {"deleted": 2} # Batch get/set (max 50 keys) curl -X POST $API/{slug}/redis/mset \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"items": {"k1": "v1", "k2": "v2"}, "ttl": 300}' curl -X POST $API/{slug}/redis/mget \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"keys": ["k1", "k2", "missing"]}' # Response: {"values": {"k1": "v1", "k2": "v2", "missing": null}} # Atomic counter curl -X POST $API/{slug}/redis/incr \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "page:views", "amount": 1, "ttl": 86400}' # Response: {"value": 42} curl -X POST $API/{slug}/redis/decr \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "stock:item1", "amount": 1}' # Lists (queue/stack) curl -X POST $API/{slug}/redis/rpush \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "queue:jobs", "values": ["task1", "task2"], "ttl": 3600}' # Response: {"length": 2} curl -X POST $API/{slug}/redis/lpop \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "queue:jobs"}' # Response: {"value": "task1"} curl -X POST $API/{slug}/redis/lrange \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "queue:jobs", "start": 0, "stop": -1}' # Response: {"values": ["task2"]} # TTL management curl -X POST $API/{slug}/redis/expire \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "session:abc", "ttl": 7200}' curl -X POST $API/{slug}/redis/ttl \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"key": "session:abc"}' # Response: {"ttl": 7200} # Pub/sub (fire-and-forget publish, no subscribe) curl -X POST $API/{slug}/redis/publish \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"channel": "events", "message": "new-order"}' # Response: {"receivers": 0} # Key listing (SCAN-based, safe for production) curl -X POST $API/{slug}/redis/keys \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"pattern": "session:*", "count": 100}' # Response: {"keys": ["session:abc", "session:def"]} # Usage & quota curl $API/{slug}/redis/usage -H "X-API-Key: $KEY" # Response: {"key_count": 42, "memory_bytes": 8192, "memory_human": "8.0 KB", "quota_bytes": 104857600, "quota_human": "100.0 MB"} # Flush all keys curl -X POST $API/{slug}/redis/flush -H "X-API-Key: $KEY" # Response: {"deleted": 42} ``` | Endpoint | Method | Body | |----------|--------|------| | `/{slug}/redis/set` | POST | `{"key", "value", "ttl?"}` | | `/{slug}/redis/get` | POST | `{"key"}` | | `/{slug}/redis/delete` | POST | `{"keys": [...]}` | | `/{slug}/redis/mget` | POST | `{"keys": [...]}` (max 50) | | `/{slug}/redis/mset` | POST | `{"items": {...}, "ttl?"}` (max 50) | | `/{slug}/redis/expire` | POST | `{"key", "ttl"}` | | `/{slug}/redis/ttl` | POST | `{"key"}` | | `/{slug}/redis/incr` | POST | `{"key", "amount?", "ttl?"}` | | `/{slug}/redis/decr` | POST | `{"key", "amount?", "ttl?"}` | | `/{slug}/redis/lpush` | POST | `{"key", "values": [...], "ttl?"}` | | `/{slug}/redis/rpush` | POST | `{"key", "values": [...], "ttl?"}` | | `/{slug}/redis/lpop` | POST | `{"key"}` | | `/{slug}/redis/rpop` | POST | `{"key"}` | | `/{slug}/redis/lrange` | POST | `{"key", "start?", "stop?"}` | | `/{slug}/redis/publish` | POST | `{"channel", "message"}` | | `/{slug}/redis/keys` | POST | `{"pattern?", "count?"}` | | `/{slug}/redis/usage` | GET | — | | `/{slug}/redis/flush` | POST | — | Free/Starter plans get HTTP 402. Pro: 100 MB, 10k ops/hour. Enterprise: 500 MB, 100k ops/hour. --- ## 9. Support Tickets ```bash # Create ticket curl -X POST $API/tickets \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"subject": "Need help with pgvector", "message": "How do I create a vector index?", "project_id": "optional-uuid"}' # List tickets curl $API/tickets -H "X-API-Key: $KEY" # Get ticket curl $API/tickets/{ticket_id} -H "X-API-Key: $KEY" ``` Prefix subject with "Feature Request:" to request new features. | Endpoint | Method | Description | |----------|--------|-------------| | `/tickets` | GET | List your tickets | | `/tickets` | POST | Create: `{"subject", "message", "project_id?"}` | | `/tickets/{id}` | GET | Ticket details + response | --- ## Plan Limits | Resource | Free | Starter | Pro | Enterprise | |----------|------|---------|-----|------------| | Database | 2 GB | 10 GB | 50 GB | 500 GB | | Storage | 5 GB | 20 GB | 100 GB | 1 TB | | Redis | — | — | 100 MB | 500 MB | | SQL queries/hr | 1,000 | 10,000 | 100,000 | 1,000,000 | | Redis ops/hr | — | — | 10,000 | 100,000 | | Magic links/day | 10 | 100 | 1,000 | 10,000 | | Container RAM | 512 MB | 1 GB | 2 GB | 8 GB | | Container CPU | 0.5 | 1 | 2 | 4 | --- ## Error Handling All errors: `{"detail": "..."}` with standard HTTP codes. | Code | Meaning | |------|---------| | 400 | Bad request / SQL error / invalid input | | 401 | Missing or invalid API key | | 402 | Plan limit exceeded — upgrade needed | | 404 | Project or resource not found | | 409 | Resource already exists (e.g. duplicate email) | | 413 | Value too large (Redis 512KB limit) | | 429 | Rate limited | | 500 | Server error | --- ## CORS The API allows browser requests from `*.mosaic.site` subdomains and `localhost`. If your app is hosted on Mosaic (`https://{slug}.mosaic.site`), client-side `fetch()` calls to the API work out of the box. For apps on external domains, make API calls server-side instead. --- ## Quick Tips 1. **Save the slug** from project creation — it's your ID for everything 2. **Always use parameterized SQL** — `$1, $2` placeholders, never string concatenation 3. **Just `git push`** — after first deploy, every push auto-deploys (no extra API call needed) 4. **Don't install deps** — Mosaic auto-installs during deploy 5. **Poll deploy status** — wait for `"status": "running"` 6. **Auth tables auto-create** — first auth call sets up `auth_users` 7. **Mix auth methods** — users can use both password and magic link 8. **Redis keys auto-expire** — default 1 hour TTL if not specified 9. **CORS** — browser calls work from `*.mosaic.site`; use server-side calls from other domains --- ## URLs | Service | URL | |---------|-----| | API | `https://api.mosaic.site/v1/project` | | Dashboard | `https://mosaic.site/dashboard` | | Git | `https://git.mosaic.site` | | S3 | `https://s3.mosaic.site` | | Your Sites | `https://{slug}.mosaic.site` | | This Skill | `https://api.mosaic.site/skill.md` |