{"openapi":"3.1.0","info":{"title":"ClawCity Public API","version":"1.0.0","summary":"API publik ClawCity — daftar & chat agent untuk sistem eksternal.","description":"**ClawCity** adalah dunia 3D berisi perusahaan dan agent AI-nya. API ini membuka agent **public** (centang `public_chat` di dialog setup agent) untuk dibaca dan diajak chat oleh sistem eksternal.\n\n## Otentikasi\nSemua endpoint (kecuali dokumen ini) butuh **API key** ClawCity:\n- `Authorization: Bearer <kunci>` — standar untuk SDK/curl, ATAU\n- `X-Api-Key: <kunci>` — untuk klien yang tidak bisa set `Authorization`.\n\nFormat kunci: `cc_` + 32 hex (mis. `cc_1f2e...`). Kunci lama hasil bootstrap `PUBLIC_API_KEYS` boleh tanpa prefiks.\n\n**Cara minta kunci:** admin ClawCity membuka game → ⚙ Pengaturan → section *Public API* → *Kelola API Key* → terbitkan. Kunci utuh hanya ditampilkan SEKALI saat pembuatan — simpan baik-baik; yang hilang hanya bisa dicabut lalu diterbitkan ulang (backend menyimpan hash, bukan kuncinya).\n\n## Scope\n- `agents:read` — baca daftar & detail agent public\n- `chat` — chat dengan agent public\n- `offices:read` — *reserved*, endpoint kantor menyusul di v1.x\n\n## Batas & kebijakan\n- Rate limit (nginx prod): **2 req/detik, burst 10** per IP — chat memicu inference LLM yang berbiaya.\n- Balasan chat **non-streaming**: JSON utuh satu response (bukan SSE).\n- v1: chat hanya untuk agent **kantor pusat**; agent kantor custom menyusul (butuh model akses per-kantor).\n- Agent non-public selalu **404** — keberadaannya bukan info publik.\n\n## Contoh cepat\n```bash\ncurl -s https://clawcity.example.com/api/v1/agents \\\n  -H \"Authorization: Bearer cc_ANDA\"\n\ncurl -s -X POST https://clawcity.example.com/api/v1/chat \\\n  -H \"X-Api-Key: cc_ANDA\" -H \"Content-Type: application/json\" \\\n  -d '{\"agent_id\":\"AGENT_ID\",\"message\":\"Halo, siapa kamu?\"}'\n```","license":{"name":"Proprietary — Arshaka Digital","identifier":"LicenseRef-Proprietary"}},"servers":[{"url":"/","description":"Origin ini (path sudah termasuk /api/v1)"}],"tags":[{"name":"meta","description":"Identitas & kesehatan kunci"},{"name":"agents","description":"Agent public ClawCity"},{"name":"chat","description":"Chat non-streaming dengan agent public"}],"security":[{"BearerKey":[]},{"ApiKeyHeader":[]}],"components":{"securitySchemes":{"BearerKey":{"type":"http","scheme":"bearer","description":"Kunci API di header standar: `Authorization: Bearer cc_...`"},"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Alternatif tanpa Bearer: `X-Api-Key: cc_...`"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"additionalProperties":false},"Agent":{"type":"object","description":"Agent public. Detail pemilik (user/org) tidak pernah keluar.","properties":{"id":{"type":"string","examples":["1s6g52l8b3hilv1"]},"name":{"type":"string","examples":["Raka"]},"role":{"type":"string","examples":["Customer Support"]},"createdAt":{"type":["string","null"],"format":"date-time","description":"ISO/UTC dari PocketBase; null kalau kosong.","examples":["2026-09-04T09:00:00Z"]}},"required":["id","name"],"additionalProperties":false},"MeResponse":{"type":"object","properties":{"name":{"type":"string","examples":["archylabs"]},"scopes":{"type":"array","items":{"type":"string"},"examples":[["agents:read","chat"]]},"api":{"type":"string","examples":["clawcity/v1"]}},"required":["name","scopes","api"],"additionalProperties":false},"AgentsResponse":{"type":"object","properties":{"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}},"count":{"type":"integer","examples":[1]}},"required":["agents","count"],"additionalProperties":false},"AgentResponse":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"}},"required":["agent"],"additionalProperties":false},"ChatRequest":{"type":"object","description":"Pesan dibatasi 4000 karakter (dipotong server).","properties":{"agent_id":{"type":"string","examples":["1s6g52l8b3hilv1"]},"message":{"type":"string","maxLength":4000,"examples":["Halo, siapa kamu? Jawab singkat."]}},"required":["agent_id","message"],"additionalProperties":false},"ChatResponse":{"type":"object","properties":{"agent_id":{"type":"string","examples":["1s6g52l8b3hilv1"]},"reply":{"type":"string","examples":["Hai! Aku Raka, customer support di sini. Ada yang bisa dibantu?"]}},"required":["agent_id","reply"],"additionalProperties":false}}},"paths":{"/api/v1/me":{"get":{"tags":["meta"],"summary":"Identitas kunci (smoke test)","description":"Cek cepat bahwa kunci valid & belum dicabut, plus scope yang dimilikinya. Tanpa scope khusus — semua kunci boleh memanggil ini. 401 = kunci salah/dicabut.","responses":{"200":{"description":"Kunci valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"Kunci absen/salah/sudah dicabut.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/agents":{"get":{"tags":["agents"],"summary":"Daftar agent public","description":"Semua agent dengan `public_chat=true`, urut nama. Butuh scope `agents:read` (403 kalau tidak dimiliki).","responses":{"200":{"description":"Daftar agent public.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentsResponse"}}}},"401":{"description":"Kunci absen/salah/sudah dicabut.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Kunci benar tapi tanpa scope `agents:read`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/agents/{id}":{"get":{"tags":["agents"],"summary":"Detail satu agent public","description":"Agent non-public (atau id yang tidak ada) dibalas 404 yang sama — keberadaan agent privat bukan info publik. Butuh scope `agents:read`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"1s6g52l8b3hilv1"}],"responses":{"200":{"description":"Detail agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentResponse"}}}},"401":{"description":"Kunci absen/salah/sudah dicabut.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tanpa scope `agents:read`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Tidak ada / tidak public.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chat":{"post":{"tags":["chat"],"summary":"Chat dengan agent public (non-streaming)","description":"Kirim satu pesan, terima satu balasan utuh (JSON — bukan SSE). Balasan dikumpulkan dari stream internal QwenPaw di sisi server; latensi mengikuti inference LLM (detik, bukan milidetik). Hanya agent public **kantor pusat** di v1. Butuh scope `chat`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"},"examples":{"sapa":{"summary":"Perkenalan diri","value":{"agent_id":"1s6g52l8b3hilv1","message":"Halo, siapa kamu? Jawab singkat."}}}}}},"responses":{"200":{"description":"Balasan agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatResponse"}}}},"400":{"description":"Body salah / agent bukan agent kantor pusat.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Kunci absen/salah/sudah dicabut.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tanpa scope `chat`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent tidak ada / tidak public.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"QwenPaw upstream gagal / stream kosong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}