0941e53c50
Revisionens föremål är hashkedjan, förseglingen och säkerhetstaket från igår. Skälet att granska det nyaste först: det farligaste ögonblicket för en skyddsmekanism är veckan efter att den byggts, när testerna är skrivna av samma person som skrev mekanismen och provar samma antaganden. K-1, KRITISK. Förseglingen larmade falskt i normal drift. Offline-synk kan lämna in händelser EFTER att ärendet stängts — en sen bild, ett sent kundbesked — och verifieringen jämförde förseglingens rot med kedjans NUVARANDE rot. Varje legitim efterhändelse gav OGILTIG. Reproducerad i integrationsmiljön innan den kallades fynd. En verifiering som larmar falskt avfärdas snart som trasig, och därefter avfärdas även de äkta larmen — falsklarmet är inte en mindre bugg i ett skydd, det är det som dödar skyddet. Åtgärd: förseglingen täcker sitt PREFIX. Den förseglade roten ska vara en länk i den omräknade kedjan; det som kom efter redovisas öppet i `efterForsegling` i stället för att smittas eller smitta. Motvikten är prövad: sabotage INUTI prefixet fäller fortfarande både kedjan och förseglingen — annars vore "täcker sitt prefix" bara ett artigare ord för "täcker ingenting". m-1. Kedjelänken vilade på ett tidsformat som garanterades tre filer bort. Inte utlösbart i dag — alla vägar går genom tillPost — men första nya skrivväg utan millisekunder hade gett en kedja som aldrig verifierar. Normaliseringen bor nu i skrivKedjat, i samma uttryck som länken och databasraden får den ur. m-2. Protokollvägens skrivna/dubbletter räknades med två count(*) runt anropet; en samtidig skrivning från någon annan hamnade i vår siffra. skrivKedjat returnerar nu räkningen ur transaktionen som gjorde jobbet. m-3. Läsordning och kedjeordning var två ordningar. Kedjan ordnas av sekvens; GET, grinden och delningsvyn sorterade på tidpunkt och id — inom en batch kunde id:ts bokstavsordning avgöra vilken händelse som var "senast". Alla läsvägar sorterar nu i kedjeordning. Granskat utan anmärkning, bokfört så nästa revision vet att "hittade inget" betyder "letade": numerisk rundresa genom jsonb (provad med 1e-7 och 0.10000000000000009, inte antagen), dubbletter flyttar inte kedjan, krypto-shredding mot kedjan, signaturens serverägdhet, takets monotoni. Mönstret i alla fyra fynd är detsamma och står i rapporten: testerna som skrevs med mekanismen provade manipulation, som är det man tänker på när man bygger ett skydd. Det som gick sönder var normal drift — sen synk, samtidiga grannar, en batch i samma millisekund. Skydd fallerar oftare genom att larma falskt än genom att missa angrepp. 766 tester, 206 integrationskontroller, genomgång 4/4, portalspärr, typkontroll, lint och artefaktmätning gröna. API-specen dokumenterar prefixsemantiken och efterForsegling. docs/QUALITY-AUDIT-3.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
1629 lines
58 KiB
YAML
1629 lines
58 KiB
YAML
openapi: 3.0.3
|
||
info:
|
||
title: Guidad Felsökning – plattforms-API
|
||
version: 1.0.0
|
||
description: |
|
||
API-first-ytan för Guidad Felsökning: autentisering, organisationens
|
||
användare, ärenden med append-only händelselogg, arbetsledaröversikt,
|
||
publik Live Share-delning samt AI-orkestern.
|
||
|
||
**Händelseloggen är append-only.** Det finns inga update- eller
|
||
delete-operationer — historik kan aldrig ändras eller raderas, vilket
|
||
även garanteras av databastriggers.
|
||
|
||
**Händelse-id är skopade till ärendet.** Nyckeln är `(arende_id, id)`,
|
||
inte `id` ensamt. En omsändning av samma händelse med samma innehåll är
|
||
idempotent och svarar `200`. Samma id med ANNAT innehåll svarar `409`
|
||
och skriver ingenting — varken det som redan står i loggen eller resten
|
||
av satsen.
|
||
|
||
**Multi-tenant.** All ärendedata är organisationsknuten. Ett ärende i
|
||
en annan organisation ger `404`, som om det inte fanns.
|
||
|
||
Specen serveras live av tjänsten på `GET /api/openapi.yaml`.
|
||
servers:
|
||
- url: https://app.exempel.se
|
||
description: Klustrets ingress (ersätt med er domän)
|
||
tags:
|
||
- name: Auth
|
||
- name: Användare
|
||
- name: Ärenden
|
||
- name: Översikt
|
||
- name: Delning
|
||
- name: AI
|
||
- name: Integrationer
|
||
- name: Drift
|
||
- name: Fakturering
|
||
|
||
paths:
|
||
/halsa:
|
||
get:
|
||
tags: [Drift]
|
||
summary: Hälsokontroll
|
||
security: []
|
||
responses:
|
||
"200":
|
||
description: Tjänsten svarar.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
status: { type: string, example: ok }
|
||
|
||
/api/openapi.yaml:
|
||
get:
|
||
tags: [Drift]
|
||
summary: Denna specifikation
|
||
security: []
|
||
responses:
|
||
"200":
|
||
description: OpenAPI-specen i YAML.
|
||
content:
|
||
application/yaml:
|
||
schema: { type: string }
|
||
|
||
/api/auth/registrera:
|
||
post:
|
||
tags: [Auth]
|
||
summary: Skapa organisation + systemadministratör
|
||
description: >
|
||
Registrerar en ny organisation (tenant) och dess första användare,
|
||
som blir systemadministratör. Kan stängas av driften
|
||
(REGISTRERING_OPPEN=false → 403).
|
||
security: []
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [epost, losenord, namn, organisation]
|
||
properties:
|
||
epost: { type: string, format: email }
|
||
losenord: { type: string, minLength: 8 }
|
||
namn: { type: string }
|
||
organisation: { type: string }
|
||
responses:
|
||
"200": { $ref: "#/components/responses/Inloggad" }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"409": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/auth/logga-in:
|
||
post:
|
||
tags: [Auth]
|
||
summary: Logga in
|
||
description: >
|
||
Takt-begränsad i databasen, så spärren håller bakom flera
|
||
repliker: 10 misslyckade försök per konto och 30 per källadress
|
||
inom 15 minuter ger 429. Spärren gäller kontot även vid rätt
|
||
lösenord — annars vore den meningslös. Ett avstängt konto svarar
|
||
403.
|
||
security: []
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [epost, losenord]
|
||
properties:
|
||
epost: { type: string, format: email }
|
||
losenord: { type: string }
|
||
responses:
|
||
"200": { $ref: "#/components/responses/Inloggad" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403":
|
||
description: Kontot är avstängt.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
"429":
|
||
description: För många misslyckade försök.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
|
||
/api/anvandare:
|
||
get:
|
||
tags: [Användare]
|
||
summary: Lista organisationens användare
|
||
description: Kräver rollen `admin` eller `arbetsledare`.
|
||
responses:
|
||
"200":
|
||
description: Användare i den egna organisationen.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
anvandare:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/Anvandare" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Användare]
|
||
summary: Skapa användare i den egna organisationen
|
||
description: Kräver rollen `admin`.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [epost, losenord, namn, roll]
|
||
properties:
|
||
epost: { type: string, format: email }
|
||
losenord: { type: string, minLength: 8 }
|
||
namn: { type: string }
|
||
roll: { $ref: "#/components/schemas/Roll" }
|
||
responses:
|
||
"200":
|
||
description: Den skapade användaren.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Anvandare" }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"409": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/ecm/regler:
|
||
get:
|
||
tags: [Drift]
|
||
summary: ECM Knowledge Library — aktuellt regelpaket
|
||
description: >
|
||
Serverdistribuerat, versionerat regelpaket (compliance-regler per
|
||
ärendetyp, orsakskategorier, evidenskällor, undantagsorsaker).
|
||
Uppdateras i driften utan appändring; klienten cachar och faller
|
||
tillbaka till sitt inbyggda standardpaket offline.
|
||
responses:
|
||
"200":
|
||
description: Regelpaketet.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
version: { type: string }
|
||
arendetypRegler: { type: object, additionalProperties: true }
|
||
undantagsorsaker: { type: array, items: { type: string } }
|
||
orsakskategorier: { type: array, items: { type: string } }
|
||
underlagskallor: { type: array, items: { type: string } }
|
||
|
||
/api/fordon/{identifierare}/historik:
|
||
get:
|
||
tags: [Ärenden]
|
||
summary: Fordonshistorik — tidigare ärenden på samma objekt
|
||
description: >
|
||
Organisationens tidigare ärenden där objektet (regnr/VIN) matchar,
|
||
med dokumenterade felorsaker. Underlag för pre-diagnostikens
|
||
historiksteg och orsakskedjan.
|
||
parameters:
|
||
- name: identifierare
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
responses:
|
||
"200":
|
||
description: Tidigare ärenden (senaste 20).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
arenden:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
nummer: { type: integer }
|
||
skapad: { type: string, format: date-time }
|
||
avslutat: { type: boolean }
|
||
felbeskrivning: { type: string, nullable: true }
|
||
felorsaker: { type: array, items: { type: object, additionalProperties: true } }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/statistik/felorsaker:
|
||
get:
|
||
tags: [Översikt]
|
||
summary: Felorsaksstatistik per orsakskategori
|
||
description: >
|
||
Flottdata ur dokumenterade felorsaksanalyser i organisationen.
|
||
Kräver rollen `arbetsledare` eller `admin`.
|
||
responses:
|
||
"200":
|
||
description: Antal per orsakskategori, fallande.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
orsaker:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
orsak: { type: string }
|
||
antal: { type: integer }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/anvandare/{anvandarId}/avaktivera:
|
||
post:
|
||
tags: [Användare]
|
||
summary: Stäng av ett konto
|
||
description: >
|
||
Kräver rollen `admin` och att användaren tillhör samma
|
||
organisation. Avstängningen höjer kontots token-version, så
|
||
**pågående sessioner upphör omedelbart** — annars vore den
|
||
verkningslös tills utfärdade tokens gick ut. Ett konto kan inte
|
||
stänga av sig självt.
|
||
parameters:
|
||
- name: anvandarId
|
||
in: path
|
||
required: true
|
||
schema: { type: string, format: uuid }
|
||
responses:
|
||
"200":
|
||
description: Kontot är avstängt.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
id: { type: string, format: uuid }
|
||
namn: { type: string }
|
||
aktiv: { type: boolean }
|
||
"400":
|
||
description: Försök att stänga av sitt eget konto.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/anvandare/{anvandarId}/aktivera:
|
||
post:
|
||
tags: [Användare]
|
||
summary: Öppna ett avstängt konto
|
||
description: >
|
||
Kräver rollen `admin`. Kontot kan logga in igen, men tokens som
|
||
återkallades vid avstängningen förblir ogiltiga.
|
||
parameters:
|
||
- name: anvandarId
|
||
in: path
|
||
required: true
|
||
schema: { type: string, format: uuid }
|
||
responses:
|
||
"200": { description: Kontot är öppnat. }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/auth/logga-ut-alla:
|
||
post:
|
||
tags: [Auth]
|
||
summary: Logga ut på alla enheter
|
||
description: >
|
||
Höjer den egna token-versionen, vilket gör samtliga utfärdade
|
||
tokens för kontot ogiltiga direkt — vägen ut när en enhet
|
||
tappats bort.
|
||
responses:
|
||
"200": { description: Alla sessioner är avslutade. }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden/{arendeId}/bilagor:
|
||
post:
|
||
tags: [Ärenden]
|
||
summary: Ladda upp en bilaga
|
||
description: >
|
||
Foton, videoklipp och instrumentbilder. Kroppen är råa bytes och
|
||
`Content-Type` anger mediatypen — endast bild och video tas emot.
|
||
Servern beräknar innehållets SHA-256 och returnerar en referens
|
||
som ska läggas i händelsen; **hashen hamnar därmed i den
|
||
append-only-skyddade loggen**, så en utbytt bild går att upptäcka.
|
||
Innehållsadresserat: samma innehåll lagras en gång.
|
||
parameters:
|
||
- name: arendeId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
image/jpeg: { schema: { type: string, format: binary } }
|
||
image/png: { schema: { type: string, format: binary } }
|
||
image/webp: { schema: { type: string, format: binary } }
|
||
video/mp4: { schema: { type: string, format: binary } }
|
||
video/webm: { schema: { type: string, format: binary } }
|
||
responses:
|
||
"200":
|
||
description: Referensen att spara i händelsen.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
hash: { type: string, description: "SHA-256 av innehållet, hex." }
|
||
mediatyp: { type: string }
|
||
storlek: { type: integer }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"413":
|
||
description: Bilagan är större än 32 MB.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
"415":
|
||
description: Endast bilder och videoklipp tas emot.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
|
||
/api/bilagor/{bilagaId}:
|
||
get:
|
||
tags: [Ärenden]
|
||
summary: Hämta en bilaga
|
||
description: >
|
||
Organisationsknuten. Innehållet kontrolleras mot hashen innan det
|
||
lämnas ut — stämmer det inte svarar tjänsten 409 i stället för att
|
||
visa en bild som kan ha bytts ut.
|
||
parameters:
|
||
- name: bilagaId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
responses:
|
||
"200":
|
||
description: Innehållet.
|
||
content:
|
||
application/octet-stream:
|
||
schema: { type: string, format: binary }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"409":
|
||
description: Innehållet stämmer inte med hashen i loggen.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
|
||
/api/delad/{delningskod}/bilagor/{bilagaId}:
|
||
get:
|
||
tags: [Delning]
|
||
summary: Hämta en bilaga via delningslänk
|
||
description: >
|
||
Samma filtrering som händelserna: bilagan lämnas bara ut om
|
||
händelsen den hör till är synlig på delningens nivå. En bild som
|
||
hör till en intern händelsetyp — t.ex. den skannade arbetsordern —
|
||
nås alltså aldrig via kundlänken.
|
||
security: []
|
||
parameters:
|
||
- name: delningskod
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
- name: bilagaId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
responses:
|
||
"200":
|
||
description: Innehållet.
|
||
content:
|
||
application/octet-stream:
|
||
schema: { type: string, format: binary }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"409": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/integrationer/leverantorer:
|
||
get:
|
||
tags: [Integrationer]
|
||
summary: Registret över märkesspecifika kopplingar
|
||
description: >
|
||
Leverantörer är data, inte kod: registret läses ur
|
||
`integrationer.json` (eller filen i `INTEGRATIONER_FIL`) och kan
|
||
bytas via ConfigMap utan att applikationen byggs om. Innehåller
|
||
endast fältdefinitioner — aldrig någon organisations uppgifter.
|
||
responses:
|
||
"200":
|
||
description: Leverantörsdefinitioner.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
version: { type: string }
|
||
leverantorer:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
namn: { type: string }
|
||
beskrivning: { type: string }
|
||
nyckeltyp:
|
||
type: string
|
||
enum: [vin, regnr]
|
||
description: Vad uppslaget sker på. Utelämnat betyder VIN.
|
||
falt:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
nyckel: { type: string }
|
||
etikett: { type: string }
|
||
hemlig:
|
||
type: boolean
|
||
description: >
|
||
Hemliga fält maskeras alltid i svar och
|
||
visas aldrig igen efter sparande.
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/integrationer:
|
||
get:
|
||
tags: [Integrationer]
|
||
summary: Organisationens konfigurerade kopplingar
|
||
description: >
|
||
Kräver rollen `admin`. Uppgifterna lagras krypterade
|
||
(AES-256-GCM) och returneras alltid maskerade — hemliga värden
|
||
lämnar aldrig servern i klartext.
|
||
responses:
|
||
"200":
|
||
description: Konfigurerade kopplingar med maskerade uppgifter.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
krypteringKonfigurerad:
|
||
type: boolean
|
||
description: Falskt om `INTEGRATION_NYCKEL` saknas — då kan inget sparas.
|
||
integrationer:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
leverantor: { type: string }
|
||
namn: { type: string }
|
||
aktiv: { type: boolean }
|
||
uppdaterad: { type: string, format: date-time }
|
||
senast_testad: { type: string, format: date-time, nullable: true }
|
||
senaste_status: { type: string, nullable: true }
|
||
uppgifter:
|
||
type: object
|
||
additionalProperties: { type: string }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Integrationer]
|
||
summary: Spara eller uppdatera en kopplings uppgifter
|
||
description: >
|
||
Kräver rollen `admin`. Endast leverantörens definierade fält
|
||
sparas och samtliga måste fyllas i. Uppgifterna krypteras innan
|
||
de skrivs. Ett sparande nollställer tidigare testresultat.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [leverantor, uppgifter]
|
||
properties:
|
||
leverantor: { type: string }
|
||
aktiv: { type: boolean, default: true }
|
||
uppgifter:
|
||
type: object
|
||
additionalProperties: { type: string }
|
||
responses:
|
||
"200": { description: Sparad. }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"503":
|
||
description: Kryptering är inte konfigurerad (`INTEGRATION_NYCKEL` saknas).
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
|
||
/api/integrationer/{leverantor}:
|
||
delete:
|
||
tags: [Integrationer]
|
||
summary: Ta bort en kopplings uppgifter
|
||
description: Kräver rollen `admin`.
|
||
parameters:
|
||
- name: leverantor
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
responses:
|
||
"200": { description: Borttagen. }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/integrationer/{leverantor}/uppslag:
|
||
post:
|
||
tags: [Integrationer]
|
||
summary: Slå upp ett fordon hos leverantören
|
||
description: >
|
||
Anropet görs alltid av servern — kundens leverantörsnycklar når
|
||
aldrig webbläsaren. Svaret mappas till våra fält enligt
|
||
registrets `svarsfalt`. Resultatet skrivs som kopplingens
|
||
senaste teststatus.
|
||
parameters:
|
||
- name: leverantor
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [identifierare]
|
||
properties:
|
||
identifierare:
|
||
type: string
|
||
description: VIN eller registreringsnummer beroende på leverantörens `nyckeltyp`.
|
||
responses:
|
||
"200":
|
||
description: Fordonsuppgifter från leverantören.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
fordon:
|
||
type: object
|
||
additionalProperties: { type: string }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404":
|
||
description: Kopplingen är inte konfigurerad för organisationen.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
"502":
|
||
description: Leverantören svarade med fel eller inga kända fält.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
"503":
|
||
description: Kryptering är inte konfigurerad.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Fel" }
|
||
|
||
/api/delad/{delningskod}/beslut:
|
||
post:
|
||
tags: [Delning]
|
||
summary: Kundens besked på ett åtgärdsförslag
|
||
description: >
|
||
Den enda skrivande publika vägen. Endast delningar på **kundnivå**
|
||
som inte återkallats får svara, det måste finnas ett
|
||
åtgärdsförslag, och **ett besked per ärende** — svaret kan inte
|
||
ändras i efterhand. Takt-begränsad per delningskod. Beskedet
|
||
loggas som `kundbeslut` med kanal `Delningslänk`.
|
||
security: []
|
||
parameters:
|
||
- name: delningskod
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [beslut]
|
||
properties:
|
||
beslut: { type: string, enum: [godkant, avbojt, delvis] }
|
||
kommentar: { type: string, maxLength: 500 }
|
||
responses:
|
||
"200":
|
||
description: Beskedet registrerat.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"409": { $ref: "#/components/responses/Fel" }
|
||
"429": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/organisation:
|
||
get:
|
||
tags: [Organisation]
|
||
summary: Hämta organisationens namn och inställningar
|
||
description: >
|
||
Inställningarna styr vad som visas när ett ärende startas
|
||
(objekttyper och identifieringsmetoder). Alla inloggade läser.
|
||
responses:
|
||
"200":
|
||
description: Organisationen.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
namn: { type: string }
|
||
installningar: { $ref: "#/components/schemas/OrganisationsInstallningar" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/organisation/installningar:
|
||
post:
|
||
tags: [Organisation]
|
||
summary: Uppdatera organisationens inställningar
|
||
description: Kräver rollen `admin`. Minst ett alternativ per lista.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/OrganisationsInstallningar" }
|
||
responses:
|
||
"200":
|
||
description: Sparat.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden:
|
||
get:
|
||
tags: [Ärenden]
|
||
summary: Lista organisationens ärenden
|
||
responses:
|
||
"200":
|
||
description: Ärenden (senaste 200).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
arenden:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/Arende" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Ärenden]
|
||
summary: Registrera ett ärende (idempotent)
|
||
description: >
|
||
Skapar ärendet i användarens organisation. Ett redan känt `id`
|
||
ignoreras tyst — ärenderaden ändras aldrig.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [id, nummer, skapad]
|
||
properties:
|
||
id: { type: string }
|
||
nummer: { type: integer }
|
||
skapad: { type: string, format: date-time }
|
||
delningskod: { type: string, nullable: true }
|
||
metodikId: { type: string, nullable: true }
|
||
responses:
|
||
"200":
|
||
description: Mottaget.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden/{arendeId}/kedja:
|
||
parameters:
|
||
- name: arendeId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
get:
|
||
tags: [Ärenden]
|
||
summary: Verifiera ärendets hashkedja (ALVA-SPEC-070)
|
||
description: >
|
||
Räknar om varje kedjelänk ur det som står i databasen och jämför
|
||
med de lagrade. Svaret pekar ut FÖRSTA brottet; allt efter det är
|
||
följdfel. `forsegling` prövas mot den omräknade roten med en
|
||
nyckel som aldrig finns i databasen.
|
||
|
||
|
||
Vad svaret bevisar, och inte: att innehållet är oförändrat sedan
|
||
mottagandet, i den ordning det togs emot. Ingenting om tiden före
|
||
serverns klocka, ingenting om sanningshalten — det är grindens
|
||
och evidensmodellens ansvar.
|
||
responses:
|
||
"200":
|
||
description: Verifieringens utfall.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
rot: { type: string, nullable: true, description: Kedjans sista länk (SHA-256, hex). }
|
||
brott:
|
||
type: object
|
||
nullable: true
|
||
description: Första raden som inte stämmer.
|
||
properties:
|
||
index: { type: integer }
|
||
id: { type: string }
|
||
kedjade: { type: integer }
|
||
okedjade:
|
||
type: integer
|
||
description: >
|
||
Rader skrivna innan kedjan fanns. De bryter ingenting
|
||
men räknas — gammal data ska inte se starkare ut än
|
||
den är.
|
||
forsegling:
|
||
type: string
|
||
description: >
|
||
saknas · giltig · OGILTIG · kan inte prövas.
|
||
Förseglingen täcker sitt PREFIX — loggen fram till
|
||
avslutet. Händelser som tillkommit efter (sen
|
||
offline-synk, sena kundbesked) gör den inte
|
||
ogiltig; de redovisas i `efterForsegling`.
|
||
efterForsegling:
|
||
type: integer
|
||
nullable: true
|
||
description: >
|
||
Kedjade händelser efter förseglingspunkten. Noll är
|
||
det normala; ett tal är inte ett fel utan ett
|
||
faktum läsaren ska se.
|
||
bevisar: { type: string }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden/{arendeId}/handelser:
|
||
parameters:
|
||
- name: arendeId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
get:
|
||
tags: [Ärenden]
|
||
summary: Hämta ärendets händelselogg
|
||
responses:
|
||
"200":
|
||
description: Händelser i tidsordning.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
handelser:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/LoggPost" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Ärenden]
|
||
summary: Lägg till händelser (append-only, idempotent)
|
||
description: >
|
||
Lägger till händelser i loggen. Max 500 per anrop. En händelse med
|
||
redan känt `id` ignoreras tyst — befintliga händelser skrivs aldrig
|
||
över, och databastriggern stoppar alla ändringsförsök.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [handelser]
|
||
properties:
|
||
handelser:
|
||
type: array
|
||
maxItems: 500
|
||
items: { $ref: "#/components/schemas/LoggPost" }
|
||
responses:
|
||
"200":
|
||
description: Mottaget.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/oversikt:
|
||
get:
|
||
tags: [Översikt]
|
||
summary: Organisationsöversikt med status och statistikunderlag
|
||
description: >
|
||
Kräver rollen `arbetsledare` eller `admin`. Status, deltagande
|
||
tekniker och sammanfattning härleds ur händelseloggen.
|
||
responses:
|
||
"200":
|
||
description: Alla ärenden i organisationen med härledd status.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
arenden:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/OversiktsRad" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden/{arendeId}/delningar:
|
||
parameters:
|
||
- name: arendeId
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
get:
|
||
tags: [Delning]
|
||
summary: Lista ärendets delningslänkar
|
||
responses:
|
||
"200":
|
||
description: Delningar (inklusive återkallade).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
delningar:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/Delning" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Delning]
|
||
summary: Skapa delningslänk med behörighetsnivå
|
||
description: >
|
||
Verkstaden kontrollerar alltid delningen: kund (det kunddelbara),
|
||
partner (även hypoteser, tydligt märkta ej verifierade) eller
|
||
intern (full insyn). Nivåfiltreringen sker på serversidan.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [niva]
|
||
properties:
|
||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||
responses:
|
||
"200":
|
||
description: Den skapade länkens kod.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
kod: { type: string }
|
||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/delningar/{kod}/aterkalla:
|
||
post:
|
||
tags: [Delning]
|
||
summary: Återkalla en delningslänk
|
||
description: Efter återkallelse ger länken 404. Kan inte ångras.
|
||
parameters:
|
||
- name: kod
|
||
in: path
|
||
required: true
|
||
schema: { type: string }
|
||
responses:
|
||
"200":
|
||
description: Återkallad.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok: { type: boolean }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/delad/{delningskod}:
|
||
get:
|
||
tags: [Delning]
|
||
summary: Publik Live Share-vy via delningskod
|
||
description: >
|
||
Kräver ingen inloggning — delningskoden är nyckeln. Filtreringen
|
||
styrs av länkens behörighetsnivå: kund utesluter kategoribyten,
|
||
hypoteser och AI-dialog; partner utesluter kategoribyten och
|
||
AI-dialog; intern visar allt. Ärendets ursprungliga delningskod
|
||
fungerar bakåtkompatibelt som kundnivå.
|
||
security: []
|
||
parameters:
|
||
- name: delningskod
|
||
in: path
|
||
required: true
|
||
schema: { type: string, pattern: "^[A-Za-z0-9_-]+$" }
|
||
responses:
|
||
"200":
|
||
description: Ärendet med nivåfiltrerad händelselogg.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
arende:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
nummer: { type: integer }
|
||
skapad: { type: string, format: date-time }
|
||
handelser:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/LoggPost" }
|
||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/ai:
|
||
post:
|
||
tags: [AI]
|
||
summary: AI-orkestern (separat tjänst bakom samma ingress)
|
||
description: >
|
||
Routas av ingressen till ai-orkester-tjänsten. Servern äger
|
||
modellval, effort, systemprompt och svarsschema per uppgift:
|
||
handledning (Claude Sonnet 5), granskning (Claude Opus 5),
|
||
sammanfattning (Claude Sonnet 5), metodikval (Claude Haiku 4.5).
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [uppgift, prompt]
|
||
properties:
|
||
uppgift:
|
||
type: string
|
||
enum: [handledning, granskning, sammanfattning, metodikval]
|
||
prompt: { type: string, maxLength: 40000 }
|
||
responses:
|
||
"200":
|
||
description: Klassificerat AI-svar med modellen som svarade.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
modell: { type: string, example: claude-sonnet-5 }
|
||
svar:
|
||
oneOf:
|
||
- $ref: "#/components/schemas/AiSvar"
|
||
- type: object
|
||
properties:
|
||
metodikId:
|
||
type: string
|
||
enum: [vibration, elsystem, generisk]
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"502": { $ref: "#/components/responses/Fel" }
|
||
"503": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/arenden/{arendeId}/sammanfattning:
|
||
get:
|
||
summary: Härledd sammanfattning av ärendet (ALVA-PROC-0030)
|
||
description: >
|
||
Några få meningar som ger vem som helst en bild av ärendet.
|
||
Sammanfattningen är HÄRLEDD ur händelseloggen, inte genererad:
|
||
samma ärende ger samma text i dag och om två år, den innehåller
|
||
inget som inte står i loggen, och den säger uttryckligen när något
|
||
saknas i stället för att utelämna det. Avsedd för handläggare,
|
||
kundtjänst och integrationer.
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- { name: arendeId, in: path, required: true, schema: { type: string } }
|
||
responses:
|
||
"200":
|
||
description: Sammanfattning
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
version: { type: string, example: ALVA-PROC-0030 }
|
||
text: { type: string }
|
||
meningar: { type: array, items: { type: string } }
|
||
enrading: { type: string }
|
||
fullstandig:
|
||
type: boolean
|
||
description: Sant när inget obligatoriskt underlag saknas.
|
||
saknas: { type: array, items: { type: string } }
|
||
"404": { description: Ärendet är inte tillgängligt }
|
||
|
||
/api/arenden/{arendeId}/protokoll:
|
||
post:
|
||
summary: Läs in ett diagnosprotokoll som evidens (ALVA-PROC-0020)
|
||
description: >
|
||
Avläsningar blir händelser i loggen, inte en bilaga. Härkomsten
|
||
bevaras i varje post, så ett värde som kommit utifrån aldrig ser ut
|
||
som något teknikern själv mätt. Lämnas instrumentets identitet
|
||
behålls den; utan den graderas värdet E1 i stället för E4.
|
||
Profilen beskriver var värdena ligger i leverantörens format —
|
||
ALVA antar aldrig ett visst leverantörsformat.
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- { name: arendeId, in: path, required: true, schema: { type: string } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [kalla, profil, protokoll]
|
||
properties:
|
||
kalla:
|
||
type: string
|
||
description: Vad som producerade protokollet. Följer med varje händelse.
|
||
example: Diagnosinstrument, plats 3
|
||
profil:
|
||
type: object
|
||
properties:
|
||
felkoder:
|
||
type: object
|
||
properties:
|
||
vag: { type: string, example: dtcs }
|
||
kod: { type: string, example: code }
|
||
text: { type: string, example: description }
|
||
matvarden:
|
||
type: object
|
||
properties:
|
||
vag: { type: string, example: liveData }
|
||
beskrivning: { type: string, example: name }
|
||
varde: { type: string, example: value }
|
||
enhet: { type: string, example: unit }
|
||
instrumentId: { type: string, example: toolSerial }
|
||
protokoll:
|
||
type: object
|
||
description: Leverantörens nyttolast, oförändrad.
|
||
responses:
|
||
"200":
|
||
description: Antal skrivna händelser
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
handelser: { type: integer }
|
||
kalla: { type: string }
|
||
"422": { description: Profilen gav inga händelser ur protokollet }
|
||
|
||
/api/statistik/oversikt:
|
||
get:
|
||
summary: Driftmått för organisationen (ALVA-REP-0100)
|
||
description: >
|
||
Samma underlag som portalens analysvy och kvartalsrapporten, så att
|
||
skärm och rapport aldrig visar olika siffror för samma period.
|
||
Ett mått utan underlag returneras som null, aldrig som noll.
|
||
Kräver arbetsledare eller administratör.
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200":
|
||
description: Driftmått
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
version: { type: string, example: ALVA-REP-0100 }
|
||
verifiering:
|
||
type: object
|
||
description: Andel avslut med fastställd orsak.
|
||
properties:
|
||
antal: { type: integer }
|
||
fastställda: { type: integer }
|
||
andel: { type: number, nullable: true }
|
||
reproduktion: { type: object }
|
||
omarbetning:
|
||
type: object
|
||
description: Samma fordon tillbaka med samma orsakskategori.
|
||
undantag:
|
||
type: array
|
||
description: Kontroller som oftast hoppas över, per steg och ALVA-fas.
|
||
items: { type: object }
|
||
evidens: { type: object }
|
||
faser: { type: object }
|
||
orsaker: { type: array, items: { type: object } }
|
||
"403": { description: Kräver arbetsledare eller administratör }
|
||
|
||
/api/integration/kategorier:
|
||
get:
|
||
summary: Systemkategorier och utgående händelser (ALVA-SPEC-020)
|
||
description: >
|
||
ALVA är leverantörsoberoende. Kategorierna beskriver vilka slags
|
||
system som kan kopplas in och åt vilket håll data går; en profil
|
||
märks validated först efter att den körts mot leverantörens
|
||
faktiska gränssnitt.
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200":
|
||
description: Kategorier och händelsetyper
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
kategorier: { type: object }
|
||
handelser:
|
||
type: object
|
||
description: Utgående händelsetyper med beskrivning.
|
||
|
||
/api/integration/prenumerationer:
|
||
get:
|
||
summary: Organisationens utgående prenumerationer (ALVA-SPEC-021)
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200": { description: Prenumerationer }
|
||
"403": { description: Kräver administratörsbehörighet }
|
||
post:
|
||
summary: Registrera en mottagare för utgående händelser
|
||
description: >
|
||
Leveranser signeras med HMAC över tidsstämpel och kropp i huvudet
|
||
`alva-signatur` (`t=<unix>,v1=<hex>`). Tidsstämpeln ligger inne i
|
||
signaturen, så en fångad leverans inte går att spela upp senare;
|
||
toleransen är fem minuter. Adressen kontrolleras mot samma
|
||
SSRF-gräns som leverantörsuppslagen.
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [namn, url, handelser]
|
||
properties:
|
||
namn: { type: string }
|
||
url: { type: string, format: uri }
|
||
handelser:
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- arende.skapat
|
||
- arende.fas
|
||
- arende.slutsats
|
||
- arende.avslutat
|
||
- media.tillagt
|
||
- atgardsforslag.lamnat
|
||
- kundbeslut.registrerat
|
||
hemlighet:
|
||
type: string
|
||
description: Utelämnas för att låta servern generera en.
|
||
responses:
|
||
"200": { description: Skapad }
|
||
"400": { description: Ogiltig adress eller okänd händelsetyp }
|
||
|
||
/api/radering:
|
||
post:
|
||
summary: Radera personuppgifter för ett fordon (krypto-shredding)
|
||
description: >
|
||
Nyckeln förstörs; loggen står kvar. Det som raderas är
|
||
identifieringen, inte protokollet över vad som kontrollerades —
|
||
den enda konstruktion där bevisvärdet överlever en
|
||
raderingsbegäran. Kräver en bekräftelse som upprepar subjektet
|
||
exakt; åtgärden går inte att ångra.
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [subjekt, bekraftelse]
|
||
properties:
|
||
subjekt: { type: string, description: Registreringsnummer eller ärende-id. }
|
||
bekraftelse: { type: string, description: Måste vara identisk med subjekt. }
|
||
responses:
|
||
"200": { description: Verkställd }
|
||
"400": { description: Bekräftelsen matchar inte }
|
||
"404": { description: Inget skyddat underlag finns }
|
||
|
||
/api/matdon:
|
||
get:
|
||
summary: Organisationens mätdon med kalibreringsstatus (ALVA-SPEC-004)
|
||
description: >
|
||
Ett mätvärde graderas E4 endast med spårbart, kalibrerat
|
||
instrument. Utan register är påståendet inte kontrollerbart.
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200": { description: Mätdon }
|
||
post:
|
||
summary: Registrera eller uppdatera ett mätdon
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200": { description: Sparat }
|
||
"403": { description: Kräver arbetsledare eller administratör }
|
||
|
||
/api/atkomstlogg:
|
||
get:
|
||
tags: [Drift]
|
||
summary: Åtkomstlogg — vem som läst vilket ärende
|
||
description: >
|
||
Kräver `arbetsledare` eller `admin`, och visar endast den egna
|
||
organisationen. Loggen omfattar även läsningar via publika
|
||
delningslänkar, med koden angiven. Tabellen är append-only: posterna
|
||
kan varken ändras eller raderas, eftersom de utgör bevisningen för
|
||
att åtkomststyrningen fungerade.
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200":
|
||
description: De 500 senaste åtkomsterna.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
atkomster:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
tidpunkt: { type: string, format: date-time }
|
||
arende_id: { type: string, nullable: true }
|
||
vag: { type: string }
|
||
anvandare: { type: string, nullable: true }
|
||
delningskod: { type: string, nullable: true }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
|
||
# ---- Fakturering (ALVA-PROC-0001) ------------------------------------
|
||
#
|
||
# Två parter, inte en. Organisationen LÄSER sina fakturor; utfärdaren
|
||
# skapar dem och registrerar betalning. Utfärdaren är inte en användare
|
||
# i någon organisation och identifieras med en egen nyckel
|
||
# (X-Fakturering) — en kund ska inte kunna bokföra sin egen betalning.
|
||
#
|
||
# Ingen betalleverantör är inblandad och inga kortuppgifter hanteras.
|
||
/api/fakturor:
|
||
get:
|
||
tags: [Fakturering]
|
||
summary: Organisationens egna fakturor
|
||
description: >
|
||
Kräver rollen `admin`. Statusen är en projektion av
|
||
fakturahändelserna, inte ett lagrat fält — en utfärdad faktura
|
||
ändras aldrig.
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
"200":
|
||
description: Fakturor, senast utfärdad först.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
fakturor:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/Faktura" }
|
||
"401": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
post:
|
||
tags: [Fakturering]
|
||
summary: Utfärda en faktura
|
||
description: >
|
||
Kräver utfärdarens nyckel i `X-Fakturering`. Beloppet kan inte
|
||
anges: det härleds ur organisationens faktiska tillstånd — antalet
|
||
aktiva konton, licensperioden, påslagna moduler. Ett anrop som
|
||
ändå innehåller `rader`, `totalt` eller `belopp` avvisas med 400.
|
||
security: [{ utfardarNyckel: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [organisation_id, period]
|
||
properties:
|
||
organisation_id: { type: string, format: uuid }
|
||
period:
|
||
type: object
|
||
required: [fran, till]
|
||
properties:
|
||
fran: { type: string, format: date }
|
||
till: { type: string, format: date }
|
||
utfardad:
|
||
type: string
|
||
format: date
|
||
description: Standard är dagens datum.
|
||
responses:
|
||
"201":
|
||
description: Fakturan, fullständig och oföränderlig.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Faktura" }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
|
||
/api/fakturor/{fakturaId}/betald:
|
||
post:
|
||
tags: [Fakturering]
|
||
summary: Registrera betalning
|
||
description: >
|
||
Kräver utfärdarens nyckel. Ingen uppdatering sker — betalningen
|
||
skrivs som en egen händelse, och statusen härleds ur den.
|
||
Plattformen registrerar aldrig en betalning av sig själv.
|
||
security: [{ utfardarNyckel: [] }]
|
||
parameters:
|
||
- { name: fakturaId, in: path, required: true, schema: { type: string, format: uuid } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [referens]
|
||
properties:
|
||
referens:
|
||
type: string
|
||
description: Betalningsreferens, minst 3 tecken — annars går betalningen inte att spåra.
|
||
betaldatum: { type: string, format: date }
|
||
responses:
|
||
"200": { description: Registrerad. }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"409": { description: Fakturan är redan betald eller krediterad. }
|
||
|
||
/api/fakturor/{fakturaId}/kreditera:
|
||
post:
|
||
tags: [Fakturering]
|
||
summary: Kreditera en faktura
|
||
description: >
|
||
Kräver utfärdarens nyckel. En felaktig faktura rättas inte — den
|
||
bemöts av en kreditfaktura som pekar tillbaka på den, med omvänt
|
||
tecken och ett granskbart skäl (minst 10 tecken).
|
||
security: [{ utfardarNyckel: [] }]
|
||
parameters:
|
||
- { name: fakturaId, in: path, required: true, schema: { type: string, format: uuid } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [orsak]
|
||
properties:
|
||
orsak: { type: string, minLength: 10 }
|
||
utfardad: { type: string, format: date }
|
||
responses:
|
||
"201":
|
||
description: Kreditfakturan.
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/Faktura" }
|
||
"400": { $ref: "#/components/responses/Fel" }
|
||
"403": { $ref: "#/components/responses/Fel" }
|
||
"404": { $ref: "#/components/responses/Fel" }
|
||
"409": { description: Fakturan är redan krediterad. }
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
description: >
|
||
HS256-JWT från /api/auth/logga-in eller /api/auth/registrera.
|
||
Anspråk: sub (användar-id), namn, org (organisations-id),
|
||
roll, iat, exp (12 timmar).
|
||
utfardarNyckel:
|
||
type: apiKey
|
||
in: header
|
||
name: X-Fakturering
|
||
description: >
|
||
Utfärdarens nyckel (FAKTURERING_NYCKEL). Fakturering är en
|
||
relation mellan installationen och kunden — ingen av rollerna i
|
||
en verkstad är motpart i det avtalet, så en organisations
|
||
administratör kan varken utfärda sin egen faktura eller bokföra
|
||
den som betald. Saknas nyckeln i miljön kan ingen faktura
|
||
utfärdas alls: faktureringen fallerar stängt.
|
||
responses:
|
||
Inloggad:
|
||
description: Inloggad — token + kontouppgifter.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
token: { type: string }
|
||
namn: { type: string }
|
||
roll: { $ref: "#/components/schemas/Roll" }
|
||
organisation: { type: string }
|
||
Fel:
|
||
description: Felsvar.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error: { type: string }
|
||
schemas:
|
||
Fel:
|
||
type: object
|
||
properties:
|
||
error: { type: string }
|
||
Faktura:
|
||
type: object
|
||
description: >
|
||
Belopp i minsta valutaenhet (öre). Varje rad bär sitt underlag —
|
||
var antalet kommer ifrån — så att summan går att granska utan att
|
||
fråga någon. `status` är härledd, inte lagrad.
|
||
properties:
|
||
id: { type: string, format: uuid }
|
||
beteckning: { type: string, example: ALVA-INV-0001 }
|
||
organisation: { type: string }
|
||
period:
|
||
type: object
|
||
properties:
|
||
fran: { type: string, format: date }
|
||
till: { type: string, format: date }
|
||
utfardad: { type: string, format: date }
|
||
forfaller: { type: string, format: date }
|
||
valuta: { type: string, example: SEK }
|
||
rader:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
benamning: { type: string }
|
||
underlag: { type: string }
|
||
antal: { type: integer }
|
||
enhet: { type: string }
|
||
apris: { type: integer }
|
||
belopp: { type: integer }
|
||
netto: { type: integer }
|
||
momssats: { type: number }
|
||
moms: { type: integer }
|
||
totalt: { type: integer }
|
||
krediterar:
|
||
type: string
|
||
nullable: true
|
||
description: Beteckningen på den faktura denna kreditfaktura rättar.
|
||
status:
|
||
type: string
|
||
enum: [utfardad, betald, krediterad]
|
||
betalningssatt: { type: string }
|
||
Roll:
|
||
type: string
|
||
enum: [tekniker, arbetsledare, admin]
|
||
OrganisationsInstallningar:
|
||
type: object
|
||
required: [objekttyper, identifieringsmetoder]
|
||
properties:
|
||
objekttyper:
|
||
type: array
|
||
minItems: 1
|
||
items: { type: string }
|
||
identifieringsmetoder:
|
||
type: array
|
||
minItems: 1
|
||
items: { type: string }
|
||
DelningsNiva:
|
||
type: string
|
||
enum: [kund, partner, intern]
|
||
Delning:
|
||
type: object
|
||
properties:
|
||
kod: { type: string }
|
||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||
skapad: { type: string, format: date-time }
|
||
aterkallad: { type: string, format: date-time, nullable: true }
|
||
Anvandare:
|
||
type: object
|
||
properties:
|
||
id: { type: string, format: uuid }
|
||
epost: { type: string, format: email }
|
||
namn: { type: string }
|
||
roll: { $ref: "#/components/schemas/Roll" }
|
||
Arende:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
nummer: { type: integer }
|
||
skapad: { type: string, format: date-time }
|
||
delningskod: { type: string, nullable: true }
|
||
metodik_id: { type: string, nullable: true }
|
||
LoggPost:
|
||
type: object
|
||
required: [id, tidpunkt, anvandare, handelse]
|
||
properties:
|
||
id: { type: string }
|
||
tidpunkt: { type: string, format: date-time }
|
||
anvandare: { type: string }
|
||
handelse: { $ref: "#/components/schemas/Handelse" }
|
||
Handelse:
|
||
type: object
|
||
description: >
|
||
Diskriminerad på `typ`; fullständiga fältdefinitioner i klientens
|
||
domänmodell (src/felsokning/domain.ts).
|
||
required: [typ]
|
||
additionalProperties: true
|
||
properties:
|
||
typ:
|
||
type: string
|
||
enum:
|
||
- objekt_identifierat
|
||
- felbeskrivning
|
||
- fraga_besvarad
|
||
- kontroll_utford
|
||
- observation
|
||
- matvarde
|
||
- hypotes
|
||
- foto
|
||
- kommentar
|
||
- kategori_byte
|
||
- inaktivitet_forklarad
|
||
- overlamning
|
||
- ansvarig_satt
|
||
- arbetsorder_skannad
|
||
- arendetyp_satt
|
||
- historik_kontrollerad
|
||
- matarstallning
|
||
- video
|
||
- atgardsforslag
|
||
- kundbeslut
|
||
- atgard_utford
|
||
- kvalitetskontroll
|
||
- reproducering
|
||
- felorsak
|
||
- export_skapad
|
||
- ai_svar
|
||
- arende_avslutat
|
||
OversiktsRad:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
nummer: { type: integer }
|
||
skapad: { type: string, format: date-time }
|
||
delningskod: { type: string, nullable: true }
|
||
metodik_id: { type: string, nullable: true }
|
||
antal_handelser: { type: integer }
|
||
forsta: { type: string, format: date-time, nullable: true }
|
||
senaste: { type: string, format: date-time, nullable: true }
|
||
avslutat: { type: boolean }
|
||
objekt: { type: string, nullable: true }
|
||
felbeskrivning: { type: string, nullable: true }
|
||
tekniker:
|
||
type: array
|
||
items: { type: string }
|
||
nullable: true
|
||
AiSvar:
|
||
type: object
|
||
required: [rader, nastaSteg]
|
||
properties:
|
||
rader:
|
||
type: array
|
||
items:
|
||
type: object
|
||
required: [typ, text]
|
||
properties:
|
||
typ:
|
||
type: string
|
||
enum: [observation, verifierat, hypotes, rekommendation]
|
||
text: { type: string }
|
||
nastaSteg: { type: string }
|
||
|
||
security:
|
||
- bearerAuth: []
|