{"openapi":"3.1.0","info":{"title":"grunden.ai API","version":"1.1.0","description":"OpenAI-kompatibelt API för open source-språkmodeller körda i EU.","contact":{"email":"hej@grunden.ai","url":"https://grunden.ai"},"license":{"name":"Grunden AI AB användarvillkor","url":"https://grunden.ai/terms"},"termsOfService":"https://grunden.ai/terms"},"externalDocs":{"description":"Utförlig dokumentation","url":"https://grunden.ai/docs"},"servers":[{"url":"https://api.grunden.ai","description":"Produktion"},{"url":"https://staging.grunden.ai","description":"Staging (icke-stabil, inga SLA)"}],"tags":[{"name":"Chat","description":"OpenAI-kompatibla chat completions. SSE-streaming stöds."},{"name":"Models","description":"Modellkatalog. Anonymt läsbar — ingen API-nyckel krävs för discovery."},{"name":"Batch","description":"Asynkrona jobb som levereras via webhook när resultatet är klart."},{"name":"Embeddings","description":"Textembedding-vektorer. OpenAI-kompatibelt svar."},{"name":"Rerank","description":"Cross-encoder reranking (BGE-reranker-v2-m3). Dedikerad endpoint — INTE modell-routad via /v1/chat/completions."},{"name":"PII","description":"PII-detektering och -maskering (GLiNER). Dedikerad endpoint — INTE modell-routad via /v1/chat/completions; skicka text direkt, inget model-fält."},{"name":"Knowledge bases","description":"Bygg och fyll kunskapsbaser (RAG): skapa, ladda upp dokument/text, länka API-nycklar. Fråga dem via knowledge_base_id på POST /v1/chat/completions."},{"name":"Messages (Anthropic-kompatibel)","description":"Anthropic Messages-API:t i wire-format. VIKTIGT: grunden.ai kör INTE Claude/Anthropic-modeller — denna endpoint servar vår egen modell glm-5.2 i Anthropics request/response-form, så att Anthropic-SDK-verktyg (Claude Code m.fl.) kan peka mot en EU-hostad modell. Inga US-leverantörer inblandade. Auth: x-api-key (Anthropic-SDK-stil) eller Authorization: Bearer."}],"x-rate-limits":{"description":"Default per API-nyckel. Höjningar möjliga via support för betalande kunder.","free":{"rpm":60,"tpm":100000},"paid":{"rpm":600,"tpm":1000000},"notes":"rpm = requests per minute, tpm = input+output tokens per minute. Värden skickas tillbaka i X-RateLimit-headers vid 429."},"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"API-nyckel som börjar på 'sk-grunden-'. Skapa via Inställningar → API-nycklar på ditt team."}},"schemas":{"ChatMessage":{"type":"object","required":["role"],"description":"Ett meddelande i konversationen. Obligatoriska fält varierar per role: user/system kräver content; assistant tillåter null content om tool_calls finns; tool kräver tool_call_id + content (sträng).","properties":{"role":{"type":"string","enum":["system","user","assistant","tool"]},"content":{"description":"Sträng (text), null (assistant-turer med bara tool_calls), eller array av content-parts för multimodala modeller.","oneOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/components/schemas/ContentPart"}},{"type":"null"}]},"name":{"type":"string","description":"Frivilligt namn för att skilja mellan deltagare."},"tool_calls":{"type":"array","description":"Verktygsanrop som assistenten vill att klienten utför. Endast på assistant-meddelanden.","items":{"$ref":"#/components/schemas/ToolCall"}},"tool_call_id":{"type":"string","description":"Refererar till vilket tool_call som detta tool-meddelande svarar på. Obligatoriskt för role=tool."}}},"ToolCall":{"type":"object","required":["id","type","function"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["function"]},"function":{"type":"object","required":["name","arguments"],"properties":{"name":{"type":"string"},"arguments":{"type":"string","description":"JSON-serialiserade argument — inte ett objekt. Klienten JSON.parse:ar själv."}}}}},"ToolDefinition":{"type":"object","required":["type","function"],"properties":{"type":{"type":"string","enum":["function"]},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Funktionsnamn. Max 64 tecken, [a-zA-Z0-9_-] rekommenderas."},"description":{"type":"string","description":"Förklaring till när modellen ska använda verktyget."},"parameters":{"type":"object","description":"JSON Schema för funktionsparametrar. Lämna tom för parameterlösa funktioner."}}}}},"ContentPart":{"oneOf":[{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}}},{"type":"object","required":["type","image_url"],"properties":{"type":{"type":"string","enum":["image_url"]},"image_url":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Antingen en publik https-URL eller en data:image/...;base64,-URI."},"detail":{"type":"string","enum":["auto","low","high"],"default":"auto"}}}}}]},"BatchWebhookPayload":{"type":"object","description":"Payload som POST:as till tier=async webhook_url när jobbet är klart. Signeras med HMAC-SHA256 i X-Grunden-Signature-header (v1) + X-Grunden-Signature-V2 (timestamp + replay-skydd, rekommenderad — se /webhooks endpoint för format). HMAC-nyckeln visas en gång vid nyckelskapande.","required":["id","status","created_at"],"properties":{"id":{"type":"string","description":"Batch-job-id."},"object":{"type":"string","enum":["batch.job.completed"]},"status":{"type":"string","enum":["completed","failed","expired"]},"model":{"type":"string"},"created_at":{"type":"integer"},"completed_at":{"type":"integer","nullable":true},"response":{"oneOf":[{"$ref":"#/components/schemas/ChatCompletionResponse"},{"type":"null"}]},"error":{"type":"string","nullable":true},"usage":{"$ref":"#/components/schemas/Usage"}}},"ChatCompletionChunk":{"type":"object","required":["id","object","created","model","choices"],"description":"En SSE-chunk i 'data: {...}\\n\\n'-format. Sista event:et före stream-slut är 'data: [DONE]' (ingen JSON).","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["chat.completion.chunk"]},"created":{"type":"integer"},"model":{"type":"string"},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"delta":{"type":"object","properties":{"role":{"type":"string","enum":["assistant"]},"content":{"type":"string"}}},"finish_reason":{"type":["string","null"],"enum":["stop","length","content_filter",null]}}}},"usage":{"description":"Endast i sista chunk:en av streamen.","oneOf":[{"$ref":"#/components/schemas/Usage"},{"type":"null"}]}}},"Usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer","minimum":0},"completion_tokens":{"type":"integer","minimum":0},"total_tokens":{"type":"integer","minimum":0}}},"ChatCompletionRequest":{"type":"object","required":["model","messages"],"properties":{"model":{"type":"string","description":"Canonical model-id (t.ex. 'glm-5.2'). Hämta lista via GET /v1/models."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/ChatMessage"}},"stream":{"type":"boolean","default":false,"description":"Om true: returnera SSE-stream istället för JSON."},"tier":{"type":"string","enum":["priority","normal","async"],"default":"normal","description":"Pris i SEK per 1M tokens (input/output): normal 60/180, priority 120/360 (garanterat först i kön), async 30/90 (webhook inom 12h)."},"webhook_url":{"type":"string","format":"uri","description":"Krävs för tier=async. Grunden POST:ar resultatet hit när jobbet är klart."},"temperature":{"type":"number","minimum":0,"maximum":2},"max_tokens":{"type":"integer","minimum":1},"top_p":{"type":"number","minimum":0,"maximum":1},"frequency_penalty":{"type":"number","minimum":-2,"maximum":2},"presence_penalty":{"type":"number","minimum":-2,"maximum":2},"knowledge_base_id":{"type":"string","format":"uuid","description":"Grunden-tillägg (RAG): id på en kunskapsbas att grunda svaret i. Relevanta utdrag hämtas och injiceras automatiskt som kontext före svaret. Kunskapsbasen måste vara länkad till API-nyckeln via POST /v1/knowledge-bases/{id}/keys. Stöds ej med tier=async eller anonym chat."},"knowledge_base_top_k":{"type":"integer","minimum":1,"maximum":10,"default":5,"description":"Antal kunskapsbas-utdrag att injicera (1–10). Endast tillsammans med knowledge_base_id."},"tools":{"type":"array","description":"Funktioner som modellen kan välja att anropa. Upstream-provider måste stödja tool-calling — annars returneras 400.","items":{"$ref":"#/components/schemas/ToolDefinition"}},"tool_choice":{"description":"Styr om/hur modellen ska använda verktyg: 'auto' (default när tools ges), 'none', 'required', eller explicit funktion.","oneOf":[{"type":"string","enum":["auto","none","required"]},{"type":"object","required":["type","function"],"properties":{"type":{"type":"string","enum":["function"]},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}]}}},"ChatCompletionResponse":{"type":"object","required":["id","object","created","model","choices"],"properties":{"id":{"type":"string"},"object":{"type":"string","enum":["chat.completion"]},"created":{"type":"integer","description":"Unix-tidsstämpel."},"model":{"type":"string","description":"Canonical grunden.ai-id (aldrig underliggande ids)."},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"message":{"$ref":"#/components/schemas/ChatMessage"},"finish_reason":{"type":"string","enum":["stop","length","content_filter"]}}}},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer"},"completion_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}}}},"BatchJobResponse":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["batch.job"]},"status":{"type":"string","enum":["queued","running","completed","failed","expired"]},"model":{"type":"string"},"tier":{"type":"string","enum":["async"]},"created_at":{"type":"integer"},"expires_at":{"type":"integer"},"completed_at":{"type":"integer","nullable":true},"webhook_url":{"type":"string","nullable":true},"response":{"type":"object","nullable":true},"error":{"type":"string","nullable":true}}},"EmbeddingRequest":{"type":"object","required":["model","input"],"properties":{"model":{"type":"string","description":"Embedding-modell. Idag: 'bge-m3' (BAAI/bge-m3, 1024-dim multilingual). Hämta lista via GET /v1/models med capabilities=embeddings."},"input":{"description":"Enskild sträng eller array av strängar för batch-embedding.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"encoding_format":{"type":"string","enum":["float","base64"],"default":"float"},"dimensions":{"type":"integer","description":"Valfri reducerad dimension — stöds bara av vissa modeller."}}},"EmbeddingResponse":{"type":"object","required":["object","data","model","usage"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string","enum":["embedding"]},"index":{"type":"integer"},"embedding":{"description":"Array av float64 eller base64-kodad buffer beroende på encoding_format.","oneOf":[{"type":"array","items":{"type":"number"}},{"type":"string"}]}}}},"model":{"type":"string"},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}}}},"RerankRequest":{"type":"object","required":["query","texts"],"description":"Reranking-request. Inget model-fält — endpointen servar BGE-reranker-v2-m3 direkt.","properties":{"query":{"type":"string","description":"Sökfrågan dokumenten ska rankas mot."},"texts":{"type":"array","items":{"type":"string"},"description":"Dokumenten/textstyckena som ska rankas."}}},"RerankResponse":{"type":"array","description":"Array sorterad efter relevans (högst score först). `index` refererar till positionen i request-`texts`.","items":{"type":"object","required":["index","score"],"properties":{"index":{"type":"integer","description":"Index i request-`texts`-arrayen."},"score":{"type":"number","description":"Relevans 0–1 (cross-encoder)."}}}},"PiiRedactRequest":{"type":"object","required":["text"],"description":"PII-redaktions-request. Inget model-fält och inga labels — endpointen servar GLiNER direkt och detekterar svenska entiteter automatiskt.","properties":{"text":{"type":"string","description":"Texten som ska analyseras och maskeras."}}},"PiiRedactResponse":{"type":"object","required":["masked","entities"],"properties":{"masked":{"type":"string","description":"Texten med varje detekterad entitet ersatt med [REDACTED]."},"entities":{"type":"array","items":{"type":"object","required":["start","end","text","label","score"],"properties":{"start":{"type":"integer","description":"Startoffset i text."},"end":{"type":"integer","description":"Slutoffset i text."},"text":{"type":"string","description":"Den detekterade strängen."},"label":{"type":"string","description":"Entitetstyp, t.ex. person, personnummer, address, email, phone."},"score":{"type":"number","description":"Konfidens 0–1."}}}}}},"AnthropicContentBlock":{"description":"Ett innehållsblock i ett Anthropic-meddelande. text/image i user-meddelanden, tool_use i assistant-svar, tool_result tillbaka från klienten. OBS: image-block stöds inte av modellen och ersätts med en text-platshållare.","oneOf":[{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}}},{"type":"object","required":["type","id","name","input"],"properties":{"type":{"type":"string","enum":["tool_use"]},"id":{"type":"string"},"name":{"type":"string"},"input":{"type":"object"}}},{"type":"object","required":["type","tool_use_id"],"properties":{"type":{"type":"string","enum":["tool_result"]},"tool_use_id":{"type":"string"},"content":{"oneOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/components/schemas/AnthropicContentBlock"}}]},"is_error":{"type":"boolean"}}}]},"AnthropicInputMessage":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"description":"Sträng (ren text) eller array av content-block (text/image/tool_use/tool_result).","oneOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/components/schemas/AnthropicContentBlock"}}]}}},"AnthropicTool":{"type":"object","required":["name","input_schema"],"description":"Verktygsdefinition i Anthropics format.","properties":{"name":{"type":"string"},"description":{"type":"string"},"input_schema":{"type":"object","description":"JSON Schema för verktygets parametrar."}}},"AnthropicMessagesRequest":{"type":"object","required":["max_tokens","messages"],"description":"Anthropic Messages-request. `model` accepteras men IGNORERAS — endpointen servar alltid glm-5.2 (vi kör inte Claude).","properties":{"model":{"type":"string","description":"Accepteras för SDK-kompatibilitet men IGNORERAS — alla anrop servas av glm-5.2."},"max_tokens":{"type":"integer","minimum":1,"description":"Max antal tokens att generera."},"messages":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AnthropicInputMessage"}},"system":{"description":"System-prompt: sträng eller array av text-block.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"object"}}]},"tools":{"type":"array","items":{"$ref":"#/components/schemas/AnthropicTool"}},"tool_choice":{"type":"object","description":"Anthropic-format: {type:'auto'|'any'|'none'|'tool', name?:string}."},"temperature":{"type":"number","minimum":0,"maximum":1},"top_p":{"type":"number","minimum":0,"maximum":1},"stop_sequences":{"type":"array","items":{"type":"string"}},"stream":{"type":"boolean","default":false,"description":"Om true: SSE-event-ström i Anthropics format (message_start, content_block_delta, message_delta, message_stop)."}}},"AnthropicMessage":{"type":"object","required":["id","type","role","model","content","stop_reason","usage"],"description":"Anthropic Messages-svar (icke-streamat). `model` är alltid 'glm-5.2'.","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["message"]},"role":{"type":"string","enum":["assistant"]},"model":{"type":"string","enum":["glm-5.2"]},"content":{"type":"array","description":"Array av text- och/eller tool_use-block. Innehåller alltid minst ett block.","items":{"oneOf":[{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}}},{"type":"object","required":["type","id","name","input"],"properties":{"type":{"type":"string","enum":["tool_use"]},"id":{"type":"string"},"name":{"type":"string"},"input":{"type":"object"}}}]}},"stop_reason":{"type":"string","enum":["end_turn","max_tokens","stop_sequence","tool_use"]},"stop_sequence":{"type":["string","null"]},"usage":{"type":"object","required":["input_tokens","output_tokens"],"properties":{"input_tokens":{"type":"integer","minimum":0},"output_tokens":{"type":"integer","minimum":0},"cache_creation_input_tokens":{"type":"integer","minimum":0},"cache_read_input_tokens":{"type":"integer","minimum":0}}}}},"ModelsListResponse":{"type":"object","description":"Modellkatalog. VIKTIGT: varje modell har `type` + `endpoint` som anger HUR den anropas. Endast `type:\"chat\"`-modeller går via /v1/chat/completions; övriga (embedding/rerank/pii) har dedikerade endpoints angivna i `endpoint`. Skicka aldrig en icke-chat-modell till /v1/chat/completions.","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["model"]},"created":{"type":"integer"},"owned_by":{"type":"string","enum":["grunden.ai"]},"type":{"type":"string","enum":["chat","embedding","rerank","stt","tts","pii"],"description":"Modellens kategori. Avgör vilken endpoint som servar den. (stt/tts är tillfälligt inaktiva.)"},"endpoint":{"type":"string","description":"Sökvägen som servar modellen, t.ex. /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/pii/redact. Anropa modellen HÄR — inte via model-fältet på en annan endpoint.","example":"/v1/pii/redact"}}}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["message","type","code"],"properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"string","enum":["invalid_request","unauthorized","insufficient_balance","no_subscription","daily_cap_reached","rate_limit_exceeded","not_found","provider_unavailable","internal_error"]}}}}}}},"paths":{"/v1/chat/completions":{"post":{"summary":"Chat completion","description":"OpenAI-kompatibel chat completion. Stödjer streaming via SSE med stream=true. För tier=async returnerar 202 med batch-job-id.","operationId":"createChatCompletion","tags":["Chat"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Klient-genererad UUID/ULID (max 128 tecken). Samma nyckel inom 24 h returnerar det cachade svaret och debiterar inte wallet igen. Rekommenderas för alla retries.","schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"},"examples":{"basic":{"summary":"Enkel chat-request","value":{"model":"glm-5.2","messages":[{"role":"user","content":"Hej!"}]}},"streaming":{"summary":"Streaming","value":{"model":"glm-5.2","stream":true,"messages":[{"role":"user","content":"Skriv en haiku om regn."}]}},"async":{"summary":"Async via webhook","value":{"model":"glm-5.2","tier":"async","webhook_url":"https://example.com/webhook","messages":[{"role":"user","content":"Sammanfatta följande text…"}]}}}}}},"responses":{"200":{"description":"Chat completion (JSON eller SSE-stream).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionResponse"}},"text/event-stream":{"schema":{"$ref":"#/components/schemas/ChatCompletionChunk"}}}},"202":{"description":"Async-jobb köat (tier=async).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobResponse"}}}},"400":{"description":"Felaktig request (invalid_request) — saknade fält, okänd tier, ogiltig webhook_url, m.m.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ogiltig eller saknad API-nyckel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Otillräckligt saldo eller krav på prenumeration (no_subscription / insufficient_balance).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Okänd modell eller tillfälligt ur drift.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit överskridet (rate_limit_exceeded / daily_cap_reached).","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Antal sekunder att vänta innan nästa försök. Använd exponentiell backoff med denna som undre gräns."},"X-RateLimit-Remaining-Requests":{"schema":{"type":"integer"},"description":"Antal requests kvar i nuvarande minut-fönster."},"X-RateLimit-Remaining-Tokens":{"schema":{"type":"integer"},"description":"Antal tokens kvar i nuvarande minut-fönster."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/models":{"get":{"summary":"Lista modeller","description":"Returnerar alla aktiva modeller i OpenAI-format. Kräver Authorization: Bearer sk-grunden-… (matchar implementation i apps/api/src/routes/models.ts).","operationId":"listModels","tags":["Models"],"responses":{"200":{"description":"Modellista.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelsListResponse"},"examples":{"list":{"value":{"object":"list","data":[{"id":"glm-5.2","object":"model","created":0,"owned_by":"grunden.ai"}]}}}}}}}}},"/v1/embeddings":{"post":{"summary":"Skapa embeddings","description":"OpenAI-kompatibelt embeddings-endpoint. Returnerar vektor(er) för en eller flera strängar.","operationId":"createEmbedding","tags":["Embeddings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingRequest"},"examples":{"single":{"summary":"Enkel sträng","value":{"model":"bge-m3","input":"Hej världen!"}},"batch":{"summary":"Batch av flera","value":{"model":"bge-m3","input":["Första texten","Andra texten"]}}}}}},"responses":{"200":{"description":"Embedding-vektorer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingResponse"}}}},"400":{"description":"Felaktig request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ogiltig eller saknad API-nyckel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Otillräckligt saldo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/rerank":{"post":{"summary":"Reranka dokument mot en query","description":"Cross-encoder reranking med BGE-reranker-v2-m3. Dedikerad endpoint — skicka `query` + `texts`, INTE `model`. Anropas inte via /v1/chat/completions.","operationId":"rerank","tags":["Rerank"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RerankRequest"},"examples":{"basic":{"summary":"Ranka två dokument","value":{"query":"huvudstad i Sverige","texts":["Stockholm är Sveriges huvudstad","Bananer är gula"]}}}}}},"responses":{"200":{"description":"Dokument sorterade efter relevans.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RerankResponse"}}}},"401":{"description":"Ogiltig eller saknad API-nyckel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Otillräckligt saldo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/pii/redact":{"post":{"summary":"Detektera och maskera PII","description":"PII-detektering och -maskering med GLiNER (svenska entiteter). Dedikerad endpoint — skicka bara `text`, INTET `model`-fält och inga `labels`. Anropas INTE via /v1/chat/completions (modellen `gliner-multi-pii` i /v1/models servas av detta endpoint).","operationId":"redactPii","tags":["PII"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PiiRedactRequest"},"examples":{"basic":{"summary":"Maskera en mening","value":{"text":"Anna Andersson, personnummer 19900101-1234, bor i Stockholm."}}}}}},"responses":{"200":{"description":"Maskerad text + detekterade entiteter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PiiRedactResponse"}}}},"401":{"description":"Ogiltig eller saknad API-nyckel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Otillräckligt saldo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages":{"post":{"summary":"Anthropic Messages (servar glm-5.2)","description":"Anthropic Messages-API i wire-format. VIKTIGT: grunden.ai kör INTE Claude/Anthropic-modeller — denna endpoint servar vår egen modell glm-5.2 i Anthropics request/response-form. Syfte: låta Anthropic-SDK-verktyg (Claude Code m.fl.) peka mot en EU-hostad modell utan att byta verktyg. Sätt ANTHROPIC_BASE_URL=https://api.grunden.ai och ANTHROPIC_API_KEY=sk-grunden-…. Auth: skicka x-api-key: sk-grunden-… (Anthropic-SDK-stil, primär) ELLER Authorization: Bearer sk-grunden-… (det globala bearerAuth-schemat). `model`-fältet ignoreras (alltid glm-5.2). SSE-streaming stöds med stream:true. Stöds ej: bild-block (ersätts med text-platshållare), thinking-läge, prompt-caching, top_k.","operationId":"createAnthropicMessage","tags":["Messages (Anthropic-kompatibel)"],"parameters":[{"name":"x-api-key","in":"header","required":false,"description":"API-nyckel (sk-grunden-…) i Anthropic-SDK-stil. Alternativ till Authorization: Bearer. Minst en av dem krävs.","schema":{"type":"string"}},{"name":"anthropic-version","in":"header","required":false,"description":"Anthropic-SDK skickar denna header (t.ex. 2023-06-01). Accepteras och ignoreras.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicMessagesRequest"},"examples":{"basic":{"summary":"Enkel Messages-request","value":{"model":"glm-5.2","max_tokens":1024,"messages":[{"role":"user","content":"Hej!"}]}},"streaming":{"summary":"Streaming","value":{"model":"glm-5.2","max_tokens":1024,"stream":true,"messages":[{"role":"user","content":"Skriv en haiku om regn."}]}}}}}},"responses":{"200":{"description":"Anthropic Messages-svar (JSON), eller SSE-event-ström när stream:true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicMessage"}},"text/event-stream":{"schema":{"type":"string","description":"Anthropic SSE-event-sekvens: message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop."}}}},"400":{"description":"Felaktig request — saknad/tom messages-array, fel role-värde, ogiltig temperature/top_p, m.m. Svar i Anthropics felformat ({type:'error', error:{type, message}}).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Saknad eller ogiltig API-nyckel. Sätt ANTHROPIC_API_KEY (eller x-api-key) till din sk-grunden-…-nyckel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Otillräckligt saldo eller krav på prenumeration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit överskridet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/batch/{id}":{"get":{"summary":"Hämta batch-jobb","description":"Status + resultat för ett batch-jobb.","operationId":"getBatchJob","tags":["Batch"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Async-jobb-status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobResponse"}}}},"404":{"description":"Jobbet finns inte eller tillhör annan user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/knowledge-bases":{"post":{"tags":["Knowledge bases"],"summary":"Skapa kunskapsbas","operationId":"createKnowledgeBase","description":"Skapar en tom kunskapsbas. Max 5 per organisation (API-nycklar). Länka sedan nyckeln med POST /v1/knowledge-bases/{id}/keys och ladda upp innehåll.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":128},"description":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Skapad.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"total_bytes":{"type":"integer"},"doc_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}}}}},"429":{"description":"Kvot överskriden (max 5 per org)."}}},"get":{"tags":["Knowledge bases"],"summary":"Lista kunskapsbaser","operationId":"listKnowledgeBases","description":"Listar organisationens kunskapsbaser.","responses":{"200":{"description":"Lista.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"total_bytes":{"type":"integer"},"total_chunks":{"type":"integer"},"doc_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}}}}}}}}}}},"/v1/knowledge-bases/{id}":{"delete":{"tags":["Knowledge bases"],"summary":"Radera kunskapsbas","operationId":"deleteKnowledgeBase","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Raderad (inkl. alla dokument/chunks)."},"404":{"description":"Hittades inte."}}}},"/v1/knowledge-bases/{id}/keys":{"post":{"tags":["Knowledge bases"],"summary":"Länka anropande API-nyckel till kunskapsbasen","operationId":"linkKnowledgeBaseKey","description":"Binder den API-nyckel som gör anropet till kunskapsbasen. Bindningen krävs för att kunna använda knowledge_base_id i chat-anrop.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Länkad."},"404":{"description":"Hittades inte."}}},"delete":{"tags":["Knowledge bases"],"summary":"Ta bort länk mellan nyckel och kunskapsbas","operationId":"unlinkKnowledgeBaseKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Avlänkad."},"404":{"description":"Länken hittades inte."}}}},"/v1/knowledge-bases/{id}/documents":{"post":{"tags":["Knowledge bases"],"summary":"Ladda upp dokument","operationId":"uploadKnowledgeBaseDocument","description":"Laddar upp en fil (base64) som extraheras, chunkas och embeddas asynkront. Stödda format: PDF, DOCX, PPTX, XLSX, CSV, TXT, Markdown, HTML. Max 50 MB per fil, 200 MB total per kunskapsbas. Statusen går extracting → chunking → embedding → ready.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","mime_type","content"],"properties":{"filename":{"type":"string"},"mime_type":{"type":"string","description":"T.ex. application/pdf, text/plain, text/markdown, text/csv, application/vnd.openxmlformats-officedocument.wordprocessingml.document."},"content":{"type":"string","description":"Filinnehåll base64-kodat."}}}}}},"responses":{"201":{"description":"Mottaget, ingest startad.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"mime_type":{"type":"string"},"size_bytes":{"type":"integer"},"status":{"type":"string","example":"extracting"}}}}}},"400":{"description":"Ogiltig fil / format som inte stöds."},"413":{"description":"Filen för stor (max 50 MB)."},"429":{"description":"Lagringskvot överskriden (200 MB)."}}}},"/v1/knowledge-bases/{id}/snippets":{"post":{"tags":["Knowledge bases"],"summary":"Lägg till textutdrag","operationId":"createKnowledgeBaseSnippet","description":"Lägger till rå text (titel + innehåll) i kunskapsbasen utan filuppladdning. Embeddas asynkront.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":256},"content":{"type":"string","maxLength":50000}}}}}},"responses":{"201":{"description":"Skapat, ingest startad."},"404":{"description":"Kunskapsbasen hittades inte."}}},"get":{"tags":["Knowledge bases"],"summary":"Lista textutdrag","operationId":"listKnowledgeBaseSnippets","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Lista av snippets."}}}}},"webhooks":{"batchJobCompleted":{"post":{"summary":"Async-jobb klart","description":"Grunden POST:ar till ditt `webhook_url` när ett batch-jobb når completed/failed/expired. Två signatur-headers skickas: (1) `X-Grunden-Signature: sha256=<hex>` (v1) — HMAC-SHA256 över raw body. (2) `X-Grunden-Signature-V2: t=<unix_ts>,v1=<hex>` (v2, rekommenderad) — HMAC-SHA256 över `v1:<unix_ts>:<raw_body>`; tidsstämpeln skyddar mot replay (avvisa om `abs(now-t) > 5 min`). Verifiera alltid signaturen INNAN payloaden processas. Retry: upp till 5 försök med exponentiell backoff (1m, 5m, 30m, 2h, 12h) — returnera 2xx inom 10 s för att bekräfta mottagandet.","operationId":"batchJobCompleted","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchWebhookPayload"}}}},"responses":{"200":{"description":"Webhook mottagen. Vi avstår från fler försök."},"2XX":{"description":"Alla 2xx-svar räknas som bekräftelse."}}}}}}