{
    "$id": "https://api.minipol.nl/docs/schema.json",
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "title": "MiniPol: de typen van de API",
    "description": "De typen van de MiniPol-API als JSON Schema (2020-12): wat de API teruggeeft en wat je stuurt, en de standaardexport van een gesprek. De OpenAPI-spec (openapi.json) verwijst hiernaar; andere tools kunnen ze ook gebruiken, bijvoorbeeld om een export te valideren of code te genereren. Velden die de server invult, zijn readOnly; welke velden in een antwoord staan, hangt af van de context (zie de beschrijving per type).",
    "$defs": {
        "Fout": {
            "type": "object",
            "required": [
                "error"
            ],
            "properties": {
                "error": {
                    "type": "string"
                }
            }
        },
        "Waarde": {
            "type": "string",
            "enum": [
                "eens",
                "neutraal",
                "oneens"
            ]
        },
        "Moderatie": {
            "type": "string",
            "enum": [
                "achteraf",
                "vooraf"
            ],
            "description": "`achteraf`: stellingen zijn zichtbaar tenzij afgekeurd. `vooraf`: alleen als goedgekeurd."
        },
        "Telling": {
            "type": "object",
            "description": "Hoeveel deelnemers elk antwoord gaven",
            "required": [
                "eens",
                "neutraal",
                "oneens"
            ],
            "properties": {
                "eens": {
                    "type": "integer",
                    "minimum": 0
                },
                "neutraal": {
                    "type": "integer",
                    "minimum": 0
                },
                "oneens": {
                    "type": "integer",
                    "minimum": 0
                }
            }
        },
        "Gesprek": {
            "description": "Een gesprek. `stellingen` staat er alleen in bij `GET /gesprekken/{id}`; `rol` alleen in de lijst voor een ingelogd account.",
            "type": "object",
            "required": [
                "titel"
            ],
            "properties": {
                "id": {
                    "type": "string",
                    "readOnly": true
                },
                "titel": {
                    "type": "string",
                    "maxLength": 200
                },
                "omschrijving": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "Optioneel; standaard leeg"
                },
                "moderatie": {
                    "allOf": [
                        {
                            "$ref": "#/$defs/Moderatie"
                        }
                    ],
                    "description": "Optioneel; bij aanmaken standaard `achteraf`, bij aanpassen standaard ongewijzigd"
                },
                "stellingen": {
                    "type": "array",
                    "readOnly": true,
                    "items": {
                        "$ref": "#/$defs/Stelling"
                    },
                    "description": "De zichtbare stellingen, in willekeurige volgorde (alleen `id` en `tekst`)"
                },
                "status": {
                    "allOf": [
                        {
                            "$ref": "#/$defs/Gespreksstatus"
                        }
                    ],
                    "readOnly": true,
                    "description": "Niet `actief`: deelnemers zien een melding in plaats van het gesprek."
                },
                "rol": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "enum": [
                        "gespreksbeheerder",
                        "moderator",
                        null
                    ],
                    "readOnly": true,
                    "description": "De rol van het ingelogde account in het team, of null"
                }
            }
        },
        "Stelling": {
            "description": "Een stelling. Welke velden erin staan, hangt af van waar hij vandaan komt:\n\n- in een gesprek (`GET /gesprekken/{id}`, de export): alleen `id` en `tekst`\n- toegevoegd (`POST /stellingen`): alles behalve `antwoorden`\n- eigen stellingen (`GET /stellingen`): alles\n- beheer (`/beoordelingen`): alles behalve `deelnemer_id`\n\n`deelnemer_id` staat alleen in antwoorden aan de indiener zelf.",
            "type": "object",
            "required": [
                "tekst"
            ],
            "properties": {
                "id": {
                    "type": "string",
                    "readOnly": true
                },
                "gesprek_id": {
                    "type": "string"
                },
                "deelnemer_id": {
                    "type": "string",
                    "description": "De deelnemer die hem toevoegde; hooguit 64 letters, cijfers en streepjes",
                    "pattern": "^[A-Za-z0-9-]{1,64}$"
                },
                "tekst": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Witruimte wordt één spatie"
                },
                "beoordeling": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "enum": [
                        "goedgekeurd",
                        "afgekeurd",
                        null
                    ],
                    "readOnly": true,
                    "description": "null: nog niet beoordeeld"
                },
                "reden": {
                    "type": "string",
                    "readOnly": true,
                    "description": "Bij afgekeurd; de indiener ziet hem"
                },
                "zichtbaar": {
                    "type": "boolean",
                    "readOnly": true,
                    "description": "Of deelnemers hem zien; volgt uit de moderatie van het gesprek en de beoordeling"
                },
                "antwoorden": {
                    "allOf": [
                        {
                            "$ref": "#/$defs/Telling"
                        }
                    ],
                    "readOnly": true,
                    "description": "Hoe hij beantwoord is"
                }
            }
        },
        "Antwoord": {
            "description": "Een antwoord van een deelnemer op een stelling; opnieuw antwoorden mag, het laatste telt.",
            "type": "object",
            "required": [
                "stelling_id",
                "waarde"
            ],
            "properties": {
                "gesprek_id": {
                    "type": "string"
                },
                "deelnemer_id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9-]{1,64}$",
                    "description": "Het id van de deelnemer; hooguit 64 letters, cijfers en streepjes"
                },
                "stelling_id": {
                    "type": "string"
                },
                "waarde": {
                    "$ref": "#/$defs/Waarde"
                }
            }
        },
        "Beoordeling": {
            "description": "Een beoordeling van een stelling door een beheerder; de laatste telt. Bij `afgekeurd` is een reden verplicht.",
            "type": "object",
            "required": [
                "stelling_id",
                "beoordeling"
            ],
            "properties": {
                "gesprek_id": {
                    "type": "string"
                },
                "stelling_id": {
                    "type": "string"
                },
                "beoordeling": {
                    "type": "string",
                    "enum": [
                        "goedgekeurd",
                        "afgekeurd"
                    ]
                },
                "reden": {
                    "type": "string",
                    "maxLength": 500
                }
            }
        },
        "EventType": {
            "type": "string",
            "enum": [
                "gesprek.aangemaakt",
                "gesprek.aangepast",
                "stelling.toegevoegd",
                "stelling.goedgekeurd",
                "stelling.afgekeurd",
                "antwoord.gegeven",
                "gesprek.opgeschort",
                "gesprek.hersteld",
                "gesprek.beeindigd",
                "lid.toegevoegd",
                "lid.opgeschort",
                "lid.hersteld",
                "lid.verwijderd"
            ]
        },
        "Event": {
            "description": "Iets wat in een gesprek gebeurde. De data is een reeks events; een gesprek, zijn stellingen en de antwoorden volgen daaruit. Naast de velden die elk event heeft, staan er de velden van zijn type in:\n\n- `gesprek.aangemaakt`: `titel`, `omschrijving`, `moderatie`\n- `gesprek.aangepast`: alleen de velden die veranderden\n- `gesprek.opgeschort`, `gesprek.beeindigd`: `reden`; `gesprek.hersteld`: niets\n- `lid.toegevoegd`: `account_id`, `gebruikersnaam`, `rol`\n- `lid.opgeschort`, `lid.verwijderd`: `account_id`, `gebruikersnaam`, `reden`; `lid.hersteld`: `account_id`, `gebruikersnaam`\n- `stelling.toegevoegd`: `stelling_id`, `tekst`\n- `stelling.goedgekeurd`: `stelling_id`, `tekst`, soms `reden`\n- `stelling.afgekeurd`: `stelling_id`, `tekst`, `reden`\n- `antwoord.gegeven`: `stelling_id`, `tekst`, `waarde`\n\nIn de opslag staat `tekst` alleen bij `stelling.toegevoegd`; de API zet hem bij elk event over een stelling.",
            "type": "object",
            "required": [
                "id",
                "tijdstip",
                "type",
                "door",
                "gesprek_id"
            ],
            "properties": {
                "id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "UUID v7: die begint met de tijd, dus op id sorteren is op tijd sorteren"
                },
                "tijdstip": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "format": "date-time",
                    "description": "null bij events uit de data van vóór de events: toen werd dat niet bewaard"
                },
                "type": {
                    "$ref": "#/$defs/EventType"
                },
                "door": {
                    "description": "Wie het deed: iemand met een account in de beheeromgeving, of een deelnemer, anoniem met het nummer uit de matrix (wie alleen stellingen toevoegde, krijgt een nummer daarna). null als het onbekend is.",
                    "oneOf": [
                        {
                            "title": "Beheerder",
                            "type": "object",
                            "required": [
                                "soort",
                                "id",
                                "gebruikersnaam"
                            ],
                            "properties": {
                                "soort": {
                                    "const": "beheerder"
                                },
                                "id": {
                                    "type": "string"
                                },
                                "gebruikersnaam": {
                                    "type": "string"
                                }
                            }
                        },
                        {
                            "title": "Deelnemer",
                            "type": "object",
                            "required": [
                                "soort",
                                "nummer"
                            ],
                            "properties": {
                                "soort": {
                                    "const": "deelnemer"
                                },
                                "nummer": {
                                    "type": "integer",
                                    "minimum": 1
                                }
                            }
                        },
                        {
                            "type": "null"
                        }
                    ]
                },
                "gesprek_id": {
                    "type": "string"
                },
                "titel": {
                    "type": "string"
                },
                "omschrijving": {
                    "type": "string"
                },
                "moderatie": {
                    "$ref": "#/$defs/Moderatie"
                },
                "stelling_id": {
                    "type": "string"
                },
                "tekst": {
                    "type": "string",
                    "description": "De tekst van de stelling"
                },
                "reden": {
                    "type": "string"
                },
                "waarde": {
                    "$ref": "#/$defs/Waarde"
                },
                "account_id": {
                    "type": "string",
                    "description": "Het account waar een event van het team over gaat"
                },
                "gebruikersnaam": {
                    "type": "string",
                    "description": "De gebruikersnaam van dat account"
                },
                "rol": {
                    "type": "string",
                    "enum": [
                        "gespreksbeheerder",
                        "moderator"
                    ]
                }
            }
        },
        "Deelnemer": {
            "description": "Een deelnemer, anoniem: genummerd op volgorde van het eerste antwoord, met per stelling het laatste antwoord. Dezelfde nummers in de matrix, de export en de analyse.",
            "type": "object",
            "required": [
                "nummer",
                "antwoorden"
            ],
            "properties": {
                "nummer": {
                    "type": "integer",
                    "minimum": 1
                },
                "antwoorden": {
                    "type": "object",
                    "description": "stelling_id => waarde",
                    "additionalProperties": {
                        "$ref": "#/$defs/Waarde"
                    }
                }
            }
        },
        "Export": {
            "type": "object",
            "required": [
                "formaat",
                "versie",
                "gegenereerd",
                "gesprek",
                "stellingen",
                "deelnemers"
            ],
            "properties": {
                "formaat": {
                    "const": "minipol-export"
                },
                "versie": {
                    "const": 1
                },
                "gegenereerd": {
                    "type": "string",
                    "format": "date-time"
                },
                "gesprek": {
                    "type": "object",
                    "required": [
                        "id",
                        "titel"
                    ],
                    "properties": {
                        "id": {
                            "type": "string"
                        },
                        "titel": {
                            "type": "string"
                        }
                    }
                },
                "stellingen": {
                    "type": "array",
                    "items": {
                        "$ref": "#/$defs/Stelling"
                    },
                    "description": "De zichtbare stellingen (alleen `id` en `tekst`)"
                },
                "deelnemers": {
                    "type": "array",
                    "items": {
                        "$ref": "#/$defs/Deelnemer"
                    }
                }
            }
        },
        "Status": {
            "type": "string",
            "enum": [
                "actief",
                "opgeschort",
                "verwijderd"
            ],
            "description": "De status van een lid van een team of van een superbeheerder. `opgeschort`: tijdelijk, met een reden; `verwijderd`: de toegang is voorgoed weg, met een reden (de events blijven bewaard)."
        },
        "Rol": {
            "type": "string",
            "enum": [
                "superbeheerder",
                "gespreksbeheerder",
                "moderator"
            ],
            "description": "`superbeheerder`: van de hele server; maakt gesprekken en beheert gesprekken, teams en superbeheerders, maar leest en modereert zelf geen inhoud. `gespreksbeheerder`: van één gesprek; past het aan, beheert het team en modereert. `moderator`: van één gesprek; keurt stellingen goed of af."
        },
        "Account": {
            "description": "Een account in de beheeromgeving, met zijn rollen",
            "type": "object",
            "required": [
                "id",
                "gebruikersnaam",
                "email",
                "superbeheerder",
                "rollen"
            ],
            "properties": {
                "id": {
                    "type": "string"
                },
                "gebruikersnaam": {
                    "type": "string"
                },
                "email": {
                    "type": "string",
                    "format": "email"
                },
                "superbeheerder": {
                    "type": "boolean",
                    "description": "Of het een actieve superbeheerder is"
                },
                "rollen": {
                    "type": "object",
                    "description": "gesprek_id => rol in het team van dat gesprek",
                    "additionalProperties": {
                        "type": "string",
                        "enum": [
                            "gespreksbeheerder",
                            "moderator"
                        ]
                    }
                }
            }
        },
        "NieuwAccount": {
            "description": "De gegevens van een nieuw account",
            "type": "object",
            "required": [
                "gebruikersnaam",
                "email",
                "wachtwoord"
            ],
            "properties": {
                "gebruikersnaam": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9._-]{1,64}$"
                },
                "email": {
                    "type": "string",
                    "format": "email"
                },
                "wachtwoord": {
                    "type": "string",
                    "format": "password",
                    "minLength": 12
                }
            }
        },
        "Sessie": {
            "description": "Wie er is ingelogd. `token` en `verloopt` alleen direct na inloggen.",
            "type": "object",
            "required": [
                "account",
                "installatie"
            ],
            "properties": {
                "account": {
                    "oneOf": [
                        {
                            "$ref": "#/$defs/Account"
                        },
                        {
                            "type": "null"
                        }
                    ]
                },
                "installatie": {
                    "type": "boolean",
                    "description": "true voor de login met admin/admin direct na installatie: die kan alleen de eerste superbeheerder maken"
                },
                "token": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$"
                },
                "verloopt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Als het token niet gebruikt wordt"
                }
            }
        },
        "Lid": {
            "description": "Een lid van het team van een gesprek",
            "type": "object",
            "required": [
                "account_id",
                "gebruikersnaam",
                "email",
                "rol",
                "status"
            ],
            "properties": {
                "account_id": {
                    "type": "string"
                },
                "gebruikersnaam": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "rol": {
                    "type": "string",
                    "enum": [
                        "gespreksbeheerder",
                        "moderator"
                    ]
                },
                "status": {
                    "$ref": "#/$defs/Status"
                }
            }
        },
        "Superbeheerder": {
            "description": "Een superbeheerder",
            "type": "object",
            "required": [
                "id",
                "gebruikersnaam",
                "email",
                "status"
            ],
            "properties": {
                "id": {
                    "type": "string"
                },
                "gebruikersnaam": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "status": {
                    "$ref": "#/$defs/Status"
                }
            }
        },
        "Statuswijziging": {
            "description": "Een nieuwe status, met een reden bij alles behalve `actief`",
            "type": "object",
            "required": [
                "status"
            ],
            "properties": {
                "status": {
                    "type": "string",
                    "enum": [
                        "actief",
                        "opgeschort",
                        "verwijderd",
                        "beeindigd"
                    ],
                    "description": "Voor een lid of een superbeheerder een `Status`, voor een gesprek een `Gespreksstatus`"
                },
                "reden": {
                    "type": "string",
                    "maxLength": 500
                }
            }
        },
        "Uitnodiging": {
            "description": "Een uitnodiging: een eenmalige link, 48 uur geldig. `token` staat er alleen in direct na aanmaken; de API bewaart alleen de sha256 ervan.",
            "type": "object",
            "required": [
                "rol"
            ],
            "properties": {
                "token": {
                    "type": "string",
                    "readOnly": true,
                    "pattern": "^[a-f0-9]{64}$"
                },
                "uitnodiging_id": {
                    "type": "string",
                    "readOnly": true
                },
                "rol": {
                    "$ref": "#/$defs/Rol"
                },
                "gesprek_id": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Voor een rol in een team; null voor superbeheerder"
                },
                "gesprek": {
                    "readOnly": true,
                    "oneOf": [
                        {
                            "type": "object",
                            "required": [
                                "id",
                                "titel"
                            ],
                            "properties": {
                                "id": {
                                    "type": "string"
                                },
                                "titel": {
                                    "type": "string"
                                }
                            }
                        },
                        {
                            "type": "null"
                        }
                    ]
                },
                "verloopt": {
                    "type": "string",
                    "format": "date-time",
                    "readOnly": true
                }
            }
        },
        "Gespreksstatus": {
            "type": "string",
            "enum": [
                "actief",
                "opgeschort",
                "beeindigd"
            ],
            "description": "`opgeschort`: gepauzeerd; `beeindigd`: voorbij. Beide met een reden: deelnemers zien een melding, er kunnen geen antwoorden of stellingen bij, en het team kan er niet bij. Een superbeheerder kan het gesprek weer openen (`actief`). Een gesprek wordt nooit verwijderd."
        }
    }
}