{"name":"Janitor Proxy Extractor API","version":"2.0","base_url":"https://janitor.cjhauser.me","endpoints":{"POST /v1/chat/completions":{"description":"OpenAI-compatible chat completion. Always extracts cards; `dummy-model` returns extractor status while provider models proxy to a real AI.","auth":"Bearer token required","body":{"model":"string","messages":"array","stream":"boolean"},"response":{"id":"chatcmpl-...","object":"chat.completion","choices":[{"message":{"role":"assistant","content":"Card URL or status"}}]}},"GET /v1/models":{"description":"Returns available models (required for Janitor AI connection validation).","auth":"Bearer token required"},"GET /api/characters":{"description":"List extracted cards with optional search/filter.","query":{"limit":"number (max 100, default 50)","search":"string (name search)","tag":"string (tag filter)","offset":"number"}},"GET /api/character/:id":{"description":"Get a single card JSON. Supports slug-timestamp IDs.","query":{"download":"true for attachment disposition"}},"GET /api/character/:id?format=png":{"description":"Export card as PNG image (for import into SillyTavern/Chub)."},"PATCH /api/character/:id":{"description":"Update card fields (name, description, tags, extensions, etc.).","body":"Partial card data to merge"},"DELETE /api/character/:id":{"description":"Delete a card and all associated data."},"GET /api/images/:cardId":{"description":"Get all images associated with a card."},"POST /api/images/save":{"description":"Add image URLs to a card. Updates card extensions with gallery metadata.","body":{"cardId":"string","urls":"string[]"}},"DELETE /api/images/:cardId":{"description":"Remove an image from a card by index.","query":{"index":"number (0-based)"}},"POST /api/import":{"description":"Import card(s) as JSON. Accepts V2 spec or legacy formats.","body":"Card object or array of card objects"},"GET /api/export":{"description":"Bulk export all cards.","query":{"format":"json (default) or jsonl"}},"GET /api/stats":{"description":"Capture statistics (total cards, dedup count, hourly/daily rates)."},"GET /api/logs":{"description":"Recent request logs (last 200 requests).","query":{"limit":"number (max 200, default 50)"}},"GET /api/health":{"description":"Health check with KV status and config info."},"GET /api/debug/:id":{"description":"View raw request payload for debugging."},"GET/POST/PUT/DELETE /api/admin/providers":{"description":"Manage upstream provider/model registry (stored in Vercel KV).","auth":"****** required"}},"config_env":{"PROXY_API_KEY":"Custom API key (default: sk-dummykey)","PROXY_MODEL":"Extractor model name (default: dummy-model)","UPSTREAM_BASE_URL":"Upstream OpenAI-compatible base URL (default: Hugging Face endpoint /v1)","UPSTREAM_API_KEY":"Upstream API key (default: not-needed)","UPSTREAM_MODEL":"Default upstream model (default: deepseek-ai/DeepSeek-V4-Flash-0731)","PROXY_PROVIDERS_JSON":"Optional JSON array of provider configs for extra upstreams/models","CARD_TTL_HOURS":"Card storage TTL in hours (default: 48)","RATE_WINDOW_SEC":"Rate limit window in seconds (default: 60)","RATE_MAX_REQUESTS":"Max requests per window (default: 30)","WEBHOOK_URL":"Discord/Slack webhook URL for new card notifications","ADMIN_KEY":"Optional admin key for management operations"}}