{"openapi":"3.1.0","info":{"title":"xmark API","version":"0.2.0","summary":"Semantic search and chat over a user's X (Twitter) bookmarks.","description":"xmark exposes a small REST surface that lets developers query their X bookmark library by semantic search and ask natural-language questions with citations. Authentication uses scoped API keys created in /settings/api, or a Supabase user JWT for native clients.","contact":{"name":"xmark","url":"https://www.xmark.dev","email":"josh@jclvsh.art"},"license":{"name":"Proprietary","url":"https://www.xmark.dev/terms"},"x-service-info":{"name":"xmark","category":"Knowledge","docs":{"homepage":"https://www.xmark.dev","apiReference":"https://www.xmark.dev/docs","llms":"https://api.xmark.dev/llms.txt"}},"x-guidance":["Use scoped API keys with the minimum scope needed.","All requests must be HTTPS. The canonical host is api.xmark.dev.","See https://api.xmark.dev/llms-full.txt for full documentation."]},"servers":[{"url":"https://api.xmark.dev","description":"Production"}],"security":[{"apiKeyAuth":[]}],"tags":[{"name":"bookmarks","description":"Read, search, and sync a user's X bookmarks."},{"name":"chat","description":"Ask questions about bookmarks with cited answers (Claude + RAG)."},{"name":"conversations","description":"List, read, and delete past chat conversations."},{"name":"usage","description":"Plan and quota usage for the authenticated user."}],"paths":{"/bookmarks":{"get":{"x-status":"live","tags":["bookmarks"],"summary":"List the authenticated user's bookmarks","operationId":"listBookmarks","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"description":"Paginated bookmarks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookmarkListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/bookmarks/{id}":{"get":{"x-status":"live","tags":["bookmarks"],"summary":"Get a single bookmark","operationId":"getBookmark","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdPathParam"}],"responses":{"200":{"description":"Bookmark","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookmarkResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"x-status":"live","tags":["bookmarks"],"summary":"Remove a bookmark","description":"Deletes the bookmark in xmark. By default this is local-only — the next sync re-creates anything still bookmarked on X. Pass `unbookmark=true` to also remove it on X via the stored OAuth token (400 when no X account is connected).","operationId":"deleteBookmark","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdPathParam"},{"name":"unbookmark","in":"query","schema":{"type":"boolean","default":false},"description":"Also remove the bookmark on X (not just locally)"}],"responses":{"200":{"description":"Removed"},"400":{"description":"X account needs to be reconnected (only with unbookmark=true)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/bookmarks/search":{"post":{"x-status":"live","tags":["bookmarks"],"summary":"Semantic search over the user's bookmarks","description":"Run a semantic search query over the authenticated user's X bookmark library. Returns ranked matches with relevance scores and full tweet metadata.","operationId":"searchBookmarks","security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/bookmarks/sync":{"post":{"x-status":"live","tags":["bookmarks"],"summary":"Trigger a sync from X","description":"Pull the latest bookmarks from the connected X account, embed new ones, and store them. Returns a sync job summary. Free tier allows one sync per day (429 on overage); 400 when no X account is connected.","operationId":"syncBookmarks","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Sync summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/chat":{"post":{"x-status":"live","tags":["chat"],"summary":"Ask a question about the user's bookmarks","description":"Submit a natural-language question. xmark retrieves the most relevant bookmarks via semantic search and has Claude generate an answer with inline citations. Free tier includes 20 chat messages per month (402 on overage).","operationId":"chat","security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}}},"responses":{"200":{"description":"Chat response with citations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/QuotaExceeded"}}}},"/conversations":{"get":{"x-status":"live","tags":["conversations"],"summary":"List chat conversations","operationId":"listConversations","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"description":"Paginated conversations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{id}":{"delete":{"x-status":"live","tags":["conversations"],"summary":"Delete a conversation","description":"Permanently deletes the conversation and its messages.","operationId":"deleteConversation","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdPathParam"}],"responses":{"200":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{id}/messages":{"get":{"x-status":"live","tags":["conversations"],"summary":"List a conversation's messages","description":"The conversation's message history, oldest first. Assistant messages carry the IDs of the bookmarks retrieved for that answer.","operationId":"listConversationMessages","security":[{"apiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdPathParam"}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageListResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/usage":{"get":{"x-status":"live","tags":["usage"],"summary":"Plan + quota usage","description":"The authenticated user's plan, chat-message usage this month, syncs today, bookmark count, and whether an X account is connected.","operationId":"getUsage","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Usage summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","description":"Scoped API key issued via xmark settings (`Authorization: Bearer nt_live_...`), or a Supabase user access token for native clients signed in as the account owner (`Authorization: Bearer <jwt>`)."}},"parameters":{"IdPathParam":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"PageParam":{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},"PageSizeParam":{"name":"pageSize","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}},"schemas":{"Bookmark":{"type":"object","required":["id","tweet_id","text","author"],"properties":{"id":{"type":"string","format":"uuid"},"tweet_id":{"type":"string","description":"Original X tweet ID"},"text":{"type":"string","description":"Full tweet text including long-form note tweets"},"author":{"type":"object","required":["username","name"],"properties":{"id":{"type":"string"},"username":{"type":"string"},"name":{"type":"string"},"profile_image_url":{"type":["string","null"],"description":"Author avatar URL"}}},"bookmarked_at":{"type":["string","null"],"description":"Tweet creation time (X does not expose bookmark timestamps); falls back to first-sync time, may be null"},"url":{"type":"string","format":"uri","description":"Canonical x.com link to the tweet"},"public_metrics":{"type":["object","null"],"additionalProperties":{"type":"integer"},"description":"X engagement counts (like_count, retweet_count, reply_count, ...)"},"media":{"type":["array","null"],"items":{"type":"object"}}}},"Pagination":{"type":"object","required":["page","pageSize","total","totalPages"],"properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}},"SearchResult":{"type":"object","required":["bookmark","score"],"properties":{"bookmark":{"$ref":"#/components/schemas/Bookmark"},"score":{"type":"number","minimum":0,"maximum":1,"description":"Relevance 0–1"}}},"SyncResult":{"type":"object","required":["count","newCount","removedCount","durationMs"],"properties":{"count":{"type":"integer","description":"Total bookmarks after sync"},"newCount":{"type":"integer","description":"Newly added bookmarks"},"removedCount":{"type":"integer","description":"Bookmarks deleted locally because they were unbookmarked on X"},"durationMs":{"type":"integer"}}},"ChatCitation":{"type":"object","required":["bookmark_id","tweet_url","author","snippet"],"properties":{"bookmark_id":{"type":"string","description":"Bookmark UUID (may be empty when unresolved)"},"tweet_url":{"type":"string","format":"uri"},"author":{"type":"string"},"snippet":{"type":"string"}}},"ChatResult":{"type":"object","required":["answer","citations","conversation_id"],"properties":{"answer":{"type":"string"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/ChatCitation"}},"conversation_id":{"type":"string","format":"uuid"}}},"Conversation":{"type":"object","required":["id","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"Message":{"type":"object","required":["id","role","content","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"role":{"type":"string","description":"`user` or `assistant`"},"content":{"type":"string"},"retrieved_bookmark_ids":{"type":["array","null"],"items":{"type":"string","format":"uuid"},"description":"Bookmark IDs retrieved for an assistant answer"},"created_at":{"type":"string"}}},"QuotaUsage":{"type":"object","required":["used"],"properties":{"used":{"type":"integer"},"limit":{"type":["integer","null"],"description":"null = unlimited"}}},"UsageData":{"type":"object","required":["plan","x_connected","bookmarks","chat","syncs_today"],"properties":{"plan":{"type":"string","description":"`free`, `paid`, or `team`"},"x_connected":{"type":"boolean","description":"Whether an X account is connected for bookmark sync"},"bookmarks":{"type":"object","required":["total"],"properties":{"total":{"type":"integer"}}},"chat":{"$ref":"#/components/schemas/QuotaUsage"},"syncs_today":{"$ref":"#/components/schemas/QuotaUsage"}}},"BookmarkResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Bookmark"}}},"BookmarkListResponse":{"type":"object","required":["data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Bookmark"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"SearchRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"Natural-language search query"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10}}},"SearchResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}}}},"SyncResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/SyncResult"}}},"ChatRequest":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"conversationId":{"type":"string","format":"uuid","description":"Optional — continue an existing conversation"},"maxCitations":{"type":"integer","minimum":1,"maximum":20,"default":5}}},"ChatResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ChatResult"}}},"ConversationListResponse":{"type":"object","required":["data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"MessageListResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}},"UsageResponse":{"type":"object","required":["data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/UsageData"}}},"ErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"field":{"type":"string"},"details":{"type":"object"},"requestId":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key / session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimited":{"description":"Rate limit exceeded — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"QuotaExceeded":{"description":"Free-tier quota reached — upgrade for unlimited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}